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