HTTP 要求・応答サーバーチュートリアル

デフォルトプロトコル

nine HTTPサーバーは、HTTP から Upgrade するプロトコルもサポートしているので、どのプロトコルに対するサーバーなのかを区別する必要がある。 通常の HTTP 要求・応答に対するサーバーを、デフォルトHTTPサーバーと称している。

サーバークラス

サンプルのHTTPサーバーは、クライアントから来るメッセージを受信し、カウントするだけのものである。

class Server
   : public nine::HttpServer
   , public nine::HttpHandler
{
   public:
      Server();
      virtual ~Server();

      bool init(int nComms);
      void run();
      inline void enableDump(bool b) { m_bDump = b; }

   public:
      virtual HttpHandler* authorizeHttp(int cid, const HttpRequest* pReq);
      virtual void onHttpRequestHeader(HttpChannel*, const HttpRequest* pReq);
      virtual void onHttpRequest(HttpChannel*, const HttpRequest* pReq, Buffer* pBuf, HttpResponse* pRes);
      virtual void onHttpResponse(HttpChannel*, const HttpResponse*, bool succeed);

デフォルトHTTPサーバーは HttpHandler クラスを継承して実装する。

サンプルでは Server クラス自身が HttpHandler となっている。もちろん独立したクラスとして実装してもよい。

Server::init

HttpServer は initialize() という初期化関数を持っている。HttpServer インスタンスを生成後、実際に使う前に呼び出して初期化しなければならない:
class HttpServer
{
   bool initialize(const HttpServerConfig* pCfg);
   ...
initialzie の引数は HttpServerConfig 構造体である。 この構造体は、HttpServer の設定項目をメンバ変数として持っているので、アプリケーションの要求に合わせてパラメータを代入しておく:
struct HttpServerConfig
{
      HttpServerConfig();
      HttpServerConfig(const HttpServerConfig&);
      HttpServerConfig& operator=(const HttpServerConfig&);

      int nAcceptors; //!< Acceptor の数。デフォルトは 1.
      int nCommunicators; //!< Communicator の数(最大同時接続数)。デフォルトは 0.
      int nConcurrency;  //!< turn() の並列数。0の場合は並列化しない。デフォルトは 0.

      int sockSendBufferSize; //!< ソケットの送信バッファサイズ。負だとOS設定値を使う。デフォルトは -1.
      int sockRecvBufferSize; //!< ソケットの受信バッファサイズ。負だとOS設定値を使う。デフォルトは -1.
      int nineSendBufferSize; //!< 送信バッファのサイズ。デフォルトは 10000.
      int nineRecvBufferSize; //!< 受信バッファのサイズ。デフォルトは 10000.
      int turnTrafficKB; //!< 一回の turn() で送受信する最大データサイズ。
      bool enableWinsockAio; //!< nine1.2.1 より。1.2以降導入のWindows非同期I/O を使うなら真。デフォルトは偽。
     int keepAliveTimeoutSec; // keep alive のタイムアウト秒数。ゼロなら keep alive しない。デフォルトは 3000(5分).
};
ほとんどの項目は、TransceiverConfig と同じである。
TransceiverConfig に無いメンバは次のものである:
int turnTrafficKB
一回の turn() で送受信する最大データサイズ。
長大なバッファにいっぱいのデータを受信した場合、一回の turn() で全てを処理しようとすると時間がかかってしまう。これを避けるため、一回の turn() で処理するバイト数を指定できる。
keepAliveTimeoutSec
KeepAlive のタイムアウト時間[秒].

Server::authorizeHttp

authorizeHttp() 関数は nine::HttpServer で定義された仮想関数であり、サブクラスでオーバーライドすることを想定している。

authorizeHttp() 関数は、upgrade されない HTTP リクエストヘッダ全体を受信した後で呼びだされる。 このリクエストがサーバーで扱うべきものであるならば、オーバーライド実装はリクエストを扱う HttpHandler オブジェクトを戻り値として返す:
  class HttpServer {
    ...
    virtual HttpHandler* authorizeHttp(int cid, const HttpRequest*) = 0;
  }

HttpRequest 引数に、HTTP要求のヘッダ情報が格納されている。複数のハンドラを使い分ける場合は、この情報を元に返すオブジェクトを変えることができる。

サンプルでは、Server クラス自身が HttpHandler であり、また全てのリクエストを受け取るので、そのまま this を返している:
nine::HttpHandler* Server::authorizeHttp(int cid, const HttpRequest* pReq)
{
   return this;
}

HTTP要求受信ハンドラ

HTTP要求の本文全体を受け取ると、onHttpRequest 関数が呼びだされる。

パラメータから要求の内容を解析し、HttpChannel を用いて応答を作る。

virtual void onHttpRequest(HttpChannel* pChannel, const HttpRequest* pReq, Buffer* pBuf, HttpResponse* pRes) = 0;
リクエストを解析する典型的なケースとして、GET の querystring と POST の本文がある。querystring は HttpRequest の uri.querystring に、本文は Buffer に格納されている。

HTTP応答

HTTP応答をするには、HttpChannel を次のように扱う:

  1. beginResponse で応答を書くバッファを得る
  2. 得たバッファに内容を書きこむ
  3. endResponse を呼ぶ

例えばサンプルコードでは次のようになっている:

   std::string s = ...; //カウンタの文字列表現を格納
   {
      Buffer* pOut = pChannel->beginResponse(s.size());
      if (pOut == NULL) {
         pChannel->closeLater();
         return;
      }
      pOut->writeArray(s.c_str(), s.size());
      pChannel->endResponse();
   }