mirror of
				https://bitbucket.org/chromiumembedded/cef
				synced 2025-06-05 21:39:12 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			388 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			388 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // 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 <memory>
 | |
| #include <string>
 | |
| 
 | |
| #include "include/base/cef_callback.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:
 | |
|   using CompleteCallback = base::OnceCallback<void(bool /* success */)>;
 | |
| 
 | |
|   ServerHandler() {}
 | |
| 
 | |
|   // |complete_callback| will be executed on the UI thread after completion.
 | |
|   void StartServer(int port, CompleteCallback complete_callback) {
 | |
|     CEF_REQUIRE_UI_THREAD();
 | |
|     DCHECK(!server_);
 | |
|     DCHECK(port >= 1025 && port <= 65535);
 | |
|     port_ = port;
 | |
|     complete_callback_ = std::move(complete_callback);
 | |
|     CefServer::CreateServer(kServerAddress, port, kServerBacklog, this);
 | |
|   }
 | |
| 
 | |
|   // |complete_callback| will be executed on the UI thread after completion.
 | |
|   void StopServer(CompleteCallback complete_callback) {
 | |
|     CEF_REQUIRE_UI_THREAD();
 | |
|     DCHECK(server_);
 | |
|     complete_callback_ = std::move(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::BindOnce(&ServerHandler::RunCompleteCallback,
 | |
|                                          this, success));
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (!complete_callback_.is_null()) {
 | |
|       std::move(complete_callback_).Run(success);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   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::BindOnce(&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::BindOnce(
 | |
|         &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 nullptr;
 | |
|   }
 | |
| 
 | |
|   // 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
 |