Thursday, August 15, 2013

Example nginx module for reading request body.

Description:

    The module reads the contents  from PUT/POST request body, saves it on the disk  and returns HTTP 202 (Accepted) status code if successful and returns the URI which the user can use to retrieve back the contents in "Location" response header and the name of the file in which the contents were saved in the response body.


Module Source Code:

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <string.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

const char*  documentRoot = "/usr/local/nginx/html/";


static void ngx_http_sample_put_handler(ngx_http_request_t *r)
{
    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "put handler called");

    char filename[200] = { '\0' };

    struct timeval tv;

    gettimeofday(&tv, NULL);

    sprintf(filename, "%s%ld.%ld", documentRoot, tv.tv_sec, tv.tv_usec);



    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "FileName:%s", filename);

    int fd = creat(filename, 0644);

    if(-1 == fd)
    {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to create mail file.%s", strerror(errno));
        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return;

    }

    if(NULL == r->request_body->temp_file)
    {
        /*
         * The entire request body is available in the list of buffers pointed by r->request_body->bufs.
         *
         * The list can have a maixmum of two buffers. One buffer contains the request body that was pre-read along with the request headers.
         * The other buffer contains the rest of the request body. The maximum size of the buffer is controlled by 'client_body_buffer_size' directive.
         * If the request body cannot be contained within these two buffers, the entire body  is writtin to the temp file and the buffers are cleared.
         */
        ngx_buf_t    *buf;

        ngx_chain_t  *cl;


        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "Writing data from buffers.");
        cl = r->request_body->bufs;
        for( ;NULL != cl; cl = cl->next )
        {
            buf = cl->buf;
            if(write(fd, buf->pos, buf->last - buf->pos) < 0)
            {

                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer.");
                ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                close(fd);
                return;
            }
        }
    }
    else
    {
        /**
         * The entire request body is available in the temporary file.
         *
         */
        size_t ret;
        size_t offset = 0;
        unsigned char data[4096];

        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "Writing data from temp file.");

        while(  (ret = ngx_read_file(&r->request_body->temp_file->file, data, 4096, offset)) > 0)
        {
            if(write(fd, data, ret) < 0)
            {
                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer.");
                ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                close(fd);
                return;
            }
            offset = offset + ret;
        }
    }

    close(fd);

    unsigned char* data;

    data = ngx_pcalloc(r->pool, strlen(filename));
    if (data == NULL) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer.");
        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
    }

    ngx_memcpy(data, filename + strlen(documentRoot), strlen(filename) - strlen(documentRoot));

    r->headers_out.status = NGX_HTTP_ACCEPTED;

    r->headers_out.content_length_n = strlen(filename) - strlen(documentRoot);
    r->headers_out.content_type.len = sizeof("text/plain") - 1;
    r->headers_out.content_type.data = (u_char *) "text/plain";

    r->headers_out.location = ngx_list_push(&r->headers_out.headers);

    if (NULL == r->headers_out.location) {
        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
    }
    r->headers_out.location->hash = 1;
    r->headers_out.location->key.len = sizeof("Location") - 1;
    r->headers_out.location->key.data = (u_char *) "Location";
    r->headers_out.location->value.len = strlen(filename) - strlen(documentRoot);
    r->headers_out.location->value.data = data;
    ngx_http_send_header(r);


    ngx_int_t rc;

    rc = ngx_http_send_header(r);

    if(rc == NGX_ERROR || rc > NGX_OK)
    {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Sending headers failed.");
        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
    }

    ngx_buf_t *b;
    ngx_chain_t out;

    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    if (b == NULL) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer.");
        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
    }


    b->pos = data;

    b->last = data + (strlen(filename));

    b->memory = 1;

    b->last_buf = 1;

    out.buf = b;

    out.next = NULL;

    ngx_http_finalize_request(r, ngx_http_output_filter(r, &out));

    return;

}



static ngx_int_t
ngx_http_sample_handler(ngx_http_request_t *r)
{
    ngx_int_t     rc;

   /**
    * Specify the handler function to be called after reading the request body.
    */
    rc = ngx_http_read_client_request_body(r, ngx_http_sample_put_handler);

    if (rc >= NGX_HTTP_SPECIAL_RESPONSE){
            return rc;
    }

   return NGX_DONE;
}

static char *
ngx_http_sample(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t  *clcf;

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);

    /**
     * Get the location configuration and specify the function
     * that would hanlde requests for this location.
     */

    clcf->handler = ngx_http_sample_handler;


    return NGX_CONF_OK;
}

static ngx_command_t  ngx_http_sample_commands[] = {
    { ngx_string("sample"),
      NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
      ngx_http_sample,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },

      ngx_null_command
};


static ngx_http_module_t  ngx_http_sample_module_ctx = {
    NULL,                          /* preconfiguration */
    NULL,                          /* postconfiguration */

    NULL,                          /* create main configuration */
    NULL,                          /* init main configuration */
    NULL,                          /* create server configuration */
    NULL,                          /* merge server configuration */

    NULL,  /* create location configuration */
    NULL /* merge location configuration */
};


ngx_module_t  ngx_http_sample_module = {
    NGX_MODULE_V1,
    &ngx_http_sample_module_ctx, /* module context */
    ngx_http_sample_commands,   /* module directives */
    NGX_HTTP_MODULE,               /* module type */
    NULL,                          /* init master */
    NULL,                          /* init module */
    NULL,                          /* init process */
    NULL,                          /* init thread */
    NULL,                          /* exit thread */
    NULL,                          /* exit process */
    NULL,                          /* exit master */
    NGX_MODULE_V1_PADDING
};

Module configuration:
http {

        location /samples {

            sample;
        }

}

Testing the module:

i) Create a sample file to send in the request.

# echo "Sample Module" > input_file
# cat input_file
Sample Module

ii) Send the http request.

# curl -T ./input_file  -D header http://192.168.1.3/samples
1376574012.497879

iii) The reqest and response headers received.
# cat header
HTTP/1.1 100 Continue

HTTP/1.1 202 Accepted
Server: nginx/1.4.2
Date: Thu, 15 Aug 2013 13:40:12 GMT
Content-Type: text/plain
Content-Length: 17
Connection: keep-alive
Location: 1376574012.497879

iv) Use the filename specified in the "Location" response header field / in the response body to retrieve it.

# wget http://192.168.1.3/1376574012.497879
--2013-08-15 19:11:55--  http://192.168.1.3/1376574012.497879
Connecting to 192.168.1.3:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 14 [application/octet-stream]
Saving to: `1376574012.497879'

100%[==============================================================================================================================>] 14          --.-K/s   in 0s

2013-08-15 19:11:55 (66.4 KB/s) - `1376574012.497879' saved [14/14]

v) The retrieved file is the same as the contents sent in the first http request.
# cat 1376574012.497879
Sample Module


Reference:


1 comment: