├── config ├── README.md └── ngx_http_footer_filter_module.c /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_footer_filter_module 2 | HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES ngx_http_footer_filter_module" 3 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_footer_filter_module.c" 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTTP Footer filter module for Nginx 2 | 3 | ## Introduction 4 | 5 | This is a module that is distributed with 6 | [tengine](http://tengine.taobao.org) which is a distribution of 7 | [Nginx](http://nginx.org) that is used by the e-commerce/auction site 8 | [Taobao.com](http://en.wikipedia.org/wiki/Taobao). This distribution 9 | contains some modules that are new on the Nginx scene. The 10 | `ngx_http_footer_filter` module is one of them. 11 | 12 | This module implements a body filter that adds a given string to the 13 | page footer. 14 | 15 | You might say that it provides a particular case of the 16 | [http sub module](http://wiki.nginx.org/HttpSubModule) in the sense 17 | that it adds something to the footer. You can do the same using the 18 | `http sub module` but using the footer filter should be faster since 19 | there's no string matching done on the request body. 20 | 21 | ## Configuration example 22 | 23 | location / { 24 | ## Using the $date_gmt variable from the SSI module (prints a 25 | ## UNIX timestamp). 26 | footer ""; 27 | index index.html; 28 | } 29 | 30 | location ^~ /assets/css { 31 | ## Add CSS to the MIME types to be added a footer. 32 | footer_types text/css; 33 | 34 | footer "/* host: $server_name - $date_local */"; 35 | } 36 | 37 | ## Module directives 38 | 39 | **footer** `string` 40 | 41 | **default:** `` 42 | 43 | **context:** `http, server, location` 44 | 45 | It defines the string to be printed at the footer of the request 46 | body. This string can have variables embedded. 47 | 48 |
49 |
50 | 51 | **footer_types** `MIME types` 52 | 53 | **default:** `footer_types: text/html` 54 | 55 | **context:** `http, server, location` 56 | 57 | Defines the [MIME types](http://en.wikipedia.org/wiki/MIME_type) of 58 | the files where the footer will be included. 59 | 60 | ## Installation 61 | 62 | 1. Clone the git repo. 63 | 64 | git clone git://github.com/taobao/nginx-http-footer-filter.git 65 | 66 | 2. Add the module to the build configuration by adding 67 | `--add-module=/path/to/nginx-http-footer-filter`. 68 | 69 | 3. Build the nginx binary. 70 | 71 | 4. Install the nginx binary. 72 | 73 | 5. Configure contexts where footer filter is enabled. 74 | 75 | 6. Done. 76 | 77 | ## Tagging releases 78 | 79 | I'm tagging each release in synch with the 80 | [Tengine](http://tengine.taobao.org) releases. 81 | 82 | ## Other tengine modules on Github 83 | 84 | + [http concat](https://github.com/taobao/nginx-http-concat): 85 | allows to concatenate a given set of files and ship a single 86 | response from the server. It's particularly useful for **aggregating** 87 | CSS and Javascript files. 88 | 89 | + [http slice](https://github.com/taobao/nginx-http-slice): allows 90 | to serve a file by slices. A sort of reverse byte-range. Useful for 91 | serving large files while not hogging the network. 92 | 93 | ## Original documentation 94 | 95 | The 96 | [original documentation](http://tengine.taobao.org/document_cn/http_footer_filter_cn.html) 97 | in Chinese. Note that the examples given therein rely on 98 | **non-standard** Nginx 99 | [variables](http://tengine.taobao.org/document_cn/variables_cn.html) 100 | that are not 101 | [available](http://nginx.org/en/docs/http/ngx_http_core_module.html#variables) 102 | on the official Nginx source but only on [tengine](http://tengine.taobao.org). 103 | 104 | ## License 105 | 106 | Copyright (C) 2010-2012 Alibaba Group Holding Limited 107 | 108 | Redistribution and use in source and binary forms, with or without 109 | modification, are permitted provided that the following conditions 110 | are met: 111 | 112 | 1. Redistributions of source code must retain the above copyright 113 | notice, this list of conditions and the following disclaimer. 114 | 115 | 2. Redistributions in binary form must reproduce the above copyright 116 | notice, this list of conditions and the following disclaimer in the 117 | documentation and/or other materials provided with the distribution. 118 | 119 | THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS "AS IS" AND ANY 120 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 121 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 122 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE 123 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 124 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 125 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 126 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 127 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 128 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 129 | IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 130 | -------------------------------------------------------------------------------- /ngx_http_footer_filter_module.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) 2010-2012 Alibaba Group Holding Limited 4 | */ 5 | 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | typedef struct { 13 | ngx_hash_t types; 14 | ngx_array_t *types_keys; 15 | ngx_http_complex_value_t *variable; 16 | } ngx_http_footer_loc_conf_t; 17 | 18 | 19 | typedef struct { 20 | ngx_str_t footer; 21 | } ngx_http_footer_ctx_t; 22 | 23 | 24 | static char *ngx_http_footer_filter(ngx_conf_t *cf, ngx_command_t *cmd, 25 | void *conf); 26 | static void *ngx_http_footer_create_loc_conf(ngx_conf_t *cf); 27 | static char *ngx_http_footer_merge_loc_conf(ngx_conf_t *cf, 28 | void *parent, void *child); 29 | static ngx_int_t ngx_http_footer_filter_init(ngx_conf_t *cf); 30 | 31 | 32 | static ngx_command_t ngx_http_footer_filter_commands[] = { 33 | 34 | { ngx_string("footer"), 35 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 36 | ngx_http_footer_filter, 37 | NGX_HTTP_LOC_CONF_OFFSET, 38 | 0, 39 | NULL }, 40 | 41 | { ngx_string("footer_types"), 42 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, 43 | ngx_http_types_slot, 44 | NGX_HTTP_LOC_CONF_OFFSET, 45 | offsetof(ngx_http_footer_loc_conf_t, types_keys), 46 | &ngx_http_html_default_types[0] }, 47 | 48 | ngx_null_command 49 | }; 50 | 51 | 52 | static ngx_http_module_t ngx_http_footer_filter_module_ctx = { 53 | NULL, /* proconfiguration */ 54 | ngx_http_footer_filter_init, /* postconfiguration */ 55 | 56 | NULL, /* create main configuration */ 57 | NULL, /* init main configuration */ 58 | 59 | NULL, /* create server configuration */ 60 | NULL, /* merge server configuration */ 61 | 62 | ngx_http_footer_create_loc_conf, /* create location configuration */ 63 | ngx_http_footer_merge_loc_conf /* merge location configuration */ 64 | }; 65 | 66 | 67 | ngx_module_t ngx_http_footer_filter_module = { 68 | NGX_MODULE_V1, 69 | &ngx_http_footer_filter_module_ctx, /* module context */ 70 | ngx_http_footer_filter_commands, /* module directives */ 71 | NGX_HTTP_MODULE, /* module type */ 72 | NULL, /* init master */ 73 | NULL, /* init module */ 74 | NULL, /* init process */ 75 | NULL, /* init thread */ 76 | NULL, /* exit thread */ 77 | NULL, /* exit process */ 78 | NULL, /* exit master */ 79 | NGX_MODULE_V1_PADDING 80 | }; 81 | 82 | 83 | static ngx_http_output_header_filter_pt ngx_http_next_header_filter; 84 | static ngx_http_output_body_filter_pt ngx_http_next_body_filter; 85 | 86 | 87 | static ngx_int_t 88 | ngx_http_footer_header_filter(ngx_http_request_t *r) 89 | { 90 | ngx_http_footer_ctx_t *ctx; 91 | ngx_http_footer_loc_conf_t *lcf; 92 | 93 | lcf = ngx_http_get_module_loc_conf(r, ngx_http_footer_filter_module); 94 | 95 | if (lcf->variable == (ngx_http_complex_value_t *) -1 96 | || r->header_only 97 | || (r->method & NGX_HTTP_HEAD) 98 | || r != r->main 99 | || r->headers_out.status == NGX_HTTP_NO_CONTENT 100 | || ngx_http_test_content_type(r, &lcf->types) == NULL) 101 | { 102 | return ngx_http_next_header_filter(r); 103 | } 104 | 105 | ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_footer_ctx_t)); 106 | if (ctx == NULL) { 107 | return NGX_ERROR; 108 | } 109 | 110 | if (ngx_http_complex_value(r, lcf->variable, &ctx->footer) != NGX_OK) { 111 | return NGX_ERROR; 112 | } 113 | 114 | ngx_http_set_ctx(r, ctx, ngx_http_footer_filter_module); 115 | 116 | if (r->headers_out.content_length_n != -1) { 117 | r->headers_out.content_length_n += ctx->footer.len; 118 | } 119 | 120 | if (r->headers_out.content_length) { 121 | r->headers_out.content_length->hash = 0; 122 | r->headers_out.content_length = NULL; 123 | } 124 | 125 | ngx_http_clear_accept_ranges(r); 126 | 127 | return ngx_http_next_header_filter(r); 128 | } 129 | 130 | 131 | static ngx_int_t 132 | ngx_http_footer_body_filter(ngx_http_request_t *r, ngx_chain_t *in) 133 | { 134 | ngx_buf_t *buf; 135 | ngx_uint_t last; 136 | ngx_chain_t *cl, *nl; 137 | ngx_http_footer_ctx_t *ctx; 138 | 139 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 140 | "http footer body filter"); 141 | 142 | ctx = ngx_http_get_module_ctx(r, ngx_http_footer_filter_module); 143 | if (ctx == NULL) { 144 | return ngx_http_next_body_filter(r, in); 145 | } 146 | 147 | last = 0; 148 | 149 | for (cl = in; cl; cl = cl->next) { 150 | if (cl->buf->last_buf) { 151 | last = 1; 152 | break; 153 | } 154 | } 155 | 156 | if (!last) { 157 | return ngx_http_next_body_filter(r, in); 158 | } 159 | 160 | buf = ngx_calloc_buf(r->pool); 161 | if (buf == NULL) { 162 | return NGX_ERROR; 163 | } 164 | 165 | buf->pos = ctx->footer.data; 166 | buf->last = buf->pos + ctx->footer.len; 167 | buf->start = buf->pos; 168 | buf->end = buf->last; 169 | buf->last_buf = 1; 170 | buf->memory = 1; 171 | 172 | if (ngx_buf_size(cl->buf) == 0) { 173 | cl->buf = buf; 174 | } else { 175 | nl = ngx_alloc_chain_link(r->pool); 176 | if (nl == NULL) { 177 | return NGX_ERROR; 178 | } 179 | 180 | nl->buf = buf; 181 | nl->next = NULL; 182 | cl->next = nl; 183 | cl->buf->last_buf = 0; 184 | } 185 | 186 | return ngx_http_next_body_filter(r, in); 187 | } 188 | 189 | 190 | static char * 191 | ngx_http_footer_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 192 | { 193 | ngx_http_footer_loc_conf_t *flcf = conf; 194 | 195 | ngx_str_t *value; 196 | ngx_http_complex_value_t **cv; 197 | 198 | cv = &flcf->variable; 199 | 200 | if (*cv != NULL) { 201 | return "is duplicate"; 202 | } 203 | 204 | value = cf->args->elts; 205 | 206 | if (value[1].len) { 207 | cmd->offset = offsetof(ngx_http_footer_loc_conf_t, variable); 208 | return ngx_http_set_complex_value_slot(cf, cmd, conf); 209 | } 210 | 211 | *cv = (ngx_http_complex_value_t *) -1; 212 | 213 | return NGX_OK; 214 | } 215 | 216 | 217 | static void * 218 | ngx_http_footer_create_loc_conf(ngx_conf_t *cf) 219 | { 220 | ngx_http_footer_loc_conf_t *conf; 221 | 222 | conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_footer_loc_conf_t)); 223 | if (conf == NULL) { 224 | return NULL; 225 | } 226 | 227 | /* 228 | * set by ngx_pcalloc(): 229 | * 230 | * conf->types = { NULL }; 231 | * conf->types_keys = NULL; 232 | * conf->variable = NULL; 233 | */ 234 | 235 | return conf; 236 | } 237 | 238 | 239 | static char * 240 | ngx_http_footer_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) 241 | { 242 | ngx_http_footer_loc_conf_t *prev = parent; 243 | ngx_http_footer_loc_conf_t *conf = child; 244 | 245 | if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types, 246 | &prev->types_keys,&prev->types, 247 | ngx_http_html_default_types) 248 | != NGX_OK) 249 | { 250 | return NGX_CONF_ERROR; 251 | } 252 | 253 | if (conf->variable == NULL) { 254 | conf->variable = prev->variable; 255 | } 256 | 257 | if (conf->variable == NULL) { 258 | conf->variable = (ngx_http_complex_value_t *) -1; 259 | } 260 | 261 | return NGX_CONF_OK; 262 | } 263 | 264 | 265 | static ngx_int_t 266 | ngx_http_footer_filter_init(ngx_conf_t *cf) 267 | { 268 | ngx_http_next_body_filter = ngx_http_top_body_filter; 269 | ngx_http_top_body_filter = ngx_http_footer_body_filter; 270 | 271 | ngx_http_next_header_filter = ngx_http_top_header_filter; 272 | ngx_http_top_header_filter = ngx_http_footer_header_filter; 273 | 274 | return NGX_OK; 275 | } 276 | 277 | --------------------------------------------------------------------------------