Index: chrome/test/webdriver/dispatch.cc |
diff --git a/chrome/test/webdriver/dispatch.cc b/chrome/test/webdriver/dispatch.cc |
index f1bfb77720770121e6e970108ce5f51897ac3eec..830a738d577a83a51a6229dff479e4d925d7704b 100644 |
--- a/chrome/test/webdriver/dispatch.cc |
+++ b/chrome/test/webdriver/dispatch.cc |
@@ -1,299 +1,340 @@ |
-// Copyright (c) 2011 The Chromium 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 "chrome/test/webdriver/dispatch.h" |
- |
-#include <sstream> |
-#include <string> |
-#include <vector> |
- |
-#include "base/format_macros.h" |
-#include "base/logging.h" |
-#include "base/message_loop_proxy.h" |
-#include "base/string_split.h" |
-#include "base/string_util.h" |
-#include "base/stringprintf.h" |
-#include "base/synchronization/waitable_event.h" |
-#include "base/threading/thread.h" |
-#include "chrome/test/webdriver/http_response.h" |
-#include "chrome/test/webdriver/commands/command.h" |
-#include "chrome/test/webdriver/session_manager.h" |
-#include "chrome/test/webdriver/utility_functions.h" |
- |
-namespace webdriver { |
- |
-namespace { |
- |
-bool ForbidsMessageBody(const std::string& request_method, |
- const HttpResponse& response) { |
- return request_method == "HEAD" || |
- response.status() == HttpResponse::kNoContent || |
- response.status() == HttpResponse::kNotModified || |
- (response.status() >= 100 && response.status() < 200); |
-} |
- |
-void DispatchCommand(Command* const command, |
- const std::string& method, |
- Response* response) { |
- if (!command->Init(response)) |
- return; |
- |
- if (method == "POST") { |
- command->ExecutePost(response); |
- } else if (method == "GET") { |
- command->ExecuteGet(response); |
- } else if (method == "DELETE") { |
- command->ExecuteDelete(response); |
- } else { |
- NOTREACHED(); |
- } |
-} |
- |
-void Shutdown(struct mg_connection* connection, |
- const struct mg_request_info* request_info, |
- void* user_data) { |
- base::WaitableEvent* shutdown_event = |
- reinterpret_cast<base::WaitableEvent*>(user_data); |
- mg_printf(connection, "HTTP/1.1 200 OK\r\n\r\n"); |
- shutdown_event->Signal(); |
-} |
- |
-void SendStatus(struct mg_connection* connection, |
- const struct mg_request_info* request_info, |
- void* user_data) { |
- std::string response = "HTTP/1.1 200 OK\r\n" |
- "Content-Length:2\r\n\r\n" |
- "ok"; |
- mg_write(connection, response.data(), response.length()); |
-} |
- |
-void SendNoContentResponse(struct mg_connection* connection, |
- const struct mg_request_info* request_info, |
- void* user_data) { |
- std::string response = "HTTP/1.1 204 No Content\r\n" |
- "Content-Length:0\r\n" |
- "\r\n"; |
- mg_write(connection, response.data(), response.length()); |
-} |
- |
-void SendForbidden(struct mg_connection* connection, |
- const struct mg_request_info* request_info, |
- void* user_data) { |
- mg_printf(connection, "HTTP/1.1 403 Forbidden\r\n\r\n"); |
-} |
- |
-void SendNotImplementedError(struct mg_connection* connection, |
- const struct mg_request_info* request_info, |
- void* user_data) { |
- // Send a well-formed WebDriver JSON error response to ensure clients |
- // handle it correctly. |
- std::string body = base::StringPrintf( |
- "{\"status\":%d,\"value\":{\"message\":" |
- "\"Command has not been implemented yet: %s %s\"}}", |
- kUnknownCommand, request_info->request_method, request_info->uri); |
- |
- std::string header = base::StringPrintf( |
- "HTTP/1.1 501 Not Implemented\r\n" |
- "Content-Type:application/json\r\n" |
- "Content-Length:%" PRIuS "\r\n" |
- "\r\n", body.length()); |
- |
- LOG(ERROR) << header << body; |
- mg_write(connection, header.data(), header.length()); |
- mg_write(connection, body.data(), body.length()); |
-} |
- |
-} // namespace |
- |
-namespace internal { |
- |
-void PrepareHttpResponse(const Response& command_response, |
- HttpResponse* const http_response) { |
- ErrorCode status = command_response.GetStatus(); |
- switch (status) { |
- case kSuccess: |
- http_response->set_status(HttpResponse::kOk); |
- break; |
- |
- // TODO(jleyba): kSeeOther, kBadRequest, kSessionNotFound, |
- // and kMethodNotAllowed should be detected before creating |
- // a command_response, and should thus not need conversion. |
- case kSeeOther: { |
- const Value* const value = command_response.GetValue(); |
- std::string location; |
- if (!value->GetAsString(&location)) { |
- // This should never happen. |
- http_response->set_status(HttpResponse::kInternalServerError); |
- http_response->SetBody("Unable to set 'Location' header: response " |
- "value is not a string: " + |
- command_response.ToJSON()); |
- return; |
- } |
- http_response->AddHeader("Location", location); |
- http_response->set_status(HttpResponse::kSeeOther); |
- break; |
- } |
- |
- case kBadRequest: |
- case kSessionNotFound: |
- http_response->set_status(status); |
- break; |
- |
- case kMethodNotAllowed: { |
- const Value* const value = command_response.GetValue(); |
- if (!value->IsType(Value::TYPE_LIST)) { |
- // This should never happen. |
- http_response->set_status(HttpResponse::kInternalServerError); |
- http_response->SetBody( |
- "Unable to set 'Allow' header: response value was " |
- "not a list of strings: " + command_response.ToJSON()); |
- return; |
- } |
- |
- const ListValue* const list_value = |
- static_cast<const ListValue* const>(value); |
- std::vector<std::string> allowed_methods; |
- for (size_t i = 0; i < list_value->GetSize(); ++i) { |
- std::string method; |
- if (list_value->GetString(i, &method)) { |
- allowed_methods.push_back(method); |
- } else { |
- // This should never happen. |
- http_response->set_status(HttpResponse::kInternalServerError); |
- http_response->SetBody( |
- "Unable to set 'Allow' header: response value was " |
- "not a list of strings: " + command_response.ToJSON()); |
- return; |
- } |
- } |
- http_response->AddHeader("Allow", JoinString(allowed_methods, ',')); |
- http_response->set_status(HttpResponse::kMethodNotAllowed); |
- break; |
- } |
- |
- // All other errors should be treated as generic 500s. The client |
- // will be responsible for inspecting the message body for details. |
- case kInternalServerError: |
- default: |
- http_response->set_status(HttpResponse::kInternalServerError); |
- break; |
- } |
- |
- http_response->SetMimeType("application/json; charset=utf-8"); |
- http_response->SetBody(command_response.ToJSON()); |
-} |
- |
-void SendResponse(struct mg_connection* const connection, |
- const std::string& request_method, |
- const Response& response) { |
- HttpResponse http_response; |
- PrepareHttpResponse(response, &http_response); |
- |
- std::string message_header = base::StringPrintf("HTTP/1.1 %d %s\r\n", |
- http_response.status(), http_response.GetReasonPhrase().c_str()); |
- |
- typedef HttpResponse::HeaderMap::const_iterator HeaderIter; |
- for (HeaderIter header = http_response.headers()->begin(); |
- header != http_response.headers()->end(); |
- ++header) { |
- message_header.append(base::StringPrintf("%s:%s\r\n", |
- header->first.c_str(), header->second.c_str())); |
- } |
- message_header.append("\r\n"); |
- |
- mg_write(connection, message_header.data(), message_header.length()); |
- if (!ForbidsMessageBody(request_method, http_response)) |
- mg_write(connection, http_response.data(), http_response.length()); |
-} |
- |
-bool ParseRequestInfo(const struct mg_request_info* const request_info, |
- std::string* method, |
- std::vector<std::string>* path_segments, |
- DictionaryValue** parameters, |
- Response* const response) { |
- *method = request_info->request_method; |
- if (*method == "HEAD") |
- *method = "GET"; |
- else if (*method == "PUT") |
- *method = "POST"; |
- |
- std::string uri(request_info->uri); |
- SessionManager* manager = SessionManager::GetInstance(); |
- uri = uri.substr(manager->url_base().length()); |
- |
- base::SplitString(uri, '/', path_segments); |
- |
- if (*method == "POST" && request_info->post_data_len > 0) { |
- VLOG(1) << "...parsing request body"; |
- std::string json(request_info->post_data, request_info->post_data_len); |
- std::string error; |
- if (!ParseJSONDictionary(json, parameters, &error)) { |
- response->SetError(new Error( |
- kBadRequest, |
- "Failed to parse command data: " + error + "\n Data: " + json)); |
- return false; |
- } |
- } |
- VLOG(1) << "Parsed " << method << " " << uri |
- << std::string(request_info->post_data, request_info->post_data_len); |
- return true; |
-} |
- |
-void DispatchHelper(Command* command_ptr, |
- const std::string& method, |
- Response* response) { |
- CHECK(method == "GET" || method == "POST" || method == "DELETE"); |
- scoped_ptr<Command> command(command_ptr); |
- |
- if ((method == "GET" && !command->DoesGet()) || |
- (method == "POST" && !command->DoesPost()) || |
- (method == "DELETE" && !command->DoesDelete())) { |
- ListValue* methods = new ListValue; |
- if (command->DoesPost()) |
- methods->Append(Value::CreateStringValue("POST")); |
- if (command->DoesGet()) { |
- methods->Append(Value::CreateStringValue("GET")); |
- methods->Append(Value::CreateStringValue("HEAD")); |
- } |
- if (command->DoesDelete()) |
- methods->Append(Value::CreateStringValue("DELETE")); |
- response->SetStatus(kMethodNotAllowed); |
- response->SetValue(methods); |
- return; |
- } |
- |
- DispatchCommand(command.get(), method, response); |
-} |
- |
-} // namespace internal |
- |
-Dispatcher::Dispatcher(struct mg_context* context, const std::string& root) |
- : context_(context), root_(root) { |
- // Overwrite mongoose's default handler for /favicon.ico to always return a |
- // 204 response so we don't spam the logs with 404s. |
- mg_set_uri_callback(context_, "/favicon.ico", &SendNoContentResponse, NULL); |
-} |
- |
-Dispatcher::~Dispatcher() {} |
- |
-void Dispatcher::AddShutdown(const std::string& pattern, |
- base::WaitableEvent* shutdown_event) { |
- mg_set_uri_callback(context_, (root_ + pattern).c_str(), &Shutdown, |
- shutdown_event); |
-} |
- |
-void Dispatcher::AddStatus(const std::string& pattern) { |
- mg_set_uri_callback(context_, (root_ + pattern).c_str(), &SendStatus, NULL); |
-} |
- |
-void Dispatcher::SetNotImplemented(const std::string& pattern) { |
- mg_set_uri_callback(context_, (root_ + pattern).c_str(), |
- &SendNotImplementedError, NULL); |
-} |
- |
-void Dispatcher::ForbidAllOtherRequests() { |
- mg_set_uri_callback(context_, "*", &SendForbidden, NULL); |
-} |
- |
-} // namespace webdriver |
+// Copyright (c) 2011 The Chromium 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 "chrome/test/webdriver/dispatch.h" |
+ |
+#include <sstream> |
+#include <string> |
+#include <vector> |
+ |
+#include "base/format_macros.h" |
+#include "base/json/json_reader.h" |
+#include "base/logging.h" |
+#include "base/memory/scoped_ptr.h" |
+#include "base/message_loop_proxy.h" |
+#include "base/string_split.h" |
+#include "base/string_util.h" |
+#include "base/stringprintf.h" |
+#include "base/synchronization/waitable_event.h" |
+#include "base/threading/thread.h" |
+#include "chrome/test/webdriver/http_response.h" |
+#include "chrome/test/webdriver/commands/command.h" |
+#include "chrome/test/webdriver/session_manager.h" |
+#include "chrome/test/webdriver/utility_functions.h" |
+#include "chrome/test/webdriver/webdriver_logging.h" |
+ |
+namespace webdriver { |
+ |
+namespace { |
+ |
+// Maximum safe size of HTTP response message. Any larger than this, |
+// the message may not be transferred at all. |
+const size_t kMaxHttpMessageSize = 1024 * 1024 * 16; // 16MB |
+ |
+bool ForbidsMessageBody(const std::string& request_method, |
+ const HttpResponse& response) { |
+ return request_method == "HEAD" || |
+ response.status() == HttpResponse::kNoContent || |
+ response.status() == HttpResponse::kNotModified || |
+ (response.status() >= 100 && response.status() < 200); |
+} |
+ |
+void DispatchCommand(Command* const command, |
+ const std::string& method, |
+ Response* response) { |
+ if (!command->Init(response)) |
+ return; |
+ |
+ if (method == "POST") { |
+ command->ExecutePost(response); |
+ } else if (method == "GET") { |
+ command->ExecuteGet(response); |
+ } else if (method == "DELETE") { |
+ command->ExecuteDelete(response); |
+ } else { |
+ NOTREACHED(); |
+ } |
+} |
+ |
+void Shutdown(struct mg_connection* connection, |
+ const struct mg_request_info* request_info, |
+ void* user_data) { |
+ base::WaitableEvent* shutdown_event = |
+ reinterpret_cast<base::WaitableEvent*>(user_data); |
+ mg_printf(connection, "HTTP/1.1 200 OK\r\n\r\n"); |
+ shutdown_event->Signal(); |
+} |
+ |
+void SendOkWithBody(struct mg_connection* connection, |
+ const std::string& content) { |
+ const char* response_fmt = "HTTP/1.1 200 OK\r\n" |
+ "Content-Length:%d\r\n\r\n" |
+ "%s"; |
+ std::string response = base::StringPrintf( |
+ response_fmt, content.length(), content.c_str()); |
+ mg_write(connection, response.data(), response.length()); |
+} |
+ |
+void SendHealthz(struct mg_connection* connection, |
+ const struct mg_request_info* request_info, |
+ void* user_data) { |
+ SendOkWithBody(connection, "ok"); |
+} |
+ |
+void SendLog(struct mg_connection* connection, |
+ const struct mg_request_info* request_info, |
+ void* user_data) { |
+ std::string content, log; |
+ if (GetLogContents(&log)) { |
+ content = "START ChromeDriver log"; |
+ const size_t kMaxSizeWithoutHeaders = kMaxHttpMessageSize - 10000; |
+ if (log.size() > kMaxSizeWithoutHeaders) { |
+ log = log.substr(log.size() - kMaxSizeWithoutHeaders); |
+ content += " (only last several MB)"; |
+ } |
+ content += ":\n" + log + "END ChromeDriver log"; |
+ } else { |
+ content = "No ChromeDriver log found"; |
+ } |
+ SendOkWithBody(connection, content); |
+} |
+ |
+void SendNoContentResponse(struct mg_connection* connection, |
+ const struct mg_request_info* request_info, |
+ void* user_data) { |
+ std::string response = "HTTP/1.1 204 No Content\r\n" |
+ "Content-Length:0\r\n" |
+ "\r\n"; |
+ mg_write(connection, response.data(), response.length()); |
+} |
+ |
+void SendForbidden(struct mg_connection* connection, |
+ const struct mg_request_info* request_info, |
+ void* user_data) { |
+ mg_printf(connection, "HTTP/1.1 403 Forbidden\r\n\r\n"); |
+} |
+ |
+void SendNotImplementedError(struct mg_connection* connection, |
+ const struct mg_request_info* request_info, |
+ void* user_data) { |
+ // Send a well-formed WebDriver JSON error response to ensure clients |
+ // handle it correctly. |
+ std::string body = base::StringPrintf( |
+ "{\"status\":%d,\"value\":{\"message\":" |
+ "\"Command has not been implemented yet: %s %s\"}}", |
+ kUnknownCommand, request_info->request_method, request_info->uri); |
+ |
+ std::string header = base::StringPrintf( |
+ "HTTP/1.1 501 Not Implemented\r\n" |
+ "Content-Type:application/json\r\n" |
+ "Content-Length:%" PRIuS "\r\n" |
+ "\r\n", body.length()); |
+ |
+ mg_write(connection, header.data(), header.length()); |
+ mg_write(connection, body.data(), body.length()); |
+} |
+ |
+} // namespace |
+ |
+namespace internal { |
+ |
+void PrepareHttpResponse(const Response& command_response, |
+ HttpResponse* const http_response) { |
+ ErrorCode status = command_response.GetStatus(); |
+ switch (status) { |
+ case kSuccess: |
+ http_response->set_status(HttpResponse::kOk); |
+ break; |
+ |
+ // TODO(jleyba): kSeeOther, kBadRequest, kSessionNotFound, |
+ // and kMethodNotAllowed should be detected before creating |
+ // a command_response, and should thus not need conversion. |
+ case kSeeOther: { |
+ const Value* const value = command_response.GetValue(); |
+ std::string location; |
+ if (!value->GetAsString(&location)) { |
+ // This should never happen. |
+ http_response->set_status(HttpResponse::kInternalServerError); |
+ http_response->SetBody("Unable to set 'Location' header: response " |
+ "value is not a string: " + |
+ command_response.ToJSON()); |
+ return; |
+ } |
+ http_response->AddHeader("Location", location); |
+ http_response->set_status(HttpResponse::kSeeOther); |
+ break; |
+ } |
+ |
+ case kBadRequest: |
+ case kSessionNotFound: |
+ http_response->set_status(status); |
+ break; |
+ |
+ case kMethodNotAllowed: { |
+ const Value* const value = command_response.GetValue(); |
+ if (!value->IsType(Value::TYPE_LIST)) { |
+ // This should never happen. |
+ http_response->set_status(HttpResponse::kInternalServerError); |
+ http_response->SetBody( |
+ "Unable to set 'Allow' header: response value was " |
+ "not a list of strings: " + command_response.ToJSON()); |
+ return; |
+ } |
+ |
+ const ListValue* const list_value = |
+ static_cast<const ListValue* const>(value); |
+ std::vector<std::string> allowed_methods; |
+ for (size_t i = 0; i < list_value->GetSize(); ++i) { |
+ std::string method; |
+ if (list_value->GetString(i, &method)) { |
+ allowed_methods.push_back(method); |
+ } else { |
+ // This should never happen. |
+ http_response->set_status(HttpResponse::kInternalServerError); |
+ http_response->SetBody( |
+ "Unable to set 'Allow' header: response value was " |
+ "not a list of strings: " + command_response.ToJSON()); |
+ return; |
+ } |
+ } |
+ http_response->AddHeader("Allow", JoinString(allowed_methods, ',')); |
+ http_response->set_status(HttpResponse::kMethodNotAllowed); |
+ break; |
+ } |
+ |
+ // All other errors should be treated as generic 500s. The client |
+ // will be responsible for inspecting the message body for details. |
+ case kInternalServerError: |
+ default: |
+ http_response->set_status(HttpResponse::kInternalServerError); |
+ break; |
+ } |
+ |
+ http_response->SetMimeType("application/json; charset=utf-8"); |
+ http_response->SetBody(command_response.ToJSON()); |
+} |
+ |
+void SendResponse(struct mg_connection* const connection, |
+ const std::string& request_method, |
+ const Response& response) { |
+ HttpResponse http_response; |
+ PrepareHttpResponse(response, &http_response); |
+ |
+ std::string message_header = base::StringPrintf("HTTP/1.1 %d %s\r\n", |
+ http_response.status(), http_response.GetReasonPhrase().c_str()); |
+ |
+ typedef HttpResponse::HeaderMap::const_iterator HeaderIter; |
+ for (HeaderIter header = http_response.headers()->begin(); |
+ header != http_response.headers()->end(); |
+ ++header) { |
+ message_header.append(base::StringPrintf("%s:%s\r\n", |
+ header->first.c_str(), header->second.c_str())); |
+ } |
+ message_header.append("\r\n"); |
+ |
+ mg_write(connection, message_header.data(), message_header.length()); |
+ if (!ForbidsMessageBody(request_method, http_response)) |
+ mg_write(connection, http_response.data(), http_response.length()); |
+} |
+ |
+bool ParseRequestInfo(const struct mg_request_info* const request_info, |
+ std::string* method, |
+ std::vector<std::string>* path_segments, |
+ DictionaryValue** parameters, |
+ Response* const response) { |
+ *method = request_info->request_method; |
+ if (*method == "HEAD") |
+ *method = "GET"; |
+ else if (*method == "PUT") |
+ *method = "POST"; |
+ |
+ std::string uri(request_info->uri); |
+ SessionManager* manager = SessionManager::GetInstance(); |
+ uri = uri.substr(manager->url_base().length()); |
+ |
+ base::SplitString(uri, '/', path_segments); |
+ |
+ if (*method == "POST" && request_info->post_data_len > 0) { |
+ std::string json(request_info->post_data, request_info->post_data_len); |
+ std::string error_msg; |
+ scoped_ptr<Value> params(base::JSONReader::ReadAndReturnError( |
+ json, true, NULL, &error_msg)); |
+ if (!params.get()) { |
+ response->SetError(new Error( |
+ kBadRequest, |
+ "Failed to parse command data: " + error_msg + "\n Data: " + json)); |
+ return false; |
+ } |
+ if (!params->IsType(Value::TYPE_DICTIONARY)) { |
+ response->SetError(new Error( |
+ kBadRequest, |
+ "Data passed in URL must be a dictionary. Data: " + json)); |
+ return false; |
+ } |
+ *parameters = static_cast<DictionaryValue*>(params.release()); |
+ } |
+ return true; |
+} |
+ |
+void DispatchHelper(Command* command_ptr, |
+ const std::string& method, |
+ Response* response) { |
+ CHECK(method == "GET" || method == "POST" || method == "DELETE"); |
+ scoped_ptr<Command> command(command_ptr); |
+ |
+ if ((method == "GET" && !command->DoesGet()) || |
+ (method == "POST" && !command->DoesPost()) || |
+ (method == "DELETE" && !command->DoesDelete())) { |
+ ListValue* methods = new ListValue; |
+ if (command->DoesPost()) |
+ methods->Append(Value::CreateStringValue("POST")); |
+ if (command->DoesGet()) { |
+ methods->Append(Value::CreateStringValue("GET")); |
+ methods->Append(Value::CreateStringValue("HEAD")); |
+ } |
+ if (command->DoesDelete()) |
+ methods->Append(Value::CreateStringValue("DELETE")); |
+ response->SetStatus(kMethodNotAllowed); |
+ response->SetValue(methods); |
+ return; |
+ } |
+ |
+ DispatchCommand(command.get(), method, response); |
+} |
+ |
+} // namespace internal |
+ |
+Dispatcher::Dispatcher(struct mg_context* context, const std::string& root) |
+ : context_(context), root_(root) { |
+ // Overwrite mongoose's default handler for /favicon.ico to always return a |
+ // 204 response so we don't spam the logs with 404s. |
+ mg_set_uri_callback(context_, "/favicon.ico", &SendNoContentResponse, NULL); |
+} |
+ |
+Dispatcher::~Dispatcher() {} |
+ |
+void Dispatcher::AddShutdown(const std::string& pattern, |
+ base::WaitableEvent* shutdown_event) { |
+ mg_set_uri_callback(context_, (root_ + pattern).c_str(), &Shutdown, |
+ shutdown_event); |
+} |
+ |
+void Dispatcher::AddHealthz(const std::string& pattern) { |
+ mg_set_uri_callback(context_, (root_ + pattern).c_str(), &SendHealthz, NULL); |
+} |
+ |
+void Dispatcher::AddLog(const std::string& pattern) { |
+ mg_set_uri_callback(context_, (root_ + pattern).c_str(), &SendLog, NULL); |
+} |
+ |
+void Dispatcher::SetNotImplemented(const std::string& pattern) { |
+ mg_set_uri_callback(context_, (root_ + pattern).c_str(), |
+ &SendNotImplementedError, NULL); |
+} |
+ |
+void Dispatcher::ForbidAllOtherRequests() { |
+ mg_set_uri_callback(context_, "*", &SendForbidden, NULL); |
+} |
+ |
+} // namespace webdriver |