[Patch] type{} directive

Grzegorz Nosek grzegorz.nosek at gmail.com
Wed Aug 18 18:19:08 MSD 2010


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 ;)

| 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 Nosek

---
 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(r);
 
-            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(r);
+
+    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




More information about the nginx mailing list