1 // Copyright (c) 2017 The Chromium Embedded Framework Authors. All rights
2 // reserved. Use of this source code is governed by a BSD-style license that
3 // can be found in the LICENSE file.
4
5 #include "tests/cefclient/browser/server_test.h"
6
7 #include <algorithm>
8 #include <memory>
9 #include <string>
10
11 #include "include/base/cef_callback.h"
12 #include "include/base/cef_weak_ptr.h"
13 #include "include/cef_parser.h"
14 #include "include/cef_server.h"
15 #include "include/wrapper/cef_closure_task.h"
16 #include "tests/shared/browser/resource_util.h"
17
18 namespace client {
19 namespace server_test {
20
21 namespace {
22
23 // Application-specific error codes.
24 const int kMessageFormatError = 1;
25 const int kActionStateError = 1;
26
27 // JSON dictionary keys.
28 const char kActionKey[] = "action";
29 const char kResultKey[] = "result";
30 const char kPortKey[] = "port";
31 const char kStatusKey[] = "status";
32 const char kMessageKey[] = "message";
33
34 // Required URL for cefQuery execution.
35 const char kTestUrl[] = "http://tests/server";
36
37 // Server default values.
38 const char kServerAddress[] = "127.0.0.1";
39 const int kServerPortDefault = 8099;
40 const int kServerBacklog = 10;
41 const char kDefaultPath[] = "websocket.html";
42
43 // Handles the HTTP/WebSocket server.
44 class ServerHandler : public CefServerHandler {
45 public:
46 using CompleteCallback = base::OnceCallback<void(bool /* success */)>;
47
ServerHandler()48 ServerHandler() {}
49
50 // |complete_callback| will be executed on the UI thread after completion.
StartServer(int port,CompleteCallback complete_callback)51 void StartServer(int port, CompleteCallback complete_callback) {
52 CEF_REQUIRE_UI_THREAD();
53 DCHECK(!server_);
54 DCHECK(port >= 1025 && port <= 65535);
55 port_ = port;
56 complete_callback_ = std::move(complete_callback);
57 CefServer::CreateServer(kServerAddress, port, kServerBacklog, this);
58 }
59
60 // |complete_callback| will be executed on the UI thread after completion.
StopServer(CompleteCallback complete_callback)61 void StopServer(CompleteCallback complete_callback) {
62 CEF_REQUIRE_UI_THREAD();
63 DCHECK(server_);
64 complete_callback_ = std::move(complete_callback);
65 server_->Shutdown();
66 }
67
68 // CefServerHandler methods are called on the server thread.
69
OnServerCreated(CefRefPtr<CefServer> server)70 void OnServerCreated(CefRefPtr<CefServer> server) override {
71 DCHECK(!server_);
72 server_ = server;
73 RunCompleteCallback(server->IsRunning());
74 }
75
OnServerDestroyed(CefRefPtr<CefServer> server)76 void OnServerDestroyed(CefRefPtr<CefServer> server) override {
77 DCHECK(server_);
78 server_ = nullptr;
79 RunCompleteCallback(true);
80 }
81
OnClientConnected(CefRefPtr<CefServer> server,int connection_id)82 void OnClientConnected(CefRefPtr<CefServer> server,
83 int connection_id) override {}
84
OnClientDisconnected(CefRefPtr<CefServer> server,int connection_id)85 void OnClientDisconnected(CefRefPtr<CefServer> server,
86 int connection_id) override {}
87
OnHttpRequest(CefRefPtr<CefServer> server,int connection_id,const CefString & client_address,CefRefPtr<CefRequest> request)88 void OnHttpRequest(CefRefPtr<CefServer> server,
89 int connection_id,
90 const CefString& client_address,
91 CefRefPtr<CefRequest> request) override {
92 // Parse the request URL and retrieve the path without leading slash.
93 CefURLParts url_parts;
94 CefParseURL(request->GetURL(), url_parts);
95 std::string path = CefString(&url_parts.path);
96 if (!path.empty() && path[0] == '/')
97 path = path.substr(1);
98
99 if (path.empty())
100 path = kDefaultPath;
101
102 std::string mime_type;
103 const size_t sep = path.find_last_of(".");
104 if (sep != std::string::npos) {
105 // Determine the mime type based on the extension.
106 mime_type = CefGetMimeType(path.substr(sep + 1));
107 } else {
108 // No extension. Assume html.
109 path += ".html";
110 }
111 if (mime_type.empty())
112 mime_type = "text/html";
113
114 CefRefPtr<CefStreamReader> stream;
115 CefResponse::HeaderMap extra_headers;
116
117 if (path == "request.html") {
118 // Return the request contents.
119 stream = test_runner::GetDumpResponse(request, extra_headers);
120 }
121
122 if (!stream) {
123 // Load any resource supported by cefclient.
124 stream = GetBinaryResourceReader(path.c_str());
125 }
126
127 if (stream) {
128 SendHttpResponseStream(server, connection_id, mime_type, stream,
129 extra_headers);
130 } else {
131 server->SendHttp404Response(connection_id);
132 }
133 }
134
OnWebSocketRequest(CefRefPtr<CefServer> server,int connection_id,const CefString & client_address,CefRefPtr<CefRequest> request,CefRefPtr<CefCallback> callback)135 void OnWebSocketRequest(CefRefPtr<CefServer> server,
136 int connection_id,
137 const CefString& client_address,
138 CefRefPtr<CefRequest> request,
139 CefRefPtr<CefCallback> callback) override {
140 // Always accept WebSocket connections.
141 callback->Continue();
142 }
143
OnWebSocketConnected(CefRefPtr<CefServer> server,int connection_id)144 void OnWebSocketConnected(CefRefPtr<CefServer> server,
145 int connection_id) override {}
146
OnWebSocketMessage(CefRefPtr<CefServer> server,int connection_id,const void * data,size_t data_size)147 void OnWebSocketMessage(CefRefPtr<CefServer> server,
148 int connection_id,
149 const void* data,
150 size_t data_size) override {
151 // Echo the reverse of the message.
152 std::string message(static_cast<const char*>(data), data_size);
153 std::reverse(message.begin(), message.end());
154
155 server->SendWebSocketMessage(connection_id, message.data(), message.size());
156 }
157
port() const158 int port() const { return port_; }
159
160 private:
RunCompleteCallback(bool success)161 void RunCompleteCallback(bool success) {
162 if (!CefCurrentlyOn(TID_UI)) {
163 CefPostTask(TID_UI, base::BindOnce(&ServerHandler::RunCompleteCallback,
164 this, success));
165 return;
166 }
167
168 if (!complete_callback_.is_null()) {
169 std::move(complete_callback_).Run(success);
170 }
171 }
172
SendHttpResponseStream(CefRefPtr<CefServer> server,int connection_id,const std::string & mime_type,CefRefPtr<CefStreamReader> stream,CefResponse::HeaderMap extra_headers)173 static void SendHttpResponseStream(CefRefPtr<CefServer> server,
174 int connection_id,
175 const std::string& mime_type,
176 CefRefPtr<CefStreamReader> stream,
177 CefResponse::HeaderMap extra_headers) {
178 // Determine the stream size.
179 stream->Seek(0, SEEK_END);
180 int64 content_length = stream->Tell();
181 stream->Seek(0, SEEK_SET);
182
183 // Send response headers.
184 server->SendHttpResponse(connection_id, 200, mime_type, content_length,
185 extra_headers);
186
187 // Send stream contents.
188 char buffer[8192];
189 size_t read;
190 do {
191 read = stream->Read(buffer, 1, sizeof(buffer));
192 if (read > 0)
193 server->SendRawData(connection_id, buffer, read);
194 } while (!stream->Eof() && read != 0);
195
196 // Close the connection.
197 server->CloseConnection(connection_id);
198 }
199
200 CefRefPtr<CefServer> server_;
201
202 // The below members are only accessed on the UI thread.
203 int port_;
204 CompleteCallback complete_callback_;
205
206 IMPLEMENT_REFCOUNTING(ServerHandler);
207 DISALLOW_COPY_AND_ASSIGN(ServerHandler);
208 };
209
210 // Handle messages in the browser process.
211 class Handler : public CefMessageRouterBrowserSide::Handler {
212 public:
Handler()213 Handler() : weak_ptr_factory_(this) {}
214
~Handler()215 virtual ~Handler() {
216 if (handler_) {
217 handler_->StopServer(ServerHandler::CompleteCallback());
218 handler_ = nullptr;
219 }
220 }
221
222 // Called due to cefQuery execution in server.html.
OnQuery(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,int64 query_id,const CefString & request,bool persistent,CefRefPtr<Callback> callback)223 virtual bool OnQuery(CefRefPtr<CefBrowser> browser,
224 CefRefPtr<CefFrame> frame,
225 int64 query_id,
226 const CefString& request,
227 bool persistent,
228 CefRefPtr<Callback> callback) override {
229 CEF_REQUIRE_UI_THREAD();
230
231 // Only handle messages from the test URL.
232 const std::string& url = frame->GetURL();
233 if (url.find(kTestUrl) != 0)
234 return false;
235
236 // Parse |request| as a JSON dictionary.
237 CefRefPtr<CefDictionaryValue> request_dict = ParseJSON(request);
238 if (!request_dict) {
239 callback->Failure(kMessageFormatError, "Incorrect message format");
240 return true;
241 }
242
243 if (!VerifyKey(request_dict, kActionKey, VTYPE_STRING, callback))
244 return true;
245
246 const std::string& action = request_dict->GetString(kActionKey);
247 if (action == "query") {
248 HandleQueryAction(request_dict, callback);
249 } else if (action == "start") {
250 HandleStartAction(request_dict, callback);
251 } else if (action == "stop") {
252 HandleStopAction(request_dict, callback);
253 } else {
254 callback->Failure(kMessageFormatError, "Unrecognized action: " + action);
255 }
256
257 return true;
258 }
259
260 private:
261 // Return current server status.
HandleQueryAction(CefRefPtr<CefDictionaryValue> request_dict,CefRefPtr<Callback> callback)262 void HandleQueryAction(CefRefPtr<CefDictionaryValue> request_dict,
263 CefRefPtr<Callback> callback) {
264 CefRefPtr<CefDictionaryValue> result_dict = CefDictionaryValue::Create();
265 if (handler_) {
266 result_dict->SetInt(kPortKey, handler_->port());
267 result_dict->SetString(kStatusKey, "running");
268 } else {
269 result_dict->SetInt(kPortKey, kServerPortDefault);
270 result_dict->SetString(kStatusKey, "stopped");
271 }
272 SendResponse(callback, true, result_dict);
273 }
274
275 // Start the server.
HandleStartAction(CefRefPtr<CefDictionaryValue> request_dict,CefRefPtr<Callback> callback)276 void HandleStartAction(CefRefPtr<CefDictionaryValue> request_dict,
277 CefRefPtr<Callback> callback) {
278 if (handler_) {
279 callback->Failure(kActionStateError, "Server is currently running");
280 return;
281 }
282
283 if (!VerifyKey(request_dict, kPortKey, VTYPE_INT, callback))
284 return;
285
286 const int port = request_dict->GetInt(kPortKey);
287 if (port < 8000 || port > 65535) {
288 callback->Failure(kMessageFormatError, "Invalid port number specified");
289 return;
290 }
291
292 handler_ = new ServerHandler();
293
294 // Start the server. OnComplete will be executed upon completion.
295 handler_->StartServer(
296 port, base::BindOnce(&Handler::OnStartComplete,
297 weak_ptr_factory_.GetWeakPtr(), callback));
298 }
299
300 // Stop the server.
HandleStopAction(CefRefPtr<CefDictionaryValue> request_dict,CefRefPtr<Callback> callback)301 void HandleStopAction(CefRefPtr<CefDictionaryValue> request_dict,
302 CefRefPtr<Callback> callback) {
303 if (!handler_) {
304 callback->Failure(kActionStateError, "Server is not currently running");
305 return;
306 }
307
308 // Stop the server. OnComplete will be executed upon completion.
309 handler_->StopServer(base::BindOnce(
310 &Handler::OnStopComplete, weak_ptr_factory_.GetWeakPtr(), callback));
311
312 handler_ = nullptr;
313 }
314
315 // Server start completed.
OnStartComplete(CefRefPtr<Callback> callback,bool success)316 void OnStartComplete(CefRefPtr<Callback> callback, bool success) {
317 CEF_REQUIRE_UI_THREAD();
318 CefRefPtr<CefDictionaryValue> result_dict = CefDictionaryValue::Create();
319 if (!success) {
320 handler_ = nullptr;
321 result_dict->SetString(kMessageKey, "Server failed to start.");
322 }
323 SendResponse(callback, success, result_dict);
324 }
325
326 // Server stop completed.
OnStopComplete(CefRefPtr<Callback> callback,bool success)327 void OnStopComplete(CefRefPtr<Callback> callback, bool success) {
328 CEF_REQUIRE_UI_THREAD();
329 CefRefPtr<CefDictionaryValue> result_dict = CefDictionaryValue::Create();
330 if (!success) {
331 result_dict->SetString(kMessageKey, "Server failed to stop.");
332 }
333 SendResponse(callback, success, result_dict);
334 }
335
336 // Send a response in the format expected by server.html.
SendResponse(CefRefPtr<Callback> callback,bool success,CefRefPtr<CefDictionaryValue> result_dict)337 static void SendResponse(CefRefPtr<Callback> callback,
338 bool success,
339 CefRefPtr<CefDictionaryValue> result_dict) {
340 if (!result_dict) {
341 result_dict = CefDictionaryValue::Create();
342 }
343 result_dict->SetString(kResultKey, success ? "success" : "failure");
344 CefRefPtr<CefValue> value = CefValue::Create();
345 value->SetDictionary(result_dict);
346 const std::string& response = CefWriteJSON(value, JSON_WRITER_DEFAULT);
347 callback->Success(response);
348 }
349
350 // Convert a JSON string to a dictionary value.
ParseJSON(const CefString & string)351 static CefRefPtr<CefDictionaryValue> ParseJSON(const CefString& string) {
352 CefRefPtr<CefValue> value = CefParseJSON(string, JSON_PARSER_RFC);
353 if (value.get() && value->GetType() == VTYPE_DICTIONARY)
354 return value->GetDictionary();
355 return nullptr;
356 }
357
358 // Verify that |key| exists in |dictionary| and has type |value_type|. Fails
359 // |callback| and returns false on failure.
VerifyKey(CefRefPtr<CefDictionaryValue> dictionary,const char * key,cef_value_type_t value_type,CefRefPtr<Callback> callback)360 static bool VerifyKey(CefRefPtr<CefDictionaryValue> dictionary,
361 const char* key,
362 cef_value_type_t value_type,
363 CefRefPtr<Callback> callback) {
364 if (!dictionary->HasKey(key) || dictionary->GetType(key) != value_type) {
365 callback->Failure(
366 kMessageFormatError,
367 "Missing or incorrectly formatted message key: " + std::string(key));
368 return false;
369 }
370 return true;
371 }
372
373 // Non-nullptr while the server is running.
374 CefRefPtr<ServerHandler> handler_;
375
376 // Must be the last member.
377 base::WeakPtrFactory<Handler> weak_ptr_factory_;
378 };
379
380 } // namespace
381
CreateMessageHandlers(test_runner::MessageHandlerSet & handlers)382 void CreateMessageHandlers(test_runner::MessageHandlerSet& handlers) {
383 handlers.insert(new Handler());
384 }
385
386 } // namespace server_test
387 } // namespace client
388