├── .gitignore
├── .buildpath
├── .settings
└── org.eclipse.ldt.prefs
├── config
├── .project
├── LICENSE
├── nginx.conf
├── README.markdown
└── ngx_http_response_body_module.c
/.gitignore:
--------------------------------------------------------------------------------
1 | install
2 | build
3 | download
4 |
--------------------------------------------------------------------------------
/.buildpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.ldt.prefs:
--------------------------------------------------------------------------------
1 | Grammar__default_id=lua-5.2
2 | eclipse.preferences.version=1
3 |
--------------------------------------------------------------------------------
/config:
--------------------------------------------------------------------------------
1 | ngx_addon_name=ngx_http_response_body_module
2 |
3 | if test -n "$ngx_module_link"; then
4 | ngx_module_type=HTTP_AUX_FILTER
5 | ngx_module_name=$ngx_addon_name
6 | ngx_module_srcs="$ngx_addon_dir/ngx_http_response_body_module.c"
7 |
8 | . auto/module
9 | else
10 | HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES $ngx_addon_name"
11 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_response_body_module.c"
12 | fi
13 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | ngx_http_response_body_module
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.dltk.core.scriptbuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.ldt.nature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2017, Aleksey Konovkin
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | * Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/nginx.conf:
--------------------------------------------------------------------------------
1 | worker_processes 1;
2 |
3 | error_log logs/error.log info;
4 |
5 | pid logs/nginx.pid;
6 |
7 | # load_module modules/ngx_http_response_body_module.so;
8 |
9 | events {
10 | worker_connections 1024;
11 | }
12 |
13 | http {
14 | include mime.types;
15 | default_type application/octet-stream;
16 |
17 | log_format main '$remote_addr - $remote_user [$time_local] "$request" $status "$err" "$response_body"';
18 | log_format test '$remote_addr - $remote_user [$time_local] "$request" $status "$err" "$test_response_body"';
19 |
20 |
21 | access_log logs/access.log main;
22 |
23 | upstream test {
24 | server 127.0.0.1:7777;
25 | }
26 |
27 | map $status $cond {
28 | 500 1;
29 | 401 1;
30 | 403 1;
31 | 404 1;
32 | default 0;
33 | }
34 |
35 | capture_response_body off;
36 | capture_response_body_buffer_size 1m;
37 | capture_response_body_buffer_size_min 4k;
38 | capture_response_body_buffer_size_multiplier 2;
39 | capture_response_body_if $cond 1;
40 | capture_response_body_if_latency_more 1s;
41 |
42 | map $response_body $err {
43 | ~\"error\":\"(?.+)\" $e;
44 | default "";
45 | }
46 |
47 | server {
48 | listen 7777;
49 | location / {
50 | echo_sleep 1.5;
51 | echo '0000000000';
52 | }
53 | location /500 {
54 | echo_status 500;
55 | echo '{"error":"internal error"}';
56 | }
57 | location /200 {
58 | echo OK;
59 | }
60 | location /404 {
61 | echo_status 404;
62 | echo '404';
63 | }
64 | location /header_in {
65 | echo 'OK';
66 | }
67 | location /header_out {
68 | add_header X-Trace-Response 1;
69 | echo 'OK';
70 | }
71 | location /test {
72 | add_header X-Trace-Response 1;
73 | echo 'OK';
74 | }
75 | }
76 |
77 | server {
78 | capture_response_body on;
79 | listen 8888;
80 | location / {
81 | proxy_pass http://test;
82 | }
83 | location /header_in {
84 | capture_response_body_if $http_x_trace *;
85 | proxy_pass http://test;
86 | }
87 | location /header_out {
88 | capture_response_body_if $sent_http_x_trace_response *;
89 | proxy_pass http://test;
90 | }
91 | location /test {
92 | access_log logs/test.log test;
93 | capture_response_body_var test_response_body;
94 | capture_response_body_if $sent_http_x_trace_response 1;
95 | proxy_pass http://test;
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | Name
2 | ====
3 |
4 | ngx_http_response_body_module - extract body response into variable.
5 |
6 | Table of Contents
7 | =================
8 |
9 | * [Name](#name)
10 | * [Status](#status)
11 | * [Synopsis](#synopsis)
12 | * [Description](#description)
13 | * [Configuration directives](#configuration-directives)
14 |
15 | Status
16 | ======
17 |
18 | This library is production ready.
19 |
20 | Description
21 | ===========
22 |
23 | Capture response body into nginx $response_body variable.
24 |
25 | [Back to TOC](#table-of-contents)
26 |
27 | Synopsis
28 | ========
29 |
30 | ```nginx
31 | http {
32 | include mime.types;
33 | default_type application/octet-stream;
34 |
35 | log_format main '$remote_addr - $remote_user [$time_local] "$request" $status "$err" "$response_body"';
36 | log_format test '$remote_addr - $remote_user [$time_local] "$request" $status "$err" "$test_response_body"';
37 |
38 |
39 | access_log logs/access.log main;
40 |
41 | upstream test {
42 | server 127.0.0.1:7777;
43 | }
44 |
45 | map $status $cond {
46 | 500 1;
47 | 401 1;
48 | 403 1;
49 | 404 1;
50 | default 0;
51 | }
52 |
53 | capture_response_body off;
54 | capture_response_body_buffer_size 1m;
55 | capture_response_body_buffer_size_min 4k;
56 | capture_response_body_buffer_size_multiplier 2;
57 | capture_response_body_if $cond 1;
58 | capture_response_body_if_latency_more 1s;
59 |
60 | map $response_body $err {
61 | ~\"error\":\"(?.+)\" $e;
62 | default "";
63 | }
64 |
65 | server {
66 | listen 7777;
67 | location / {
68 | echo_sleep 1.5;
69 | echo '0000000000';
70 | }
71 | location /500 {
72 | echo_status 500;
73 | echo '{"error":"internal error"}';
74 | }
75 | location /200 {
76 | echo OK;
77 | }
78 | location /404 {
79 | echo_status 404;
80 | echo '404';
81 | }
82 | location /header_in {
83 | echo 'OK';
84 | }
85 | location /header_out {
86 | add_header X-Trace-Response 1;
87 | echo 'OK';
88 | }
89 | location /test {
90 | add_header X-Trace-Response 1;
91 | echo 'OK';
92 | }
93 | }
94 |
95 | server {
96 | capture_response_body on;
97 | listen 8888;
98 | location / {
99 | proxy_pass http://test;
100 | }
101 | location /header_in {
102 | capture_response_body_if $http_x_trace *;
103 | proxy_pass http://test;
104 | }
105 | location /header_out {
106 | capture_response_body_if $sent_http_x_trace_response *;
107 | proxy_pass http://test;
108 | }
109 | location /test {
110 | access_log logs/test.log test;
111 | capture_response_body_var test_response_body;
112 | capture_response_body_if $sent_http_x_trace_response 1;
113 | proxy_pass http://test;
114 | }
115 | }
116 | }
117 | ```
118 |
119 | [Back to TOC](#table-of-contents)
120 |
121 | Configuration directives
122 | ========================
123 |
124 | capture_response_body
125 | --------------
126 | * **syntax**: `capture_response_body on|off`
127 | * **default**: `off`
128 | * **context**: `http,server,location`
129 |
130 | Turn on response body capture.
131 |
132 | capture_response_body_var
133 | --------------
134 | * **syntax**: `capture_response_body_var `
135 | * **default**: `response_body`
136 | * **context**: `http,server,location`
137 |
138 | Variable name.
139 |
140 | capture_response_body_buffer_size
141 | --------------
142 | * **syntax**: `capture_response_body_buffer_size `
143 | * **default**: `pagesize`
144 | * **context**: `http,server,location`
145 |
146 | Maximum buffer size.
147 |
148 | capture_response_body_buffer_size_min
149 | --------------
150 | * **syntax**: `capture_response_body_buffer_size_min `
151 | * **default**: `pagesize`
152 | * **context**: `http,server,location`
153 |
154 | Minimum amount of memory allocated for chunked response.
155 |
156 | capture_response_body_buffer_size_multiplier
157 | --------------
158 | * **syntax**: `capture_response_body_buffer_size_multiplier `
159 | * **default**: `2`
160 | * **context**: `http,server,location`
161 |
162 | Reallocation multiplier.
163 |
164 | capture_response_body_if
165 | --------------
166 | * **syntax**: `capture_response_body_if `
167 | * **default**: `none`
168 | * **context**: `http,server,location`
169 |
170 | Capture response body if result of calculation is equal .
171 | may be empty string or special '*'.
172 |
173 | capture_response_body_if_1xx
174 | --------------
175 | * **syntax**: `capture_response_body_if_1xx on`
176 | * **default**: `off`
177 | * **context**: `http,server,location`
178 |
179 | Capture response body for http statuses 1xx.
180 |
181 | capture_response_body_if_2xx
182 | --------------
183 | * **syntax**: `capture_response_body_if_2xx on`
184 | * **default**: `off`
185 | * **context**: `http,server,location`
186 |
187 | Capture response body for http statuses 2xx.
188 |
189 | capture_response_body_if_3xx
190 | --------------
191 | * **syntax**: `capture_response_body_if_3xx on`
192 | * **default**: `off`
193 | * **context**: `http,server,location`
194 |
195 | Capture response body for http statuses 3xx.
196 |
197 | capture_response_body_if_4xx
198 | --------------
199 | * **syntax**: `capture_response_body_if_4xx on`
200 | * **default**: `off`
201 | * **context**: `http,server,location`
202 |
203 | Capture response body for http statuses 4xx.
204 |
205 | capture_response_body_if_5xx
206 | --------------
207 | * **syntax**: `capture_response_body_if_5xx on`
208 | * **default**: `off`
209 | * **context**: `http,server,location`
210 |
211 | Capture response body for http statuses 5xx.
212 |
213 | capture_response_body_if_latency_more
214 | --------------
215 | * **syntax**: `capture_response_body_if_latency_more `
216 | * **default**: `none`
217 | * **context**: `http,server,location`
218 |
219 | Capture response body only if request time is greather than specified in the parameter.
220 |
221 | [Back to TOC](#table-of-contents)
222 |
--------------------------------------------------------------------------------
/ngx_http_response_body_module.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 |
6 |
7 | typedef struct {
8 | ngx_msec_t latency;
9 | ngx_flag_t status_1xx;
10 | ngx_flag_t status_2xx;
11 | ngx_flag_t status_3xx;
12 | ngx_flag_t status_4xx;
13 | ngx_flag_t status_5xx;
14 | size_t buffer_size_min;
15 | ngx_uint_t buffer_size_multiplier;
16 | size_t buffer_size;
17 | ngx_flag_t capture_body;
18 | ngx_str_t capture_body_var;
19 | ngx_array_t *conditions;
20 | ngx_array_t *cv;
21 | } ngx_http_response_body_loc_conf_t;
22 |
23 |
24 | typedef struct {
25 | ngx_http_response_body_loc_conf_t *blcf;
26 | ngx_buf_t buffer;
27 | } ngx_http_response_body_ctx_t;
28 |
29 |
30 | static ngx_int_t
31 | ngx_http_response_body_add_variables(ngx_conf_t *cf);
32 |
33 | static ngx_int_t
34 | ngx_http_response_body_variable(ngx_http_request_t *r,
35 | ngx_http_variable_value_t *v, uintptr_t data);
36 |
37 | static void *ngx_http_response_body_create_loc_conf(ngx_conf_t *cf);
38 | static char *ngx_http_response_body_merge_loc_conf(ngx_conf_t *cf, void *parent,
39 | void *child);
40 |
41 | static char *
42 | ngx_http_response_body_request_var(ngx_conf_t *cf, ngx_command_t *cmd,
43 | void *conf);
44 |
45 | static ngx_int_t
46 | ngx_http_response_body_set_ctx(ngx_http_request_t *r);
47 |
48 | static ngx_int_t ngx_http_response_body_filter_header(ngx_http_request_t *r);
49 | static ngx_int_t ngx_http_response_body_filter_body(ngx_http_request_t *r,
50 | ngx_chain_t *in);
51 |
52 | static ngx_int_t ngx_http_response_body_init(ngx_conf_t *cf);
53 |
54 |
55 | static char *
56 | ngx_conf_set_flag(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
57 |
58 | static char *
59 | ngx_conf_set_msec(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
60 |
61 | static char *
62 | ngx_conf_set_size(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
63 |
64 | static char *
65 | ngx_conf_set_keyval(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
66 |
67 | static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
68 | static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
69 |
70 |
71 | static ngx_int_t
72 | ngx_http_next_header_filter_stub(ngx_http_request_t *r)
73 | {
74 | return NGX_OK;
75 | }
76 |
77 |
78 | static ngx_int_t
79 | ngx_http_next_body_filter_stub(ngx_http_request_t *r, ngx_chain_t *in)
80 | {
81 | return NGX_OK;
82 | }
83 |
84 |
85 | static ngx_command_t ngx_http_response_body_commands[] = {
86 |
87 | { ngx_string("capture_response_body"),
88 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
89 | ngx_conf_set_flag,
90 | NGX_HTTP_LOC_CONF_OFFSET,
91 | offsetof(ngx_http_response_body_loc_conf_t, capture_body),
92 | NULL },
93 |
94 | { ngx_string("capture_response_body_var"),
95 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
96 | ngx_http_response_body_request_var,
97 | NGX_HTTP_LOC_CONF_OFFSET,
98 | 0,
99 | NULL },
100 |
101 | { ngx_string("capture_response_body_if"),
102 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
103 | ngx_conf_set_keyval,
104 | NGX_HTTP_LOC_CONF_OFFSET,
105 | offsetof(ngx_http_response_body_loc_conf_t, conditions),
106 | NULL },
107 |
108 | { ngx_string("capture_response_body_if_1xx"),
109 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
110 | ngx_conf_set_flag,
111 | NGX_HTTP_LOC_CONF_OFFSET,
112 | offsetof(ngx_http_response_body_loc_conf_t, status_1xx),
113 | NULL },
114 |
115 | { ngx_string("capture_response_body_if_2xx"),
116 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
117 | ngx_conf_set_flag,
118 | NGX_HTTP_LOC_CONF_OFFSET,
119 | offsetof(ngx_http_response_body_loc_conf_t, status_2xx),
120 | NULL },
121 |
122 | { ngx_string("capture_response_body_if_3xx"),
123 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
124 | ngx_conf_set_flag,
125 | NGX_HTTP_LOC_CONF_OFFSET,
126 | offsetof(ngx_http_response_body_loc_conf_t, status_3xx),
127 | NULL },
128 |
129 | { ngx_string("capture_response_body_if_4xx"),
130 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
131 | ngx_conf_set_flag,
132 | NGX_HTTP_LOC_CONF_OFFSET,
133 | offsetof(ngx_http_response_body_loc_conf_t, status_4xx),
134 | NULL },
135 |
136 | { ngx_string("capture_response_body_if_5xx"),
137 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
138 | ngx_conf_set_flag,
139 | NGX_HTTP_LOC_CONF_OFFSET,
140 | offsetof(ngx_http_response_body_loc_conf_t, status_5xx),
141 | NULL },
142 |
143 | { ngx_string("capture_response_body_if_latency_more"),
144 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
145 | ngx_conf_set_msec,
146 | NGX_HTTP_LOC_CONF_OFFSET,
147 | offsetof(ngx_http_response_body_loc_conf_t, latency),
148 | NULL },
149 |
150 | { ngx_string("capture_response_body_buffer_size"),
151 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
152 | ngx_conf_set_size,
153 | NGX_HTTP_LOC_CONF_OFFSET,
154 | offsetof(ngx_http_response_body_loc_conf_t, buffer_size),
155 | NULL },
156 |
157 | { ngx_string("capture_response_body_buffer_size_min"),
158 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
159 | ngx_conf_set_size,
160 | NGX_HTTP_LOC_CONF_OFFSET,
161 | offsetof(ngx_http_response_body_loc_conf_t, buffer_size_min),
162 | NULL },
163 |
164 | { ngx_string("capture_response_body_buffer_size_multiplier"),
165 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
166 | ngx_conf_set_num_slot,
167 | NGX_HTTP_LOC_CONF_OFFSET,
168 | offsetof(ngx_http_response_body_loc_conf_t, buffer_size_multiplier),
169 | NULL },
170 |
171 | ngx_null_command
172 |
173 | };
174 |
175 |
176 | static ngx_http_module_t ngx_http_response_body_module_ctx = {
177 | ngx_http_response_body_add_variables, /* preconfiguration */
178 | ngx_http_response_body_init, /* postconfiguration */
179 |
180 | NULL, /* create main configuration */
181 | NULL, /* init main configuration */
182 |
183 | NULL, /* create server configuration */
184 | NULL, /* merge server configuration */
185 |
186 | ngx_http_response_body_create_loc_conf, /* create location configuration */
187 | ngx_http_response_body_merge_loc_conf /* merge location configuration */
188 | };
189 |
190 |
191 | ngx_module_t ngx_http_response_body_module = {
192 | NGX_MODULE_V1,
193 | &ngx_http_response_body_module_ctx, /* module context */
194 | ngx_http_response_body_commands, /* module directives */
195 | NGX_HTTP_MODULE, /* module type */
196 | NULL, /* init master */
197 | NULL, /* init module */
198 | NULL, /* init process */
199 | NULL, /* init thread */
200 | NULL, /* exit thread */
201 | NULL, /* exit process */
202 | NULL, /* exit master */
203 | NGX_MODULE_V1_PADDING
204 | };
205 |
206 |
207 | static ngx_http_variable_t ngx_http_upstream_vars[] = {
208 |
209 | { ngx_string("response_body"), NULL,
210 | ngx_http_response_body_variable, 0,
211 | NGX_HTTP_VAR_NOCACHEABLE, 0 },
212 |
213 | { ngx_null_string, NULL, NULL, 0, 0, 0 }
214 |
215 | };
216 |
217 |
218 | static char *
219 | ngx_conf_set_flag(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
220 | {
221 | ngx_http_response_body_loc_conf_t *blcf = conf;
222 | char *p = conf;
223 | ngx_flag_t *fp = (ngx_flag_t *) (p + cmd->offset);
224 | ngx_flag_t prev = *fp;
225 |
226 | *fp = NGX_CONF_UNSET;
227 |
228 | if (ngx_conf_set_flag_slot(cf, cmd, conf) != NGX_CONF_OK)
229 | return NGX_CONF_ERROR;
230 |
231 | if (prev != NGX_CONF_UNSET)
232 | *fp = ngx_max(prev, *fp);
233 |
234 | blcf->capture_body = *fp;
235 |
236 | return NGX_CONF_OK;
237 | }
238 |
239 |
240 | char *
241 | ngx_conf_set_msec(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
242 | {
243 | ngx_http_response_body_loc_conf_t *blcf = conf;
244 | char *p = conf;
245 | ngx_msec_t *fp = (ngx_msec_t *) (p + cmd->offset);
246 | ngx_msec_t prev = *fp;
247 |
248 | *fp = NGX_CONF_UNSET_MSEC;
249 |
250 | if (ngx_conf_set_msec_slot(cf, cmd, conf) != NGX_CONF_OK)
251 | return NGX_CONF_ERROR;
252 |
253 | if (prev != NGX_CONF_UNSET_MSEC)
254 | *fp = ngx_min(prev, *fp);
255 |
256 | blcf->capture_body = 1;
257 |
258 | return NGX_CONF_OK;
259 | }
260 |
261 |
262 | static char *
263 | ngx_conf_set_size(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
264 | {
265 | char *p = conf;
266 | size_t *fp = (size_t *) (p + cmd->offset);
267 | size_t prev = *fp;
268 |
269 | *fp = NGX_CONF_UNSET_SIZE;
270 |
271 | if (ngx_conf_set_size_slot(cf, cmd, conf) != NGX_CONF_OK)
272 | return NGX_CONF_ERROR;
273 |
274 | if (prev != NGX_CONF_UNSET_SIZE)
275 | *fp = ngx_max(prev, *fp);
276 |
277 | return NGX_CONF_OK;
278 | }
279 |
280 |
281 | static char *
282 | ngx_conf_set_keyval(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
283 | {
284 | ngx_http_response_body_loc_conf_t *blcf = conf;
285 |
286 | if (ngx_conf_set_keyval_slot(cf, cmd, conf) != NGX_CONF_OK)
287 | return NGX_CONF_ERROR;
288 |
289 | blcf->capture_body = 1;
290 |
291 | return NGX_CONF_OK;
292 | }
293 |
294 |
295 | static ngx_int_t
296 | ngx_http_response_body_add_variables(ngx_conf_t *cf)
297 | {
298 | ngx_http_variable_t *var, *v;
299 |
300 | for (v = ngx_http_upstream_vars; v->name.len; v++) {
301 | var = ngx_http_add_variable(cf, &v->name, v->flags);
302 | if (var == NULL) {
303 | return NGX_ERROR;
304 | }
305 |
306 | var->get_handler = v->get_handler;
307 | var->data = v->data;
308 | }
309 |
310 | return NGX_OK;
311 | }
312 |
313 |
314 | static ngx_int_t
315 | ngx_http_response_body_variable(ngx_http_request_t *r,
316 | ngx_http_variable_value_t *v, uintptr_t data)
317 | {
318 | ngx_http_response_body_ctx_t *ctx;
319 | ngx_buf_t *b;
320 |
321 | v->valid = 1;
322 | v->no_cacheable = 0;
323 | v->not_found = 0;
324 |
325 | ctx = ngx_http_get_module_ctx(r, ngx_http_response_body_module);
326 | if (ctx == NULL) {
327 | v->not_found = 1;
328 | return NGX_OK;
329 | }
330 |
331 | b = &ctx->buffer;
332 |
333 | if (b->start == NULL) {
334 | v->not_found = 1;
335 | return NGX_OK;
336 | }
337 |
338 | v->data = b->start;
339 | v->len = b->last - b->start;
340 |
341 | return NGX_OK;
342 | }
343 |
344 |
345 | static char *
346 | ngx_http_response_body_request_var(ngx_conf_t *cf, ngx_command_t *cmd,
347 | void *conf)
348 | {
349 | ngx_http_response_body_loc_conf_t *ulcf = conf;
350 | ngx_http_variable_t *var;
351 |
352 | ulcf->capture_body_var = ((ngx_str_t *)cf->args->elts) [1];
353 |
354 | var = ngx_http_add_variable(cf, &ulcf->capture_body_var,
355 | NGX_HTTP_VAR_NOCACHEABLE);
356 | if (var == NULL) {
357 | return NGX_CONF_ERROR;
358 | }
359 |
360 | var->get_handler = ngx_http_response_body_variable;
361 | var->data = 0;
362 |
363 | return NGX_CONF_OK;
364 | }
365 |
366 |
367 | static void *
368 | ngx_http_response_body_create_loc_conf(ngx_conf_t *cf)
369 | {
370 | ngx_http_response_body_loc_conf_t *blcf;
371 |
372 | blcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_response_body_loc_conf_t));
373 |
374 | if (blcf == NULL)
375 | return NULL;
376 |
377 | blcf->latency = NGX_CONF_UNSET_MSEC;
378 | blcf->buffer_size = NGX_CONF_UNSET_SIZE;
379 | blcf->buffer_size_min = NGX_CONF_UNSET_SIZE;
380 | blcf->buffer_size_multiplier = NGX_CONF_UNSET_UINT;
381 | blcf->conditions = ngx_array_create(cf->pool, 2,
382 | sizeof(ngx_keyval_t));
383 | blcf->cv = ngx_array_create(cf->pool, 2,
384 | sizeof(ngx_http_complex_value_t));
385 | blcf->status_1xx = NGX_CONF_UNSET;
386 | blcf->status_2xx = NGX_CONF_UNSET;
387 | blcf->status_3xx = NGX_CONF_UNSET;
388 | blcf->status_4xx = NGX_CONF_UNSET;
389 | blcf->status_5xx = NGX_CONF_UNSET;
390 | blcf->capture_body = NGX_CONF_UNSET;
391 |
392 | if (blcf->conditions == NULL || blcf->cv == NULL)
393 | return NULL;
394 |
395 | return blcf;
396 | }
397 |
398 |
399 | static ngx_int_t
400 | ngx_array_merge(ngx_array_t *l, ngx_array_t *r)
401 | {
402 | void *p;
403 |
404 | if (r->nelts == 0)
405 | return NGX_OK;
406 |
407 | if (r->size != l->size)
408 | return NGX_ERROR;
409 |
410 | p = ngx_array_push_n(l, r->nelts);
411 | if (p == NULL)
412 | return NGX_ERROR;
413 |
414 | ngx_memcpy(p, r->elts, r->size * r->nelts);
415 |
416 | return NGX_OK;
417 | }
418 |
419 |
420 | static char *
421 | ngx_http_response_body_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
422 | {
423 | ngx_http_response_body_loc_conf_t *prev = parent;
424 | ngx_http_response_body_loc_conf_t *conf = child;
425 | ngx_http_compile_complex_value_t ccv;
426 | ngx_http_complex_value_t *cv;
427 | ngx_uint_t j;
428 | ngx_keyval_t *kv;
429 |
430 | ngx_conf_merge_msec_value(conf->latency, prev->latency, (ngx_msec_int_t) 0);
431 | ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size,
432 | (size_t) ngx_pagesize);
433 | ngx_conf_merge_size_value(conf->buffer_size_min, prev->buffer_size_min,
434 | (size_t) ngx_pagesize);
435 | ngx_conf_merge_uint_value(conf->buffer_size_multiplier,
436 | prev->buffer_size_multiplier, 2);
437 | if (ngx_array_merge(conf->conditions, prev->conditions) == NGX_ERROR)
438 | return NGX_CONF_ERROR;
439 | ngx_conf_merge_value(conf->status_1xx, prev->status_1xx, 0);
440 | ngx_conf_merge_value(conf->status_2xx, prev->status_2xx, 0);
441 | ngx_conf_merge_value(conf->status_3xx, prev->status_3xx, 0);
442 | ngx_conf_merge_value(conf->status_4xx, prev->status_4xx, 0);
443 | ngx_conf_merge_value(conf->status_5xx, prev->status_5xx, 0);
444 | ngx_conf_merge_value(conf->capture_body, prev->capture_body, 0);
445 |
446 | cv = ngx_array_push_n(conf->cv, conf->conditions->nelts);
447 | if (cv == NULL)
448 | return NGX_CONF_ERROR;
449 | ngx_memzero(cv, conf->cv->size * conf->cv->nalloc);
450 | kv = conf->conditions->elts;
451 |
452 | for (j = 0; j < conf->cv->nelts; j++) {
453 |
454 | ngx_memzero(&ccv, sizeof(ccv));
455 |
456 | ccv.cf = cf;
457 | ccv.value = &kv[j].key;
458 | ccv.complex_value = &cv[j];
459 | ccv.zero = 0;
460 |
461 | if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
462 |
463 | ngx_conf_log_error(NGX_LOG_ERR, cf, 0,
464 | "can't compile '%V'", &kv[j].key);
465 | return NGX_CONF_ERROR;
466 | }
467 | }
468 |
469 | return NGX_CONF_OK;
470 | }
471 |
472 |
473 | static ngx_int_t
474 | ngx_http_response_body_init(ngx_conf_t *cf)
475 | {
476 | ngx_http_next_header_filter = ngx_http_top_header_filter;
477 | ngx_http_top_header_filter = ngx_http_response_body_filter_header;
478 |
479 | ngx_http_next_body_filter = ngx_http_top_body_filter;
480 | ngx_http_top_body_filter = ngx_http_response_body_filter_body;
481 |
482 | if (ngx_http_next_header_filter == NULL)
483 | ngx_http_next_header_filter = ngx_http_next_header_filter_stub;
484 |
485 | if (ngx_http_next_body_filter == NULL)
486 | ngx_http_next_body_filter = ngx_http_next_body_filter_stub;
487 |
488 | return NGX_OK;
489 | }
490 |
491 |
492 | static ngx_int_t
493 | ngx_http_response_body_set_ctx(ngx_http_request_t *r)
494 | {
495 | ngx_http_response_body_loc_conf_t *ulcf;
496 | ngx_http_response_body_ctx_t *ctx;
497 |
498 | ulcf = ngx_http_get_module_loc_conf(r, ngx_http_response_body_module);
499 |
500 | if (!ulcf->capture_body)
501 | return NGX_DECLINED;
502 |
503 | ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_response_body_ctx_t));
504 | if (ctx == NULL)
505 | return NGX_ERROR;
506 |
507 | ctx->blcf = ulcf;
508 |
509 | ngx_http_set_ctx(r, ctx, ngx_http_response_body_module);
510 |
511 | return NGX_OK;
512 | }
513 |
514 |
515 | static ngx_msec_t
516 | ngx_http_response_body_request_time(ngx_http_request_t *r)
517 | {
518 | ngx_time_t *tp;
519 | ngx_msec_int_t ms;
520 |
521 | tp = ngx_timeofday();
522 |
523 | ms = (ngx_msec_int_t)
524 | ((tp->sec - r->start_sec) * 1000 + (tp->msec - r->start_msec));
525 |
526 | return (ngx_msec_t) ngx_max(ms, 0);
527 | }
528 |
529 |
530 | static ngx_int_t
531 | ngx_http_response_body_filter_header(ngx_http_request_t *r)
532 | {
533 | ngx_http_response_body_ctx_t *ctx;
534 | ngx_uint_t j;
535 | ngx_http_complex_value_t *cv;
536 | ngx_str_t value;
537 | ngx_keyval_t *kv;
538 |
539 | switch (ngx_http_response_body_set_ctx(r)) {
540 | case NGX_OK:
541 | break;
542 |
543 | case NGX_DECLINED:
544 | return ngx_http_next_header_filter(r);
545 |
546 | case NGX_ERROR:
547 | ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
548 | "ngx_http_response_body_filter_header: no memory");
549 | return NGX_HTTP_INTERNAL_SERVER_ERROR;
550 |
551 | default:
552 | return ngx_http_next_header_filter(r);
553 | }
554 |
555 | ctx = ngx_http_get_module_ctx(r, ngx_http_response_body_module);
556 |
557 | if (r->headers_out.status < 200) {
558 |
559 | if (ctx->blcf->status_1xx)
560 | return ngx_http_next_header_filter(r);
561 |
562 | } else if (r->headers_out.status < 300) {
563 |
564 | if (ctx->blcf->status_2xx)
565 | return ngx_http_next_header_filter(r);
566 |
567 | } else if (r->headers_out.status < 400) {
568 |
569 | if (ctx->blcf->status_3xx)
570 | return ngx_http_next_header_filter(r);
571 |
572 | } else if (r->headers_out.status < 500) {
573 |
574 | if (ctx->blcf->status_4xx)
575 | return ngx_http_next_header_filter(r);
576 |
577 | } else {
578 |
579 | if (ctx->blcf->status_5xx)
580 | return ngx_http_next_header_filter(r);
581 |
582 | }
583 |
584 | if (ctx->blcf->latency != 0
585 | && ctx->blcf->latency <= ngx_http_response_body_request_time(r))
586 | return ngx_http_next_header_filter(r);
587 |
588 | cv = ctx->blcf->cv->elts;
589 | kv = ctx->blcf->conditions->elts;
590 |
591 | for (j = 0; j < ctx->blcf->cv->nelts; ++j) {
592 |
593 | if (ngx_http_complex_value(r, &cv[j], &value) != NGX_OK)
594 | continue;
595 |
596 | if (value.len == 0)
597 | continue;
598 |
599 | if (kv[j].value.len == 0
600 | || (kv[j].value.len == 1 && kv[j].value.data[0] == '*'))
601 | return ngx_http_next_header_filter(r);
602 |
603 | if (kv[j].value.len == value.len
604 | && ngx_strncasecmp(value.data, kv[j].value.data, value.len) == 0)
605 | return ngx_http_next_header_filter(r);
606 | }
607 |
608 | ngx_http_set_ctx(r, NULL, ngx_http_response_body_module);
609 |
610 | return ngx_http_next_header_filter(r);
611 | }
612 |
613 |
614 | static ngx_int_t
615 | ngx_http_response_body_filter_body(ngx_http_request_t *r, ngx_chain_t *in)
616 | {
617 | ngx_http_response_body_ctx_t *ctx;
618 | ngx_chain_t *cl;
619 | ngx_buf_t *b, new_buf;
620 | ngx_http_response_body_loc_conf_t *conf;
621 |
622 | ctx = ngx_http_get_module_ctx(r, ngx_http_response_body_module);
623 | if (ctx == NULL
624 | || in == NULL
625 | || r->headers_out.content_length_n == 0
626 | || !ngx_buf_in_memory(in->buf))
627 | return ngx_http_next_body_filter(r, in);
628 |
629 | conf = ngx_http_get_module_loc_conf(r, ngx_http_response_body_module);
630 |
631 | b = &ctx->buffer;
632 |
633 | if (b->start == NULL) {
634 |
635 | size_t len;
636 |
637 | ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0,
638 | "[ngx_http_response_body] content_length: %i",
639 | r->headers_out.content_length_n);
640 |
641 | /* initial buffer alloc */
642 |
643 | if (r->headers_out.content_length_n > 0) {
644 |
645 | /* fixed size */
646 | len = ngx_min((size_t) r->headers_out.content_length_n,
647 | conf->buffer_size);
648 | } else {
649 |
650 | /* chunked response */
651 | len = ngx_min(ngx_max(conf->buffer_size_min,
652 | (size_t) (in->buf->last - in->buf->pos)),
653 | conf->buffer_size);
654 | }
655 |
656 | b->start = ngx_palloc(r->pool, len);
657 | if (b->start == NULL)
658 | return NGX_ERROR;
659 |
660 | b->end = b->start + len;
661 | b->pos = b->last = b->start;
662 | }
663 |
664 | for (cl = in;
665 | cl && ngx_buf_in_memory(cl->buf) && (size_t) (b->last - b->start) < conf->buffer_size;
666 | cl = cl->next)
667 | {
668 | size_t extra = cl->buf->last - cl->buf->pos;
669 |
670 | if (extra > (size_t) (b->end - b->last)) {
671 |
672 | /* we need to allocate more space */
673 |
674 | size_t old_alloc = b->end - b->start;
675 | size_t new_alloc = ngx_min(conf->buffer_size,
676 | ngx_max(conf->buffer_size_multiplier * old_alloc,
677 | old_alloc + extra));
678 |
679 | if (old_alloc < new_alloc) {
680 |
681 | ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0,
682 | "[ngx_http_response_body] realloc: %ui -> %ui",
683 | old_alloc, new_alloc);
684 |
685 | ngx_memzero(&new_buf, sizeof(ngx_buf_t));
686 |
687 | new_buf.start = ngx_palloc(r->pool, new_alloc);
688 | if (new_buf.start == NULL)
689 | return NGX_ERROR;
690 |
691 | new_buf.end = new_buf.start + new_alloc;
692 | new_buf.last = ngx_copy(new_buf.start, b->start,
693 | b->last - b->start);
694 | new_buf.pos = new_buf.start;
695 |
696 | ngx_pfree(r->pool, b->start);
697 |
698 | *b = new_buf;
699 | }
700 | }
701 |
702 | extra = ngx_min(extra, (size_t) (b->end - b->last));
703 | if (extra > 0)
704 | b->last = ngx_copy(b->last, cl->buf->pos, extra);
705 | }
706 |
707 | return ngx_http_next_body_filter(r, in);
708 | }
709 |
--------------------------------------------------------------------------------