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

Unified Diff: mojo/services/network/http_server_apptest.cc

Issue 1128863004: Mojo service implementation for HTTP server - part 2 (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 7 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 | « mojo/services/network/http_connection_impl.cc ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: mojo/services/network/http_server_apptest.cc
diff --git a/mojo/services/network/http_server_apptest.cc b/mojo/services/network/http_server_apptest.cc
new file mode 100644
index 0000000000000000000000000000000000000000..2e8e8f788fe663793fb653053231b1eeca40f983
--- /dev/null
+++ b/mojo/services/network/http_server_apptest.cc
@@ -0,0 +1,481 @@
+// Copyright 2015 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 "base/macros.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/run_loop.h"
+#include "base/strings/string_util.h"
+#include "mojo/application/application_test_base_chromium.h"
+#include "mojo/application/public/cpp/application_connection.h"
+#include "mojo/application/public/cpp/application_impl.h"
+#include "mojo/common/data_pipe_utils.h"
+#include "mojo/services/network/net_address_type_converters.h"
+#include "mojo/services/network/public/interfaces/http_server.mojom.h"
+#include "mojo/services/network/public/interfaces/network_service.mojom.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+#include "net/socket/tcp_client_socket.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+const int kMaxExpectedResponseLength = 2048;
+
+NetAddressPtr GetLocalHostWithAnyPort() {
+ NetAddressPtr addr(NetAddress::New());
+ addr->family = NET_ADDRESS_FAMILY_IPV4;
+ addr->ipv4 = NetAddressIPv4::New();
+ addr->ipv4->port = 0;
+ addr->ipv4->addr.resize(4);
+ addr->ipv4->addr[0] = 127;
+ addr->ipv4->addr[1] = 0;
+ addr->ipv4->addr[2] = 0;
+ addr->ipv4->addr[3] = 1;
+
+ return addr.Pass();
+}
+
+using TestHeaders = std::vector<std::pair<std::string, std::string>>;
+
+struct TestRequest {
+ std::string method;
+ std::string url;
+ TestHeaders headers;
+ scoped_ptr<std::string> body;
+};
+
+struct TestResponse {
+ uint32_t status_code;
+ TestHeaders headers;
+ scoped_ptr<std::string> body;
+};
+
+std::string MakeRequestMessage(const TestRequest& data) {
+ std::string message = data.method + " " + data.url + " HTTP/1.1\r\n";
+ for (const auto& item : data.headers)
+ message += item.first + ": " + item.second + "\r\n";
+ message += "\r\n";
+ if (data.body)
+ message += *data.body;
+
+ return message;
+}
+
+URLResponsePtr MakeResponseStruct(const TestResponse& data) {
+ URLResponsePtr response(URLResponse::New());
+ response->status_code = data.status_code;
+ response->headers.resize(data.headers.size());
+ size_t index = 0;
+ for (const auto& item : data.headers) {
+ HTTPHeaderPtr header(HTTPHeader::New());
+ header->name = item.first;
+ header->value = item.second;
+ response->headers[index++] = header.Pass();
+ }
+
+ if (data.body) {
+ uint32_t num_bytes = static_cast<uint32_t>(data.body->size());
+ MojoCreateDataPipeOptions options;
+ options.struct_size = sizeof(MojoCreateDataPipeOptions);
+ options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE;
+ options.element_num_bytes = 1;
+ options.capacity_num_bytes = num_bytes;
+ DataPipe data_pipe(options);
+ response->body = data_pipe.consumer_handle.Pass();
+ MojoResult result =
+ WriteDataRaw(data_pipe.producer_handle.get(), data.body->data(),
+ &num_bytes, MOJO_WRITE_DATA_FLAG_ALL_OR_NONE);
+ EXPECT_EQ(MOJO_RESULT_OK, result);
+ }
+
+ return response.Pass();
+}
+
+void CheckHeaders(const TestHeaders& expected,
+ const Array<HTTPHeaderPtr>& headers) {
+ // The server impl fiddles with Content-Length and Content-Type. So we don't
+ // do a strict check here.
+ std::map<std::string, std::string> header_map;
+ for (size_t i = 0; i < headers.size(); ++i) {
+ std::string lower_name =
+ base::StringToLowerASCII(headers[i]->name.To<std::string>());
+ header_map[lower_name] = headers[i]->value;
+ }
+
+ for (const auto& item : expected) {
+ std::string lower_name = base::StringToLowerASCII(item.first);
+ EXPECT_NE(header_map.end(), header_map.find(lower_name));
+ EXPECT_EQ(item.second, header_map[lower_name]);
+ }
+}
+
+void CheckRequest(const TestRequest& expected, URLRequestPtr request) {
+ EXPECT_EQ(expected.method, request->method);
+ EXPECT_EQ(expected.url, request->url);
+ CheckHeaders(expected.headers, request->headers);
+ if (expected.body) {
+ EXPECT_EQ(1u, request->body.size());
+ std::string body;
+ common::BlockingCopyToString(request->body[0].Pass(), &body);
+ EXPECT_EQ(*expected.body, body);
+ } else {
+ EXPECT_EQ(0u, request->body.size());
+ }
+}
+
+void CheckResponse(const TestResponse& expected, const std::string& response) {
+ int header_end =
+ net::HttpUtil::LocateEndOfHeaders(response.c_str(), response.size());
+ std::string assembled_headers =
+ net::HttpUtil::AssembleRawHeaders(response.c_str(), header_end);
+ scoped_refptr<net::HttpResponseHeaders> parsed_headers(
+ new net::HttpResponseHeaders(assembled_headers));
+ EXPECT_EQ(expected.status_code,
+ static_cast<uint32_t>(parsed_headers->response_code()));
+ for (const auto& item : expected.headers)
+ EXPECT_TRUE(parsed_headers->HasHeaderValue(item.first, item.second));
+
+ if (expected.body) {
+ EXPECT_NE(-1, header_end);
+ std::string body(response, static_cast<size_t>(header_end));
+ EXPECT_EQ(*expected.body, body);
+ } else {
+ EXPECT_EQ(response.size(), static_cast<size_t>(header_end));
+ }
+}
+
+class TestHttpClient {
+ public:
+ TestHttpClient() : connect_result_(net::OK) {}
+
+ void Connect(const net::IPEndPoint& address) {
+ net::AddressList addresses(address);
+ net::NetLog::Source source;
+ socket_.reset(new net::TCPClientSocket(addresses, NULL, source));
+
+ base::RunLoop run_loop;
+ connect_result_ = socket_->Connect(base::Bind(&TestHttpClient::OnConnect,
+ base::Unretained(this),
+ run_loop.QuitClosure()));
+ if (connect_result_ == net::ERR_IO_PENDING)
+ run_loop.Run();
+
+ ASSERT_EQ(net::OK, connect_result_);
+ }
+
+ void Send(const std::string& data) {
+ write_buffer_ = new net::DrainableIOBuffer(new net::StringIOBuffer(data),
+ data.length());
+ Write();
+ }
+
+ // Note: This method determines the end of the response only by Content-Length
+ // and connection termination. Besides, it doesn't truncate at the end of the
+ // response, so |message| may return more data (e.g., part of the next
+ // response).
+ void ReadResponse(std::string* message) {
+ if (!Read(message, 1))
+ return;
+ while (!IsCompleteResponse(*message)) {
+ std::string chunk;
+ if (!Read(&chunk, 1))
+ return;
+ message->append(chunk);
+ }
+ return;
+ }
+
+ private:
+ void OnConnect(const base::Closure& quit_loop, int result) {
+ connect_result_ = result;
+ quit_loop.Run();
+ }
+
+ void Write() {
+ int result = socket_->Write(
+ write_buffer_.get(), write_buffer_->BytesRemaining(),
+ base::Bind(&TestHttpClient::OnWrite, base::Unretained(this)));
+ if (result != net::ERR_IO_PENDING)
+ OnWrite(result);
+ }
+
+ void OnWrite(int result) {
+ ASSERT_GT(result, 0);
+ write_buffer_->DidConsume(result);
+ if (write_buffer_->BytesRemaining())
+ Write();
+ }
+
+ bool Read(std::string* message, int expected_bytes) {
+ int total_bytes_received = 0;
+ message->clear();
+ while (total_bytes_received < expected_bytes) {
+ net::TestCompletionCallback callback;
+ ReadInternal(callback.callback());
+ int bytes_received = callback.WaitForResult();
+ if (bytes_received <= 0)
+ return false;
+
+ total_bytes_received += bytes_received;
+ message->append(read_buffer_->data(), bytes_received);
+ }
+ return true;
+ }
+
+ void ReadInternal(const net::CompletionCallback& callback) {
+ read_buffer_ = new net::IOBufferWithSize(kMaxExpectedResponseLength);
+ int result =
+ socket_->Read(read_buffer_.get(), kMaxExpectedResponseLength, callback);
+ if (result != net::ERR_IO_PENDING)
+ callback.Run(result);
+ }
+
+ bool IsCompleteResponse(const std::string& response) {
+ // Check end of headers first.
+ int end_of_headers =
+ net::HttpUtil::LocateEndOfHeaders(response.data(), response.size());
+ if (end_of_headers < 0)
+ return false;
+
+ // Return true if response has data equal to or more than content length.
+ int64 body_size = static_cast<int64>(response.size()) - end_of_headers;
+ DCHECK_LE(0, body_size);
+ scoped_refptr<net::HttpResponseHeaders> headers(
+ new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders(
+ response.data(), end_of_headers)));
+ return body_size >= headers->GetContentLength();
+ }
+
+ scoped_refptr<net::IOBufferWithSize> read_buffer_;
+ scoped_refptr<net::DrainableIOBuffer> write_buffer_;
+ scoped_ptr<net::TCPClientSocket> socket_;
+ int connect_result_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestHttpClient);
+};
+
+class HttpConnectionDelegateImpl : public HttpConnectionDelegate {
+ public:
+ struct PendingRequest {
+ URLRequestPtr request;
+ OnReceivedRequestCallback callback;
+ };
+
+ HttpConnectionDelegateImpl(HttpConnectionPtr connection,
+ InterfaceRequest<HttpConnectionDelegate> request)
+ : connection_(connection.Pass()),
+ binding_(this, request.Pass()),
+ run_loop_(nullptr),
+ wait_for_request_count_(0) {}
+ ~HttpConnectionDelegateImpl() override {}
+
+ // HttpConnectionDelegate implementation:
+ void OnReceivedRequest(URLRequestPtr request,
+ const OnReceivedRequestCallback& callback) override {
+ linked_ptr<PendingRequest> pending_request(new PendingRequest);
+ pending_request->request = request.Pass();
+ pending_request->callback = callback;
+ pending_requests_.push_back(pending_request);
+ if (run_loop_ && pending_requests_.size() >= wait_for_request_count_) {
+ wait_for_request_count_ = 0;
+ run_loop_->Quit();
+ }
+ }
+
+ void OnReceivedWebSocketRequest(
+ URLRequestPtr request,
+ const OnReceivedWebSocketRequestCallback& callback) override {
+ NOTREACHED();
+ }
+
+ void SendResponse(URLResponsePtr response) {
+ ASSERT_FALSE(pending_requests_.empty());
+ linked_ptr<PendingRequest> request = pending_requests_[0];
+ pending_requests_.erase(pending_requests_.begin());
+ request->callback.Run(response.Pass());
+ }
+
+ void WaitForRequest(size_t count) {
+ DCHECK(!run_loop_);
+
+ wait_for_request_count_ = count;
+ base::RunLoop run_loop;
+ run_loop_ = &run_loop;
+ run_loop.Run();
+ run_loop_ = nullptr;
+ }
+
+ std::vector<linked_ptr<PendingRequest>>& pending_requests() {
+ return pending_requests_;
+ }
+
+ private:
+ HttpConnectionPtr connection_;
+ Binding<HttpConnectionDelegate> binding_;
+ std::vector<linked_ptr<PendingRequest>> pending_requests_;
+ // Pointing to a stack-allocated RunLoop instance.
+ base::RunLoop* run_loop_;
+ size_t wait_for_request_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpConnectionDelegateImpl);
+};
+
+class HttpServerDelegateImpl : public HttpServerDelegate {
+ public:
+ explicit HttpServerDelegateImpl(HttpServerDelegatePtr* delegate_ptr)
+ : binding_(this, delegate_ptr),
+ run_loop_(nullptr),
+ wait_for_connection_count_(0) {}
+ ~HttpServerDelegateImpl() override {}
+
+ // HttpServerDelegate implementation.
+ void OnConnected(HttpConnectionPtr connection,
+ InterfaceRequest<HttpConnectionDelegate> delegate) override {
+ connections_.push_back(make_linked_ptr(
+ new HttpConnectionDelegateImpl(connection.Pass(), delegate.Pass())));
+ if (run_loop_ && connections_.size() >= wait_for_connection_count_) {
+ wait_for_connection_count_ = 0;
+ run_loop_->Quit();
+ }
+ }
+
+ void WaitForConnection(size_t count) {
+ DCHECK(!run_loop_);
+
+ wait_for_connection_count_ = count;
+ base::RunLoop run_loop;
+ run_loop_ = &run_loop;
+ run_loop.Run();
+ run_loop_ = nullptr;
+ }
+
+ std::vector<linked_ptr<HttpConnectionDelegateImpl>>& connections() {
+ return connections_;
+ }
+
+ private:
+ Binding<HttpServerDelegate> binding_;
+ std::vector<linked_ptr<HttpConnectionDelegateImpl>> connections_;
+ // Pointing to a stack-allocated RunLoop instance.
+ base::RunLoop* run_loop_;
+ size_t wait_for_connection_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpServerDelegateImpl);
+};
+
+class HttpServerAppTest : public test::ApplicationTestBase {
+ public:
+ HttpServerAppTest() : message_loop_(base::MessageLoop::TYPE_IO) {}
+ ~HttpServerAppTest() override {}
+
+ protected:
+ bool ShouldCreateDefaultRunLoop() override { return false; }
+
+ void SetUp() override {
+ ApplicationTestBase::SetUp();
+
+ ApplicationConnection* connection =
+ application_impl()->ConnectToApplication("mojo:network_service");
+ connection->ConnectToService(&network_service_);
+ }
+
+ void CreateHttpServer(HttpServerDelegatePtr delegate,
+ NetAddressPtr* out_bound_to) {
+ network_service_->CreateHttpServer(
+ GetLocalHostWithAnyPort(), delegate.Pass(),
+ [out_bound_to](NetworkErrorPtr result, NetAddressPtr bound_to) {
+ ASSERT_EQ(net::OK, result->code);
+ EXPECT_NE(0u, bound_to->ipv4->port);
+ *out_bound_to = bound_to.Pass();
+ });
+ network_service_.WaitForIncomingMethodCall();
+ }
+
+ NetworkServicePtr network_service_;
+
+ private:
+ base::MessageLoop message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpServerAppTest);
+};
+
+} // namespace
+
+TEST_F(HttpServerAppTest, BasicHttpRequestResponse) {
+ NetAddressPtr bound_to;
+ HttpServerDelegatePtr server_delegate_ptr;
+ HttpServerDelegateImpl server_delegate_impl(&server_delegate_ptr);
+ CreateHttpServer(server_delegate_ptr.Pass(), &bound_to);
+
+ TestHttpClient client;
+ client.Connect(bound_to.To<net::IPEndPoint>());
+
+ server_delegate_impl.WaitForConnection(1);
+ HttpConnectionDelegateImpl& connection =
+ *server_delegate_impl.connections()[0];
+
+ TestRequest request_data = {"HEAD", "/test", {{"Hello", "World"}}, nullptr};
+ client.Send(MakeRequestMessage(request_data));
+
+ connection.WaitForRequest(1);
+
+ CheckRequest(request_data, connection.pending_requests()[0]->request.Pass());
+
+ TestResponse response_data = {200, {{"Content-Length", "4"}}, nullptr};
+ connection.SendResponse(MakeResponseStruct(response_data));
+ // This causes the underlying TCP connection to be closed. The client can
+ // determine the end of the response based on that.
+ server_delegate_impl.connections().clear();
+
+ std::string response_message;
+ client.ReadResponse(&response_message);
+
+ CheckResponse(response_data, response_message);
+}
+
+TEST_F(HttpServerAppTest, HttpRequestResponseWithBody) {
+ NetAddressPtr bound_to;
+ HttpServerDelegatePtr server_delegate_ptr;
+ HttpServerDelegateImpl server_delegate_impl(&server_delegate_ptr);
+ CreateHttpServer(server_delegate_ptr.Pass(), &bound_to);
+
+ TestHttpClient client;
+ client.Connect(bound_to.To<net::IPEndPoint>());
+
+ server_delegate_impl.WaitForConnection(1);
+ HttpConnectionDelegateImpl& connection =
+ *server_delegate_impl.connections()[0];
+
+ TestRequest request_data = {
+ "Post",
+ "/test",
+ {{"Hello", "World"},
+ {"Content-Length", "23"},
+ {"Content-Type", "text/plain"}},
+ make_scoped_ptr(new std::string("This is a test request!"))};
+ client.Send(MakeRequestMessage(request_data));
+
+ connection.WaitForRequest(1);
+
+ CheckRequest(request_data, connection.pending_requests()[0]->request.Pass());
+
+ TestResponse response_data = {
+ 200,
+ {{"Content-Length", "26"}},
+ make_scoped_ptr(new std::string("This is a test response..."))};
+ connection.SendResponse(MakeResponseStruct(response_data));
+
+ std::string response_message;
+ client.ReadResponse(&response_message);
+
+ CheckResponse(response_data, response_message);
+}
+
+} // namespace mojo
« no previous file with comments | « mojo/services/network/http_connection_impl.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698