nginx で C++14 の FCGI を動作させる簡単な方法の紹介

# 使用環境例

この記事で紹介する方法で動作可能な事を確認済みの環境を例示します。

- ubuntu-16.04
- nginx-1.10.0
- clang-3.8 or gcc-5.4
- libfcgi-2.4.0
- spawn-fcgi-1.6.4
- curl-7.47.0 （http動作確認用なので w3m でも telnet でも chromium でも何でもOK）

# 準備

少なくとも ubuntu-16.04 ではすべて `apt` で導入できます。他の環境ではそれぞれの環境に合わせて適当に調達して下さい。

```shell-session
sudo apt install nginx clang-3.8 libfcgi-dev spawn-fcgi
```

# nginx で C++ の FCGI を動作させる簡単な方法

## C++14 FCGI サンプルコード

```cpp
// EXTERNAL
#include "fcgio.h"
// STD
#include <iostream>
#include <memory>
#include <functional>
#include <cstring>

auto main() -> int
{
  // 元の標準入出力系の streambuf 群をバックアップ＆カスタムデリーターによるリストア処理の予約
  using streambuf_restorer_signature_type = auto ( std::streambuf* ) -> void;
  using streambuf_restorer_functor_type = std::function< streambuf_restorer_signature_type >;
  using streambuf_restorer_type = std::unique_ptr< std::streambuf, streambuf_restorer_functor_type >;

  const auto cin_restorer  = streambuf_restorer_type( std::cin.rdbuf(), []( auto in ) { std::cin.rdbuf( in ); } );
  const auto cout_restorer = streambuf_restorer_type( std::cout.rdbuf(), []( auto in ) { std::cout.rdbuf( in ); } );
  const auto cerr_restorer = streambuf_restorer_type( std::cerr.rdbuf(), []( auto in ) { std::cerr.rdbuf( in ); } );

  FCGX_Request request;

  FCGX_Init();
  FCGX_InitRequest( &request, 0, 0 );

  while ( FCGX_Accept_r( &request ) == 0 )
  {
    auto cin_buffer  = fcgi_streambuf( request.in );
    auto cout_buffer = fcgi_streambuf( request.out );
    auto cerr_buffer = fcgi_streambuf( request.err );

    std::cin.rdbuf( &cin_buffer );
    std::cout.rdbuf( &cout_buffer );
    std::cerr.rdbuf( &cerr_buffer );

    constexpr auto content_header_part = u8R"(Content-type: text/html

<html>
  <head>
    <title>Hello, World!</title>
  </head>
  <body>
)";

    constexpr auto content_footer_part = u8R"(  </body>
</html>
)";
    
    // リクエストにいわゆる POST で投げられたデータがあるか CONTENT_LENGTH で確認
    const auto in_content_length_string = FCGX_GetParam( "CONTENT_LENGTH", request.envp );
    const auto in_content_length = std::strlen( in_content_length_string )
      ? std::stoull( std::string( in_content_length_string ) )
      : 0ull
      ;

    // リクエストの CONTENT_LENGTH だけリクエストボディーを読み出す
    std::string in_content;
    in_content.resize( in_content_length );
    std::cin.read( &in_content[0], in_content_length );
    
    // レスポンスを出力する
    std::cout
      << content_header_part
      << "    <h1>Hello, World!</h>\n"
         "    <p>REQUEST_URI: " << FCGX_GetParam( "REQUEST_URI", request.envp ) << "</p>\n"
         "    <p>REQUEST CONTENT_LENGTH: " << in_content_length << "</p>\n"
         "    <p>REQUEST CONTENT: " << in_content << "</p>\n"
      << content_footer_part
      ;

    // スコープの終了により明示的に std::flush して居なくても fcgi_streambuf のデストラクターでフラッシュされる
  }

  // スコープの終了により標準入出力系の streambuf 群は自動的にバックアップからリストアされる
}
```

## ビルド

Clang:

```shell-session
clang++-3.8 -std=c++14 hello_world.cxx -lfcgi++ -lfcgi -o hello_world
```

または GCC:

```shell-session
g++-5 -std=c++14 hello_world.cxx -lfcgi++ -lfcgi -o hello_world
```

## spawn-fcgi による FCGI プロセスの生成

```shell-session
spawn-fcgi -p 58000 -n hello_world
```

## nginx による FCGI の動作設定

適当な `server { }` 設定の中で `location /hello_world` で動作させる例:

```nginx
server
{
  listen 80;
  server_name localhost;
  
  location /hello_world
  {
    # spawn-fcgi でプロセスを動作させる host:port
    fastcgi_pass   127.0.0.1:58000;
    # nginx の FCGI 各種パラメーター群の設定
    fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
    fastcgi_param  SERVER_SOFTWARE    nginx;
    fastcgi_param  QUERY_STRING       $query_string;
    fastcgi_param  REQUEST_METHOD     $request_method;
    fastcgi_param  CONTENT_TYPE       $content_type;
    fastcgi_param  CONTENT_LENGTH     $content_length;
    fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
    fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
    fastcgi_param  REQUEST_URI        $request_uri;
    fastcgi_param  DOCUMENT_URI       $document_uri;
    fastcgi_param  DOCUMENT_ROOT      $document_root;
    fastcgi_param  SERVER_PROTOCOL    $server_protocol;
    fastcgi_param  REMOTE_ADDR        $remote_addr;
    fastcgi_param  REMOTE_PORT        $remote_port;
    fastcgi_param  SERVER_ADDR        $server_addr;
    fastcgi_param  SERVER_PORT        $server_port;
    fastcgi_param  SERVER_NAME        $server_name;
  }
}
```

## 動作確認

```shell-session
curl -i -s -X POST -d 'this is the post content.' http://localhost/test1/hoge/fuga/piyo
```

```html
HTTP/1.1 200 OK
Server: nginx/1.10.0 (Ubuntu)
Date: Sun, 28 Aug 2016 02:21:31 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive

<html>
  <head>
    <title>Hello, World!</title>
  </head>
  <body>
    <h1>Hello, World!</h>
    <p>REQUEST_URI: /test1/hoge/fuga/piyo</p>
    <p>REQUEST CONTENT_LENGTH: 25</p>
    <p>REQUEST CONTENT: this is the post content.</p>
  </body>
</html>
```

ここは `curl` である必要は特に無いので、お好みの動作確認可能な http クライアントを使って下さい。

# 参考

- [Chris Wu - Writing Hello World in FCGI with C++](http://chriswu.me/blog/writing-hello-world-in-fcgi-with-c-plus-plus/)
- [Chris Wu - Getting Request URI and Content in C++ FCGI](http://chriswu.me/blog/getting-request-uri-and-content-in-c-plus-plus-fcgi/)