Monday, June 3, 2013

NginX with FastCGI and C

NginX with FastCGI and C

# uname -a
FreeBSD bsd91-test 9.1-RELEASE FreeBSD 9.1-RELEASE #0 r243825: Tue Dec 4 09:23:10 UTC 2012 root@farrell.cse.buffalo.edu:/usr/obj/usr/src/sys/GENERIC amd64

Install nginx:
# cd /usr/ports/www/nginx
# make config-recursive
# make install clean

Install FastCGI Development Kit
# cd /usr/ports/www/fcgi
# make config-recursive
# make install

Install spawn-fcgi
# cd /usr/ports/www/spawn-fcgi
# make config-recursive
# make install

Check the version of the installed ports:
# pkg_info | grep -iE 'cgi|nginx'
fcgi-devkit-2.4.0 FastCGI Development Kit
nginx-1.4.1,1 Robust and small WWW server
spawn-fcgi-1.6.3 spawn-fcgi is used to spawn fastcgi applications

Edit nginx.conf
# vim /usr/local/etc/nginx/nginx.conf
server {
        listen   80;
        server_name testcgi.local;
 
       location / {
                # host and port to fastcgi server
                root   /home/srv/bsd-mon/nginx/testcgi.local;
                index  index.html;
 
                fastcgi_pass 127.0.0.1:9000;
                include fastcgi_params; # need this line, or query string will be empty.
                #fastcgi_index index.php;
       }
}

View a list of fastcgi parameters:
# cat /usr/local/etc/nginx/fastcgi_params

Create the www directory:
# mkdir -p /home/srv/bsd-mon/nginx/testcgi.local

Start nginx:
# /usr/local/etc/rc.d/nginx start

Hello World Example:
# vim hello.c
#include "fcgi_stdio.h"
#include <stdio.h>
#include <stdlib.h>

int main( int argc, char *argv[] )
{
  while( FCGI_Accept() >= 0 )
  {
    printf("Content-type: text/html; charset=UTF-8\n");
    printf("Status: 200 OK\n");
    printf("\n");
    printf("<br>Hello world in C");

    printf("<br>HTTP_HOST: %s\n", getenv("HTTP_HOST"));
    printf("<br>HTTP_CONNECTION: %s\n", getenv("HTTP_CONNECTION"));
    //printf("<br>HTTP_CACHE_CONTROL: %s\n", getenv("HTTP_CACHE_CONTROL"));
    printf("<br>HTTP_ACCEPT: %s\n", getenv("HTTP_ACCEPT"));
    printf("<br>HTTP_USER_AGENT: %s\n", getenv("HTTP_USER_AGENT"));
    printf("<br>HTTP_ACCEPT_ENCODING: %s\n", getenv("HTTP_ACCEPT_ENCODING"));
    printf("<br>HTTP_ACCEPT_LANGUAGE: %s\n", getenv("HTTP_ACCEPT_LANGUAGE"));
    //printf("<br>PATH: %s\n", getenv("PATH"));
    //printf("<br>SystemRoot: %s\n", getenv("SystemRoot"));
    //printf("<br>COMSPEC: %s\n", getenv("COMSPEC"));
    //printf("<br>PATHEXT: %s\n", getenv("PATHEXT"));
    //printf("<br>WINDIR: %s\n", getenv("WINDIR"));
    //printf("<br>SERVER_SIGNATURE: %s\n", getenv("SERVER_SIGNATURE"));
    //printf("<br>SERVER_SOFTWARE: %s\n", getenv("SERVER_SOFTWARE"));
    printf("<br>SERVER_NAME: %s\n", getenv("SERVER_NAME"));
    printf("<br>SERVER_ADDR: %s\n", getenv("SERVER_ADDR"));
    printf("<br>SERVER_PORT: %s\n", getenv("SERVER_PORT"));
    printf("<br>REMOTE_ADDR: %s\n", getenv("REMOTE_ADDR"));
    printf("<br>DOCUMENT_ROOT: %s\n", getenv("DOCUMENT_ROOT"));
    //printf("<br>SERVER_ADMIN: %s\n", getenv("SERVER_ADMIN"));
    //printf("<br>SCRIPT_FILENAME: %s\n", getenv("SCRIPT_FILENAME"));
    printf("<br>REMOTE_PORT: %s\n", getenv("REMOTE_PORT"));
    printf("<br>GATEWAY_INTERFACE: %s\n", getenv("GATEWAY_INTERFACE"));
    printf("<br>SERVER_PROTOCOL: %s\n", getenv("SERVER_PROTOCOL"));
    printf("<br>REQUEST_METHOD: %s\n", getenv("REQUEST_METHOD"));
    printf("<br>QUERY_STRING: %s\n", getenv("QUERY_STRING"));
    printf("<br>REQUEST_URI: %s\n", getenv("REQUEST_URI"));
    printf("<br>SCRIPT_NAME: %s\n", getenv("SCRIPT_NAME"));
    //printf("<br>REQUEST_TIME_FLOAT: %s\n", getenv("REQUEST_TIME_FLOAT"));
    //printf("<br>REQUEST_TIME: %s\n", getenv("REQUEST_TIME"));
  }

  return 0;
}

Compile and link against fcgi library
# gcc -o hello hello.c -I/usr/local/include -L/usr/local/lib -lfcgi

Run the code using spawn-cgi:
# spawn-fcgi -a127.0.0.1 -p9000 -n ./hello

Or you can try to run the code using:
# cgi-fcgi -start -connect 127.0.0.1:9000 ./hello

Edit C:\Windows\System32\drivers\etc\hosts
192.168.0.128 testcgi.local

Use browser
Go to http://testcgi.local/index.html?q1=q1&q2=q2

Output:

Hello world in C
HTTP_HOST: testcgi.local
HTTP_CONNECTION: keep-alive
HTTP_ACCEPT: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
HTTP_USER_AGENT: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36
HTTP_ACCEPT_ENCODING: gzip,deflate,sdch
HTTP_ACCEPT_LANGUAGE: en-US,en;q=0.8
SERVER_NAME: testcgi.local
SERVER_ADDR: 192.168.0.128
SERVER_PORT: 80
REMOTE_ADDR: 192.168.0.121
DOCUMENT_ROOT: /home/srv/bsd-mon/nginx/testcgi.local
REMOTE_PORT: 3791
GATEWAY_INTERFACE: CGI/1.1
SERVER_PROTOCOL: HTTP/1.1
REQUEST_METHOD: GET
QUERY_STRING: q1=q1&q2=q2
REQUEST_URI: /index.html?q1=q1&q2=q2
SCRIPT_NAME: /index.html

FastCGI libfcgi HTTP GET and POST requests example:
/* 
 * fastcgi_http_get_post_sample.c - FastCGI libfcgi HTTP GET and POST requests example.
 * Compile with: gcc -Wall -I/usr/local/include -L/usr/local/lib -lfcgi fastcgi_http_get_post_sample.c -o fastcgi_http_get_post_sample
 *
 * This link contains documentation for most public functions: http://www.fastcgi.com/devkit/include/fcgiapp.h.
 *
 * Download the "Development Kit" which includes a C library implementation of FastCGI and C/C++ examples. The library is very easy to learn, so you should be up and running quickly. Read the fcgiapp.h file to get going. http://www.fastcgi.com/drupal/node/5.
 *
 * Reference: https://code.google.com/p/c-obix-tools/
 * Reference: https://code.google.com/p/c-obix-tools/source/browse/trunk/diem/CoT/src/server/obix_fcgi.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcgiapp.h>

char* obix_fcgi_readRequestInput(FCGX_Request* request);
void handle_request(FCGX_Request *request);

#define printf(...) FCGX_FPrintF(request->out, __VA_ARGS__)
#define get_param(KEY) FCGX_GetParam(KEY, request->envp)

#define LISTENSOCK_FILENO 0
#define LISTENSOCK_FLAGS 0

int main(void) {
  FCGX_Request request;

  /* Method 1 */
  // Note: If you want to use TCP rather than a unix socket, change the first argument of FCGX_OpenSocket to ":portnum" (i.e. ":2000" -- you need the colon). Then use "fastcgi_pass yourfcgiserver:2000;" in the nginx config.
  //
  // int sock;
  // sock = FCGX_OpenSocket(":2005", 5);
  // FCGX_InitRequest(&request, sock, 0);

  /* Method 2 */
  // Note: edit nginx.conf:
  // fastcgi_pass    unix:/var/run/myfcgiserver.sock;
  // With this, any request to yourserver/fcgi-bin/... will be sent to your fastcgi server.
  //
  // int sockfd = FCGX_OpenSocket("/var/run/myfcgiserver.sock", 1024);
  // FCGX_Init(); 
  // FCGX_InitRequest(&request, sockfd, 0);

  /* Method 3 */
  // Note: spawn-fcgi -a127.0.0.1 -p9000 -n fastcgi_http_post_sample
  //
  int err = FCGX_Init(); // call before Accept in multithreaded apps 
  // if (err) { syslog (LOG_INFO, "FCGX_Init failed: %d", err); return 1; }
  err = FCGX_InitRequest(&request, LISTENSOCK_FILENO, LISTENSOCK_FLAGS);
  // if (err) { syslog(LOG_INFO, "FCGX_InitRequest failed: %d", err); return 2; }

  while (FCGX_Accept_r(&request) >= 0) {
    handle_request(&request);
    FCGX_Finish_r(&request);
  }

  return EXIT_SUCCESS;
}

char* obix_fcgi_readRequestInput(FCGX_Request* request)
{
  char* buffer = NULL;
  int bufferSize = 1024;
  int bytesRead = 0;
  int error;

  do
  {
      // we start from buffer 2KB
      // and than double it's size on every iteration
      bufferSize = bufferSize << 1;

      buffer = (char*) realloc(buffer, bufferSize);
      if (buffer == NULL)
      {
          //log_error("Not enough memory to read the contents of the request.");
          return NULL;
      }

      bytesRead += FCGX_GetStr(buffer + bytesRead, bufferSize - bytesRead, request->in);

      if (bytesRead == 0)
      {
          //empty input
          free(buffer);
          return NULL;
      }

      error = FCGX_GetError(request->in);
      if (error != 0)
      {
          //log_error("Error occurred while reading request input (code %d).", error);
          free(buffer);
          return NULL;
      }

      // repeat until buffer is big enough to store whole input
  }
  while(bytesRead == bufferSize);
  // finalize input string
  buffer[bytesRead] = '\0';
  //log_debug("Received request input (size = %d):\n%s\n", bytesRead, buffer);

  return buffer;
}

void handle_request(FCGX_Request *request) {
  char *value;

  printf("Content-Type: text/html\r\n");
  printf("\r\n");

  // print the basic environment
  printf("<h1>Print the basic environment:</h1>");
  if ((value = get_param("REQUEST_METHOD")) != NULL) {
    printf("%s ", value);
  }
  if ((value = get_param("REQUEST_URI")) != NULL) {
    printf("%s", value);
  }
  if ((value = get_param("QUERY_STRING")) != NULL) {
    printf("?%s", value);
  }
  if ((value = get_param("SERVER_PROTOCOL")) != NULL) {
    printf(" %s", value);
  }

  // print out all the environment
  printf("<h1>Print out all the environment:</h1>");

  char **env = request->envp;

  while (*(++env))
  {
    printf("%s<br>", (*env));
  }

  // Handle POST request
  if (strcmp((value = get_param("REQUEST_METHOD")), "POST") == 0)
  {
    printf("<h1>Handle POST request:</h1>");

    char* input = obix_fcgi_readRequestInput(request);
    //obix_server_handlePOST(response, uri, input);

    printf("test %s", input);

    if (input != NULL)
    {
        free(input);
    }
  }

  printf("\n");
}

HTML form example:
<form action="http://testcgi.local/index.html?q1=q1&q2=q2" method="POST">
  <input type="text" name="asdf0" value="ASDF0&ASDF0">
  <input type="text" name="asdf1" value="ASDF1">
  <input type="submit">
</form>

Reference:
http://www.fastcgi.com/devkit/doc/fastcgi-prog-guide/ch2c.htm#3659

No comments: