C++程序  |  450行  |  13.4 KB

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