| // 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/run_loop.h" |
| #include "mojo/public/cpp/application/application_impl.h" |
| #include "mojo/public/cpp/application/application_test_base.h" |
| #include "mojo/public/cpp/application/connect.h" |
| #include "mojo/public/cpp/bindings/strong_binding.h" |
| #include "mojo/public/cpp/system/data_pipe.h" |
| #include "mojo/services/network/interfaces/network_service.mojom.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace mojo { |
| namespace service { |
| namespace { |
| |
| const char kMessage[] = "Hello World\n"; |
| const char kUrl1[] = "http://www.example.com/url1"; |
| const char kUrl2[] = "http://www.example.com/url2"; |
| const char kHeaderName[] = "X-Interceptor-Header"; |
| |
| void addHeader(Array<HttpHeaderPtr>* headers) { |
| HttpHeaderPtr header = HttpHeader::New(); |
| header->name = kHeaderName; |
| header->value = kMessage; |
| headers->push_back(header.Pass()); |
| } |
| |
| class BaseInterceptor : public URLLoaderInterceptor { |
| public: |
| BaseInterceptor(InterfaceRequest<URLLoaderInterceptor> request) |
| : binding_(this, request.Pass()) {} |
| ~BaseInterceptor() override {} |
| |
| protected: |
| void InterceptRequest(URLRequestPtr request, |
| const InterceptRequestCallback& callback) override { |
| URLLoaderInterceptorResponsePtr interceptor_response = |
| URLLoaderInterceptorResponse::New(); |
| interceptor_response->request = request.Pass(); |
| callback.Run(interceptor_response.Pass()); |
| } |
| |
| void InterceptResponse(URLResponsePtr response, |
| const InterceptResponseCallback& callback) override { |
| URLLoaderInterceptorResponsePtr interceptor_response = |
| URLLoaderInterceptorResponse::New(); |
| interceptor_response->response = response.Pass(); |
| callback.Run(interceptor_response.Pass()); |
| } |
| |
| void InterceptFollowRedirect( |
| const InterceptFollowRedirectCallback& callback) override { |
| callback.Run(URLLoaderInterceptorResponsePtr()); |
| } |
| |
| private: |
| StrongBinding<URLLoaderInterceptor> binding_; |
| }; |
| |
| class SendHelloInterceptor : public BaseInterceptor { |
| public: |
| SendHelloInterceptor(InterfaceRequest<URLLoaderInterceptor> request) |
| : BaseInterceptor(request.Pass()) {} |
| ~SendHelloInterceptor() override {} |
| |
| protected: |
| void InterceptRequest(URLRequestPtr request, |
| const InterceptRequestCallback& callback) override { |
| URLResponsePtr response = URLResponse::New(); |
| response->url = request->url; |
| response->status_code = 200; |
| response->status_line = "200 OK"; |
| response->mime_type = "text/plain"; |
| response->headers = request->headers.Pass(); |
| uint32_t num_bytes = arraysize(kMessage); |
| 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(), kMessage, &num_bytes, |
| MOJO_WRITE_DATA_FLAG_ALL_OR_NONE); |
| EXPECT_EQ(MOJO_RESULT_OK, result); |
| |
| URLLoaderInterceptorResponsePtr interceptor_response = |
| URLLoaderInterceptorResponse::New(); |
| interceptor_response->response = response.Pass(); |
| callback.Run(interceptor_response.Pass()); |
| } |
| }; |
| |
| class CheckCallsInterceptor : public SendHelloInterceptor { |
| public: |
| CheckCallsInterceptor(InterfaceRequest<URLLoaderInterceptor> request) |
| : SendHelloInterceptor(request.Pass()) {} |
| ~CheckCallsInterceptor() override {} |
| |
| private: |
| void InterceptRequest(URLRequestPtr request, |
| const InterceptRequestCallback& callback) override { |
| SendHelloInterceptor::InterceptRequest( |
| request.Pass(), |
| [&callback](URLLoaderInterceptorResponsePtr interceptor_response) { |
| addHeader(&interceptor_response->response->headers); |
| callback.Run(interceptor_response.Pass()); |
| }); |
| } |
| |
| void InterceptResponse(URLResponsePtr response, |
| const InterceptResponseCallback& callback) override { |
| SendHelloInterceptor::InterceptResponse( |
| response.Pass(), |
| [&callback](URLLoaderInterceptorResponsePtr interceptor_response) { |
| addHeader(&interceptor_response->response->headers); |
| callback.Run(interceptor_response.Pass()); |
| }); |
| } |
| }; |
| |
| class AddHeaderInterceptor : public BaseInterceptor { |
| public: |
| AddHeaderInterceptor(InterfaceRequest<URLLoaderInterceptor> request) |
| : BaseInterceptor(request.Pass()) {} |
| ~AddHeaderInterceptor() override {} |
| |
| private: |
| void InterceptRequest(URLRequestPtr request, |
| const InterceptRequestCallback& callback) override { |
| addHeader(&request->headers); |
| URLLoaderInterceptorResponsePtr interceptor_response = |
| URLLoaderInterceptorResponse::New(); |
| interceptor_response->request = request.Pass(); |
| callback.Run(interceptor_response.Pass()); |
| } |
| }; |
| |
| class RedirectInterceptor : public AddHeaderInterceptor { |
| public: |
| RedirectInterceptor(InterfaceRequest<URLLoaderInterceptor> request) |
| : AddHeaderInterceptor(request.Pass()) {} |
| ~RedirectInterceptor() override {} |
| |
| private: |
| void InterceptResponse(URLResponsePtr response, |
| const InterceptRequestCallback& callback) override { |
| if (response->url == kUrl1) { |
| URLRequestPtr request = URLRequest::New(); |
| request->url = kUrl2; |
| request->headers = response->headers.Pass(); |
| URLLoaderInterceptorResponsePtr interceptor_response = |
| URLLoaderInterceptorResponse::New(); |
| interceptor_response->request = request.Pass(); |
| callback.Run(interceptor_response.Pass()); |
| } else { |
| BaseInterceptor::InterceptResponse(response.Pass(), callback); |
| } |
| } |
| }; |
| |
| template <class I> |
| class URLLoaderInterceptorFactoryImpl : public URLLoaderInterceptorFactory { |
| public: |
| URLLoaderInterceptorFactoryImpl( |
| InterfaceRequest<URLLoaderInterceptorFactory> request) |
| : binding_(this, request.Pass()) {} |
| ~URLLoaderInterceptorFactoryImpl() override {} |
| |
| private: |
| void Create( |
| mojo::InterfaceRequest<URLLoaderInterceptor> interceptor) override { |
| new I(interceptor.Pass()); |
| } |
| |
| StrongBinding<URLLoaderInterceptorFactory> binding_; |
| }; |
| |
| class URLLoaderInterceptorAppTest : public test::ApplicationTestBase { |
| public: |
| URLLoaderInterceptorAppTest() {} |
| ~URLLoaderInterceptorAppTest() override {} |
| |
| void SetUp() override { |
| ApplicationTestBase::SetUp(); |
| |
| ConnectToService(application_impl()->shell(), "mojo:network_service", |
| GetProxy(&network_service_)); |
| } |
| |
| URLResponsePtr GetResponse(const std::string& url) { |
| URLRequestPtr request = URLRequest::New(); |
| request->url = url; |
| URLLoaderPtr loader; |
| network_service_->CreateURLLoader(GetProxy(&loader)); |
| URLResponsePtr response; |
| { |
| base::RunLoop loop; |
| loader->Start(request.Pass(), [&response, &loop](URLResponsePtr r) { |
| response = r.Pass(); |
| loop.Quit(); |
| |
| }); |
| loop.Run(); |
| } |
| return response.Pass(); |
| } |
| |
| template <class I> |
| void AddInterceptor() { |
| URLLoaderInterceptorFactoryPtr factory; |
| new URLLoaderInterceptorFactoryImpl<I>(GetProxy(&factory)); |
| network_service_->RegisterURLLoaderInterceptor(factory.Pass()); |
| } |
| |
| protected: |
| NetworkServicePtr network_service_; |
| |
| DISALLOW_COPY_AND_ASSIGN(URLLoaderInterceptorAppTest); |
| }; |
| |
| } // namespace |
| |
| TEST_F(URLLoaderInterceptorAppTest, TestSimpleInterceptor) { |
| AddInterceptor<SendHelloInterceptor>(); |
| |
| URLResponsePtr response = GetResponse(kUrl1); |
| EXPECT_TRUE(response); |
| EXPECT_EQ(kUrl1, response->url); |
| EXPECT_EQ(0u, response->headers.size()); |
| char received_message[arraysize(kMessage)]; |
| uint32_t num_bytes = arraysize(kMessage); |
| EXPECT_EQ(MOJO_RESULT_OK, ReadDataRaw(response->body.get(), received_message, |
| &num_bytes, MOJO_READ_DATA_FLAG_NONE)); |
| EXPECT_EQ(arraysize(kMessage), num_bytes); |
| EXPECT_EQ(0, memcmp(kMessage, received_message, num_bytes)); |
| } |
| |
| // Tests that a header added to the request by an initial interceptor is |
| // preserved when the request is sent to a second interceptor. |
| TEST_F(URLLoaderInterceptorAppTest, AddHeader) { |
| AddInterceptor<SendHelloInterceptor>(); |
| AddInterceptor<AddHeaderInterceptor>(); |
| |
| URLResponsePtr response = GetResponse(kUrl1); |
| EXPECT_TRUE(response); |
| EXPECT_EQ(kUrl1, response->url); |
| EXPECT_EQ(1u, response->headers.size()); |
| |
| HttpHeaderPtr header = response->headers[0].Pass(); |
| EXPECT_EQ(kHeaderName, header->name); |
| EXPECT_EQ(kMessage, header->value); |
| } |
| |
| // Tests that interceptor are called in the right order, in this case that the |
| // last interceptor can prevent the previous one to run. |
| TEST_F(URLLoaderInterceptorAppTest, InterceptorOrder) { |
| AddInterceptor<AddHeaderInterceptor>(); |
| AddInterceptor<SendHelloInterceptor>(); |
| |
| URLResponsePtr response = GetResponse(kUrl1); |
| EXPECT_TRUE(response); |
| EXPECT_EQ(kUrl1, response->url); |
| EXPECT_EQ(0u, response->headers.size()); |
| } |
| |
| // Tests that an interceptor can make the loader load a new request after having |
| // received a first one. |
| TEST_F(URLLoaderInterceptorAppTest, RedirectOnResponse) { |
| AddInterceptor<SendHelloInterceptor>(); |
| AddInterceptor<RedirectInterceptor>(); |
| |
| URLResponsePtr response = GetResponse(kUrl1); |
| EXPECT_TRUE(response); |
| EXPECT_EQ(kUrl2, response->url); |
| EXPECT_EQ(1u, response->headers.size()); |
| } |
| |
| TEST_F(URLLoaderInterceptorAppTest, CheckCallsInterceptor) { |
| AddInterceptor<CheckCallsInterceptor>(); |
| |
| URLResponsePtr response = GetResponse(kUrl1); |
| EXPECT_TRUE(response); |
| EXPECT_EQ(kUrl1, response->url); |
| EXPECT_EQ(1u, response->headers.size()); |
| } |
| |
| } // namespace service |
| } // namespace mojo |