blob: 38c226c6da378998da69db7c355a9267bf376270 [file] [log] [blame]
// Copyright (c) 2017 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
#include "libcef/browser/server_impl.h"
#include "libcef/browser/thread_util.h"
#include "libcef/common/request_impl.h"
#include "libcef/common/task_runner_impl.h"
#include "base/bind.h"
#include "base/format_macros.h"
#include "base/memory/ptr_util.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread.h"
#include "net/base/net_errors.h"
#include "net/http/http_request_headers.h"
#include "net/server/http_server_request_info.h"
#include "net/server/http_server_response_info.h"
#include "net/socket/server_socket.h"
#include "net/socket/tcp_server_socket.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#define CEF_CURRENTLY_ON_HT() CurrentlyOnHandlerThread()
#define CEF_REQUIRE_HT() DCHECK(CEF_CURRENTLY_ON_HT())
#define CEF_REQUIRE_HT_RETURN(var) \
if (!CEF_CURRENTLY_ON_HT()) { \
NOTREACHED() << "called on invalid thread"; \
return var; \
}
#define CEF_REQUIRE_HT_RETURN_VOID() \
if (!CEF_CURRENTLY_ON_HT()) { \
NOTREACHED() << "called on invalid thread"; \
return; \
}
#define CEF_POST_TASK_HT(task) task_runner_->PostTask(FROM_HERE, task);
namespace {
const char kReferrerLowerCase[] = "referer";
// Wrap a string in a unique_ptr to avoid extra copies.
std::unique_ptr<std::string> CreateUniqueString(const void* data,
size_t data_size) {
std::unique_ptr<std::string> ptr;
if (data && data_size > 0) {
ptr.reset(new std::string(static_cast<const char*>(data), data_size));
} else {
ptr.reset(new std::string());
}
return ptr;
}
CefRefPtr<CefRequest> CreateRequest(const std::string& address,
const net::HttpServerRequestInfo& info,
bool is_websocket) {
DCHECK(!address.empty());
DCHECK(!info.method.empty());
DCHECK(!info.path.empty());
CefRefPtr<CefPostData> post_data;
if (!info.data.empty()) {
post_data = CefPostData::Create();
CefRefPtr<CefPostDataElement> post_element = CefPostDataElement::Create();
post_element->SetToBytes(info.data.size(), info.data.data());
post_data->AddElement(post_element);
}
std::string referer;
CefRequest::HeaderMap header_map;
if (!info.headers.empty()) {
net::HttpServerRequestInfo::HeadersMap::const_iterator it =
info.headers.begin();
for (; it != info.headers.end(); ++it) {
// Don't include Referer in the header map.
if (base::LowerCaseEqualsASCII(it->first, kReferrerLowerCase)) {
referer = it->second;
} else {
header_map.insert(std::make_pair(it->first, it->second));
}
}
}
CefRefPtr<CefRequestImpl> request = new CefRequestImpl();
request->Set((is_websocket ? "ws://" : "http://") + address + info.path,
info.method, post_data, header_map);
if (!referer.empty())
request->SetReferrer(referer, REFERRER_POLICY_DEFAULT);
request->SetReadOnly(true);
return request;
}
// Callback implementation for WebSocket acceptance. Always executes on the UI
// thread so we can avoid multiple execution by clearing the |impl_| reference.
class AcceptWebSocketCallback : public CefCallback {
public:
AcceptWebSocketCallback(CefRefPtr<CefServerImpl> impl,
int connection_id,
net::HttpServerRequestInfo request_info)
: impl_(impl),
connection_id_(connection_id),
request_info_(request_info) {}
~AcceptWebSocketCallback() override {
if (impl_)
impl_->ContinueWebSocketRequest(connection_id_, request_info_, false);
}
void Continue() override {
if (!CEF_CURRENTLY_ON_UIT()) {
CEF_POST_TASK(CEF_UIT,
base::BindOnce(&AcceptWebSocketCallback::Continue, this));
return;
}
if (!impl_)
return;
impl_->ContinueWebSocketRequest(connection_id_, request_info_, true);
impl_ = nullptr;
}
void Cancel() override {
if (!CEF_CURRENTLY_ON_UIT()) {
CEF_POST_TASK(CEF_UIT,
base::BindOnce(&AcceptWebSocketCallback::Cancel, this));
return;
}
if (!impl_)
return;
impl_->ContinueWebSocketRequest(connection_id_, request_info_, false);
impl_ = nullptr;
}
private:
CefRefPtr<CefServerImpl> impl_;
int connection_id_;
net::HttpServerRequestInfo request_info_;
IMPLEMENT_REFCOUNTING_DELETE_ON_UIT(AcceptWebSocketCallback);
DISALLOW_COPY_AND_ASSIGN(AcceptWebSocketCallback);
};
} // namespace
// CefServer
// static
void CefServer::CreateServer(const CefString& address,
uint16 port,
int backlog,
CefRefPtr<CefServerHandler> handler) {
CefRefPtr<CefServerImpl> server(new CefServerImpl(handler));
server->Start(address, port, backlog);
}
// CefServerImpl
struct CefServerImpl::ConnectionInfo {
ConnectionInfo() : is_websocket(false), is_websocket_pending(false) {}
// True if this connection is a WebSocket connection.
bool is_websocket;
bool is_websocket_pending;
};
CefServerImpl::CefServerImpl(CefRefPtr<CefServerHandler> handler)
: handler_(handler) {
DCHECK(handler_);
}
void CefServerImpl::Start(const std::string& address,
uint16 port,
int backlog) {
DCHECK(!address.empty());
CEF_POST_TASK(CEF_UIT, base::BindOnce(&CefServerImpl::StartOnUIThread, this,
address, port, backlog));
}
CefRefPtr<CefTaskRunner> CefServerImpl::GetTaskRunner() {
if (task_runner_)
return new CefTaskRunnerImpl(task_runner_);
return nullptr;
}
void CefServerImpl::Shutdown() {
CEF_POST_TASK_HT(
base::BindOnce(&CefServerImpl::ShutdownOnHandlerThread, this));
}
bool CefServerImpl::IsRunning() {
CEF_REQUIRE_HT_RETURN(false);
return !!server_.get();
}
CefString CefServerImpl::GetAddress() {
return address_;
}
bool CefServerImpl::HasConnection() {
CEF_REQUIRE_HT_RETURN(false);
return !connection_info_map_.empty();
}
bool CefServerImpl::IsValidConnection(int connection_id) {
CEF_REQUIRE_HT_RETURN(false);
return connection_info_map_.find(connection_id) != connection_info_map_.end();
}
void CefServerImpl::SendHttp200Response(int connection_id,
const CefString& content_type,
const void* data,
size_t data_size) {
SendHttp200ResponseInternal(connection_id, content_type,
CreateUniqueString(data, data_size));
}
void CefServerImpl::SendHttp404Response(int connection_id) {
if (!CEF_CURRENTLY_ON_HT()) {
CEF_POST_TASK_HT(base::BindOnce(&CefServerImpl::SendHttp404Response, this,
connection_id));
return;
}
if (!ValidateServer())
return;
ConnectionInfo* info = GetConnectionInfo(connection_id);
if (!info)
return;
if (info->is_websocket) {
LOG(ERROR) << "Invalid attempt to send HTTP response for connection_id "
<< connection_id;
return;
}
server_->Send404(connection_id, MISSING_TRAFFIC_ANNOTATION);
server_->Close(connection_id);
}
void CefServerImpl::SendHttp500Response(int connection_id,
const CefString& error_message) {
if (!CEF_CURRENTLY_ON_HT()) {
CEF_POST_TASK_HT(base::BindOnce(&CefServerImpl::SendHttp500Response, this,
connection_id, error_message));
return;
}
if (!ValidateServer())
return;
ConnectionInfo* info = GetConnectionInfo(connection_id);
if (!info)
return;
if (info->is_websocket) {
LOG(ERROR) << "Invalid attempt to send HTTP response for connection_id "
<< connection_id;
return;
}
server_->Send500(connection_id, error_message, MISSING_TRAFFIC_ANNOTATION);
server_->Close(connection_id);
}
void CefServerImpl::SendHttpResponse(int connection_id,
int response_code,
const CefString& content_type,
int64 content_length,
const HeaderMap& extra_headers) {
if (!CEF_CURRENTLY_ON_HT()) {
CEF_POST_TASK_HT(base::BindOnce(&CefServerImpl::SendHttpResponse, this,
connection_id, response_code, content_type,
content_length, extra_headers));
return;
}
if (!ValidateServer())
return;
ConnectionInfo* info = GetConnectionInfo(connection_id);
if (!info)
return;
if (info->is_websocket) {
LOG(ERROR) << "Invalid attempt to send HTTP response for connection_id "
<< connection_id;
return;
}
net::HttpServerResponseInfo response(
static_cast<net::HttpStatusCode>(response_code));
HeaderMap::const_iterator it = extra_headers.begin();
for (; it != extra_headers.end(); ++it)
response.AddHeader(it->first, it->second);
response.AddHeader(net::HttpRequestHeaders::kContentType, content_type);
if (content_length >= 0) {
response.AddHeader(
net::HttpRequestHeaders::kContentLength,
base::StringPrintf("%" PRIuS, static_cast<size_t>(content_length)));
}
server_->SendResponse(connection_id, response, MISSING_TRAFFIC_ANNOTATION);
if (content_length == 0) {
server_->Close(connection_id);
}
}
void CefServerImpl::SendRawData(int connection_id,
const void* data,
size_t data_size) {
if (!data || data_size == 0)
return;
SendRawDataInternal(connection_id, CreateUniqueString(data, data_size));
}
void CefServerImpl::CloseConnection(int connection_id) {
if (!CEF_CURRENTLY_ON_HT()) {
CEF_POST_TASK_HT(
base::BindOnce(&CefServerImpl::CloseConnection, this, connection_id));
return;
}
if (ValidateServer() && GetConnectionInfo(connection_id)) {
server_->Close(connection_id);
}
}
void CefServerImpl::SendWebSocketMessage(int connection_id,
const void* data,
size_t data_size) {
if (!data || data_size == 0)
return;
SendWebSocketMessageInternal(connection_id,
CreateUniqueString(data, data_size));
}
void CefServerImpl::ContinueWebSocketRequest(
int connection_id,
const net::HttpServerRequestInfo& request_info,
bool allow) {
if (!CEF_CURRENTLY_ON_HT()) {
CEF_POST_TASK_HT(base::BindOnce(&CefServerImpl::ContinueWebSocketRequest,
this, connection_id, request_info, allow));
return;
}
if (!ValidateServer())
return;
ConnectionInfo* info = GetConnectionInfo(connection_id);
DCHECK(info);
if (!info)
return;
DCHECK(info->is_websocket);
DCHECK(info->is_websocket_pending);
if (!info->is_websocket || !info->is_websocket_pending)
return;
info->is_websocket_pending = false;
if (allow) {
server_->AcceptWebSocket(connection_id, request_info,
MISSING_TRAFFIC_ANNOTATION);
handler_->OnWebSocketConnected(this, connection_id);
} else {
server_->Close(connection_id);
}
}
void CefServerImpl::SendHttp200ResponseInternal(
int connection_id,
const CefString& content_type,
std::unique_ptr<std::string> data) {
if (!CEF_CURRENTLY_ON_HT()) {
CEF_POST_TASK_HT(base::BindOnce(&CefServerImpl::SendHttp200ResponseInternal,
this, connection_id, content_type,
std::move(data)));
return;
}
if (!ValidateServer())
return;
ConnectionInfo* info = GetConnectionInfo(connection_id);
if (!info)
return;
if (info->is_websocket) {
LOG(ERROR) << "Invalid attempt to send HTTP response for connection_id "
<< connection_id;
return;
}
server_->Send200(connection_id, *data, content_type,
MISSING_TRAFFIC_ANNOTATION);
server_->Close(connection_id);
}
void CefServerImpl::SendRawDataInternal(int connection_id,
std::unique_ptr<std::string> data) {
if (!CEF_CURRENTLY_ON_HT()) {
CEF_POST_TASK_HT(base::BindOnce(&CefServerImpl::SendRawDataInternal, this,
connection_id, std::move(data)));
return;
}
if (!ValidateServer())
return;
if (!GetConnectionInfo(connection_id))
return;
server_->SendRaw(connection_id, *data, MISSING_TRAFFIC_ANNOTATION);
}
void CefServerImpl::SendWebSocketMessageInternal(
int connection_id,
std::unique_ptr<std::string> data) {
if (!CEF_CURRENTLY_ON_HT()) {
CEF_POST_TASK_HT(
base::BindOnce(&CefServerImpl::SendWebSocketMessageInternal, this,
connection_id, std::move(data)));
return;
}
if (!ValidateServer())
return;
ConnectionInfo* info = GetConnectionInfo(connection_id);
if (!info)
return;
if (!info->is_websocket || info->is_websocket_pending) {
LOG(ERROR) << "Invalid attempt to send WebSocket message for connection_id "
<< connection_id;
return;
}
server_->SendOverWebSocket(connection_id, *data, MISSING_TRAFFIC_ANNOTATION);
}
void CefServerImpl::OnConnect(int connection_id) {
CEF_REQUIRE_HT();
CreateConnectionInfo(connection_id);
handler_->OnClientConnected(this, connection_id);
}
void CefServerImpl::OnHttpRequest(
int connection_id,
const net::HttpServerRequestInfo& request_info) {
CEF_REQUIRE_HT();
ConnectionInfo* info = GetConnectionInfo(connection_id);
DCHECK(info);
if (!info)
return;
DCHECK(!info->is_websocket);
handler_->OnHttpRequest(this, connection_id, request_info.peer.ToString(),
CreateRequest(address_, request_info, false));
}
void CefServerImpl::OnWebSocketRequest(
int connection_id,
const net::HttpServerRequestInfo& request_info) {
CEF_REQUIRE_HT();
ConnectionInfo* info = GetConnectionInfo(connection_id);
DCHECK(info);
if (!info)
return;
DCHECK(!info->is_websocket);
info->is_websocket = true;
info->is_websocket_pending = true;
// Will eventually result in a call to ContinueWebSocketRequest.
CefRefPtr<CefCallback> callback =
new AcceptWebSocketCallback(this, connection_id, request_info);
handler_->OnWebSocketRequest(
this, connection_id, request_info.peer.ToString(),
CreateRequest(address_, request_info, true), callback);
}
void CefServerImpl::OnWebSocketMessage(int connection_id, std::string data) {
CEF_REQUIRE_HT();
ConnectionInfo* info = GetConnectionInfo(connection_id);
if (!info)
return;
DCHECK(info->is_websocket);
DCHECK(!info->is_websocket_pending);
handler_->OnWebSocketMessage(this, connection_id, data.data(), data.size());
}
void CefServerImpl::OnClose(int connection_id) {
CEF_REQUIRE_HT();
RemoveConnectionInfo(connection_id);
handler_->OnClientDisconnected(this, connection_id);
}
void CefServerImpl::StartOnUIThread(const std::string& address,
uint16 port,
int backlog) {
CEF_REQUIRE_UIT();
DCHECK(!thread_);
std::unique_ptr<base::Thread> thread(
new base::Thread(base::StringPrintf("%s:%d", address.c_str(), port)));
base::Thread::Options options;
options.message_pump_type = base::MessagePumpType::IO;
if (thread->StartWithOptions(options)) {
// Add a reference that will be released in ShutdownOnUIThread().
AddRef();
thread_ = std::move(thread);
task_runner_ = thread_->task_runner();
CEF_POST_TASK_HT(base::BindOnce(&CefServerImpl::StartOnHandlerThread, this,
address, port, backlog));
}
}
void CefServerImpl::StartOnHandlerThread(const std::string& address,
uint16 port,
int backlog) {
CEF_REQUIRE_HT();
std::unique_ptr<net::ServerSocket> socket(
new net::TCPServerSocket(nullptr, net::NetLogSource()));
if (socket->ListenWithAddressAndPort(address, port, backlog) == net::OK) {
server_.reset(new net::HttpServer(std::move(socket), this));
net::IPEndPoint address;
if (server_->GetLocalAddress(&address) == net::OK)
address_ = address.ToString();
}
handler_->OnServerCreated(this);
if (!server_) {
// Server failed to start.
handler_->OnServerDestroyed(this);
CEF_POST_TASK(CEF_UIT,
base::BindOnce(&CefServerImpl::ShutdownOnUIThread, this));
}
}
void CefServerImpl::ShutdownOnHandlerThread() {
CEF_REQUIRE_HT();
if (server_) {
// Stop the server.
server_.reset();
if (!connection_info_map_.empty()) {
// Clear |connection_info_map_| first so any calls from
// OnClientDisconnected will fail as expected.
ConnectionInfoMap temp_map;
temp_map.swap(connection_info_map_);
// OnClose won't be called for clients that are connected when the server
// shuts down, so send the disconnected notification here.
ConnectionInfoMap::const_iterator it = temp_map.begin();
for (; it != temp_map.end(); ++it) {
handler_->OnClientDisconnected(this, it->first);
}
}
handler_->OnServerDestroyed(this);
}
CEF_POST_TASK(CEF_UIT,
base::BindOnce(&CefServerImpl::ShutdownOnUIThread, this));
}
void CefServerImpl::ShutdownOnUIThread() {
CEF_REQUIRE_UIT();
handler_ = nullptr;
if (thread_) {
// Stop the handler thread as a background task so the UI thread isn't
// blocked.
CEF_POST_BACKGROUND_TASK(BindOnce(
[](std::unique_ptr<base::Thread> thread) {
// Calling PlatformThread::Join() on the UI thread is otherwise
// disallowed.
base::ScopedAllowBaseSyncPrimitivesForTesting
scoped_allow_sync_primitives;
thread.reset();
},
std::move(thread_)));
// Release the reference that was added in StartupOnUIThread().
Release();
}
}
bool CefServerImpl::ValidateServer() const {
CEF_REQUIRE_HT();
if (!server_) {
LOG(ERROR) << "Server is not running";
return false;
}
return true;
}
CefServerImpl::ConnectionInfo* CefServerImpl::CreateConnectionInfo(
int connection_id) {
CEF_REQUIRE_HT();
#if DCHECK_IS_ON()
ConnectionInfoMap::const_iterator it =
connection_info_map_.find(connection_id);
DCHECK(it == connection_info_map_.end());
#endif
ConnectionInfo* info = new ConnectionInfo();
connection_info_map_.insert(
std::make_pair(connection_id, base::WrapUnique(info)));
return info;
}
CefServerImpl::ConnectionInfo* CefServerImpl::GetConnectionInfo(
int connection_id) const {
CEF_REQUIRE_HT();
ConnectionInfoMap::const_iterator it =
connection_info_map_.find(connection_id);
if (it != connection_info_map_.end())
return it->second.get();
LOG(ERROR) << "Invalid connection_id " << connection_id;
return nullptr;
}
void CefServerImpl::RemoveConnectionInfo(int connection_id) {
CEF_REQUIRE_HT();
ConnectionInfoMap::iterator it = connection_info_map_.find(connection_id);
DCHECK(it != connection_info_map_.end());
if (it != connection_info_map_.end())
connection_info_map_.erase(it);
}
bool CefServerImpl::CurrentlyOnHandlerThread() const {
return task_runner_ && task_runner_->BelongsToCurrentThread();
}