// 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_