// Copyright (c) 2012 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. #ifndef CHROME_FRAME_TEST_TEST_SERVER_H_ #define CHROME_FRAME_TEST_TEST_SERVER_H_ // Implementation of an HTTP server for tests. // To instantiate the server, make sure you have a message loop on the // current thread and then create an instance of the SimpleWebServer class. // The server uses two basic concepts, a request and a response. // The Response interface represents an item (e.g. a document) available from // the server. A Request object represents a request from a client (e.g. a // browser). There are several basic Response classes implemented in this file, // all derived from the Response interface. // // Here's a simple example that starts a web server that can serve up // a single document (http://<server.host()>:1337/foo). // All other requests will get a 404. // // MessageLoopForUI loop; // test_server::SimpleWebServer server(1337); // test_server::SimpleResponse document("/foo", "Hello World!"); // test_server.AddResponse(&document); // loop.MessageLoop::Run(); // // To close the web server, just go to http://<server.host()>:1337/quit. // // All Response classes count how many times they have been accessed. Just // call Response::accessed(). // // To implement a custom response object (e.g. to match against a request // based on some data, serve up dynamic content or take some action on the // server), just inherit from one of the response classes or directly from the // Response interface and add your response object to the server's list of // response objects. #include <list> #include <string> #include "base/basictypes.h" #include "base/files/file_path.h" #include "base/files/memory_mapped_file.h" #include "base/message_loop/message_loop.h" #include "net/socket/stream_listen_socket.h" namespace test_server { class Request { public: Request() : content_length_(0) { } void ParseHeaders(const std::string& headers); const std::string& method() const { return method_; } const std::string& path() const { return path_; } // Returns the argument section of a GET path. // Note: does currently not work for POST request. std::string arguments() const { std::string ret; std::string::size_type pos = path_.find('?'); if (pos != std::string::npos) ret = path_.substr(pos + 1); return ret; } const std::string& headers() const { return headers_; } const std::string& content() const { return content_; } size_t content_length() const { return content_length_; } bool AllContentReceived() const { return method_.length() && content_.size() >= content_length_; } void OnDataReceived(const std::string& data); protected: std::string method_; std::string path_; std::string version_; std::string headers_; std::string content_; size_t content_length_; private: DISALLOW_COPY_AND_ASSIGN(Request); }; // Manages request headers for a single request. // For each successful request that's made, the server will keep an instance // of this class so that they can be checked even after the server has been // shut down. class Connection { public: explicit Connection(scoped_ptr<net::StreamListenSocket> sock) : socket_(sock.Pass()) { } ~Connection() { } bool IsSame(const net::StreamListenSocket* socket) const { return socket_ == socket; } const Request& request() const { return request_; } Request& request() { return request_; } void OnSocketClosed() { socket_.reset(); } protected: scoped_ptr<net::StreamListenSocket> socket_; Request request_; private: DISALLOW_COPY_AND_ASSIGN(Connection); }; // Abstract interface with default implementations for some of the methods and // a counter for how many times the response object has served requests. class Response { public: Response() : accessed_(0) { } virtual ~Response() { } // Returns true if this response object should be used for a given request. virtual bool Matches(const Request& r) const = 0; // Response objects can optionally supply their own HTTP headers, completely // bypassing the default ones. virtual bool GetCustomHeaders(std::string* headers) const { return false; } // Optionally provide a content type. Return false if you don't specify // a content type. virtual bool GetContentType(std::string* content_type) const { return false; } virtual size_t ContentLength() const { return 0; } virtual void WriteContents(net::StreamListenSocket* socket) const { } virtual void IncrementAccessCounter() { accessed_++; } size_t accessed() const { return accessed_; } protected: size_t accessed_; private: DISALLOW_COPY_AND_ASSIGN(Response); }; // Partial implementation of Response that matches a request's path. // This is just a convenience implementation for the boilerplate implementation // of Matches(). Don't instantiate directly. class ResponseForPath : public Response { public: explicit ResponseForPath(const char* request_path) : request_path_(request_path) { } virtual ~ResponseForPath(); virtual bool Matches(const Request& r) const { std::string path = r.path(); std::string::size_type pos = path.find('?'); if (pos != std::string::npos) path = path.substr(0, pos); return path.compare(request_path_) == 0; } protected: std::string request_path_; private: DISALLOW_COPY_AND_ASSIGN(ResponseForPath); }; // A very basic implementation of a response. // A simple response matches a single document path on the server // (e.g. "/foo") and returns a document in the form of a string. class SimpleResponse : public ResponseForPath { public: SimpleResponse(const char* request_path, const std::string& contents) : ResponseForPath(request_path), contents_(contents) { } virtual ~SimpleResponse(); virtual void WriteContents(net::StreamListenSocket* socket) const { socket->Send(contents_.c_str(), contents_.length(), false); } virtual size_t ContentLength() const { return contents_.length(); } protected: std::string contents_; private: DISALLOW_COPY_AND_ASSIGN(SimpleResponse); }; // To serve up files from the web server, create an instance of FileResponse // and add it to the server's list of responses. The content type of the // file will be determined by calling FindMimeFromData which examines the // contents of the file and performs registry lookups. class FileResponse : public ResponseForPath { public: FileResponse(const char* request_path, const base::FilePath& file_path) : ResponseForPath(request_path), file_path_(file_path) { } virtual bool GetContentType(std::string* content_type) const; virtual void WriteContents(net::StreamListenSocket* socket) const; virtual size_t ContentLength() const; protected: base::FilePath file_path_; mutable scoped_ptr<base::MemoryMappedFile> file_; private: DISALLOW_COPY_AND_ASSIGN(FileResponse); }; // Returns a 302 (temporary redirect) to redirect the client from a path // on the test server to a different URL. class RedirectResponse : public ResponseForPath { public: RedirectResponse(const char* request_path, const std::string& redirect_url) : ResponseForPath(request_path), redirect_url_(redirect_url) { } virtual bool GetCustomHeaders(std::string* headers) const; protected: std::string redirect_url_; private: DISALLOW_COPY_AND_ASSIGN(RedirectResponse); }; // typedef for a list of connections. Used by SimpleWebServer. typedef std::list<Connection*> ConnectionList; // Implementation of a simple http server. // Before creating an instance of the server, make sure the current thread // has a message loop. class SimpleWebServer : public net::StreamListenSocket::Delegate { public: // Constructs a server listening at the given port on a local IPv4 address. // An address on a NIC is preferred over the loopback address. explicit SimpleWebServer(int port); // Constructs a server listening at the given address:port. SimpleWebServer(const std::string& address, int port); virtual ~SimpleWebServer(); void AddResponse(Response* response); // Ownership of response objects is by default assumed to be outside // of the SimpleWebServer class. // However, if the caller doesn't wish to maintain a list of response objects // but rather let this class hold the only references to those objects, // the caller can call this method to delete the objects as part of // the cleanup process. void DeleteAllResponses(); // StreamListenSocket::Delegate overrides. virtual void DidAccept(net::StreamListenSocket* server, scoped_ptr<net::StreamListenSocket> connection); virtual void DidRead(net::StreamListenSocket* connection, const char* data, int len); virtual void DidClose(net::StreamListenSocket* sock); // Returns the host on which the server is listening. This is suitable for // use in URLs for resources served by this instance. const std::string& host() const { return host_; } const ConnectionList& connections() const { return connections_; } protected: class QuitResponse : public SimpleResponse { public: QuitResponse() : SimpleResponse("/quit", "So long and thanks for all the fish.") { } virtual void WriteContents(net::StreamListenSocket* socket) const { SimpleResponse::WriteContents(socket); base::MessageLoop::current()->Quit(); } }; Response* FindResponse(const Request& request) const; Connection* FindConnection(const net::StreamListenSocket* socket) const; std::string host_; scoped_ptr<net::StreamListenSocket> server_; ConnectionList connections_; std::list<Response*> responses_; QuitResponse quit_; private: void Construct(const std::string& address, int port); DISALLOW_COPY_AND_ASSIGN(SimpleWebServer); }; // Simple class holding incoming HTTP request. Can send the HTTP response // at different rate - small chunks, on regular interval. class ConfigurableConnection : public base::RefCounted<ConfigurableConnection> { public: struct SendOptions { enum Speed { IMMEDIATE, DELAYED, IMMEDIATE_HEADERS_DELAYED_CONTENT }; SendOptions() : speed_(IMMEDIATE), chunk_size_(0), timeout_(0) { } SendOptions(Speed speed, int chunk_size, int64 timeout) : speed_(speed), chunk_size_(chunk_size), timeout_(timeout) { } Speed speed_; int chunk_size_; int64 timeout_; }; explicit ConfigurableConnection(scoped_ptr<net::StreamListenSocket> sock) : socket_(sock.Pass()), cur_pos_(0) {} // Send HTTP response with provided |headers| and |content|. Appends // "Context-Length:" header if the |content| is not empty. void Send(const std::string& headers, const std::string& content); // Send HTTP response with provided |headers| and |content|. Appends // "Context-Length:" header if the |content| is not empty. // Use the |options| to tweak the network speed behaviour. void SendWithOptions(const std::string& headers, const std::string& content, const SendOptions& options); private: friend class HTTPTestServer; // Sends a chunk of the response and queues itself as a task for sending // next chunk of |data_|. void SendChunk(); // Closes the connection by releasing this instance's reference on its socket. void Close(); scoped_ptr<net::StreamListenSocket> socket_; Request r_; SendOptions options_; std::string data_; int cur_pos_; DISALLOW_COPY_AND_ASSIGN(ConfigurableConnection); }; // Simple class used as a base class for mock webserver. // Override virtual functions Get and Post and use passed ConfigurableConnection // instance to send the response. class HTTPTestServer : public net::StreamListenSocket::Delegate { public: HTTPTestServer(int port, const std::wstring& address, base::FilePath root_dir); virtual ~HTTPTestServer(); // HTTP GET request is received. Override in derived classes. // |connection| can be used to send the response. virtual void Get(ConfigurableConnection* connection, const std::wstring& path, const Request& r) = 0; // HTTP POST request is received. Override in derived classes. // |connection| can be used to send the response virtual void Post(ConfigurableConnection* connection, const std::wstring& path, const Request& r) = 0; // Return the appropriate url with the specified path for this server. std::wstring Resolve(const std::wstring& path); base::FilePath root_dir() { return root_dir_; } protected: int port_; std::wstring address_; base::FilePath root_dir_; private: typedef std::list<scoped_refptr<ConfigurableConnection> > ConnectionList; ConnectionList::iterator FindConnection( const net::StreamListenSocket* socket); scoped_refptr<ConfigurableConnection> ConnectionFromSocket( const net::StreamListenSocket* socket); // StreamListenSocket::Delegate overrides. virtual void DidAccept(net::StreamListenSocket* server, scoped_ptr<net::StreamListenSocket> socket); virtual void DidRead(net::StreamListenSocket* socket, const char* data, int len); virtual void DidClose(net::StreamListenSocket* socket); scoped_ptr<net::StreamListenSocket> server_; ConnectionList connection_list_; DISALLOW_COPY_AND_ASSIGN(HTTPTestServer); }; } // namespace test_server #endif // CHROME_FRAME_TEST_TEST_SERVER_H_