| // Copyright 2014 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. |
| |
| #import "ios/web/public/test/http_server/http_server.h" |
| |
| #import <Foundation/Foundation.h> |
| |
| #include <string> |
| |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/path_service.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/sys_string_conversions.h" |
| #import "net/base/mac/url_conversions.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "net/test/embedded_test_server/http_request.h" |
| #include "net/test/embedded_test_server/http_response.h" |
| |
| #include "url/gurl.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| namespace { |
| |
| // Converts a net::test_server::HttpRequest (received from the |
| // EmbeddedTestServer servlet) to a request object that the ResponseProvider |
| // expects. |
| web::ResponseProvider::Request |
| ResponseProviderRequestFromEmbeddedTestServerRequest( |
| const net::test_server::HttpRequest& request) { |
| net::HttpRequestHeaders headers; |
| for (auto it = request.headers.begin(); it != request.headers.end(); ++it) { |
| headers.SetHeader(it->first, it->second); |
| } |
| return web::ResponseProvider::Request(request.GetURL(), request.method_string, |
| request.content, headers); |
| } |
| |
| } // namespace |
| |
| namespace web { |
| namespace test { |
| |
| RefCountedResponseProviderWrapper::RefCountedResponseProviderWrapper( |
| std::unique_ptr<ResponseProvider> response_provider) |
| : response_provider_(std::move(response_provider)) {} |
| |
| RefCountedResponseProviderWrapper::~RefCountedResponseProviderWrapper() {} |
| |
| // static |
| HttpServer& HttpServer::GetSharedInstance() { |
| static web::test::HttpServer* shared_instance = nullptr; |
| static dispatch_once_t once; |
| dispatch_once(&once, ^{ |
| shared_instance = new HttpServer(); |
| }); |
| return *shared_instance; |
| } |
| |
| // static |
| HttpServer& HttpServer::GetSharedInstanceWithResponseProviders( |
| ProviderList response_providers) { |
| DCHECK([NSThread isMainThread]); |
| HttpServer& server = HttpServer::GetSharedInstance(); |
| // Use non-const reference as the response_provider ownership is transfered. |
| for (std::unique_ptr<ResponseProvider>& provider : response_providers) |
| server.AddResponseProvider(std::move(provider)); |
| return server; |
| } |
| |
| std::unique_ptr<net::test_server::HttpResponse> HttpServer::GetResponse( |
| const net::test_server::HttpRequest& request) { |
| if (isSuspended) { |
| return base::MakeUnique<net::test_server::HungResponse>(); |
| } |
| ResponseProvider::Request provider_request = |
| ResponseProviderRequestFromEmbeddedTestServerRequest(request); |
| ResponseProvider* response_provider = |
| GetResponseProviderForProviderRequest(provider_request); |
| |
| if (!response_provider) { |
| return nullptr; |
| } |
| return response_provider->GetEmbeddedTestServerResponse(provider_request); |
| } |
| |
| ResponseProvider* HttpServer::GetResponseProviderForProviderRequest( |
| const web::ResponseProvider::Request& request) { |
| auto response_provider = GetResponseProviderForRequest(request); |
| if (!response_provider) { |
| return nullptr; |
| } |
| return response_provider->GetResponseProvider(); |
| } |
| |
| HttpServer::HttpServer() : port_(0) {} |
| |
| HttpServer::~HttpServer() {} |
| |
| void HttpServer::StartOrDie() { |
| DCHECK([NSThread isMainThread]); |
| |
| // Registers request handler which serves files from the http test files |
| // directory. The current tests calls full path relative to DIR_SOURCE_ROOT. |
| // Registers the DIR_SOURCE_ROOT to avoid massive test changes. |
| embedded_test_server_ = base::MakeUnique<net::EmbeddedTestServer>(); |
| embedded_test_server_->ServeFilesFromSourceDirectory("."); |
| |
| embedded_test_server_->RegisterDefaultHandler( |
| base::Bind(&HttpServer::GetResponse, this)); |
| |
| if (embedded_test_server_->Start()) { |
| SetPort((NSUInteger)embedded_test_server_->port()); |
| } |
| isSuspended = NO; |
| CHECK(IsRunning()); |
| } |
| |
| void HttpServer::Stop() { |
| DCHECK([NSThread isMainThread]); |
| DCHECK(IsRunning()) << "Cannot stop an already stopped server."; |
| RemoveAllResponseProviders(); |
| // TODO(crbug.com/711723): Re-write the Stop() function when shutting down |
| // server works for iOS. |
| embedded_test_server_.release(); |
| SetPort(0); |
| } |
| |
| void HttpServer::SetSuspend(bool suspended) { |
| isSuspended = suspended; |
| } |
| |
| bool HttpServer::IsRunning() const { |
| DCHECK([NSThread isMainThread]); |
| if (embedded_test_server_ == nullptr) { |
| return false; |
| } |
| return embedded_test_server_->Started(); |
| } |
| |
| NSUInteger HttpServer::GetPort() const { |
| base::AutoLock autolock(port_lock_); |
| return port_; |
| } |
| |
| // static |
| GURL HttpServer::MakeUrl(const std::string& url) { |
| return HttpServer::GetSharedInstance().MakeUrlForHttpServer(url); |
| } |
| |
| GURL HttpServer::MakeUrlForHttpServer(const std::string& url) const { |
| GURL result(url); |
| DCHECK(result.is_valid()); |
| return embedded_test_server_->GetURL("/" + result.GetContent()); |
| } |
| |
| scoped_refptr<RefCountedResponseProviderWrapper> |
| HttpServer::GetResponseProviderForRequest( |
| const web::ResponseProvider::Request& request) { |
| base::AutoLock autolock(provider_list_lock_); |
| // Relax the cross-thread access restriction to non-thread-safe RefCount. |
| // The lock above protects non-thread-safe RefCount in HTTPServer. |
| base::ScopedAllowCrossThreadRefCountAccess |
| allow_cross_thread_ref_count_access; |
| scoped_refptr<RefCountedResponseProviderWrapper> result; |
| for (const auto& ref_counted_response_provider : providers_) { |
| ResponseProvider* response_provider = |
| ref_counted_response_provider.get()->GetResponseProvider(); |
| if (response_provider->CanHandleRequest(request)) { |
| DCHECK(!result) |
| << "No more than one response provider can handle the same request."; |
| result = ref_counted_response_provider; |
| } |
| } |
| return result; |
| } |
| |
| void HttpServer::AddResponseProvider( |
| std::unique_ptr<ResponseProvider> response_provider) { |
| DCHECK([NSThread isMainThread]); |
| DCHECK(IsRunning()) << "Can add a response provider only when the server is " |
| << "running."; |
| base::AutoLock autolock(provider_list_lock_); |
| // Relax the cross-thread access restriction to non-thread-safe RefCount. |
| // The lock above protects non-thread-safe RefCount in HTTPServer. |
| base::ScopedAllowCrossThreadRefCountAccess |
| allow_cross_thread_ref_count_access; |
| scoped_refptr<RefCountedResponseProviderWrapper> |
| ref_counted_response_provider( |
| new RefCountedResponseProviderWrapper(std::move(response_provider))); |
| providers_.push_back(ref_counted_response_provider); |
| } |
| |
| void HttpServer::RemoveResponseProvider(ResponseProvider* response_provider) { |
| DCHECK([NSThread isMainThread]); |
| base::AutoLock autolock(provider_list_lock_); |
| // Relax the cross-thread access restriction to non-thread-safe RefCount. |
| // The lock above protects non-thread-safe RefCount in HTTPServer. |
| base::ScopedAllowCrossThreadRefCountAccess |
| allow_cross_thread_ref_count_access; |
| auto found_iter = providers_.end(); |
| for (auto it = providers_.begin(); it != providers_.end(); ++it) { |
| if ((*it)->GetResponseProvider() == response_provider) { |
| found_iter = it; |
| break; |
| } |
| } |
| if (found_iter != providers_.end()) { |
| providers_.erase(found_iter); |
| } |
| } |
| |
| void HttpServer::RemoveAllResponseProviders() { |
| DCHECK([NSThread isMainThread]); |
| base::AutoLock autolock(provider_list_lock_); |
| // Relax the cross-thread access restriction to non-thread-safe RefCount. |
| // The lock above protects non-thread-safe RefCount in HTTPServer. |
| base::ScopedAllowCrossThreadRefCountAccess |
| allow_cross_thread_ref_count_access; |
| providers_.clear(); |
| } |
| |
| void HttpServer::SetPort(NSUInteger port) { |
| base::AutoLock autolock(port_lock_); |
| port_ = port; |
| } |
| |
| } // namespace test |
| } // namespace web |