Type{} directive

Hi all,

After a recent discussion I decided to whip up a patch implementing the
type {} directive to route requests according to MIME types of requested
files.

The main advantage is a clearer config file, as instead of:

| location ~ .(gif|png|jpe?g|tif|ico) {
| expires max;
| access_log off;
| }

you can now say:

| type image/* {
| expires max;
| access_log off;
| }

(foo/* is the only wildcard type available right now, so e.g. text/ml
or application/vnd.
won’t work).

A particularly nice aspect of this is that type{}s are looked up
independently from location{}s, so the config below sets “expires max”
both on /foo.txt and on /foo/foo.txt:

| location / {
| allow all;
| }
|
| location /foo {
| allow 127.0.0.1;
| deny all;
| }
|
| type text/plain {
| expires max;
| }

The catch is that it only works on static files, as processed by the
ngx_http_static_module, not on e.g. proxied responses (sorry Mike!)

There’s another use case, but Igor said he doesn’t like it, so pretend
you haven’t seen it :wink:

| types {
| application/x-httpd-php php;
| }
|
| location / {
| # all the usual stuff
| }
|
| type application/x-httpd-php {
| fastcgi_pass … # etc.
| }

The above config (inside a server{} block, of course) pushes all .php
files via FastCGI (to php-fpm or something). This happens very late in
the request processing stage, just before the raw php file would have
been served to the client.

The patch is based on 0.8.49. I’m sure it has flaws but all comments are
gladly accepted (both for high-level feature discussion and low-level
code review).

Best regards,
Grzegorz N.


src/http/modules/ngx_http_static_module.c | 30 ++±
src/http/ngx_http_core_module.c | 351
+++++++++++++++++++++++++±–
src/http/ngx_http_core_module.h | 10 +
3 files changed, 363 insertions(+), 28 deletions(-)

diff --git a/src/http/modules/ngx_http_static_module.c
b/src/http/modules/ngx_http_static_module.c
index 57b5130…b30ce8b 100644
— a/src/http/modules/ngx_http_static_module.c
+++ b/src/http/modules/ngx_http_static_module.c
@@ -49,14 +49,15 @@ ngx_http_static_handler(ngx_http_request_t *r)
{
u_char *last, *location;
size_t root, len;

  • ngx_str_t path;
  • ngx_str_t path, content_type;
    ngx_int_t rc;
    ngx_uint_t level;
    ngx_log_t *log;
    ngx_buf_t *b;
    ngx_chain_t out;
    ngx_open_file_info_t of;
  • ngx_http_core_loc_conf_t *clcf;
  • ngx_http_core_loc_conf_t *clcf, *sclcf, *type_clcf;

  • ngx_http_core_srv_conf_t *cscf;

    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_POST))) {
    return NGX_HTTP_NOT_ALLOWED;
    @@ -193,6 +194,31 @@ ngx_http_static_handler(ngx_http_request_t *r)

#endif

  • content_type.data = NULL;
  • content_type.len = 0;
  • if (!clcf->type_loc) {
  •    /*
    
  •     * try to find a type { } block matching the content type
    
  •     * as determined by file extension
    
  •     */
    
  •    rc = ngx_http_find_content_type(r, &content_type);
    
  •    if (rc == NGX_OK && content_type.data) {
    
  •        /* look in type { } blocks in location { } */
    
  •        type_clcf = ngx_http_core_find_type_location(clcf, 
    

&content_type, 0);

  •        if (type_clcf) {
    
  •            return ngx_http_use_location(r, type_clcf);
    
  •        }
    
  •        /* look in type { } blocks in server { } */
    
  •        cscf = r->srv_conf[ngx_http_core_module.ctx_index];
    
  •        sclcf = 
    

cscf->ctx->loc_conf[ngx_http_core_module.ctx_index];

  •        type_clcf = ngx_http_core_find_type_location(sclcf, 
    

&content_type, 0);

  •        if (type_clcf) {
    
  •            return ngx_http_use_location(r, type_clcf);
    
  •        }
    
  •    }
    
  • }
  • if (r->method & NGX_HTTP_POST) {
    return NGX_HTTP_NOT_ALLOWED;
    }
    diff --git a/src/http/ngx_http_core_module.c
    b/src/http/ngx_http_core_module.c
    index 6a11a87…9fb54fb 100644
    — a/src/http/ngx_http_core_module.c
    +++ b/src/http/ngx_http_core_module.c
    @@ -15,6 +15,13 @@ typedef struct {
    } ngx_http_method_name_t;

+#define DEFAULT_NUM_TYPES 4
+typedef struct {

  • ngx_str_t mime_type;
  • ngx_http_core_loc_conf_t *clcf;
    +} ngx_http_type_conf_t;

#define NGX_HTTP_REQUEST_BODY_FILE_OFF 0
#define NGX_HTTP_REQUEST_BODY_FILE_ON 1
#define NGX_HTTP_REQUEST_BODY_FILE_CLEAN 2
@@ -77,6 +84,9 @@ static char *ngx_http_gzip_disable(ngx_conf_t *cf,
ngx_command_t *cmd,
static char *ngx_http_core_lowat_check(ngx_conf_t *cf, void *post, void
*data);
static char *ngx_http_core_pool_size(ngx_conf_t *cf, void *post, void
*data);

+static char *ngx_http_type(ngx_conf_t *cf, ngx_command_t *cmd,

  • void *conf);

static ngx_conf_post_t ngx_http_core_lowat_post =
{ ngx_http_core_lowat_check };

@@ -724,6 +734,13 @@ static ngx_command_t ngx_http_core_commands[] = {

#endif

  • { ngx_string(“type”),

NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1,

  •  ngx_http_type,
    
  •  NGX_HTTP_LOC_CONF_OFFSET,
    
  •  0,
    
  •  NULL },
    
  •  ngx_null_command
    

};

@@ -1657,17 +1674,13 @@ ngx_http_test_content_type(ngx_http_request_t
*r, ngx_hash_t *types_hash)

ngx_int_t
-ngx_http_set_content_type(ngx_http_request_t *r)
+ngx_http_find_content_type(ngx_http_request_t *r, ngx_str_t *typeptr)
{
u_char c, *exten;
ngx_str_t *type;
ngx_uint_t i, hash;
ngx_http_core_loc_conf_t *clcf;

  • if (r->headers_out.content_type.len) {

  •    return NGX_OK;
    
  • }

  • clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

    if (r->exten.len) {
    @@ -1698,15 +1711,35 @@ ngx_http_set_content_type(ngx_http_request_t *r)
    r->exten.data, r->exten.len);

       if (type) {
    
  •        r->headers_out.content_type_len = type->len;
    
  •        r->headers_out.content_type = *type;
    
  •        *typeptr = *type;
    
           return NGX_OK;
       }
    
    }
  • r->headers_out.content_type_len = clcf->default_type.len;
  • r->headers_out.content_type = clcf->default_type;
  • *typeptr = clcf->default_type;
  • return NGX_OK;
    +}

+ngx_int_t
+ngx_http_set_content_type(ngx_http_request_t *r)
+{

  • ngx_str_t content_type;

  • ngx_int_t rv;

  • if (r->headers_out.content_type.len) {

  •    return NGX_OK;
    
  • }

  • rv = ngx_http_find_content_type(r, &content_type);

  • if (rv != NGX_OK)

  •    return rv;
    
  • r->headers_out.content_type_len = content_type.len;

  • r->headers_out.content_type = content_type;

    return NGX_OK;
    }
    @@ -2332,14 +2365,11 @@ ngx_http_internal_redirect(ngx_http_request_t
    *r,
    }

-ngx_int_t
-ngx_http_named_location(ngx_http_request_t *r, ngx_str_t *name)
+ngx_http_core_loc_conf_t *
+ngx_http_find_named_location(ngx_http_request_t *r, ngx_str_t *name)
{
ngx_http_core_srv_conf_t *cscf;
ngx_http_core_loc_conf_t **clcfp;

  • ngx_http_core_main_conf_t *cmcf;

  • r->main->count++;

    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);

@@ -2356,24 +2386,48 @@ ngx_http_named_location(ngx_http_request_t *r,
ngx_str_t *name)
continue;
}

  •        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
    
  •                       "using location: %V \"%V?%V\"",
    
  •                       name, &r->uri, &r->args);
    
  •        return *clcfp;
    
  •    }
    
  • }
  • return NULL;
    +}
  •        r->internal = 1;
    
  •        r->content_handler = NULL;
    
  •        r->loc_conf = (*clcfp)->loc_conf;
    

+ngx_int_t
+ngx_http_use_location(ngx_http_request_t *r, ngx_http_core_loc_conf_t
*clcf)
+{

  • ngx_http_core_main_conf_t *cmcf;
  •        ngx_http_update_location_config(r);
    
  • r->main->count++;
  •        cmcf = ngx_http_get_module_main_conf(r, 
    

ngx_http_core_module);

  • ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  •               "using location: %V \"%V?%V\"",
    
  •               name, &r->uri, &r->args);
    
  •        r->phase_handler = 
    

cmcf->phase_engine.location_rewrite_index;

  • r->internal = 1;
  • r->content_handler = NULL;
  • r->loc_conf = clcf->loc_conf;
  •        ngx_http_core_run_phases(r);
    
  • ngx_http_update_location_config®;
  •        return NGX_DONE;
    
  •    }
    
  • cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
  • r->phase_handler = cmcf->phase_engine.location_rewrite_index;
  • ngx_http_core_run_phases®;
  • return NGX_DONE;
    +}

+ngx_int_t
+ngx_http_named_location(ngx_http_request_t *r, ngx_str_t *name)
+{

  • ngx_http_core_loc_conf_t *clcf;

  • clcf = ngx_http_find_named_location(r, name);

  • if (clcf) {

  •    return ngx_http_use_location(r, clcf);
    

    }

    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
    @@ -3375,6 +3429,9 @@ ngx_http_core_merge_loc_conf(ngx_conf_t *cf, void
    *parent, void *child)

#endif

  • ngx_conf_merge_ptr_value(conf->type_locations,
  •                          prev->type_locations, NULL);
    
  • return NGX_CONF_OK;
    }

@@ -4479,3 +4536,245 @@ ngx_http_core_pool_size(ngx_conf_t *cf, void
*post, void *data)

 return NGX_CONF_OK;

}
+
+
+static char *
+ngx_http_add_type_location(ngx_conf_t *cf, ngx_str_t *mime_type,

  • ngx_http_core_loc_conf_t *pclcf, ngx_http_core_loc_conf_t *clcf)
    +{
  • ngx_http_type_conf_t *tcf;
  • if (!pclcf->type_locations) {
  •    pclcf->type_locations = ngx_list_create(cf->pool, 
    

DEFAULT_NUM_TYPES,

  •        sizeof(ngx_http_type_conf_t));
    
  •    if (!pclcf->type_locations) {
    
  •        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
    
  •                           "failed to allocate memory for mime type 
    

list");

  •        return NGX_CONF_ERROR;
    
  •    }
    
  • }
  • tcf = ngx_list_push(pclcf->type_locations);
  • if (!tcf) {
  •    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
    
  •                       "failed to allocate memory for mime type 
    

data");

  •    return NGX_CONF_ERROR;
    
  • }
  • tcf->mime_type = *mime_type;
  • tcf->clcf = clcf;
  • return NGX_CONF_OK;
    +}

+static inline ngx_int_t
+ngx_str_equal(ngx_str_t *a, ngx_str_t *b)
+{

  • if (a->len != b->len) {
  •    return 0;
    
  • }
  • return !ngx_strncmp(a->data, b->data, a->len);
    +}

+static inline ngx_int_t
+ngx_mimetype_equal(ngx_str_t *a, ngx_str_t *b)
+{

  • size_t pos;
  • const u_char *p;
  • p = memchr(a->data, ‘/’, a->len);
  • if (!p)
  •    return 0;
    
  • pos = p - a->data;
  • if (pos >= b->len || pos == a->len || b->data[pos] != ‘/’) {
  •    return 0;
    
  • }
  • if ((char)a->data[pos + 1] == ‘*’) {
  •    return !memcmp(a->data, b->data, pos);
    
  • }
  • return ngx_str_equal(a, b);
    +}

+static inline ngx_int_t
+ngx_is_mimetype_valid(ngx_str_t *type)
+{

  • size_t pos;
  • const u_char *p;
  • p = memchr(type->data, ‘/’, type->len);
  • if (!p) {
  •    return 0;
    
  • }
  • pos = p - type->data;
  • if (pos == 0 || pos == type->len - 1) {
  •    return 0;
    
  • }
  • if (pos == type->len - 2 && p[1] == ‘*’) {
  •    /* a lone asterisk is allowed after the slash... */
    
  •    return 1;
    
  • }
  • if (memchr(p+1, ‘/’, type->len - pos - 2) != NULL)
  •    return 0;
    
  • /* … but nowhere else */
  • p = memchr(type->data, ‘*’, type->len);
  • return (p == NULL);
    +}

+ngx_http_core_loc_conf_t *
+ngx_http_core_find_type_location(ngx_http_core_loc_conf_t *pclcf,

  • ngx_str_t *mime_type, ngx_int_t exact)
    +{
  • ngx_list_part_t *part;
  • ngx_http_type_conf_t *data;
  • ngx_http_core_loc_conf_t *clcf;
  • ngx_uint_t i;
  • if (!pclcf->type_locations) {
  •    return NULL;
    
  • }
  • part = &pclcf->type_locations->part;
  • data = part->elts;
  • clcf = NULL;
  • for (i = 0 ;; i++) {
  •    if (i >= part->nelts) {
    
  •        if (part->next == NULL) {
    
  •            break;
    
  •        }
    
  •        part = part->next;
    
  •        data = part->elts;
    
  •        i = 0;
    
  •    }
    
  •    if (exact) {
    
  •        if (ngx_str_equal(&data[i].mime_type, mime_type)) {
    
  •            clcf = data[i].clcf;
    
  •            break;
    
  •        }
    
  •    } else {
    
  •        if (ngx_mimetype_equal(&data[i].mime_type, mime_type)) {
    
  •            clcf = data[i].clcf;
    
  •            break;
    
  •        }
    
  •    }
    
  • }
  • return clcf;
    +}

+static char *
+ngx_http_type(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{

  • void *mconf;

  • char *rv;

  • ngx_str_t *value, mime_type;

  • ngx_uint_t i, ctx_index;

  • ngx_conf_t save;

  • ngx_http_module_t *module;

  • ngx_http_conf_ctx_t *ctx, *pctx;

  • ngx_http_core_loc_conf_t *clcf, *pclcf;

  • ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));

  • if (ctx == NULL) {

  •    return NGX_CONF_ERROR;
    
  • }

  • value = cf->args->elts;

  • mime_type = value[1];

  • if (!ngx_is_mimetype_valid(&mime_type)) {

  •    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
    
  •                       "invalid mime type %V", &mime_type);
    
  •    return NGX_CONF_ERROR;
    
  • }

  • pctx = cf->ctx;

  • pclcf = pctx->loc_conf[ngx_http_core_module.ctx_index];

  • if (ngx_http_core_find_type_location(pclcf, &mime_type, 1)) {

  •    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
    
  •                       "duplicate mime type %V", &mime_type);
    
  •    return NGX_CONF_ERROR;
    
  • }

  • ctx->main_conf = pctx->main_conf;

  • ctx->srv_conf = pctx->srv_conf;

  • ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) *
    ngx_http_max_module);

  • if (ctx->loc_conf == NULL) {

  •    return NGX_CONF_ERROR;
    
  • }

  • for (i = 0; ngx_modules[i]; i++) {

  •    if (ngx_modules[i]->type != NGX_HTTP_MODULE) {
    
  •        continue;
    
  •    }
    
  •    module = ngx_modules[i]->ctx;
    
  •    if (module->create_loc_conf) {
    
  •        mconf = module->create_loc_conf(cf);
    
  •        if (mconf == NULL) {
    
  •             return NGX_CONF_ERROR;
    
  •        }
    
  •        ctx->loc_conf[ngx_modules[i]->ctx_index] = mconf;
    
  •    }
    
  • }

  • clcf = ctx->loc_conf[ngx_http_core_module.ctx_index];

  • clcf->loc_conf = ctx->loc_conf;

  • clcf->name = mime_type;

  • clcf->noname = 1;

  • clcf->type_loc = 1;

  • save = *cf;

  • cf->ctx = ctx;

  • cf->cmd_type = NGX_HTTP_LOC_CONF;

  • rv = ngx_conf_parse(cf, NULL);

  • for (i = 0; ngx_modules[i]; i++) {

  •    if (ngx_modules[i]->type != NGX_HTTP_MODULE) {
    
  •        continue;
    
  •    }
    
  •    module = ngx_modules[i]->ctx;
    
  •    ctx_index = ngx_modules[i]->ctx_index;
    
  •    if (module->merge_loc_conf) {
    
  •        rv = module->merge_loc_conf(cf,
    
  •                    pctx->loc_conf[ctx_index],
    
  •                    ctx->loc_conf[ctx_index]);
    
  •        if (rv != NGX_CONF_OK) {
    
  •             break;
    
  •        }
    
  •    }
    
  • }

  • *cf = save;

  • if (rv != NGX_CONF_OK) {

  •    return rv;
    
  • }

  • rv = ngx_http_add_type_location(cf, &mime_type, pclcf, clcf);

  • return rv;
    +}
    diff --git a/src/http/ngx_http_core_module.h
    b/src/http/ngx_http_core_module.h
    index f163e9a…982db27 100644
    — a/src/http/ngx_http_core_module.h
    +++ b/src/http/ngx_http_core_module.h
    @@ -288,6 +288,7 @@ struct ngx_http_core_loc_conf_s {
    unsigned noname:1; /* “if () {}” block or limit_except */
    unsigned lmt_excpt:1;
    unsigned named:1;

  • unsigned type_loc:1;

    unsigned exact_match:1;
    unsigned noregex:1;
    @@ -398,6 +399,7 @@ struct ngx_http_core_loc_conf_s {
    ngx_uint_t types_hash_bucket_size;

    ngx_queue_t *locations;

  • ngx_list_t *type_locations;

#if 0
ngx_http_core_loc_conf_t *prev_location;
@@ -450,6 +452,7 @@ ngx_int_t
ngx_http_core_content_phase(ngx_http_request_t *r,

void *ngx_http_test_content_type(ngx_http_request_t *r, ngx_hash_t
*types_hash);
+ngx_int_t ngx_http_find_content_type(ngx_http_request_t *r, ngx_str_t
*typeptr);
ngx_int_t ngx_http_set_content_type(ngx_http_request_t *r);
void ngx_http_set_exten(ngx_http_request_t *r);
ngx_int_t ngx_http_send_response(ngx_http_request_t *r, ngx_uint_t
status,
@@ -467,6 +470,10 @@ ngx_int_t ngx_http_subrequest(ngx_http_request_t
*r,
ngx_http_post_subrequest_t *psr, ngx_uint_t flags);
ngx_int_t ngx_http_internal_redirect(ngx_http_request_t *r,
ngx_str_t *uri, ngx_str_t *args);
+ngx_http_core_loc_conf_t
*ngx_http_find_named_location(ngx_http_request_t *r,

  • ngx_str_t *name);
    +ngx_int_t ngx_http_use_location(ngx_http_request_t *r,
  • ngx_http_core_loc_conf_t *clcf);
    ngx_int_t ngx_http_named_location(ngx_http_request_t *r, ngx_str_t
    *name);

@@ -482,6 +489,9 @@ ngx_int_t ngx_http_output_filter(ngx_http_request_t
*r, ngx_chain_t *chain);
ngx_int_t ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t
*chain);

+ngx_http_core_loc_conf_t *ngx_http_core_find_type_location(

  • ngx_http_core_loc_conf_t *pclcf, ngx_str_t *mime_type, ngx_int_t
    exact);

extern ngx_module_t ngx_http_core_module;

extern ngx_uint_t ngx_http_max_module;

1.6.2.4

On Wed, Aug 18, 2010 at 04:19:08PM +0200, Grzegorz N. wrote:

| access_log off;
| }

you can now say:

| type image/* {
| expires max;
| access_log off;
| }

I forgot to say that the type{} directive works inside location{} blocks
too:

| location /dev/ {
| root /www/dev;
|
| type text/* {
| expires epoch;
| }
| }
|
| location /prod/ {
| root /www/prod;
|
| type text/* {
| expires max;
| }
| }

Although if you put a text{} block inside a location{}, this location no
longer interprets e.g. server{}-level types. We’ll see if this is a
problem by popular demand :slight_smile:

Best regards,
Grzegorz N.

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs