├── 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 |
--------------------------------------------------------------------------------