cef/tests/ceftests/test_server.cc

533 lines
14 KiB
C++

// Copyright (c) 2020 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 "tests/ceftests/test_server.h"
#include <vector>
#include "include/wrapper/cef_closure_task.h"
#include "include/wrapper/cef_helpers.h"
namespace test_server {
// Must use a different port than server_unittest.cc.
const char kServerAddress[] = "127.0.0.1";
const uint16 kServerPort = 8098;
const char kServerScheme[] = "http";
const char kServerOrigin[] = "http://127.0.0.1:8098";
namespace {
class ServerManager;
ServerManager* g_manager = nullptr;
// True if Stop() has been called.
bool g_stopping = false;
// Created on the UI thread and called on the dedicated server thread.
class ServerHandler : public CefServerHandler {
public:
ServerHandler() {
CefServer::CreateServer(kServerAddress, kServerPort, 10, this);
}
~ServerHandler() override {
DCHECK(!server_);
NotifyServerHandlerDeleted();
}
void Shutdown() { server_->Shutdown(); }
protected:
// CefServerHandler methods:
void OnServerCreated(CefRefPtr<CefServer> server) override {
server_ = server;
NotifyServerCreated(kServerOrigin);
}
void OnServerDestroyed(CefRefPtr<CefServer> server) override {
server_ = nullptr;
NotifyServerDestroyed();
}
void OnClientConnected(CefRefPtr<CefServer> server,
int connection_id) override {
DCHECK(server->HasConnection());
DCHECK(server->IsValidConnection(connection_id));
NotifyClientConnected(server, connection_id);
}
void OnClientDisconnected(CefRefPtr<CefServer> server,
int connection_id) override {
DCHECK(!server->IsValidConnection(connection_id));
NotifyClientDisconnected(server, connection_id);
}
void OnHttpRequest(CefRefPtr<CefServer> server,
int connection_id,
const CefString& client_address,
CefRefPtr<CefRequest> request) override {
NotifyHttpRequest(server, connection_id, client_address, request);
}
void OnWebSocketRequest(CefRefPtr<CefServer> server,
int connection_id,
const CefString& client_address,
CefRefPtr<CefRequest> request,
CefRefPtr<CefCallback> callback) override {}
void OnWebSocketConnected(CefRefPtr<CefServer> server,
int connection_id) override {}
void OnWebSocketMessage(CefRefPtr<CefServer> server,
int connection_id,
const void* data,
size_t data_size) override {}
private:
static void NotifyServerCreated(const std::string& server_origin);
static void NotifyServerDestroyed();
static void NotifyServerHandlerDeleted();
static void NotifyClientConnected(CefRefPtr<CefServer> server,
int connection_id);
static void NotifyClientDisconnected(CefRefPtr<CefServer> server,
int connection_id);
static void NotifyHttpRequest(CefRefPtr<CefServer> server,
int connection_id,
const CefString& client_address,
CefRefPtr<CefRequest> request);
CefRefPtr<CefServer> server_;
IMPLEMENT_REFCOUNTING(ServerHandler);
DISALLOW_COPY_AND_ASSIGN(ServerHandler);
};
// Only accessed on the UI thread. Deletes itself after the server is stopped.
class ServerManager {
public:
ServerManager() {
CEF_REQUIRE_UI_THREAD();
DCHECK(!g_manager);
g_manager = this;
}
~ServerManager() {
CEF_REQUIRE_UI_THREAD();
DCHECK(observer_list_.empty());
DCHECK(start_callback_list_.empty());
DCHECK(stop_callback_.is_null());
g_manager = nullptr;
}
void Start(const StartDoneCallback& callback) {
CEF_REQUIRE_UI_THREAD();
if (!origin_.empty()) {
// The server is already running.
callback.Run(origin_);
return;
}
// If tests run in parallel, and the server is starting, then there may be
// multiple pending callbacks.
start_callback_list_.push_back(callback);
// Only create the handler a single time.
if (!handler_) {
handler_ = new ServerHandler();
}
}
void Stop(const DoneCallback& callback) {
CEF_REQUIRE_UI_THREAD();
if (!handler_) {
// The server is not currently running.
callback.Run();
return;
}
// Only 1 stop callback supported.
DCHECK(stop_callback_.is_null());
stop_callback_ = callback;
handler_->Shutdown();
}
void AddObserver(Observer* observer) {
CEF_REQUIRE_UI_THREAD();
observer_list_.push_back(observer);
}
void RemoveObserver(Observer* observer) {
CEF_REQUIRE_UI_THREAD();
bool found = false;
ObserverList::iterator it = observer_list_.begin();
for (; it != observer_list_.end(); ++it) {
if (*it == observer) {
observer_list_.erase(it);
found = true;
break;
}
}
DCHECK(found);
}
void NotifyServerCreated(const std::string& server_origin) {
CEF_REQUIRE_UI_THREAD();
DCHECK(origin_.empty());
origin_ = server_origin;
StartDoneCallbackList::const_iterator it = start_callback_list_.begin();
for (; it != start_callback_list_.end(); ++it) {
(*it).Run(origin_);
}
start_callback_list_.clear();
}
void NotifyServerDestroyed() {
CEF_REQUIRE_UI_THREAD();
origin_.clear();
handler_ = nullptr;
}
// All server-related objects have been torn down.
void NotifyServerHandlerDeleted() {
CEF_REQUIRE_UI_THREAD();
DCHECK(!stop_callback_.is_null());
stop_callback_.Run();
stop_callback_.Reset();
delete this;
}
void NotifyClientConnected(CefRefPtr<CefServer> server, int connection_id) {
CEF_REQUIRE_UI_THREAD();
if (observer_list_.empty())
return;
// Use a copy in case |observer_list_| is modified during iteration.
ObserverList list = observer_list_;
ObserverList::const_iterator it = list.begin();
for (; it != list.end(); ++it) {
if ((*it)->OnClientConnected(server, connection_id)) {
break;
}
}
}
void NotifyClientDisconnected(CefRefPtr<CefServer> server,
int connection_id) {
CEF_REQUIRE_UI_THREAD();
if (observer_list_.empty())
return;
// Use a copy in case |observer_list_| is modified during iteration.
ObserverList list = observer_list_;
ObserverList::const_iterator it = list.begin();
for (; it != list.end(); ++it) {
if ((*it)->OnClientDisconnected(server, connection_id)) {
break;
}
}
}
void NotifyHttpRequest(CefRefPtr<CefServer> server,
int connection_id,
const CefString& client_address,
CefRefPtr<CefRequest> request) {
CEF_REQUIRE_UI_THREAD();
// TODO(chrome-runtime): Debug why favicon requests don't always have the
// correct resource type.
const std::string& url = request->GetURL();
if (request->GetResourceType() == RT_FAVICON ||
url.find("/favicon.ico") != std::string::npos) {
// We don't currently handle favicon requests.
server->SendHttp404Response(connection_id);
return;
}
DCHECK(!observer_list_.empty()) << url;
// Use a copy in case |observer_list_| is modified during iteration.
ObserverList list = observer_list_;
bool handled = false;
ObserverList::const_iterator it = list.begin();
for (; it != list.end(); ++it) {
if ((*it)->OnHttpRequest(server, connection_id, client_address,
request)) {
handled = true;
break;
}
}
if (!handled) {
server->SendHttp500Response(connection_id, "Unhandled request.");
}
}
private:
CefRefPtr<ServerHandler> handler_;
std::string origin_;
typedef std::vector<StartDoneCallback> StartDoneCallbackList;
StartDoneCallbackList start_callback_list_;
DoneCallback stop_callback_;
typedef std::vector<Observer*> ObserverList;
ObserverList observer_list_;
DISALLOW_COPY_AND_ASSIGN(ServerManager);
};
ServerManager* GetServerManager() {
return g_manager;
}
ServerManager* GetOrCreateServerManager() {
if (!g_manager) {
new ServerManager();
DCHECK(g_manager);
}
return g_manager;
}
// static
void ServerHandler::NotifyServerCreated(const std::string& server_origin) {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI,
base::Bind(ServerHandler::NotifyServerCreated, server_origin));
return;
}
GetServerManager()->NotifyServerCreated(server_origin);
}
// static
void ServerHandler::NotifyServerDestroyed() {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI, base::Bind(ServerHandler::NotifyServerDestroyed));
return;
}
GetServerManager()->NotifyServerDestroyed();
}
// static
void ServerHandler::NotifyServerHandlerDeleted() {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI, base::Bind(ServerHandler::NotifyServerHandlerDeleted));
return;
}
GetServerManager()->NotifyServerHandlerDeleted();
}
// static
void ServerHandler::NotifyClientConnected(CefRefPtr<CefServer> server,
int connection_id) {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI, base::Bind(ServerHandler::NotifyClientConnected, server,
connection_id));
return;
}
GetServerManager()->NotifyClientConnected(server, connection_id);
}
// static
void ServerHandler::NotifyClientDisconnected(CefRefPtr<CefServer> server,
int connection_id) {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI, base::Bind(ServerHandler::NotifyClientDisconnected,
server, connection_id));
return;
}
GetServerManager()->NotifyClientDisconnected(server, connection_id);
}
// static
void ServerHandler::NotifyHttpRequest(CefRefPtr<CefServer> server,
int connection_id,
const CefString& client_address,
CefRefPtr<CefRequest> request) {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI, base::Bind(ServerHandler::NotifyHttpRequest, server,
connection_id, client_address, request));
return;
}
GetServerManager()->NotifyHttpRequest(server, connection_id, client_address,
request);
}
// May be created on any thread but will be destroyed on the UI thread.
class ObserverRegistration : public CefRegistration {
public:
explicit ObserverRegistration(Observer* const observer)
: observer_(observer) {
DCHECK(observer_);
}
~ObserverRegistration() override {
CEF_REQUIRE_UI_THREAD();
ServerManager* manager = GetServerManager();
if (manager) {
manager->RemoveObserver(observer_);
observer_->OnUnregistered();
}
}
void Initialize() {
CEF_REQUIRE_UI_THREAD();
GetOrCreateServerManager()->AddObserver(observer_);
observer_->OnRegistered();
}
private:
Observer* const observer_;
IMPLEMENT_REFCOUNTING_DELETE_ON_UIT(ObserverRegistration);
DISALLOW_COPY_AND_ASSIGN(ObserverRegistration);
};
void InitializeRegistration(CefRefPtr<ObserverRegistration> registration,
const DoneCallback& callback) {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI,
base::Bind(InitializeRegistration, registration, callback));
return;
}
DCHECK(!g_stopping);
registration->Initialize();
if (!callback.is_null())
callback.Run();
}
} // namespace
void Start(const StartDoneCallback& callback) {
DCHECK(!callback.is_null());
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI, base::Bind(Start, callback));
return;
}
DCHECK(!g_stopping);
GetOrCreateServerManager()->Start(callback);
}
void Stop(const DoneCallback& callback) {
DCHECK(!callback.is_null());
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI, base::Bind(Stop, callback));
return;
}
// Stop will be called one time on test framework shutdown.
DCHECK(!g_stopping);
g_stopping = true;
ServerManager* manager = GetServerManager();
if (manager) {
manager->Stop(callback);
} else {
callback.Run();
}
}
CefRefPtr<CefRegistration> AddObserver(Observer* observer,
const DoneCallback& callback) {
DCHECK(observer);
CefRefPtr<ObserverRegistration> registration =
new ObserverRegistration(observer);
InitializeRegistration(registration, callback);
return registration.get();
}
CefRefPtr<CefRegistration> AddObserverAndStart(
Observer* observer,
const StartDoneCallback& callback) {
return AddObserver(observer, base::Bind(Start, callback));
}
void SendResponse(CefRefPtr<CefServer> server,
int connection_id,
CefRefPtr<CefResponse> response,
const std::string& response_data) {
const int response_code = response->GetStatus();
const CefString& content_type = response->GetMimeType();
int64 content_length = static_cast<int64>(response_data.size());
CefResponse::HeaderMap extra_headers;
response->GetHeaderMap(extra_headers);
server->SendHttpResponse(connection_id, response_code, content_type,
content_length, extra_headers);
if (content_length != 0) {
server->SendRawData(connection_id, response_data.data(),
response_data.size());
server->CloseConnection(connection_id);
}
}
// ObserverHelper
ObserverHelper::ObserverHelper() : weak_ptr_factory_(this) {
CEF_REQUIRE_UI_THREAD();
}
ObserverHelper::~ObserverHelper() {
DCHECK(state_ == State::NONE);
}
void ObserverHelper::Initialize() {
CEF_REQUIRE_UI_THREAD();
DCHECK(state_ == State::NONE);
state_ = State::INITIALIZING;
registration_ = AddObserverAndStart(
this,
base::Bind(&ObserverHelper::OnStartDone, weak_ptr_factory_.GetWeakPtr()));
}
void ObserverHelper::Shutdown() {
CEF_REQUIRE_UI_THREAD();
DCHECK(state_ == State::INITIALIZED);
state_ = State::SHUTTINGDOWN;
registration_ = nullptr;
}
void ObserverHelper::OnStartDone(const std::string& server_origin) {
DCHECK(state_ == State::INITIALIZING);
state_ = State::INITIALIZED;
OnInitialized(server_origin);
}
void ObserverHelper::OnRegistered() {
DCHECK(state_ == State::INITIALIZING);
}
void ObserverHelper::OnUnregistered() {
DCHECK(state_ == State::SHUTTINGDOWN);
state_ = State::NONE;
OnShutdown();
}
} // namespace test_server