• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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