mirror of
https://bitbucket.org/chromiumembedded/cef
synced 2025-06-05 21:39:12 +02:00
Add CefServer API for handling HTTP/WebSocket requests (issue #2278)
This commit is contained in:
387
tests/cefclient/browser/server_test.cc
Normal file
387
tests/cefclient/browser/server_test.cc
Normal file
@@ -0,0 +1,387 @@
|
||||
// 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 "tests/cefclient/browser/server_test.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#include "include/base/cef_bind.h"
|
||||
#include "include/base/cef_weak_ptr.h"
|
||||
#include "include/cef_parser.h"
|
||||
#include "include/cef_server.h"
|
||||
#include "include/wrapper/cef_closure_task.h"
|
||||
#include "tests/shared/browser/resource_util.h"
|
||||
|
||||
namespace client {
|
||||
namespace server_test {
|
||||
|
||||
namespace {
|
||||
|
||||
// Application-specific error codes.
|
||||
const int kMessageFormatError = 1;
|
||||
const int kActionStateError = 1;
|
||||
|
||||
// JSON dictionary keys.
|
||||
const char kActionKey[] = "action";
|
||||
const char kResultKey[] = "result";
|
||||
const char kPortKey[] = "port";
|
||||
const char kStatusKey[] = "status";
|
||||
const char kMessageKey[] = "message";
|
||||
|
||||
// Required URL for cefQuery execution.
|
||||
const char kTestUrl[] = "http://tests/server";
|
||||
|
||||
// Server default values.
|
||||
const char kServerAddress[] = "127.0.0.1";
|
||||
const int kServerPortDefault = 8099;
|
||||
const int kServerBacklog = 10;
|
||||
const char kDefaultPath[] = "websocket.html";
|
||||
|
||||
// Handles the HTTP/WebSocket server.
|
||||
class ServerHandler : public CefServerHandler {
|
||||
public:
|
||||
typedef base::Callback<void(bool /* success */)> CompleteCallback;
|
||||
|
||||
ServerHandler() {}
|
||||
|
||||
// |complete_callback| will be executed on the UI thread after completion.
|
||||
void StartServer(int port, const CompleteCallback& complete_callback) {
|
||||
CEF_REQUIRE_UI_THREAD();
|
||||
DCHECK(!server_);
|
||||
DCHECK(port >= 1025 && port <= 65535);
|
||||
port_ = port;
|
||||
complete_callback_ = complete_callback;
|
||||
CefServer::CreateServer(kServerAddress, port, kServerBacklog, this);
|
||||
}
|
||||
|
||||
// |complete_callback| will be executed on the UI thread after completion.
|
||||
void StopServer(const CompleteCallback& complete_callback) {
|
||||
CEF_REQUIRE_UI_THREAD();
|
||||
DCHECK(server_);
|
||||
complete_callback_ = complete_callback;
|
||||
server_->Shutdown();
|
||||
}
|
||||
|
||||
// CefServerHandler methods are called on the server thread.
|
||||
|
||||
void OnServerCreated(CefRefPtr<CefServer> server) override {
|
||||
DCHECK(!server_);
|
||||
server_ = server;
|
||||
RunCompleteCallback(server->IsRunning());
|
||||
}
|
||||
|
||||
void OnServerDestroyed(CefRefPtr<CefServer> server) override {
|
||||
DCHECK(server_);
|
||||
server_ = nullptr;
|
||||
RunCompleteCallback(true);
|
||||
}
|
||||
|
||||
void OnClientConnected(CefRefPtr<CefServer> server,
|
||||
int connection_id) override {}
|
||||
|
||||
void OnClientDisconnected(CefRefPtr<CefServer> server,
|
||||
int connection_id) override {}
|
||||
|
||||
void OnHttpRequest(CefRefPtr<CefServer> server,
|
||||
int connection_id,
|
||||
const CefString& client_address,
|
||||
CefRefPtr<CefRequest> request) override {
|
||||
// Parse the request URL and retrieve the path without leading slash.
|
||||
CefURLParts url_parts;
|
||||
CefParseURL(request->GetURL(), url_parts);
|
||||
std::string path = CefString(&url_parts.path);
|
||||
if (!path.empty() && path[0] == '/')
|
||||
path = path.substr(1);
|
||||
|
||||
if (path.empty())
|
||||
path = kDefaultPath;
|
||||
|
||||
std::string mime_type;
|
||||
const size_t sep = path.find_last_of(".");
|
||||
if (sep != std::string::npos) {
|
||||
// Determine the mime type based on the extension.
|
||||
mime_type = CefGetMimeType(path.substr(sep + 1));
|
||||
} else {
|
||||
// No extension. Assume html.
|
||||
path += ".html";
|
||||
}
|
||||
if (mime_type.empty())
|
||||
mime_type = "text/html";
|
||||
|
||||
CefRefPtr<CefStreamReader> stream;
|
||||
CefResponse::HeaderMap extra_headers;
|
||||
|
||||
if (path == "request.html") {
|
||||
// Return the request contents.
|
||||
stream = test_runner::GetDumpResponse(request, extra_headers);
|
||||
}
|
||||
|
||||
if (!stream) {
|
||||
// Load any resource supported by cefclient.
|
||||
stream = GetBinaryResourceReader(path.c_str());
|
||||
}
|
||||
|
||||
if (stream) {
|
||||
SendHttpResponseStream(server, connection_id, mime_type, stream,
|
||||
extra_headers);
|
||||
} else {
|
||||
server->SendHttp404Response(connection_id);
|
||||
}
|
||||
}
|
||||
|
||||
void OnWebSocketRequest(CefRefPtr<CefServer> server,
|
||||
int connection_id,
|
||||
const CefString& client_address,
|
||||
CefRefPtr<CefRequest> request,
|
||||
CefRefPtr<CefCallback> callback) override {
|
||||
// Always accept WebSocket connections.
|
||||
callback->Continue();
|
||||
}
|
||||
|
||||
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 {
|
||||
// Echo the reverse of the message.
|
||||
std::string message(static_cast<const char*>(data), data_size);
|
||||
std::reverse(message.begin(), message.end());
|
||||
|
||||
server->SendWebSocketMessage(connection_id, message.data(), message.size());
|
||||
}
|
||||
|
||||
int port() const { return port_; }
|
||||
|
||||
private:
|
||||
void RunCompleteCallback(bool success) {
|
||||
if (!CefCurrentlyOn(TID_UI)) {
|
||||
CefPostTask(TID_UI, base::Bind(&ServerHandler::RunCompleteCallback, this,
|
||||
success));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!complete_callback_.is_null()) {
|
||||
complete_callback_.Run(success);
|
||||
complete_callback_.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
static void SendHttpResponseStream(CefRefPtr<CefServer> server,
|
||||
int connection_id,
|
||||
const std::string& mime_type,
|
||||
CefRefPtr<CefStreamReader> stream,
|
||||
CefResponse::HeaderMap extra_headers) {
|
||||
// Determine the stream size.
|
||||
stream->Seek(0, SEEK_END);
|
||||
int64 content_length = stream->Tell();
|
||||
stream->Seek(0, SEEK_SET);
|
||||
|
||||
// Send response headers.
|
||||
server->SendHttpResponse(connection_id, 200, mime_type, content_length,
|
||||
extra_headers);
|
||||
|
||||
// Send stream contents.
|
||||
char buffer[8192];
|
||||
size_t read;
|
||||
do {
|
||||
read = stream->Read(buffer, 1, sizeof(buffer));
|
||||
if (read > 0)
|
||||
server->SendRawData(connection_id, buffer, read);
|
||||
} while (!stream->Eof() && read != 0);
|
||||
|
||||
// Close the connection.
|
||||
server->CloseConnection(connection_id);
|
||||
}
|
||||
|
||||
CefRefPtr<CefServer> server_;
|
||||
|
||||
// The below members are only accessed on the UI thread.
|
||||
int port_;
|
||||
CompleteCallback complete_callback_;
|
||||
|
||||
IMPLEMENT_REFCOUNTING(ServerHandler);
|
||||
DISALLOW_COPY_AND_ASSIGN(ServerHandler);
|
||||
};
|
||||
|
||||
// Handle messages in the browser process.
|
||||
class Handler : public CefMessageRouterBrowserSide::Handler {
|
||||
public:
|
||||
Handler() : weak_ptr_factory_(this) {}
|
||||
|
||||
virtual ~Handler() {
|
||||
if (handler_) {
|
||||
handler_->StopServer(ServerHandler::CompleteCallback());
|
||||
handler_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Called due to cefQuery execution in server.html.
|
||||
virtual bool OnQuery(CefRefPtr<CefBrowser> browser,
|
||||
CefRefPtr<CefFrame> frame,
|
||||
int64 query_id,
|
||||
const CefString& request,
|
||||
bool persistent,
|
||||
CefRefPtr<Callback> callback) OVERRIDE {
|
||||
CEF_REQUIRE_UI_THREAD();
|
||||
|
||||
// Only handle messages from the test URL.
|
||||
const std::string& url = frame->GetURL();
|
||||
if (url.find(kTestUrl) != 0)
|
||||
return false;
|
||||
|
||||
// Parse |request| as a JSON dictionary.
|
||||
CefRefPtr<CefDictionaryValue> request_dict = ParseJSON(request);
|
||||
if (!request_dict) {
|
||||
callback->Failure(kMessageFormatError, "Incorrect message format");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!VerifyKey(request_dict, kActionKey, VTYPE_STRING, callback))
|
||||
return true;
|
||||
|
||||
const std::string& action = request_dict->GetString(kActionKey);
|
||||
if (action == "query") {
|
||||
HandleQueryAction(request_dict, callback);
|
||||
} else if (action == "start") {
|
||||
HandleStartAction(request_dict, callback);
|
||||
} else if (action == "stop") {
|
||||
HandleStopAction(request_dict, callback);
|
||||
} else {
|
||||
callback->Failure(kMessageFormatError, "Unrecognized action: " + action);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
// Return current server status.
|
||||
void HandleQueryAction(CefRefPtr<CefDictionaryValue> request_dict,
|
||||
CefRefPtr<Callback> callback) {
|
||||
CefRefPtr<CefDictionaryValue> result_dict = CefDictionaryValue::Create();
|
||||
if (handler_) {
|
||||
result_dict->SetInt(kPortKey, handler_->port());
|
||||
result_dict->SetString(kStatusKey, "running");
|
||||
} else {
|
||||
result_dict->SetInt(kPortKey, kServerPortDefault);
|
||||
result_dict->SetString(kStatusKey, "stopped");
|
||||
}
|
||||
SendResponse(callback, true, result_dict);
|
||||
}
|
||||
|
||||
// Start the server.
|
||||
void HandleStartAction(CefRefPtr<CefDictionaryValue> request_dict,
|
||||
CefRefPtr<Callback> callback) {
|
||||
if (handler_) {
|
||||
callback->Failure(kActionStateError, "Server is currently running");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!VerifyKey(request_dict, kPortKey, VTYPE_INT, callback))
|
||||
return;
|
||||
|
||||
const int port = request_dict->GetInt(kPortKey);
|
||||
if (port < 8000 || port > 65535) {
|
||||
callback->Failure(kMessageFormatError, "Invalid port number specified");
|
||||
return;
|
||||
}
|
||||
|
||||
handler_ = new ServerHandler();
|
||||
|
||||
// Start the server. OnComplete will be executed upon completion.
|
||||
handler_->StartServer(port,
|
||||
base::Bind(&Handler::OnStartComplete,
|
||||
weak_ptr_factory_.GetWeakPtr(), callback));
|
||||
}
|
||||
|
||||
// Stop the server.
|
||||
void HandleStopAction(CefRefPtr<CefDictionaryValue> request_dict,
|
||||
CefRefPtr<Callback> callback) {
|
||||
if (!handler_) {
|
||||
callback->Failure(kActionStateError, "Server is not currently running");
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop the server. OnComplete will be executed upon completion.
|
||||
handler_->StopServer(base::Bind(&Handler::OnStopComplete,
|
||||
weak_ptr_factory_.GetWeakPtr(), callback));
|
||||
|
||||
handler_ = nullptr;
|
||||
}
|
||||
|
||||
// Server start completed.
|
||||
void OnStartComplete(CefRefPtr<Callback> callback, bool success) {
|
||||
CEF_REQUIRE_UI_THREAD();
|
||||
CefRefPtr<CefDictionaryValue> result_dict = CefDictionaryValue::Create();
|
||||
if (!success) {
|
||||
handler_ = nullptr;
|
||||
result_dict->SetString(kMessageKey, "Server failed to start.");
|
||||
}
|
||||
SendResponse(callback, success, result_dict);
|
||||
}
|
||||
|
||||
// Server stop completed.
|
||||
void OnStopComplete(CefRefPtr<Callback> callback, bool success) {
|
||||
CEF_REQUIRE_UI_THREAD();
|
||||
CefRefPtr<CefDictionaryValue> result_dict = CefDictionaryValue::Create();
|
||||
if (!success) {
|
||||
result_dict->SetString(kMessageKey, "Server failed to stop.");
|
||||
}
|
||||
SendResponse(callback, success, result_dict);
|
||||
}
|
||||
|
||||
// Send a response in the format expected by server.html.
|
||||
static void SendResponse(CefRefPtr<Callback> callback,
|
||||
bool success,
|
||||
CefRefPtr<CefDictionaryValue> result_dict) {
|
||||
if (!result_dict) {
|
||||
result_dict = CefDictionaryValue::Create();
|
||||
}
|
||||
result_dict->SetString(kResultKey, success ? "success" : "failure");
|
||||
CefRefPtr<CefValue> value = CefValue::Create();
|
||||
value->SetDictionary(result_dict);
|
||||
const std::string& response = CefWriteJSON(value, JSON_WRITER_DEFAULT);
|
||||
callback->Success(response);
|
||||
}
|
||||
|
||||
// Convert a JSON string to a dictionary value.
|
||||
static CefRefPtr<CefDictionaryValue> ParseJSON(const CefString& string) {
|
||||
CefRefPtr<CefValue> value = CefParseJSON(string, JSON_PARSER_RFC);
|
||||
if (value.get() && value->GetType() == VTYPE_DICTIONARY)
|
||||
return value->GetDictionary();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Verify that |key| exists in |dictionary| and has type |value_type|. Fails
|
||||
// |callback| and returns false on failure.
|
||||
static bool VerifyKey(CefRefPtr<CefDictionaryValue> dictionary,
|
||||
const char* key,
|
||||
cef_value_type_t value_type,
|
||||
CefRefPtr<Callback> callback) {
|
||||
if (!dictionary->HasKey(key) || dictionary->GetType(key) != value_type) {
|
||||
callback->Failure(
|
||||
kMessageFormatError,
|
||||
"Missing or incorrectly formatted message key: " + std::string(key));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Non-nullptr while the server is running.
|
||||
CefRefPtr<ServerHandler> handler_;
|
||||
|
||||
// Must be the last member.
|
||||
base::WeakPtrFactory<Handler> weak_ptr_factory_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void CreateMessageHandlers(test_runner::MessageHandlerSet& handlers) {
|
||||
handlers.insert(new Handler());
|
||||
}
|
||||
|
||||
} // namespace server_test
|
||||
} // namespace client
|
Reference in New Issue
Block a user