Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(168)

Unified Diff: chrome/test/webdriver/dispatch.cc

Issue 7104085: In chromedriver, add /log handler (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: ... Created 9 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/test/webdriver/dispatch.h ('k') | chrome/test/webdriver/keymap.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « chrome/test/webdriver/dispatch.h ('k') | chrome/test/webdriver/keymap.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698