view ngx_http_compose_filter_module.c @ 7:88272d0f29f9

Make sure upstream's status line won't be reused. Range filter may change status to 206 Partial Content, but if we got status line from upstream this won't have any effect on status sent to client. Clear r->status_line when enabling range filter.
author Maxim Dounin <mdounin@mdounin.ru>
date Sat, 20 Sep 2008 03:49:24 +0400
parents 27628b3310ab
children
line wrap: on
line source


/*
 * Copyright (C) Maxim Dounin
 */

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>


typedef struct {
    ngx_flag_t         enable;
} ngx_http_compose_conf_t;


typedef struct {
    ngx_uint_t         done;
    ngx_array_t        parts;
} ngx_http_compose_ctx_t;


static void *ngx_http_compose_create_conf(ngx_conf_t *cf);
static char *ngx_http_compose_merge_conf(ngx_conf_t *cf, void *parent,
    void *child);
static ngx_int_t ngx_http_compose_init(ngx_conf_t *cf);
static ngx_int_t ngx_http_compose_body_init(ngx_conf_t *cf);


static ngx_command_t  ngx_http_compose_commands[] = {

    { ngx_string("compose"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_compose_conf_t, enable),
      NULL },

      ngx_null_command
};


static ngx_http_module_t  ngx_http_compose_module_ctx = {
    NULL,                          /* preconfiguration */
    ngx_http_compose_init,         /* postconfiguration */

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

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

    ngx_http_compose_create_conf,  /* create location configuration */
    ngx_http_compose_merge_conf    /* merge location configuration */
};


ngx_module_t  ngx_http_compose_filter_module = {
    NGX_MODULE_V1,
    &ngx_http_compose_module_ctx,  /* module context */
    ngx_http_compose_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
};


static ngx_http_module_t  ngx_http_compose_body_module_ctx = {
    NULL,                          /* preconfiguration */
    ngx_http_compose_body_init,    /* 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_compose_body_filter_module = {
    NGX_MODULE_V1,
    &ngx_http_compose_body_module_ctx,  /* module context */
    NULL,                          /* 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
};


static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;


static ngx_int_t
ngx_http_compose_header_filter(ngx_http_request_t *r)
{
    off_t                     len;
    ngx_uint_t                i;
    ngx_str_t                *uri;
    ngx_list_part_t          *part;
    ngx_table_elt_t          *header;
    ngx_http_compose_conf_t  *conf;
    ngx_http_compose_ctx_t   *ctx;

    conf = ngx_http_get_module_loc_conf(r, ngx_http_compose_filter_module);

    if (!conf->enable) {
        return ngx_http_next_header_filter(r);
    }

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "compose header filter");

    /* create context */

    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_compose_ctx_t));
    if (ctx == NULL) {
        return NGX_ERROR;
    }

    if (ngx_array_init(&ctx->parts, r->pool, 1, sizeof(ngx_str_t))
        == NGX_ERROR)
    {
        return NGX_ERROR;
    }


    /*
     * Collect all X-Compose headers (or combined one?), store in context
     * for our body filter to make actual subrequests.  Hide them from the
     * response.
     */

    part = &r->headers_out.headers.part;
    header = part->elts;
    len = -1;

    for (i = 0; /* void */; i++) {

        if (i >= part->nelts) {
            if (part->next == NULL) {
                break;
            }

            part = part->next;
            header = part->elts;
            i = 0;
        }

        if (header[i].hash == 0) {
            continue;
        }

        if (header[i].key.len == sizeof("X-Compose-Length") - 1
            && ngx_strncasecmp(header[i].key.data, "X-Compose-Length",
                               sizeof("X-Compose-Length") - 1)
               == 0)
        {
            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                           "compose body filter: bingo, %V, %V",
                           &header[i].key, &header[i].value);

            header[i].hash = 0;

            len = ngx_atoof(header[i].value.data, header[i].value.len);
        }

        if (header[i].key.len == sizeof("X-Compose") - 1
            && ngx_strncasecmp(header[i].key.data, "X-Compose",
                               sizeof("X-Compose") - 1)
               == 0)
        {
            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                           "compose body filter: bingo, %V, %V",
                           &header[i].key, &header[i].value);

            header[i].hash = 0;

            /*
             * XXX multiple headers with the same name must be combinable,
             * see RFC 2616 4.2 Message Headers
             */

            uri = ngx_array_push(&ctx->parts);
            if (uri == NULL) {
                return NGX_ERROR;
            }

            *uri = header[i].value;
        }
    }

    if (ctx->parts.nelts == 0) {
        return ngx_http_next_header_filter(r);
    }

    r->headers_out.content_length_n = len;

    if (r->headers_out.content_length) {
        r->headers_out.content_length->hash = 0;
        r->headers_out.content_length = NULL;
    }

    ngx_http_set_ctx(r, ctx, ngx_http_compose_filter_module);

    if (len != -1) {
        r->allow_ranges = 1;
        r->late_ranges = 1;
        r->headers_out.status_line.len = 0;

    } else {
        ngx_http_clear_accept_ranges(r);
    }

    return ngx_http_next_header_filter(r);
}


static ngx_int_t
ngx_http_compose_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_str_t                 *uri, args;
    ngx_int_t                  rc;
    ngx_uint_t                 i, flags, last;
    ngx_http_request_t        *sr;
    ngx_http_compose_ctx_t    *ctx;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "compose body filter");

    ctx = ngx_http_get_module_ctx(r, ngx_http_compose_filter_module);

    if (ctx == NULL) {
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "compose body filter: no ctx");
        return ngx_http_next_body_filter(r, in);
    }

    if (ctx->done) {
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "compose body filter: done");
        return ngx_http_next_body_filter(r, in);
    }

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "compose body filter, doing work");

    /*
     * Ignore body that comes to us, replace it with subrequests.
     */

    last = 0;

    for ( ; in; in = in->next) {
        in->buf->pos = in->buf->last;
        if (in->buf->last_buf) {
            last = 1;
            in->buf->last_buf = 0;
        }
    }

    if (!last) {
        return NGX_OK;
    }

    ctx->done = 1;

    uri = ctx->parts.elts;

    for (i = 0; i < ctx->parts.nelts; i++) {

        args.len = 0;
        args.data = NULL;
        flags = 0;

        if (ngx_http_parse_unsafe_uri(r, &uri[i], &args, &flags) != NGX_OK) {
            return NGX_ERROR;
        }

        rc = ngx_http_subrequest(r, &uri[i], &args, &sr, NULL, flags);

        if (rc == NGX_ERROR || rc == NGX_DONE) {
            return rc;
        }
    }

    return ngx_http_send_special(r, NGX_HTTP_LAST);
}


static void *
ngx_http_compose_create_conf(ngx_conf_t *cf)
{
    ngx_http_compose_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_compose_conf_t));
    if (conf == NULL) {
        return NGX_CONF_ERROR;
    }

    conf->enable = NGX_CONF_UNSET;

    return conf;
}


static char *
ngx_http_compose_merge_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_compose_conf_t *prev = parent;
    ngx_http_compose_conf_t *conf = child;

    ngx_conf_merge_value(conf->enable, prev->enable, 0);

    return NGX_CONF_OK;
}


static ngx_int_t
ngx_http_compose_init(ngx_conf_t *cf)
{
    ngx_http_next_header_filter = ngx_http_top_header_filter;
    ngx_http_top_header_filter = ngx_http_compose_header_filter;

    return NGX_OK;
}


static ngx_int_t
ngx_http_compose_body_init(ngx_conf_t *cf)
{
    ngx_http_next_body_filter = ngx_http_top_body_filter;
    ngx_http_top_body_filter = ngx_http_compose_body_filter;

    return NGX_OK;
}