├── .gitattributes ├── src ├── ngx_http_echo_var.h ├── ngx_http_echo_filter.h ├── ngx_http_echo_timer.h ├── ngx_http_echo_location.h ├── ngx_http_echo_handler.h ├── ngx_http_echo_sleep.h ├── ngx_http_echo_foreach.h ├── ngx_http_echo_subrequest.h ├── ngx_http_echo_echo.h ├── ngx_http_echo_request_info.h ├── ngx_http_echo_util.h ├── ddebug.h ├── ngx_http_echo_timer.c ├── ngx_http_echo_var.c ├── ngx_http_echo_module.h ├── ngx_http_echo_location.c ├── ngx_http_echo_sleep.c ├── ngx_http_echo_foreach.c ├── ngx_http_echo_util.c ├── ngx_http_echo_filter.c ├── ngx_http_echo_echo.c ├── ngx_http_echo_handler.c └── ngx_http_echo_request_info.c ├── util ├── releng ├── build.sh └── wiki2pod.pl ├── t ├── gzip.t ├── incr.t ├── filter-used.t ├── request-body.t ├── abort-parent.t ├── echo-duplicate.t ├── mixed.t ├── unused.t ├── echo-timer.t ├── status.t ├── blocking-sleep.t ├── if.t ├── sleep.t ├── exec.t ├── echo-after-body.t ├── echo-before-body.t ├── foreach-split.t ├── echo.t ├── location-async.t ├── location.t ├── subrequest-async.t └── subrequest.t ├── .gitignore ├── valgrind.suppress ├── LICENSE ├── .travis.yml └── config /.gitattributes: -------------------------------------------------------------------------------- 1 | *.t linguist-language=Text 2 | -------------------------------------------------------------------------------- /src/ngx_http_echo_var.h: -------------------------------------------------------------------------------- 1 | #ifndef ECHO_VAR_H 2 | #define ECHO_VAR_H 3 | 4 | #include "ngx_http_echo_module.h" 5 | 6 | ngx_int_t ngx_http_echo_add_variables(ngx_conf_t *cf); 7 | 8 | #endif /* ECHO_VAR_H */ 9 | 10 | -------------------------------------------------------------------------------- /util/releng: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./update-readme 4 | ack '.{81}' src/ngx_http_*.[ch] 5 | ack '(?<=\#define)\s*DDEBUG\s*[12]' src 6 | echo ======================================= 7 | ack '(?<=This document describes echo-nginx-module v)\d+\.\d+' README 8 | 9 | -------------------------------------------------------------------------------- /src/ngx_http_echo_filter.h: -------------------------------------------------------------------------------- 1 | #ifndef ECHO_FILTER_H 2 | #define ECHO_FILTER_H 3 | 4 | #include "ngx_http_echo_module.h" 5 | 6 | 7 | extern ngx_http_output_header_filter_pt ngx_http_echo_next_header_filter; 8 | 9 | extern ngx_http_output_body_filter_pt ngx_http_echo_next_body_filter; 10 | 11 | 12 | ngx_int_t ngx_http_echo_filter_init (ngx_conf_t *cf); 13 | 14 | #endif /* ECHO_FILTER_H */ 15 | 16 | -------------------------------------------------------------------------------- /src/ngx_http_echo_timer.h: -------------------------------------------------------------------------------- 1 | #ifndef ECHO_TIMER_H 2 | #define ECHO_TIMER_H 3 | 4 | #include "ngx_http_echo_module.h" 5 | 6 | ngx_int_t ngx_http_echo_timer_elapsed_variable(ngx_http_request_t *r, 7 | ngx_http_variable_value_t *v, uintptr_t data); 8 | 9 | ngx_int_t ngx_http_echo_exec_echo_reset_timer(ngx_http_request_t *r, 10 | ngx_http_echo_ctx_t *ctx); 11 | 12 | #endif /* ECHO_TIMER_H */ 13 | 14 | -------------------------------------------------------------------------------- /src/ngx_http_echo_location.h: -------------------------------------------------------------------------------- 1 | #ifndef ECHO_LOCATION_H 2 | #define ECHO_LOCATION_H 3 | 4 | #include "ngx_http_echo_module.h" 5 | 6 | ngx_int_t ngx_http_echo_exec_echo_location_async(ngx_http_request_t *r, 7 | ngx_http_echo_ctx_t *ctx, ngx_array_t *computed_args); 8 | 9 | ngx_int_t ngx_http_echo_exec_echo_location(ngx_http_request_t *r, 10 | ngx_http_echo_ctx_t *ctx, ngx_array_t *computed_args); 11 | 12 | #endif /* ECHO_LOCATION_H */ 13 | 14 | -------------------------------------------------------------------------------- /src/ngx_http_echo_handler.h: -------------------------------------------------------------------------------- 1 | #ifndef ECHO_HANDLER_H 2 | #define ECHO_HANDLER_H 3 | 4 | #include "ngx_http_echo_module.h" 5 | 6 | 7 | void ngx_http_echo_wev_handler(ngx_http_request_t *r); 8 | 9 | ngx_int_t ngx_http_echo_handler(ngx_http_request_t *r); 10 | 11 | ngx_int_t ngx_http_echo_run_cmds(ngx_http_request_t *r); 12 | 13 | ngx_int_t ngx_http_echo_post_subrequest(ngx_http_request_t *r, 14 | void *data, ngx_int_t rc); 15 | 16 | 17 | #endif /* ECHO_HANDLER_H */ 18 | 19 | -------------------------------------------------------------------------------- /src/ngx_http_echo_sleep.h: -------------------------------------------------------------------------------- 1 | #ifndef ECHO_SLEEP_H 2 | #define ECHO_SLEEP_H 3 | 4 | #include "ngx_http_echo_module.h" 5 | 6 | ngx_int_t ngx_http_echo_exec_echo_sleep( 7 | ngx_http_request_t *r, ngx_http_echo_ctx_t *ctx, 8 | ngx_array_t *computed_args); 9 | 10 | ngx_int_t ngx_http_echo_exec_echo_blocking_sleep(ngx_http_request_t *r, 11 | ngx_http_echo_ctx_t *ctx, ngx_array_t *computed_args); 12 | 13 | void ngx_http_echo_sleep_event_handler(ngx_event_t *ev); 14 | 15 | #endif /* ECHO_SLEEP_H */ 16 | 17 | -------------------------------------------------------------------------------- /src/ngx_http_echo_foreach.h: -------------------------------------------------------------------------------- 1 | #ifndef ECHO_FOREACH_H 2 | #define ECHO_FOREACH_H 3 | 4 | #include "ngx_http_echo_module.h" 5 | 6 | ngx_int_t ngx_http_echo_exec_echo_foreach_split(ngx_http_request_t *r, 7 | ngx_http_echo_ctx_t *ctx, ngx_array_t *computed_args); 8 | 9 | ngx_int_t ngx_http_echo_exec_echo_end(ngx_http_request_t *r, 10 | ngx_http_echo_ctx_t *ctx); 11 | 12 | ngx_int_t ngx_http_echo_it_variable(ngx_http_request_t *r, 13 | ngx_http_variable_value_t *v, uintptr_t data); 14 | 15 | #endif /* ECHO_FOREACH_H */ 16 | 17 | -------------------------------------------------------------------------------- /t/gzip.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | repeat_each(1); 7 | 8 | plan tests => repeat_each() * 2 * blocks(); 9 | 10 | #$Test::Nginx::LWP::LogLevel = 'debug'; 11 | 12 | run_tests(); 13 | 14 | __DATA__ 15 | 16 | === TEST 1: sanity 17 | --- config 18 | location /gzip { 19 | gzip on; 20 | gzip_min_length 10; 21 | gzip_types text/plain; 22 | 23 | echo_duplicate 1000 hello; 24 | } 25 | --- request 26 | GET /gzip 27 | --- more_headers 28 | Accept-Encoding: gzip 29 | --- response_headers 30 | Content-Encoding: gzip 31 | --- timeout: 20 32 | -------------------------------------------------------------------------------- /t/incr.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | plan tests => 2 * blocks(); 7 | 8 | #$Test::Nginx::LWP::LogLevel = 'debug'; 9 | 10 | run_tests(); 11 | 12 | __DATA__ 13 | 14 | === TEST 1: sanity 15 | --- config 16 | location /main { 17 | echo "main pre: $echo_incr"; 18 | echo_location_async /sub; 19 | echo_location_async /sub; 20 | echo "main post: $echo_incr"; 21 | } 22 | location /sub { 23 | echo "sub: $echo_incr"; 24 | } 25 | --- request 26 | GET /main 27 | --- response_body 28 | main pre: 1 29 | sub: 3 30 | sub: 4 31 | main post: 2 32 | 33 | -------------------------------------------------------------------------------- /src/ngx_http_echo_subrequest.h: -------------------------------------------------------------------------------- 1 | #ifndef ECHO_SUBREQUEST_H 2 | #define ECHO_SUBREQUEST_H 3 | 4 | #include "ngx_http_echo_module.h" 5 | 6 | ngx_int_t ngx_http_echo_exec_echo_subrequest(ngx_http_request_t *r, 7 | ngx_http_echo_ctx_t *ctx, ngx_array_t *computed_args); 8 | 9 | ngx_int_t ngx_http_echo_exec_echo_subrequest_async(ngx_http_request_t *r, 10 | ngx_http_echo_ctx_t *ctx, ngx_array_t *computed_args); 11 | 12 | ngx_int_t ngx_http_echo_exec_abort_parent(ngx_http_request_t *r, 13 | ngx_http_echo_ctx_t *ctx); 14 | 15 | ngx_int_t ngx_http_echo_exec_exec(ngx_http_request_t *r, 16 | ngx_http_echo_ctx_t *ctx, ngx_array_t *computed_args); 17 | 18 | #endif /* ECHO_SUBREQUEST_H */ 19 | 20 | -------------------------------------------------------------------------------- /src/ngx_http_echo_echo.h: -------------------------------------------------------------------------------- 1 | #ifndef ECHO_ECHO_H 2 | #define ECHO_ECHO_H 3 | 4 | #include "ngx_http_echo_module.h" 5 | 6 | ngx_int_t ngx_http_echo_echo_init(ngx_conf_t *cf); 7 | 8 | ngx_int_t ngx_http_echo_exec_echo_sync(ngx_http_request_t *r, 9 | ngx_http_echo_ctx_t *ctx); 10 | 11 | ngx_int_t ngx_http_echo_exec_echo(ngx_http_request_t *r, 12 | ngx_http_echo_ctx_t *ctx, ngx_array_t *computed_args, 13 | ngx_flag_t in_filter, ngx_array_t *opts); 14 | 15 | ngx_int_t ngx_http_echo_exec_echo_request_body(ngx_http_request_t *r, 16 | ngx_http_echo_ctx_t *ctx); 17 | 18 | ngx_int_t ngx_http_echo_exec_echo_flush(ngx_http_request_t *r, 19 | ngx_http_echo_ctx_t *ctx); 20 | 21 | ngx_int_t ngx_http_echo_exec_echo_duplicate(ngx_http_request_t *r, 22 | ngx_http_echo_ctx_t *ctx, ngx_array_t *computed_args); 23 | 24 | #endif /* ECHO_ECHO_H */ 25 | 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .libs 2 | *.swp 3 | *.slo 4 | *.la 5 | *.swo 6 | *.lo 7 | *.mobi 8 | genmobi.sh 9 | *~ 10 | *.o 11 | print.txt 12 | .rsync 13 | *.tar.gz 14 | dist 15 | build[789] 16 | build 17 | tags 18 | update-readme 19 | *.tmp 20 | test/Makefile 21 | test/blib 22 | test.sh 23 | t.sh 24 | t/t.sh 25 | test/t/servroot/ 26 | releng 27 | reset 28 | *.t_ 29 | reindex 30 | src/location.h 31 | src/filter.c 32 | src/subrequest.h 33 | src/sleep.h 34 | src/util.c 35 | src/echo.c 36 | src/info.c 37 | src/util.h 38 | src/var.h 39 | src/filter.h 40 | src/sleep.c 41 | src/var.c 42 | src/timer.c 43 | src/module.h 44 | src/echo.h 45 | src/info.h 46 | src/foreach.c 47 | src/location.c 48 | src/timer.h 49 | src/module.c 50 | src/subrequest.c 51 | src/handler.h 52 | src/foreach.h 53 | src/handler.c 54 | nginx 55 | *.html 56 | ctags 57 | t/servroot 58 | all 59 | buildroot/ 60 | go 61 | Changes 62 | build1[0-9] 63 | analyze 64 | Makefile 65 | *.plist 66 | -------------------------------------------------------------------------------- /t/filter-used.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (3 * blocks()); 9 | 10 | no_long_string(); 11 | log_level('warn'); 12 | 13 | #master_on(); 14 | #workers(1); 15 | 16 | run_tests(); 17 | 18 | __DATA__ 19 | 20 | === TEST 1: filter indeed used 21 | --- http_config 22 | postpone_output 1; 23 | --- config 24 | location /echo { 25 | echo_after_body hello; 26 | echo world; 27 | } 28 | --- request 29 | GET /echo 30 | --- stap 31 | F(ngx_http_echo_header_filter) { 32 | println("echo header filter called") 33 | } 34 | --- stap_out 35 | echo header filter called 36 | --- response_body 37 | world 38 | hello 39 | 40 | 41 | 42 | === TEST 2: filter not used 43 | --- http_config 44 | postpone_output 1; 45 | --- config 46 | location /echo { 47 | #echo_after_body hello; 48 | echo world; 49 | } 50 | --- request 51 | GET /echo 52 | --- stap 53 | F(ngx_http_echo_header_filter) { 54 | println("echo header filter called") 55 | } 56 | --- stap_out 57 | --- response_body 58 | world 59 | 60 | -------------------------------------------------------------------------------- /valgrind.suppress: -------------------------------------------------------------------------------- 1 | { 2 | 3 | Memcheck:Param 4 | epoll_ctl(event) 5 | fun:epoll_ctl 6 | } 7 | { 8 | 9 | Memcheck:Leak 10 | fun:malloc 11 | fun:ngx_alloc 12 | fun:ngx_event_process_init 13 | } 14 | { 15 | 16 | Memcheck:Cond 17 | fun:index 18 | fun:expand_dynamic_string_token 19 | fun:_dl_map_object 20 | fun:map_doit 21 | fun:_dl_catch_error 22 | fun:do_preload 23 | fun:dl_main 24 | } 25 | { 26 | 27 | Memcheck:Leak 28 | match-leak-kinds: definite 29 | fun:malloc 30 | fun:ngx_alloc 31 | fun:ngx_set_environment 32 | fun:ngx_single_process_cycle 33 | fun:main 34 | } 35 | { 36 | 37 | Memcheck:Leak 38 | match-leak-kinds: definite 39 | fun:malloc 40 | fun:ngx_alloc 41 | fun:ngx_set_environment 42 | fun:ngx_single_process_cycle 43 | } 44 | { 45 | 46 | Memcheck:Leak 47 | match-leak-kinds: definite 48 | fun:malloc 49 | fun:ngx_alloc 50 | fun:ngx_set_environment 51 | fun:ngx_worker_process_init 52 | fun:ngx_worker_process_cycle 53 | } 54 | -------------------------------------------------------------------------------- /t/request-body.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * 2 * blocks(); 9 | 10 | run_tests(); 11 | 12 | __DATA__ 13 | 14 | === TEST 1: big client body buffered into temp files 15 | --- config 16 | location /echo { 17 | client_body_buffer_size 1k; 18 | echo_read_request_body; 19 | echo_request_body; 20 | } 21 | --- request eval 22 | "POST /echo 23 | " . 'a' x 4096 . 'end'; 24 | --- response_body eval 25 | 'a' x 4096 . 'end' 26 | 27 | 28 | 29 | === TEST 2: in memory request body (trailing echo) 30 | --- config 31 | location /echo { 32 | client_body_buffer_size 1k; 33 | echo_read_request_body; 34 | echo_request_body; 35 | echo done; 36 | } 37 | --- request 38 | POST /echo 39 | hello world 40 | --- response_body 41 | hello worlddone 42 | 43 | 44 | 45 | === TEST 3: big client body buffered into temp files (trailing echo) 46 | --- config 47 | location /echo { 48 | client_body_buffer_size 1k; 49 | echo_read_request_body; 50 | echo_request_body; 51 | echo done; 52 | } 53 | --- request eval 54 | "POST /echo 55 | " . 'a' x 4096 . "end\n"; 56 | --- response_body eval 57 | 'a' x 4096 . "enddone\n" 58 | 59 | -------------------------------------------------------------------------------- /src/ngx_http_echo_request_info.h: -------------------------------------------------------------------------------- 1 | #ifndef ECHO_REQUEST_INFO_H 2 | #define ECHO_REQUEST_INFO_H 3 | 4 | 5 | #include "ngx_http_echo_module.h" 6 | 7 | 8 | ngx_int_t ngx_http_echo_exec_echo_read_request_body( 9 | ngx_http_request_t *r, ngx_http_echo_ctx_t *ctx); 10 | 11 | ngx_int_t ngx_http_echo_request_method_variable(ngx_http_request_t *r, 12 | ngx_http_variable_value_t *v, uintptr_t data); 13 | 14 | ngx_int_t ngx_http_echo_client_request_method_variable(ngx_http_request_t *r, 15 | ngx_http_variable_value_t *v, uintptr_t data); 16 | 17 | ngx_int_t ngx_http_echo_request_body_variable(ngx_http_request_t *r, 18 | ngx_http_variable_value_t *v, uintptr_t data); 19 | 20 | ngx_int_t ngx_http_echo_client_request_headers_variable(ngx_http_request_t *r, 21 | ngx_http_variable_value_t *v, uintptr_t data); 22 | 23 | ngx_int_t ngx_http_echo_cacheable_request_uri_variable(ngx_http_request_t *r, 24 | ngx_http_variable_value_t *v, uintptr_t data); 25 | 26 | ngx_int_t ngx_http_echo_request_uri_variable(ngx_http_request_t *r, 27 | ngx_http_variable_value_t *v, uintptr_t data); 28 | 29 | ngx_int_t ngx_http_echo_response_status_variable(ngx_http_request_t *r, 30 | ngx_http_variable_value_t *v, uintptr_t data); 31 | 32 | #if nginx_version >= 1011011 33 | void ngx_http_echo_request_headers_cleanup(void *data); 34 | #endif 35 | 36 | #endif /* ECHO_REQUEST_INFO_H */ 37 | -------------------------------------------------------------------------------- /t/abort-parent.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::LWP skip_all => 5 | 'not working at all'; 6 | 7 | plan tests => 2 * blocks(); 8 | 9 | run_tests(); 10 | 11 | __DATA__ 12 | 13 | === TEST 1: sanity 14 | --- config 15 | location /abort { 16 | echo hello; 17 | echo_flush; 18 | echo_location_async '/foo'; 19 | echo_location_async '/bar'; 20 | echo_location_async '/baz'; 21 | echo world; 22 | echo_flush; 23 | } 24 | 25 | location /proxy { 26 | proxy_pass "http://127.0.0.1:$server_port/sleep?$query_string"; 27 | } 28 | 29 | location /sleep { 30 | echo_sleep $arg_sleep; 31 | echo $arg_echo; 32 | echo_flush; 33 | } 34 | 35 | location /foo { 36 | echo_location '/proxy?sleep=1&echo=foo'; 37 | #echo_flush; 38 | echo_abort_parent; 39 | } 40 | 41 | location /bar { 42 | proxy_pass 'http://127.0.0.1:$server_port/sleep_bar'; 43 | } 44 | 45 | location /baz { 46 | proxy_pass 'http://127.0.0.1:$server_port/sleep_baz'; 47 | } 48 | 49 | location /sleep_bar { 50 | echo_sleep 2; 51 | echo bar; 52 | } 53 | 54 | location /sleep_baz { 55 | echo_sleep 3; 56 | echo baz; 57 | } 58 | --- request 59 | GET /abort 60 | --- response_body 61 | hello 62 | bar 63 | 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2009-2014, Yichun "agentzh" Zhang . 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 21 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 22 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 23 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 24 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /t/echo-duplicate.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | plan tests => 2 * blocks(); 7 | 8 | #$Test::Nginx::LWP::LogLevel = 'debug'; 9 | 10 | run_tests(); 11 | 12 | __DATA__ 13 | 14 | === TEST 1: sanity 15 | --- config 16 | location /dup { 17 | echo_duplicate 3 a; 18 | } 19 | --- request 20 | GET /dup 21 | --- response_body: aaa 22 | 23 | 24 | 25 | === TEST 2: abc abc 26 | --- config 27 | location /dup { 28 | echo_duplicate 2 abc; 29 | } 30 | --- request 31 | GET /dup 32 | --- response_body: abcabc 33 | 34 | 35 | 36 | === TEST 3: big size with underscores 37 | --- config 38 | location /dup { 39 | echo_duplicate 10_000 A; 40 | } 41 | --- request 42 | GET /dup 43 | --- response_body eval 44 | 'A' x 10_000 45 | 46 | 47 | 48 | === TEST 4: 0 duplicate 0 empty strings 49 | --- config 50 | location /dup { 51 | echo_duplicate 0 ""; 52 | } 53 | --- request 54 | GET /dup 55 | --- response_body 56 | 57 | 58 | 59 | === TEST 5: 0 duplicate non-empty strings 60 | --- config 61 | location /dup { 62 | echo_duplicate 0 "abc"; 63 | } 64 | --- request 65 | GET /dup 66 | --- response_body 67 | 68 | 69 | 70 | === TEST 6: duplication of empty strings 71 | --- config 72 | location /dup { 73 | echo_duplicate 2 ""; 74 | } 75 | --- request 76 | GET /dup 77 | --- response_body 78 | 79 | 80 | 81 | === TEST 7: sanity (HEAD) 82 | --- config 83 | location /dup { 84 | echo_duplicate 3 a; 85 | } 86 | --- request 87 | HEAD /dup 88 | --- response_body 89 | 90 | -------------------------------------------------------------------------------- /t/mixed.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | plan tests => 2 * blocks(); 7 | 8 | run_tests(); 9 | 10 | __DATA__ 11 | 12 | === TEST 1: echo before echo_client_request_headers 13 | --- config 14 | location /echo { 15 | echo "headers:"; 16 | echo -n $echo_client_request_headers; 17 | } 18 | --- request 19 | GET /echo 20 | --- response_body eval 21 | "headers: 22 | GET /echo HTTP/1.1\r 23 | Host: localhost\r 24 | Connection: close\r 25 | \r 26 | " 27 | 28 | 29 | 30 | === TEST 2: echo_client_request_headers before echo 31 | --- config 32 | location /echo { 33 | echo -n $echo_client_request_headers; 34 | echo "...these are the headers"; 35 | } 36 | --- request 37 | GET /echo 38 | --- response_body eval 39 | "GET /echo HTTP/1.1\r 40 | Host: localhost\r 41 | Connection: close\r 42 | \r 43 | ...these are the headers 44 | " 45 | 46 | 47 | 48 | === TEST 3: echo & headers & echo 49 | --- config 50 | location /echo { 51 | echo "headers are"; 52 | echo -n $echo_client_request_headers; 53 | echo "...these are the headers"; 54 | } 55 | --- request 56 | GET /echo 57 | --- response_body eval 58 | "headers are 59 | GET /echo HTTP/1.1\r 60 | Host: localhost\r 61 | Connection: close\r 62 | \r 63 | ...these are the headers 64 | " 65 | 66 | 67 | 68 | === TEST 4: mixed with echo_duplicate 69 | --- config 70 | location /mixed { 71 | echo hello; 72 | echo_duplicate 2 ---; 73 | echo_duplicate 1 ' END '; 74 | echo_duplicate 2 ---; 75 | echo; 76 | } 77 | --- request 78 | GET /mixed 79 | --- response_body 80 | hello 81 | ------ END ------ 82 | 83 | -------------------------------------------------------------------------------- /src/ngx_http_echo_util.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Yichun Zhang (agentzh) 4 | */ 5 | 6 | 7 | #ifndef NGX_HTTP_ECHO_UTIL_H 8 | #define NGX_HTTP_ECHO_UTIL_H 9 | 10 | 11 | #include "ngx_http_echo_module.h" 12 | 13 | 14 | #define ngx_http_echo_strcmp_const(a, b) \ 15 | ngx_strncmp(a, b, sizeof(b) - 1) 16 | 17 | 18 | #define ngx_http_echo_hash_literal(s) \ 19 | ngx_http_echo_hash_str((u_char *) s, sizeof(s) - 1) 20 | 21 | 22 | static ngx_inline ngx_uint_t 23 | ngx_http_echo_hash_str(u_char *src, size_t n) 24 | { 25 | ngx_uint_t key; 26 | 27 | key = 0; 28 | 29 | while (n--) { 30 | key = ngx_hash(key, *src); 31 | src++; 32 | } 33 | 34 | return key; 35 | } 36 | 37 | 38 | extern ngx_uint_t ngx_http_echo_content_length_hash; 39 | 40 | 41 | ngx_http_echo_ctx_t *ngx_http_echo_create_ctx(ngx_http_request_t *r); 42 | ngx_int_t ngx_http_echo_eval_cmd_args(ngx_http_request_t *r, 43 | ngx_http_echo_cmd_t *cmd, ngx_array_t *computed_args, 44 | ngx_array_t *opts); 45 | ngx_int_t ngx_http_echo_send_header_if_needed(ngx_http_request_t *r, 46 | ngx_http_echo_ctx_t *ctx); 47 | ngx_int_t ngx_http_echo_send_chain_link(ngx_http_request_t *r, 48 | ngx_http_echo_ctx_t *ctx, ngx_chain_t *cl); 49 | ssize_t ngx_http_echo_atosz(u_char *line, size_t n); 50 | u_char *ngx_http_echo_strlstrn(u_char *s1, u_char *last, u_char *s2, size_t n); 51 | ngx_int_t ngx_http_echo_post_request_at_head(ngx_http_request_t *r, 52 | ngx_http_posted_request_t *pr); 53 | u_char *ngx_http_echo_rebase_path(ngx_pool_t *pool, u_char *src, size_t osize, 54 | size_t *nsize); 55 | ngx_int_t ngx_http_echo_flush_postponed_outputs(ngx_http_request_t *r); 56 | 57 | 58 | #endif /* NGX_HTTP_ECHO_UTIL_H */ 59 | -------------------------------------------------------------------------------- /util/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # this file is mostly meant to be used by the author himself. 4 | 5 | root=`pwd` 6 | version=$1 7 | force=$2 8 | home=~ 9 | 10 | #--with-cc=gcc46 \ 11 | 12 | ngx-build $force $version \ 13 | --with-ld-opt="-L$PCRE_LIB -Wl,-rpath,$PCRE_LIB:$LIBDRIZZLE_LIB:/usr/local/lib" \ 14 | --with-cc-opt="-DDEBUG_MALLOC" \ 15 | --with-http_stub_status_module \ 16 | --with-http_image_filter_module \ 17 | --without-mail_pop3_module \ 18 | --without-mail_imap_module \ 19 | --without-mail_smtp_module \ 20 | --without-http_upstream_ip_hash_module \ 21 | --without-http_memcached_module \ 22 | --without-http_referer_module \ 23 | --without-http_autoindex_module \ 24 | --without-http_auth_basic_module \ 25 | --without-http_userid_module \ 26 | --add-module=$root/../ndk-nginx-module \ 27 | --add-module=$root/../set-misc-nginx-module \ 28 | --add-module=$root/../eval-nginx-module \ 29 | --add-module=$root/../xss-nginx-module \ 30 | --add-module=$root/../rds-json-nginx-module \ 31 | --add-module=$root/../headers-more-nginx-module \ 32 | --add-module=$root/../lua-nginx-module \ 33 | --add-module=$root $opts \ 34 | --with-http_v2_module \ 35 | --with-select_module \ 36 | --with-poll_module \ 37 | --without-http_ssi_module \ 38 | --with-debug || exit 1 39 | #--add-module=$root/../lz-session-nginx-module \ 40 | #--add-module=$home/work/ndk \ 41 | #--add-module=$home/work/ndk/examples/http/set_var \ 42 | #--add-module=$root/../eval-nginx-module \ 43 | #--add-module=/home/agentz/work/nginx_eval_module-1.0.1 \ 44 | 45 | -------------------------------------------------------------------------------- /t/unused.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (5 * blocks()); 9 | 10 | no_long_string(); 11 | log_level('warn'); 12 | 13 | #master_on(); 14 | #workers(1); 15 | 16 | run_tests(); 17 | 18 | __DATA__ 19 | 20 | === TEST 1: filters used 21 | --- http_config 22 | postpone_output 1; 23 | --- config 24 | location /echo { 25 | echo world; 26 | echo_after_body hello; 27 | } 28 | --- request 29 | GET /echo?blah 30 | --- response_body 31 | world 32 | hello 33 | --- error_log 34 | echo header filter, uri "/echo?blah" 35 | echo body filter, uri "/echo?blah" 36 | --- no_error_log 37 | [error] 38 | --- log_level: debug 39 | 40 | 41 | 42 | === TEST 2: filters not used 43 | --- http_config 44 | postpone_output 1; 45 | --- config 46 | location /echo { 47 | echo world; 48 | #echo_after_body hello; 49 | } 50 | --- request 51 | GET /echo?blah 52 | --- response_body 53 | world 54 | --- no_error_log 55 | echo header filter, uri "/echo?blah" 56 | echo body filter, uri "/echo?blah" 57 | [error] 58 | --- log_level: debug 59 | 60 | 61 | 62 | === TEST 3: (after) filters used (multiple http {} blocks) 63 | This test case won't run with nginx 1.9.3+ since duplicate http {} blocks 64 | have been prohibited since then. 65 | --- SKIP 66 | --- http_config 67 | postpone_output 1; 68 | --- config 69 | location /echo { 70 | echo world; 71 | echo_after_body hello; 72 | } 73 | 74 | --- post_main_config 75 | http { 76 | } 77 | 78 | --- request 79 | GET /echo?blah 80 | --- response_body 81 | world 82 | hello 83 | --- error_log 84 | echo header filter, uri "/echo?blah" 85 | echo body filter, uri "/echo?blah" 86 | --- no_error_log 87 | [error] 88 | --- log_level: debug 89 | 90 | 91 | 92 | === TEST 4: (before) filters used (multiple http {} blocks) 93 | This test case won't run with nginx 1.9.3+ since duplicate http {} blocks 94 | have been prohibited since then. 95 | --- SKIP 96 | --- http_config 97 | postpone_output 1; 98 | --- config 99 | location /echo { 100 | echo world; 101 | echo_before_body hello; 102 | } 103 | 104 | --- post_main_config 105 | http { 106 | } 107 | 108 | --- request 109 | GET /echo?blah 110 | --- response_body 111 | hello 112 | world 113 | --- error_log 114 | echo header filter, uri "/echo?blah" 115 | echo body filter, uri "/echo?blah" 116 | --- no_error_log 117 | [error] 118 | --- log_level: debug 119 | 120 | -------------------------------------------------------------------------------- /t/echo-timer.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | plan tests => 2 * blocks(); 7 | 8 | run_tests(); 9 | 10 | __DATA__ 11 | 12 | === TEST 1: timer without explicit reset 13 | --- config 14 | location /timer { 15 | echo_sleep 0.03; 16 | echo "elapsed $echo_timer_elapsed sec."; 17 | } 18 | --- request 19 | GET /timer 20 | --- response_body_like 21 | ^elapsed 0\.0(2[6-9]|3[0-6]) sec\.$ 22 | 23 | 24 | 25 | === TEST 2: timer without explicit reset and sleep 26 | --- config 27 | location /timer { 28 | echo "elapsed $echo_timer_elapsed sec."; 29 | } 30 | --- request 31 | GET /timer 32 | --- response_body_like 33 | ^elapsed 0\.00[0-5] sec\.$ 34 | 35 | 36 | 37 | === TEST 3: timing accumulated sleeps 38 | --- config 39 | location /timer { 40 | echo_sleep 0.03; 41 | echo_sleep 0.02; 42 | echo "elapsed $echo_timer_elapsed sec."; 43 | } 44 | --- request 45 | GET /timer 46 | --- response_body_like 47 | ^elapsed 0\.0(4[6-9]|5[0-6]) sec\.$ 48 | 49 | 50 | 51 | === TEST 4: timer with explicit reset but without sleep 52 | --- config 53 | location /timer { 54 | echo_reset_timer; 55 | echo "elapsed $echo_timer_elapsed sec."; 56 | } 57 | --- request 58 | GET /timer 59 | --- response_body_like 60 | ^elapsed 0\.00[0-5] sec\.$ 61 | 62 | 63 | 64 | === TEST 5: reset timer between sleeps 65 | --- config 66 | location /timer { 67 | echo_sleep 0.02; 68 | echo "elapsed $echo_timer_elapsed sec."; 69 | echo_reset_timer; 70 | echo_sleep 0.03; 71 | echo "elapsed $echo_timer_elapsed sec."; 72 | } 73 | --- request 74 | GET /timer 75 | --- response_body_like 76 | ^elapsed 0\.0(1[6-9]|2[0-6]) sec\. 77 | elapsed 0\.0(2[6-9]|3[0-6]) sec\.$ 78 | 79 | 80 | 81 | === TEST 6: reset timer between blocking sleeps 82 | --- config 83 | location /timer { 84 | echo_blocking_sleep 0.02; 85 | echo "elapsed $echo_timer_elapsed sec."; 86 | echo_reset_timer; 87 | echo_blocking_sleep 0.03; 88 | echo "elapsed $echo_timer_elapsed sec."; 89 | } 90 | --- request 91 | GET /timer 92 | --- response_body_like 93 | ^elapsed 0\.0(1[6-9]|2[0-9]) sec\. 94 | elapsed 0\.0(2[6-9]|3[0-6]) sec\.$ 95 | 96 | 97 | 98 | === TEST 7: timer without explicit reset 99 | --- config 100 | location = /timer { 101 | return 200 "$echo_timer_elapsed"; 102 | } 103 | --- request 104 | GET /timer 105 | --- response_body_like chop 106 | ^0(\.0\d*)$ 107 | 108 | -------------------------------------------------------------------------------- /t/status.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | repeat_each(1); 7 | 8 | plan tests => repeat_each() * (2 * blocks()); 9 | 10 | #$Test::Nginx::LWP::LogLevel = 'debug'; 11 | 12 | run_tests(); 13 | 14 | __DATA__ 15 | 16 | === TEST 1: 200 17 | --- config 18 | location /echo { 19 | echo_status 200; 20 | echo hello; 21 | } 22 | --- request 23 | GET /echo 24 | --- response_body 25 | hello 26 | --- error_code: 200 27 | 28 | 29 | 30 | === TEST 2: if location (200) 31 | --- config 32 | location /echo { 33 | set $true 1; 34 | if ($true) { 35 | } 36 | echo_status 200; 37 | echo hello; 38 | } 39 | --- request 40 | GET /echo 41 | --- response_body 42 | hello 43 | --- error_code: 200 44 | 45 | 46 | 47 | === TEST 3: 404 48 | --- config 49 | location /echo { 50 | echo_status 404; 51 | echo hello; 52 | } 53 | --- request 54 | GET /echo 55 | --- response_body 56 | hello 57 | --- error_code: 404 58 | 59 | 60 | 61 | === TEST 4: if location (404) 62 | --- config 63 | location /echo { 64 | set $true 1; 65 | if ($true) { 66 | } 67 | echo_status 404; 68 | echo hello; 69 | } 70 | --- request 71 | GET /echo 72 | --- response_body 73 | hello 74 | --- error_code: 404 75 | 76 | 77 | 78 | === TEST 5: 500 79 | --- config 80 | location /echo { 81 | echo_status 500; 82 | echo hello; 83 | } 84 | --- request 85 | GET /echo 86 | --- response_body 87 | hello 88 | --- error_code: 500 89 | 90 | 91 | 92 | === TEST 6: if location (500) 93 | --- config 94 | location /echo { 95 | set $true 1; 96 | if ($true) { 97 | } 98 | echo_status 500; 99 | echo hello; 100 | } 101 | --- request 102 | GET /echo 103 | --- response_body 104 | hello 105 | --- error_code: 500 106 | 107 | 108 | 109 | === TEST 7: if location (500) no inherit 110 | --- config 111 | location /echo { 112 | set $true 1; 113 | if ($true) { 114 | echo_status 503; 115 | } 116 | echo_status 500; 117 | echo hello; 118 | } 119 | --- request 120 | GET /echo 121 | --- response_body 122 | hello 123 | --- error_code: 503 124 | 125 | 126 | 127 | === TEST 8: subrequest 128 | --- config 129 | location /echo { 130 | echo_location /sub; 131 | echo_status 503; 132 | } 133 | 134 | location /sub { 135 | echo blah blah; 136 | } 137 | --- request 138 | GET /echo 139 | --- response_body 140 | blah blah 141 | --- error_code: 503 142 | 143 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: focal 3 | 4 | branches: 5 | only: 6 | - "master" 7 | 8 | os: linux 9 | 10 | language: c 11 | 12 | compiler: 13 | - gcc 14 | 15 | env: 16 | global: 17 | - LUAJIT_PREFIX=/opt/luajit21 18 | - LUAJIT_LIB=$LUAJIT_PREFIX/lib 19 | - LUAJIT_INC=$LUAJIT_PREFIX/include/luajit-2.1 20 | - LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH 21 | matrix: 22 | - NGINX_VERSION=1.29.4 23 | #- NGINX_VERSION=1.21.4 24 | 25 | before_install: 26 | - sudo apt-get install -qq -y cpanminus libgd-dev ca-certificates 27 | - sudo cpanm -v --notest Test::Nginx > build.log 2>&1 || (cat build.log && exit 1) 28 | 29 | install: 30 | - wget http://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz && tar -xzf nginx-${NGINX_VERSION}.tar.gz 31 | - git clone https://github.com/simpl/ngx_devel_kit.git ../ngx_devel_kit 32 | - git clone https://github.com/openresty/set-misc-nginx-module.git ../set-misc-nginx-module 33 | - git clone https://github.com/openresty/xss-nginx-module.git ../xss-nginx-module 34 | - git clone https://github.com/openresty/rds-json-nginx-module.git ../rds-json-nginx-module 35 | - git clone https://github.com/openresty/headers-more-nginx-module.git ../headers-more-nginx-module 36 | - git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module 37 | - git clone https://github.com/openresty/openresty.git ../openresty 38 | - git clone https://github.com/openresty/lua-resty-core.git ../lua-resty-core 39 | - git clone https://github.com/openresty/lua-resty-lrucache.git ../lua-resty-lrucache 40 | - git clone https://github.com/openresty/nginx-eval-module.git ../nginx-eval-module 41 | - git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git ../luajit2 42 | 43 | script: 44 | - cd ../luajit2/ 45 | - make -j$JOBS CCDEBUG=-g Q= PREFIX=$LUAJIT_PREFIX CC=$CC XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT -msse4.2' > build.log 2>&1 || (cat build.log && exit 1) 46 | - sudo make install PREFIX=$LUAJIT_PREFIX > build.log 2>&1 || (cat build.log && exit 1) 47 | - cd ../echo-nginx-module 48 | - cd nginx-${NGINX_VERSION}/ 49 | - ./configure --without-pcre2 --without-http_ssi_module --with-debug --with-select_module --with-poll_module --with-http_stub_status_module --with-http_image_filter_module --add-module=../../ngx_devel_kit --add-module=../../set-misc-nginx-module --add-module=../../nginx-eval-module --add-module=../../xss-nginx-module --add-module=../../rds-json-nginx-module --add-module=../../headers-more-nginx-module --add-module=../../lua-nginx-module --add-module=.. > build.log 2>&1 || (cat build.log && exit 1) 50 | - make -j2 > build.log 2>&1 || (cat build.log && exit 1) 51 | - export PATH=`pwd`/objs:$PATH 52 | - cd .. 53 | - prove -I. -r t 54 | -------------------------------------------------------------------------------- /src/ddebug.h: -------------------------------------------------------------------------------- 1 | #ifndef DDEBUG_H 2 | #define DDEBUG_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #if defined(DDEBUG) && (DDEBUG) 9 | 10 | # if (NGX_HAVE_VARIADIC_MACROS) 11 | 12 | # define dd(...) fprintf(stderr, "echo *** %s: ", __func__); \ 13 | fprintf(stderr, __VA_ARGS__); \ 14 | fprintf(stderr, " at %s line %d.\n", __FILE__, __LINE__) 15 | 16 | # else 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | static ngx_inline void 24 | dd(const char * fmt, ...) { 25 | } 26 | 27 | # endif 28 | 29 | # if DDEBUG > 1 30 | 31 | # define dd_enter() dd_enter_helper(r, __func__) 32 | 33 | static ngx_inline void 34 | dd_enter_helper(ngx_http_request_t *r, const char *func) { 35 | ngx_http_posted_request_t *pr; 36 | 37 | fprintf(stderr, ">enter %s %.*s %.*s?%.*s c:%d m:%p r:%p ar:%p pr:%p", 38 | func, 39 | (int) r->method_name.len, r->method_name.data, 40 | (int) r->uri.len, r->uri.data, 41 | (int) r->args.len, r->args.data, 42 | 0/*(int) r->main->count*/, r->main, 43 | r, r->connection->data, r->parent); 44 | 45 | if (r->posted_requests) { 46 | fprintf(stderr, " posted:"); 47 | 48 | for (pr = r->posted_requests; pr; pr = pr->next) { 49 | fprintf(stderr, "%p,", pr); 50 | } 51 | } 52 | 53 | fprintf(stderr, "\n"); 54 | } 55 | 56 | # else 57 | 58 | # define dd_enter() 59 | 60 | # endif 61 | 62 | #else 63 | 64 | # if (NGX_HAVE_VARIADIC_MACROS) 65 | 66 | # define dd(...) 67 | 68 | # define dd_enter() 69 | 70 | # else 71 | 72 | #include 73 | 74 | static ngx_inline void 75 | dd(const char * fmt, ...) { 76 | } 77 | 78 | static ngx_inline void 79 | dd_enter() { 80 | } 81 | 82 | # endif 83 | 84 | #endif 85 | 86 | #if defined(DDEBUG) && (DDEBUG) 87 | 88 | #define dd_check_read_event_handler(r) \ 89 | dd("r->read_event_handler = %s", \ 90 | r->read_event_handler == ngx_http_block_reading ? \ 91 | "ngx_http_block_reading" : \ 92 | r->read_event_handler == ngx_http_test_reading ? \ 93 | "ngx_http_test_reading" : \ 94 | r->read_event_handler == ngx_http_request_empty_handler ? \ 95 | "ngx_http_request_empty_handler" : "UNKNOWN") 96 | 97 | #define dd_check_write_event_handler(r) \ 98 | dd("r->write_event_handler = %s", \ 99 | r->write_event_handler == ngx_http_handler ? \ 100 | "ngx_http_handler" : \ 101 | r->write_event_handler == ngx_http_core_run_phases ? \ 102 | "ngx_http_core_run_phases" : \ 103 | r->write_event_handler == ngx_http_request_empty_handler ? \ 104 | "ngx_http_request_empty_handler" : "UNKNOWN") 105 | 106 | #else 107 | 108 | #define dd_check_read_event_handler(r) 109 | #define dd_check_write_event_handler(r) 110 | 111 | #endif 112 | 113 | #endif /* DDEBUG_H */ 114 | 115 | -------------------------------------------------------------------------------- /t/blocking-sleep.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | plan tests => 2 * blocks(); 7 | 8 | #$Test::Nginx::LWP::LogLevel = 'debug'; 9 | 10 | run_tests(); 11 | 12 | __DATA__ 13 | 14 | === TEST 1: sanity 15 | --- config 16 | location /echo { 17 | echo_blocking_sleep 1; 18 | } 19 | --- request 20 | GET /echo 21 | --- response_body 22 | 23 | 24 | 25 | === TEST 2: fractional delay 26 | --- config 27 | location /echo { 28 | echo_blocking_sleep 0.01; 29 | } 30 | --- request 31 | GET /echo 32 | --- response_body 33 | 34 | 35 | 36 | === TEST 3: leading echo 37 | --- config 38 | location /echo { 39 | echo before...; 40 | echo_blocking_sleep 0.01; 41 | } 42 | --- request 43 | GET /echo 44 | --- response_body 45 | before... 46 | 47 | 48 | 49 | === TEST 4: trailing echo 50 | --- config 51 | location /echo { 52 | echo_blocking_sleep 0.01; 53 | echo after...; 54 | } 55 | --- request 56 | GET /echo 57 | --- response_body 58 | after... 59 | 60 | 61 | 62 | === TEST 5: two echos around sleep 63 | --- config 64 | location /echo { 65 | echo before...; 66 | echo_blocking_sleep 0.01; 67 | echo after...; 68 | } 69 | --- request 70 | GET /echo 71 | --- response_body 72 | before... 73 | after... 74 | 75 | 76 | 77 | === TEST 6: interleaving sleep and echo 78 | --- config 79 | location /echo { 80 | echo 1; 81 | echo_blocking_sleep 0.01; 82 | echo 2; 83 | echo_blocking_sleep 0.01; 84 | } 85 | --- request 86 | GET /echo 87 | --- response_body 88 | 1 89 | 2 90 | 91 | 92 | 93 | === TEST 7: interleaving sleep and echo with echo at the end... 94 | --- config 95 | location /echo { 96 | echo 1; 97 | echo_blocking_sleep 0.01; 98 | echo 2; 99 | echo_blocking_sleep 0.01; 100 | echo 3; 101 | } 102 | --- request 103 | GET /echo 104 | --- response_body 105 | 1 106 | 2 107 | 3 108 | 109 | 110 | 111 | === TEST 8: flush before sleep 112 | we didn't really test the actual effect of "echo_flush" here... 113 | merely checks if it croaks if appears. 114 | --- config 115 | location /flush { 116 | echo hi; 117 | echo_flush; 118 | echo_blocking_sleep 0.01; 119 | echo trees; 120 | } 121 | --- request 122 | GET /flush 123 | --- response_body 124 | hi 125 | trees 126 | 127 | 128 | 129 | === TEST 9: flush does not increment opcode pointer itself 130 | --- config 131 | location /flush { 132 | echo hi; 133 | echo_flush; 134 | echo trees; 135 | } 136 | --- request 137 | GET /flush 138 | --- response_body 139 | hi 140 | trees 141 | 142 | 143 | 144 | === TEST 10: blocking sleep by variable 145 | --- config 146 | location ~ ^/sleep/(.+) { 147 | echo before...; 148 | echo_blocking_sleep $1; 149 | echo after...; 150 | } 151 | --- request 152 | GET /sleep/0.01 153 | --- response_body 154 | before... 155 | after... 156 | 157 | -------------------------------------------------------------------------------- /src/ngx_http_echo_timer.c: -------------------------------------------------------------------------------- 1 | #ifndef DDEBUG 2 | #define DDEBUG 0 3 | #endif 4 | 5 | #include "ddebug.h" 6 | 7 | #include "ngx_http_echo_timer.h" 8 | #include "ngx_http_echo_util.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | ngx_int_t 16 | ngx_http_echo_timer_elapsed_variable(ngx_http_request_t *r, 17 | ngx_http_variable_value_t *v, uintptr_t data) 18 | { 19 | ngx_http_echo_ctx_t *ctx; 20 | ngx_msec_int_t ms; 21 | u_char *p; 22 | #if !(defined freenginx && nginx_version >= 1029000) 23 | ngx_time_t *tp; 24 | #endif 25 | size_t size; 26 | 27 | ctx = ngx_http_get_module_ctx(r, ngx_http_echo_module); 28 | if (ctx == NULL) { 29 | ctx = ngx_http_echo_create_ctx(r); 30 | if (ctx == NULL) { 31 | return NGX_ERROR; 32 | } 33 | 34 | ngx_http_set_ctx(r, ctx, ngx_http_echo_module); 35 | } 36 | 37 | #if (defined freenginx && nginx_version >= 1029000) 38 | if (ctx->timer_begin == 0) 39 | ctx->timer_begin = r->start_time; 40 | #else 41 | if (ctx->timer_begin.sec == 0) { 42 | ctx->timer_begin.sec = r->start_sec; 43 | ctx->timer_begin.msec = (ngx_msec_t) r->start_msec; 44 | } 45 | #endif 46 | 47 | /* force the ngx timer to update */ 48 | 49 | #if (nginx_version >= 8035) || (nginx_version < 8000 && nginx_version >= 7066) 50 | ngx_time_update(); 51 | #else 52 | ngx_time_update(0, 0); 53 | #endif 54 | 55 | #if (defined freenginx && nginx_version >= 1029000) 56 | ms = (ngx_msec_int_t) (ngx_current_msec - ctx->timer_begin); 57 | #else 58 | 59 | tp = ngx_timeofday(); 60 | 61 | dd("old sec msec: %ld %d\n", (long) ctx->timer_begin.sec, 62 | (int) ctx->timer_begin.msec); 63 | 64 | dd("new sec msec: %ld %d\n", (long) tp->sec, (int) tp->msec); 65 | 66 | ms = (ngx_msec_int_t) 67 | ((tp->sec - ctx->timer_begin.sec) * 1000 + 68 | (tp->msec - ctx->timer_begin.msec)); 69 | #endif 70 | ms = (ms >= 0) ? ms : 0; 71 | 72 | size = sizeof("-9223372036854775808.000") - 1; 73 | 74 | p = ngx_palloc(r->pool, size); 75 | if (p == NULL) { 76 | return NGX_ERROR; 77 | } 78 | 79 | v->len = ngx_snprintf(p, size, "%T.%03M", ms / 1000, ms % 1000) - p; 80 | v->data = p; 81 | 82 | v->valid = 1; 83 | v->no_cacheable = 1; 84 | v->not_found = 0; 85 | 86 | return NGX_OK; 87 | } 88 | 89 | 90 | ngx_int_t 91 | ngx_http_echo_exec_echo_reset_timer(ngx_http_request_t *r, 92 | ngx_http_echo_ctx_t *ctx) 93 | { 94 | dd("Exec timer..."); 95 | 96 | /* force the ngx timer to update */ 97 | 98 | #if (nginx_version >= 8035) || (nginx_version < 8000 && nginx_version >= 7066) 99 | ngx_time_update(); 100 | #else 101 | ngx_time_update(0, 0); 102 | #endif 103 | 104 | #if (defined freenginx && nginx_version >= 1029000) 105 | ctx->timer_begin = ngx_current_msec; 106 | #else 107 | ctx->timer_begin = *ngx_timeofday(); 108 | #endif 109 | return NGX_OK; 110 | } 111 | 112 | -------------------------------------------------------------------------------- /src/ngx_http_echo_var.c: -------------------------------------------------------------------------------- 1 | #ifndef DDEBUG 2 | #define DDEBUG 0 3 | #endif 4 | #include "ddebug.h" 5 | 6 | #include "ngx_http_echo_var.h" 7 | #include "ngx_http_echo_timer.h" 8 | #include "ngx_http_echo_request_info.h" 9 | #include "ngx_http_echo_foreach.h" 10 | 11 | 12 | static ngx_int_t ngx_http_echo_incr_variable(ngx_http_request_t *r, 13 | ngx_http_variable_value_t *v, uintptr_t data); 14 | 15 | 16 | static ngx_http_variable_t ngx_http_echo_variables[] = { 17 | 18 | { ngx_string("echo_timer_elapsed"), NULL, 19 | ngx_http_echo_timer_elapsed_variable, 0, 20 | NGX_HTTP_VAR_NOCACHEABLE, 0 }, 21 | 22 | { ngx_string("echo_request_method"), NULL, 23 | ngx_http_echo_request_method_variable, 0, 24 | NGX_HTTP_VAR_NOCACHEABLE, 0 }, 25 | 26 | { ngx_string("echo_cacheable_request_uri"), NULL, 27 | ngx_http_echo_cacheable_request_uri_variable, 0, 28 | 0, 0 }, 29 | 30 | { ngx_string("echo_request_uri"), NULL, 31 | ngx_http_echo_request_uri_variable, 0, 32 | 0, 0 }, 33 | 34 | { ngx_string("echo_client_request_method"), NULL, 35 | ngx_http_echo_client_request_method_variable, 0, 36 | NGX_HTTP_VAR_NOCACHEABLE, 0 }, 37 | 38 | { ngx_string("echo_request_body"), NULL, 39 | ngx_http_echo_request_body_variable, 0, 40 | NGX_HTTP_VAR_NOCACHEABLE, 0 }, 41 | 42 | { ngx_string("echo_client_request_headers"), NULL, 43 | ngx_http_echo_client_request_headers_variable, 0, 44 | NGX_HTTP_VAR_NOCACHEABLE, 0 }, 45 | 46 | { ngx_string("echo_it"), NULL, 47 | ngx_http_echo_it_variable, 0, 48 | NGX_HTTP_VAR_NOCACHEABLE, 0 }, 49 | 50 | { ngx_string("echo_incr"), NULL, 51 | ngx_http_echo_incr_variable, 0, 52 | NGX_HTTP_VAR_NOCACHEABLE, 0 }, 53 | 54 | { ngx_string("echo_response_status"), NULL, 55 | ngx_http_echo_response_status_variable, 0, 56 | NGX_HTTP_VAR_NOCACHEABLE, 0 }, 57 | 58 | { ngx_null_string, NULL, NULL, 0, 0, 0 } 59 | }; 60 | 61 | 62 | ngx_int_t 63 | ngx_http_echo_add_variables(ngx_conf_t *cf) 64 | { 65 | ngx_http_variable_t *var, *v; 66 | 67 | for (v = ngx_http_echo_variables; v->name.len; v++) { 68 | var = ngx_http_add_variable(cf, &v->name, v->flags); 69 | if (var == NULL) { 70 | return NGX_ERROR; 71 | } 72 | 73 | var->get_handler = v->get_handler; 74 | var->data = v->data; 75 | } 76 | 77 | return NGX_OK; 78 | } 79 | 80 | 81 | static ngx_int_t 82 | ngx_http_echo_incr_variable(ngx_http_request_t *r, 83 | ngx_http_variable_value_t *v, uintptr_t data) 84 | { 85 | ngx_http_echo_ctx_t *ctx; 86 | u_char *p; 87 | 88 | ctx = ngx_http_get_module_ctx(r->main, ngx_http_echo_module); 89 | 90 | if (ctx == NULL) { 91 | return NGX_ERROR; 92 | } 93 | 94 | ctx->counter++; 95 | 96 | p = ngx_palloc(r->pool, NGX_INT_T_LEN); 97 | if (p == NULL) { 98 | return NGX_ERROR; 99 | } 100 | 101 | v->len = ngx_sprintf(p, "%ui", ctx->counter) - p; 102 | v->data = p; 103 | 104 | v->valid = 1; 105 | v->not_found = 0; 106 | v->no_cacheable = 1; 107 | 108 | return NGX_OK; 109 | } 110 | 111 | -------------------------------------------------------------------------------- /t/if.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | plan tests => 2 * blocks(); 7 | 8 | #$Test::Nginx::LWP::LogLevel = 'debug'; 9 | 10 | run_tests(); 11 | 12 | __DATA__ 13 | 14 | === TEST 1: sanity (hit) 15 | --- config 16 | location ^~ /if { 17 | set $res miss; 18 | if ($arg_val ~* '^a') { 19 | set $res hit; 20 | echo $res; 21 | } 22 | echo $res; 23 | } 24 | --- request 25 | GET /if?val=abc 26 | --- response_body 27 | hit 28 | 29 | 30 | 31 | === TEST 2: sanity (miss) 32 | --- config 33 | location ^~ /if { 34 | set $res miss; 35 | if ($arg_val ~* '^a') { 36 | set $res hit; 37 | echo $res; 38 | } 39 | echo $res; 40 | } 41 | --- request 42 | GET /if?val=bcd 43 | --- response_body 44 | miss 45 | 46 | 47 | 48 | === TEST 3: proxy in if (hit) 49 | --- config 50 | location ^~ /if { 51 | set $res miss; 52 | if ($arg_val ~* '^a') { 53 | set $res hit; 54 | proxy_pass $scheme://127.0.0.1:$server_port/foo?res=$res; 55 | } 56 | proxy_pass $scheme://127.0.0.1:$server_port/foo?res=$res; 57 | } 58 | location /foo { 59 | echo "res = $arg_res"; 60 | } 61 | --- request 62 | GET /if?val=abc 63 | --- response_body 64 | res = hit 65 | 66 | 67 | 68 | === TEST 4: proxy in if (miss) 69 | --- config 70 | location ^~ /if { 71 | set $res miss; 72 | if ($arg_val ~* '^a') { 73 | set $res hit; 74 | proxy_pass $scheme://127.0.0.1:$server_port/foo?res=$res; 75 | } 76 | proxy_pass $scheme://127.0.0.1:$server_port/foo?res=$res; 77 | } 78 | location /foo { 79 | echo "res = $arg_res"; 80 | } 81 | --- request 82 | GET /if?val=bcd 83 | --- response_body 84 | res = miss 85 | 86 | 87 | 88 | === TEST 5: if too long url (hit) 89 | --- config 90 | location /foo { 91 | if ($request_uri ~ '.{20,}') { 92 | echo too long; 93 | } 94 | echo ok; 95 | } 96 | --- request 97 | GET /foo?a=12345678901234567890 98 | --- response_body 99 | too long 100 | 101 | 102 | 103 | === TEST 6: if too long url (miss) 104 | --- config 105 | location /foo { 106 | if ($request_uri ~ '.{20,}') { 107 | echo too long; 108 | } 109 | echo ok; 110 | } 111 | --- request 112 | GET /foo?a=1234567890 113 | --- response_body 114 | ok 115 | 116 | 117 | 118 | === TEST 7: echo should be inherited by if blocks 119 | --- config 120 | location /foo { 121 | if ($uri ~ 'foo') { 122 | } 123 | echo ok; 124 | } 125 | --- request 126 | GET /foo 127 | --- response_body 128 | ok 129 | 130 | 131 | 132 | === TEST 8: echo_after_body and echo_before_body should be inherited by if blocks 133 | --- config 134 | location /foo { 135 | if ($uri ~ 'foo') { 136 | } 137 | echo_before_body -n 'hello'; 138 | echo_location /comma; 139 | echo_after_body 'world'; 140 | } 141 | 142 | location = /comma { 143 | internal; 144 | echo -n ', '; 145 | } 146 | --- request 147 | GET /foo 148 | --- response_body 149 | hello, world 150 | 151 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_echo_module 2 | 3 | ECHO_SRCS=" \ 4 | $ngx_addon_dir/src/ngx_http_echo_module.c \ 5 | $ngx_addon_dir/src/ngx_http_echo_util.c \ 6 | $ngx_addon_dir/src/ngx_http_echo_timer.c \ 7 | $ngx_addon_dir/src/ngx_http_echo_var.c \ 8 | $ngx_addon_dir/src/ngx_http_echo_handler.c \ 9 | $ngx_addon_dir/src/ngx_http_echo_filter.c \ 10 | $ngx_addon_dir/src/ngx_http_echo_sleep.c \ 11 | $ngx_addon_dir/src/ngx_http_echo_location.c \ 12 | $ngx_addon_dir/src/ngx_http_echo_echo.c \ 13 | $ngx_addon_dir/src/ngx_http_echo_request_info.c \ 14 | $ngx_addon_dir/src/ngx_http_echo_subrequest.c \ 15 | $ngx_addon_dir/src/ngx_http_echo_foreach.c \ 16 | " 17 | 18 | ECHO_DEPS=" \ 19 | $ngx_addon_dir/src/ddebug.h \ 20 | $ngx_addon_dir/src/ngx_http_echo_module.h \ 21 | $ngx_addon_dir/src/ngx_http_echo_handler.h \ 22 | $ngx_addon_dir/src/ngx_http_echo_util.h \ 23 | $ngx_addon_dir/src/ngx_http_echo_sleep.h \ 24 | $ngx_addon_dir/src/ngx_http_echo_filter.h \ 25 | $ngx_addon_dir/src/ngx_http_echo_var.h \ 26 | $ngx_addon_dir/src/ngx_http_echo_location.h \ 27 | $ngx_addon_dir/src/ngx_http_echo_echo.h \ 28 | $ngx_addon_dir/src/ngx_http_echo_request_info.h \ 29 | $ngx_addon_dir/src/ngx_http_echo_subrequest.h \ 30 | $ngx_addon_dir/src/ngx_http_echo_foreach.h \ 31 | " 32 | 33 | # nginx 1.17.0+ unconditionally enables the postpone filter 34 | if [ ! -z "$HTTP_POSTPONE" ]; then 35 | # nginx won't have HTTP_POSTPONE_FILTER_MODULE & HTTP_POSTPONE_FILTER_SRCS 36 | # defined since 1.9.11 37 | if [ -z "$HTTP_POSTPONE_FILTER_MODULE" ]; then 38 | HTTP_POSTPONE_FILTER_MODULE=ngx_http_postpone_filter_module 39 | HTTP_POSTPONE_FILTER_SRCS=src/http/ngx_http_postpone_filter_module.c 40 | fi 41 | 42 | # This module depends upon the postpone filter being activated 43 | if [ "$HTTP_POSTPONE" != YES ]; then 44 | HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES $HTTP_POSTPONE_FILTER_MODULE" 45 | HTTP_SRCS="$HTTP_SRCS $HTTP_POSTPONE_FILTER_SRCS" 46 | HTTP_POSTPONE=YES 47 | fi 48 | fi 49 | 50 | if [ -n "$ngx_module_link" ]; then 51 | ngx_module_type=HTTP_AUX_FILTER 52 | ngx_module_name=$ngx_addon_name 53 | ngx_module_incs= 54 | ngx_module_deps="$ECHO_DEPS" 55 | ngx_module_srcs="$ECHO_SRCS" 56 | ngx_module_libs= 57 | 58 | . auto/module 59 | else 60 | HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES $ngx_addon_name" 61 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ECHO_SRCS" 62 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ECHO_DEPS" 63 | fi 64 | -------------------------------------------------------------------------------- /util/wiki2pod.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | use bytes; 6 | 7 | my @nl_counts; 8 | my $last_nl_count_level; 9 | 10 | my @bl_counts; 11 | my $last_bl_count_level; 12 | 13 | sub fmt_pos ($) { 14 | (my $s = $_[0]) =~ s{\#(.*)}{/"$1"}; 15 | $s; 16 | } 17 | 18 | sub fmt_mark ($$) { 19 | my ($tag, $s) = @_; 20 | my $max_level = 0; 21 | while ($s =~ /([<>])\1*/g) { 22 | my $level = length $&; 23 | if ($level > $max_level) { 24 | $max_level = $level; 25 | } 26 | } 27 | 28 | my $times = $max_level + 1; 29 | if ($times > 1) { 30 | $s = " $s "; 31 | } 32 | return $tag . ('<' x $times) . $s . ('>' x $times); 33 | } 34 | 35 | print "=encoding utf-8\n\n"; 36 | 37 | while (<>) { 38 | if ($. == 1) { 39 | # strip the leading U+FEFF byte in MS-DOS text files 40 | my $first = ord(substr($_, 0, 1)); 41 | #printf STDERR "0x%x", $first; 42 | #my $second = ord(substr($_, 2, 1)); 43 | #printf STDERR "0x%x", $second; 44 | if ($first == 0xEF) { 45 | substr($_, 0, 1, ''); 46 | #warn "Hit!"; 47 | } 48 | } 49 | s{\[(http[^ \]]+) ([^\]]*)\]}{$2 (L<$1>)}gi; 50 | s{ \[\[ ( [^\]\|]+ ) \| ([^\]]*) \]\] }{"L<$2|" . fmt_pos($1) . ">"}gixe; 51 | s{(.*?)}{fmt_mark('C', $1)}gie; 52 | s{'''(.*?)'''}{fmt_mark('B', $1)}ge; 53 | s{''(.*?)''}{fmt_mark('I', $1)}ge; 54 | if (s{^\s*<[^>]+>\s*$}{}) { 55 | next; 56 | } 57 | 58 | if (/^\s*$/) { 59 | print "\n"; 60 | next; 61 | } 62 | 63 | =begin cmt 64 | 65 | if ($. == 1) { 66 | warn $_; 67 | for my $i (0..length($_) - 1) { 68 | my $chr = substr($_, $i, 1); 69 | warn "chr ord($i): ".ord($chr)." \"$chr\"\n"; 70 | } 71 | } 72 | 73 | =end cmt 74 | =cut 75 | 76 | if (/(=+) (.*) \1$/) { 77 | #warn "HERE! $_" if $. == 1; 78 | my ($level, $title) = (length $1, $2); 79 | collapse_lists(); 80 | 81 | print "\n=head$level $title\n\n"; 82 | } elsif (/^(\#+) (.*)/) { 83 | my ($level, $txt) = (length($1) - 1, $2); 84 | if (defined $last_nl_count_level && $level != $last_nl_count_level) { 85 | print "\n=back\n\n"; 86 | } 87 | $last_nl_count_level = $level; 88 | $nl_counts[$level] ||= 0; 89 | if ($nl_counts[$level] == 0) { 90 | print "\n=over\n\n"; 91 | } 92 | $nl_counts[$level]++; 93 | print "\n=item $nl_counts[$level].\n\n"; 94 | print "$txt\n"; 95 | } elsif (/^(\*+) (.*)/) { 96 | my ($level, $txt) = (length($1) - 1, $2); 97 | if (defined $last_bl_count_level && $level != $last_bl_count_level) { 98 | print "\n=back\n\n"; 99 | } 100 | $last_bl_count_level = $level; 101 | $bl_counts[$level] ||= 0; 102 | if ($bl_counts[$level] == 0) { 103 | print "\n=over\n\n"; 104 | } 105 | $bl_counts[$level]++; 106 | print "\n=item *\n\n"; 107 | print "$txt\n"; 108 | } else { 109 | collapse_lists(); 110 | print; 111 | } 112 | } 113 | 114 | collapse_lists(); 115 | 116 | sub collapse_lists { 117 | while (defined $last_nl_count_level && $last_nl_count_level >= 0) { 118 | print "\n=back\n\n"; 119 | $last_nl_count_level--; 120 | } 121 | undef $last_nl_count_level; 122 | undef @nl_counts; 123 | 124 | while (defined $last_bl_count_level && $last_bl_count_level >= 0) { 125 | print "\n=back\n\n"; 126 | $last_bl_count_level--; 127 | } 128 | undef $last_bl_count_level; 129 | undef @bl_counts; 130 | } 131 | 132 | -------------------------------------------------------------------------------- /t/sleep.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | plan tests => 2 * blocks(); 7 | 8 | run_tests(); 9 | 10 | __DATA__ 11 | 12 | === TEST 1: sanity 13 | --- config 14 | location /echo { 15 | echo_sleep 1; 16 | } 17 | --- request 18 | GET /echo 19 | --- response_body 20 | 21 | 22 | 23 | === TEST 2: fractional delay 24 | --- config 25 | location /echo { 26 | echo_sleep 0.01; 27 | } 28 | --- request 29 | GET /echo 30 | --- response_body 31 | 32 | 33 | 34 | === TEST 3: leading echo 35 | --- config 36 | location /echo { 37 | echo before...; 38 | echo_sleep 0.01; 39 | } 40 | --- request 41 | GET /echo 42 | --- response_body 43 | before... 44 | 45 | 46 | 47 | === TEST 4: trailing echo 48 | --- config 49 | location /echo { 50 | echo_sleep 0.01; 51 | echo after...; 52 | } 53 | --- request 54 | GET /echo 55 | --- response_body 56 | after... 57 | 58 | 59 | 60 | === TEST 5: two echos around sleep 61 | --- config 62 | location /echo { 63 | echo before...; 64 | echo_sleep 0.01; 65 | echo after...; 66 | } 67 | --- request 68 | GET /echo 69 | --- response_body 70 | before... 71 | after... 72 | 73 | 74 | 75 | === TEST 6: interleaving sleep and echo 76 | --- config 77 | location /echo { 78 | echo 1; 79 | echo_sleep 0.01; 80 | echo 2; 81 | echo_sleep 0.01; 82 | } 83 | --- request 84 | GET /echo 85 | --- response_body 86 | 1 87 | 2 88 | 89 | 90 | 91 | === TEST 7: interleaving sleep and echo with echo at the end... 92 | --- config 93 | location /echo { 94 | echo 1; 95 | echo_sleep 0.01; 96 | echo 2; 97 | echo_sleep 0.01; 98 | echo 3; 99 | } 100 | --- request 101 | GET /echo 102 | --- response_body 103 | 1 104 | 2 105 | 3 106 | 107 | 108 | 109 | === TEST 8: flush before sleep 110 | we didn't really test the actual effect of "echo_flush" here... 111 | merely checks if it croaks if appears. 112 | --- config 113 | location /flush { 114 | echo hi; 115 | echo_flush; 116 | echo_sleep 0.01; 117 | echo trees; 118 | } 119 | --- request 120 | GET /flush 121 | --- response_body 122 | hi 123 | trees 124 | 125 | 126 | 127 | === TEST 9: flush does not increment opcode pointer itself 128 | --- config 129 | location /flush { 130 | echo hi; 131 | echo_flush; 132 | echo trees; 133 | } 134 | --- request 135 | GET /flush 136 | --- response_body 137 | hi 138 | trees 139 | 140 | 141 | 142 | === TEST 10: sleep through a proxy 143 | this reveals a bug in v0.19 and the bug is fixed in v0.20. 144 | --- config 145 | location /proxy { 146 | proxy_pass $scheme://127.0.0.1:$server_port/entry'; 147 | } 148 | location /entry { 149 | echo_sleep 0.001; 150 | echo done; 151 | } 152 | --- request 153 | GET /proxy 154 | --- response_body_like 155 | done 156 | 157 | 158 | 159 | === TEST 11: abnormally quit 160 | --- config 161 | location /quit { 162 | echo before; 163 | echo_flush; 164 | echo_sleep 1; 165 | echo after; 166 | } 167 | --- request 168 | GET /quit 169 | --- response_body 170 | before 171 | after 172 | 173 | 174 | 175 | === TEST 12: two echos around sleep (HEAD) 176 | --- config 177 | location /echo { 178 | echo before...; 179 | echo_sleep 0.01; 180 | echo after...; 181 | } 182 | --- request 183 | HEAD /echo 184 | --- response_body 185 | 186 | 187 | 188 | === TEST 13: sleep by variable 189 | --- config 190 | location ~ ^/sleep/(.+) { 191 | echo before...; 192 | echo_sleep $1; 193 | echo after...; 194 | } 195 | --- request 196 | GET /sleep/0.01 197 | --- response_body 198 | before... 199 | after... 200 | 201 | -------------------------------------------------------------------------------- /t/exec.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (2 * blocks() + 1); 9 | 10 | #$Test::Nginx::LWP::LogLevel = 'debug'; 11 | 12 | run_tests(); 13 | 14 | __DATA__ 15 | 16 | === TEST 1: exec normal location 17 | --- config 18 | location /main { 19 | echo_exec /bar; 20 | echo end; 21 | } 22 | location = /bar { 23 | echo "$echo_request_uri:"; 24 | echo bar; 25 | } 26 | --- request 27 | GET /main 28 | --- response_body 29 | /bar: 30 | bar 31 | 32 | 33 | 34 | === TEST 2: location with args (inlined in uri) 35 | --- config 36 | location /main { 37 | echo_exec /bar?a=32; 38 | echo end; 39 | } 40 | location /bar { 41 | echo "a: [$arg_a]"; 42 | } 43 | --- request 44 | GET /main 45 | --- response_body 46 | a: [32] 47 | 48 | 49 | 50 | === TEST 3: location with args (in separate arg) 51 | --- config 52 | location /main { 53 | echo_exec /bar a=56; 54 | echo end; 55 | } 56 | location /bar { 57 | echo "a: [$arg_a]"; 58 | } 59 | --- request 60 | GET /main 61 | --- response_body 62 | a: [56] 63 | 64 | 65 | 66 | === TEST 4: exec named location 67 | --- config 68 | location /main { 69 | echo_exec @bar; 70 | echo end; 71 | } 72 | location @bar { 73 | echo bar; 74 | } 75 | --- request 76 | GET /main 77 | --- response_body 78 | bar 79 | 80 | 81 | 82 | === TEST 5: query string ignored for named locations 83 | --- config 84 | location /main { 85 | echo_exec @bar?a=32; 86 | echo end; 87 | } 88 | location @bar { 89 | echo "a: [$arg_a]"; 90 | } 91 | --- request 92 | GET /main 93 | --- response_body 94 | a: [] 95 | --- error_log 96 | querystring a=32 ignored when exec'ing named location @bar 97 | 98 | 99 | 100 | === TEST 6: query string ignored for named locations 101 | --- config 102 | location /foo { 103 | echo_exec @bar; 104 | } 105 | location @bar { 106 | echo "uri: [$echo_request_uri]"; 107 | } 108 | --- request 109 | GET /foo 110 | --- response_body 111 | uri: [/foo] 112 | 113 | 114 | 115 | === TEST 7: exec(named location) in subrequests 116 | --- config 117 | location /entry { 118 | echo_location /foo; 119 | echo_sleep 0.001; 120 | echo_location /foo2; 121 | } 122 | location /foo { 123 | echo_exec @bar; 124 | } 125 | location /foo2 { 126 | echo_exec @bar; 127 | } 128 | 129 | location @bar { 130 | proxy_pass http://127.0.0.1:$server_port/bar; 131 | } 132 | location /bar { 133 | echo_sleep 0.01; 134 | echo hello; 135 | } 136 | --- request 137 | GET /entry 138 | --- response_body 139 | hello 140 | hello 141 | 142 | 143 | 144 | === TEST 8: exec(normal loctions) in subrequests 145 | --- config 146 | location /entry { 147 | echo_location /foo; 148 | echo_sleep 0.001; 149 | echo_location /foo2; 150 | } 151 | location /foo { 152 | echo_exec /baz; 153 | } 154 | location /foo2 { 155 | echo_exec /baz; 156 | } 157 | 158 | location /baz { 159 | proxy_pass http://127.0.0.1:$server_port/bar; 160 | } 161 | location /bar { 162 | echo_sleep 0.01; 163 | echo hello; 164 | } 165 | --- request 166 | GET /entry 167 | --- response_body 168 | hello 169 | hello 170 | 171 | 172 | 173 | === TEST 9: exec should clear ctx 174 | --- config 175 | location @bar { 176 | echo hello; 177 | echo world; 178 | echo heh; 179 | } 180 | location /foo { 181 | #echo_sleep 0.001; 182 | echo_reset_timer; 183 | echo_exec @bar; 184 | } 185 | --- request 186 | GET /foo 187 | --- response_body 188 | hello 189 | world 190 | heh 191 | 192 | 193 | 194 | === TEST 10: reset ctx 195 | --- config 196 | location @proxy { 197 | rewrite_by_lua return; 198 | echo hello; 199 | } 200 | location /main { 201 | rewrite_by_lua return; 202 | echo_exec @proxy; 203 | } 204 | --- request 205 | GET /main 206 | --- response_body 207 | hello 208 | 209 | 210 | 211 | === TEST 11: yield before exec 212 | --- config 213 | location @bar { 214 | echo hello; 215 | echo world; 216 | echo heh; 217 | } 218 | location /foo { 219 | echo_sleep 0.001; 220 | echo_exec @bar; 221 | } 222 | --- request 223 | GET /foo 224 | --- response_body 225 | hello 226 | world 227 | heh 228 | 229 | -------------------------------------------------------------------------------- /src/ngx_http_echo_module.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Yichun Zhang (agentzh) 4 | */ 5 | 6 | 7 | #ifndef NGX_HTTP_ECHO_MODULE_H 8 | #define NGX_HTTP_ECHO_MODULE_H 9 | 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | 16 | extern ngx_module_t ngx_http_echo_module; 17 | 18 | 19 | /* config directive's opcode */ 20 | typedef enum { 21 | echo_opcode_echo_sync, 22 | echo_opcode_echo, 23 | echo_opcode_echo_request_body, 24 | echo_opcode_echo_sleep, 25 | echo_opcode_echo_flush, 26 | echo_opcode_echo_blocking_sleep, 27 | echo_opcode_echo_reset_timer, 28 | echo_opcode_echo_before_body, 29 | echo_opcode_echo_after_body, 30 | echo_opcode_echo_location_async, 31 | echo_opcode_echo_location, 32 | echo_opcode_echo_subrequest_async, 33 | echo_opcode_echo_subrequest, 34 | echo_opcode_echo_duplicate, 35 | echo_opcode_echo_read_request_body, 36 | echo_opcode_echo_foreach_split, 37 | echo_opcode_echo_end, 38 | echo_opcode_echo_abort_parent, 39 | echo_opcode_echo_exec 40 | } ngx_http_echo_opcode_t; 41 | 42 | 43 | /* all the various config directives (or commands) are 44 | * divided into two categories: "handler commands", 45 | * and "filter commands". For instance, the "echo" 46 | * directive is a handler command while 47 | * "echo_before_body" is a filter one. */ 48 | typedef enum { 49 | echo_handler_cmd, 50 | echo_filter_cmd 51 | 52 | } ngx_http_echo_cmd_category_t; 53 | 54 | 55 | /* compiled form of a config directive argument's value */ 56 | typedef struct { 57 | /* holds the raw string of the argument value */ 58 | ngx_str_t raw_value; 59 | 60 | /* fields "lengths" and "values" are set by 61 | * the function ngx_http_script_compile, 62 | * iff the argument value indeed contains 63 | * nginx variables like "$foo" */ 64 | ngx_array_t *lengths; 65 | ngx_array_t *values; 66 | 67 | } ngx_http_echo_arg_template_t; 68 | 69 | 70 | /* represent a config directive (or command) like "echo". */ 71 | typedef struct { 72 | ngx_http_echo_opcode_t opcode; 73 | 74 | /* each argument is of type echo_arg_template_t: */ 75 | ngx_array_t *args; 76 | } ngx_http_echo_cmd_t; 77 | 78 | 79 | /* location config struct */ 80 | typedef struct { 81 | /* elements of the following arrays are of type 82 | * ngx_http_echo_cmd_t */ 83 | ngx_array_t *handler_cmds; 84 | ngx_array_t *before_body_cmds; 85 | ngx_array_t *after_body_cmds; 86 | 87 | unsigned seen_leading_output; 88 | 89 | ngx_int_t status; 90 | } ngx_http_echo_loc_conf_t; 91 | 92 | 93 | typedef struct { 94 | ngx_int_t requires_filter; 95 | #if nginx_version >= 1011011 96 | ngx_buf_t **busy_buf_ptrs; 97 | ngx_int_t busy_buf_ptr_count; 98 | #endif 99 | } ngx_http_echo_main_conf_t; 100 | 101 | 102 | typedef struct { 103 | ngx_array_t *choices; /* items after splitting */ 104 | ngx_uint_t next_choice; /* current item index */ 105 | ngx_uint_t cmd_index; /* cmd index for the echo_foreach direcitve */ 106 | } ngx_http_echo_foreach_ctx_t; 107 | 108 | 109 | /* context struct in the request handling cycle, holding 110 | * the current states of the command evaluator */ 111 | typedef struct { 112 | /* index of the next handler command in 113 | * ngx_http_echo_loc_conf_t's "handler_cmds" array. */ 114 | ngx_uint_t next_handler_cmd; 115 | 116 | /* index of the next before-body filter command in 117 | * ngx_http_echo_loc_conf_t's "before_body_cmds" array. */ 118 | ngx_uint_t next_before_body_cmd; 119 | 120 | /* index of the next after-body filter command in 121 | * ngx_http_echo_loc_conf_t's "after_body_cmds" array. */ 122 | ngx_uint_t next_after_body_cmd; 123 | 124 | ngx_http_echo_foreach_ctx_t *foreach; 125 | 126 | #if (defined freenginx && nginx_version >= 1029000) 127 | ngx_msec_t timer_begin; 128 | #else 129 | ngx_time_t timer_begin; 130 | #endif 131 | 132 | ngx_event_t sleep; 133 | 134 | ngx_uint_t counter; 135 | 136 | unsigned before_body_sent:1; 137 | unsigned skip_filter:1; 138 | 139 | unsigned wait_read_request_body:1; 140 | 141 | unsigned waiting:1; 142 | unsigned done:1; 143 | 144 | unsigned run_post_subrequest:1; 145 | unsigned header_sent:1; /* r->header_sent is not sufficient 146 | * because special header filters like 147 | * ngx_http_image_filter_module's may 148 | * intercept the whole header filter chain 149 | * leaving r->header_sent unset. So we 150 | * should always test both flags. */ 151 | 152 | } ngx_http_echo_ctx_t; 153 | 154 | 155 | #endif /* NGX_HTTP_ECHO_MODULE_H */ 156 | -------------------------------------------------------------------------------- /src/ngx_http_echo_location.c: -------------------------------------------------------------------------------- 1 | #ifndef DDEBUG 2 | #define DDEBUG 0 3 | #endif 4 | #include "ddebug.h" 5 | 6 | #include "ngx_http_echo_util.h" 7 | #include "ngx_http_echo_location.h" 8 | #include "ngx_http_echo_handler.h" 9 | 10 | #include 11 | 12 | 13 | static ngx_int_t ngx_http_echo_adjust_subrequest(ngx_http_request_t *sr); 14 | 15 | 16 | ngx_int_t 17 | ngx_http_echo_exec_echo_location_async(ngx_http_request_t *r, 18 | ngx_http_echo_ctx_t *ctx, ngx_array_t *computed_args) 19 | { 20 | ngx_int_t rc; 21 | ngx_http_request_t *sr; /* subrequest object */ 22 | ngx_str_t *computed_arg_elts; 23 | ngx_str_t location; 24 | ngx_str_t *url_args; 25 | ngx_str_t args; 26 | ngx_uint_t flags = 0; 27 | 28 | dd_enter(); 29 | 30 | computed_arg_elts = computed_args->elts; 31 | 32 | location = computed_arg_elts[0]; 33 | 34 | if (location.len == 0) { 35 | return NGX_ERROR; 36 | } 37 | 38 | if (computed_args->nelts > 1) { 39 | url_args = &computed_arg_elts[1]; 40 | } else { 41 | url_args = NULL; 42 | } 43 | 44 | args.data = NULL; 45 | args.len = 0; 46 | 47 | if (ngx_http_parse_unsafe_uri(r, &location, &args, &flags) != NGX_OK) { 48 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 49 | "echo_location_async sees unsafe uri: \"%V\"", 50 | &location); 51 | return NGX_ERROR; 52 | } 53 | 54 | if (args.len > 0 && url_args == NULL) { 55 | url_args = &args; 56 | } 57 | 58 | rc = ngx_http_echo_send_header_if_needed(r, ctx); 59 | if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { 60 | return rc; 61 | } 62 | 63 | rc = ngx_http_subrequest(r, &location, url_args, &sr, NULL, 0); 64 | 65 | if (rc != NGX_OK) { 66 | return NGX_ERROR; 67 | } 68 | 69 | rc = ngx_http_echo_adjust_subrequest(sr); 70 | if (rc != NGX_OK) { 71 | return NGX_ERROR; 72 | } 73 | 74 | return NGX_OK; 75 | } 76 | 77 | 78 | ngx_int_t 79 | ngx_http_echo_exec_echo_location(ngx_http_request_t *r, 80 | ngx_http_echo_ctx_t *ctx, ngx_array_t *computed_args) 81 | { 82 | ngx_int_t rc; 83 | ngx_http_request_t *sr; /* subrequest object */ 84 | ngx_str_t *computed_arg_elts; 85 | ngx_str_t location; 86 | ngx_str_t *url_args; 87 | ngx_http_post_subrequest_t *psr; 88 | ngx_str_t args; 89 | ngx_uint_t flags = 0; 90 | ngx_http_echo_ctx_t *sr_ctx; 91 | 92 | if (computed_args == NULL) { 93 | return NGX_ERROR; 94 | } 95 | 96 | computed_arg_elts = computed_args->elts; 97 | 98 | location = computed_arg_elts[0]; 99 | 100 | if (location.len == 0) { 101 | return NGX_ERROR; 102 | } 103 | 104 | if (computed_args->nelts > 1) { 105 | url_args = &computed_arg_elts[1]; 106 | 107 | } else { 108 | url_args = NULL; 109 | } 110 | 111 | args.data = NULL; 112 | args.len = 0; 113 | 114 | if (ngx_http_parse_unsafe_uri(r, &location, &args, &flags) != NGX_OK) { 115 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 116 | "echo_location sees unsafe uri: \"%V\"", 117 | &location); 118 | return NGX_ERROR; 119 | } 120 | 121 | if (args.len > 0 && url_args == NULL) { 122 | url_args = &args; 123 | } 124 | 125 | rc = ngx_http_echo_send_header_if_needed(r, ctx); 126 | if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { 127 | return rc; 128 | } 129 | 130 | sr_ctx = ngx_http_echo_create_ctx(r); 131 | 132 | psr = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t)); 133 | if (psr == NULL) { 134 | return NGX_ERROR; 135 | } 136 | 137 | psr->handler = ngx_http_echo_post_subrequest; 138 | psr->data = sr_ctx; 139 | 140 | rc = ngx_http_subrequest(r, &location, url_args, &sr, psr, 0); 141 | 142 | if (rc != NGX_OK) { 143 | return NGX_ERROR; 144 | } 145 | 146 | rc = ngx_http_echo_adjust_subrequest(sr); 147 | 148 | if (rc != NGX_OK) { 149 | return NGX_ERROR; 150 | } 151 | 152 | return NGX_AGAIN; 153 | } 154 | 155 | 156 | static ngx_int_t 157 | ngx_http_echo_adjust_subrequest(ngx_http_request_t *sr) 158 | { 159 | ngx_http_core_main_conf_t *cmcf; 160 | ngx_http_request_t *r; 161 | 162 | /* we do not inherit the parent request's variables */ 163 | cmcf = ngx_http_get_module_main_conf(sr, ngx_http_core_module); 164 | 165 | r = sr->parent; 166 | 167 | sr->header_in = r->header_in; 168 | 169 | /* XXX work-around a bug in ngx_http_subrequest */ 170 | if (r->headers_in.headers.last == &r->headers_in.headers.part) { 171 | sr->headers_in.headers.last = &sr->headers_in.headers.part; 172 | } 173 | 174 | sr->variables = ngx_pcalloc(sr->pool, cmcf->variables.nelts 175 | * sizeof(ngx_http_variable_value_t)); 176 | 177 | if (sr->variables == NULL) { 178 | return NGX_ERROR; 179 | } 180 | 181 | return NGX_OK; 182 | } 183 | -------------------------------------------------------------------------------- /t/echo-after-body.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (2 * blocks() + 1); 9 | 10 | no_long_string(); 11 | log_level('warn'); 12 | 13 | #master_on(); 14 | #workers(1); 15 | 16 | run_tests(); 17 | 18 | __DATA__ 19 | 20 | === TEST 1: sanity 21 | --- http_config 22 | postpone_output 1; 23 | --- config 24 | location /echo { 25 | echo_after_body hello; 26 | echo world; 27 | } 28 | --- request 29 | GET /echo 30 | --- response_body 31 | world 32 | hello 33 | 34 | 35 | 36 | === TEST 2: echo after proxy 37 | --- config 38 | location /echo { 39 | echo_after_body hello; 40 | proxy_pass http://127.0.0.1:$server_port$request_uri/more; 41 | } 42 | location /echo/more { 43 | echo world; 44 | } 45 | --- request 46 | GET /echo 47 | --- response_body 48 | world 49 | hello 50 | 51 | 52 | 53 | === TEST 3: with variables 54 | --- config 55 | location /echo { 56 | echo_after_body $request_method; 57 | echo world; 58 | } 59 | --- request 60 | GET /echo 61 | --- response_body 62 | world 63 | GET 64 | 65 | 66 | 67 | === TEST 4: w/o args 68 | --- config 69 | location /echo { 70 | echo_after_body; 71 | echo world; 72 | } 73 | --- request 74 | GET /echo 75 | --- response_body eval 76 | "world\n\n" 77 | 78 | 79 | 80 | === TEST 5: order is not important 81 | --- config 82 | location /reversed { 83 | echo world; 84 | echo_after_body hello; 85 | } 86 | --- request 87 | GET /reversed 88 | --- response_body 89 | world 90 | hello 91 | 92 | 93 | 94 | === TEST 6: multiple echo_after_body instances 95 | --- config 96 | location /echo { 97 | echo_after_body hello; 98 | echo_after_body world; 99 | echo !; 100 | } 101 | --- request 102 | GET /echo 103 | --- response_body 104 | ! 105 | hello 106 | world 107 | 108 | 109 | 110 | === TEST 7: multiple echo_after_body instances with multiple echo cmds 111 | --- config 112 | location /echo { 113 | echo_after_body hello; 114 | echo_after_body world; 115 | echo i; 116 | echo say; 117 | } 118 | --- request 119 | GET /echo 120 | --- response_body 121 | i 122 | say 123 | hello 124 | world 125 | 126 | 127 | 128 | === TEST 8: echo-after-body & echo-before-body 129 | --- config 130 | location /mixed { 131 | echo_before_body hello; 132 | echo_after_body world; 133 | echo_before_body hiya; 134 | echo_after_body igor; 135 | echo ////////; 136 | } 137 | --- request 138 | GET /mixed 139 | --- response_body 140 | hello 141 | hiya 142 | //////// 143 | world 144 | igor 145 | 146 | 147 | 148 | === TEST 9: echo around proxy 149 | --- config 150 | location /echo { 151 | echo_before_body hello; 152 | echo_before_body world; 153 | #echo $scheme://$host:$server_port$request_uri/more; 154 | proxy_pass $scheme://127.0.0.1:$server_port$request_uri/more; 155 | echo_after_body hiya; 156 | echo_after_body igor; 157 | } 158 | location /echo/more { 159 | echo blah; 160 | } 161 | --- request 162 | GET /echo 163 | --- response_body 164 | hello 165 | world 166 | blah 167 | hiya 168 | igor 169 | 170 | 171 | 172 | === TEST 10: with $echo_response_status 173 | --- config 174 | location /status { 175 | echo_after_body "status: $echo_response_status"; 176 | return 404; 177 | } 178 | --- request 179 | GET /status 180 | --- response_body_like 181 | .*404 Not Found.* 182 | status: 404$ 183 | --- error_code: 404 184 | 185 | 186 | 187 | === TEST 11: in subrequests 188 | --- config 189 | location /main { 190 | echo_location_async /hello; 191 | } 192 | location /hello { 193 | echo_after_body 'world!'; 194 | echo 'hello'; 195 | } 196 | --- request 197 | GET /main 198 | --- response_body 199 | hello 200 | world! 201 | 202 | 203 | 204 | === TEST 12: echo_after_body + gzip 205 | --- config 206 | gzip on; 207 | gzip_min_length 1; 208 | location /main { 209 | echo_after_body 'world!'; 210 | echo_duplicate 1024 'hello'; 211 | } 212 | --- request 213 | GET /main 214 | --- response_body_like 215 | hello 216 | --- SKIP 217 | 218 | 219 | 220 | === TEST 13: echo_after_body + proxy output 221 | --- config 222 | #gzip on; 223 | #gzip_min_length 1; 224 | location /main { 225 | echo_after_body 'world'; 226 | proxy_pass http://127.0.0.1:$server_port/foo; 227 | } 228 | location /foo { 229 | echo_duplicate 10 hello; 230 | } 231 | --- request 232 | GET /main 233 | --- response_body_like 234 | ^(?:hello){10}world$ 235 | 236 | 237 | 238 | === TEST 14: in subrequests (we get last_in_chain set properly) 239 | --- config 240 | location /main { 241 | echo_location_async /hello; 242 | } 243 | location /hello { 244 | echo 'hello'; 245 | echo_after_body 'world!'; 246 | body_filter_by_lua ' 247 | local eof = ngx.arg[2] 248 | if eof then 249 | print("lua: eof found in body") 250 | end 251 | '; 252 | } 253 | --- request 254 | GET /main 255 | --- response_body 256 | hello 257 | world! 258 | --- log_level: notice 259 | --- error_log 260 | lua: eof found in body 261 | 262 | -------------------------------------------------------------------------------- /src/ngx_http_echo_sleep.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Yichun Zhang (agentzh) 4 | */ 5 | 6 | 7 | #ifndef DDEBUG 8 | #define DDEBUG 0 9 | #endif 10 | #include "ddebug.h" 11 | 12 | 13 | #include "ngx_http_echo_sleep.h" 14 | #include "ngx_http_echo_handler.h" 15 | 16 | #include 17 | #include 18 | 19 | 20 | /* event handler for echo_sleep */ 21 | 22 | static void ngx_http_echo_post_sleep(ngx_http_request_t *r); 23 | static void ngx_http_echo_sleep_cleanup(void *data); 24 | 25 | 26 | ngx_int_t 27 | ngx_http_echo_exec_echo_sleep(ngx_http_request_t *r, 28 | ngx_http_echo_ctx_t *ctx, ngx_array_t *computed_args) 29 | { 30 | ngx_str_t *computed_arg; 31 | ngx_str_t *computed_arg_elts; 32 | ngx_int_t delay; /* in msec */ 33 | ngx_http_cleanup_t *cln; 34 | 35 | computed_arg_elts = computed_args->elts; 36 | computed_arg = &computed_arg_elts[0]; 37 | 38 | delay = ngx_atofp(computed_arg->data, computed_arg->len, 3); 39 | 40 | if (delay == NGX_ERROR) { 41 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 42 | "invalid sleep duration \"%V\"", &computed_arg_elts[0]); 43 | 44 | return NGX_HTTP_BAD_REQUEST; 45 | } 46 | 47 | dd("adding timer with delay %lu ms, r:%.*s", (unsigned long) delay, 48 | (int) r->uri.len, r->uri.data); 49 | 50 | ngx_add_timer(&ctx->sleep, (ngx_msec_t) delay); 51 | 52 | /* we don't check broken downstream connections 53 | * ourselves so even if the client shuts down 54 | * the connection prematurely, nginx will still 55 | * go on waiting for our timers to get properly 56 | * expired. However, we'd still register a 57 | * cleanup handler for completeness. */ 58 | 59 | cln = ngx_http_cleanup_add(r, 0); 60 | if (cln == NULL) { 61 | return NGX_ERROR; 62 | } 63 | 64 | cln->handler = ngx_http_echo_sleep_cleanup; 65 | cln->data = r; 66 | 67 | return NGX_AGAIN; 68 | } 69 | 70 | 71 | static void 72 | ngx_http_echo_post_sleep(ngx_http_request_t *r) 73 | { 74 | ngx_http_echo_ctx_t *ctx; 75 | /* ngx_int_t rc; */ 76 | 77 | dd("post sleep, r:%.*s", (int) r->uri.len, r->uri.data); 78 | 79 | ctx = ngx_http_get_module_ctx(r, ngx_http_echo_module); 80 | 81 | if (ctx == NULL) { 82 | return; 83 | } 84 | 85 | ctx->waiting = 0; 86 | ctx->done = 1; 87 | 88 | dd("sleep: after get module ctx"); 89 | 90 | dd("timed out? %d", ctx->sleep.timedout); 91 | dd("timer set? %d", ctx->sleep.timer_set); 92 | 93 | if (!ctx->sleep.timedout) { 94 | dd("HERE reached!"); 95 | return; 96 | } 97 | 98 | ctx->sleep.timedout = 0; 99 | 100 | if (ctx->sleep.timer_set) { 101 | dd("deleting timer for echo_sleep"); 102 | 103 | ngx_del_timer(&ctx->sleep); 104 | } 105 | 106 | /* r->write_event_handler = ngx_http_request_empty_handler; */ 107 | 108 | ngx_http_echo_wev_handler(r); 109 | } 110 | 111 | 112 | void 113 | ngx_http_echo_sleep_event_handler(ngx_event_t *ev) 114 | { 115 | ngx_connection_t *c; 116 | ngx_http_request_t *r; 117 | ngx_http_log_ctx_t *ctx; 118 | 119 | r = ev->data; 120 | c = r->connection; 121 | 122 | if (c->destroyed) { 123 | return; 124 | } 125 | 126 | if (c->error) { 127 | ngx_http_finalize_request(r, NGX_ERROR); 128 | return; 129 | } 130 | 131 | ctx = c->log->data; 132 | ctx->current_request = r; 133 | 134 | /* XXX when r->done == 1 we should do cleaning immediately 135 | * and delete our timer and then quit. */ 136 | 137 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, 138 | "echo sleep event handler: \"%V?%V\"", &r->uri, &r->args); 139 | 140 | /* 141 | if (r->done) { 142 | return; 143 | } 144 | */ 145 | 146 | ngx_http_echo_post_sleep(r); 147 | 148 | #if defined(nginx_version) 149 | 150 | dd("before run posted requests"); 151 | 152 | ngx_http_run_posted_requests(c); 153 | 154 | dd("after run posted requests"); 155 | 156 | #endif 157 | } 158 | 159 | 160 | ngx_int_t 161 | ngx_http_echo_exec_echo_blocking_sleep(ngx_http_request_t *r, 162 | ngx_http_echo_ctx_t *ctx, ngx_array_t *computed_args) 163 | { 164 | ngx_str_t *computed_arg; 165 | ngx_str_t *computed_arg_elts; 166 | ngx_int_t delay; /* in msec */ 167 | 168 | computed_arg_elts = computed_args->elts; 169 | computed_arg = &computed_arg_elts[0]; 170 | 171 | delay = ngx_atofp(computed_arg->data, computed_arg->len, 3); 172 | 173 | if (delay == NGX_ERROR) { 174 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 175 | "invalid sleep duration \"%V\"", &computed_arg_elts[0]); 176 | return NGX_HTTP_BAD_REQUEST; 177 | } 178 | 179 | dd("blocking delay: %lu ms", (unsigned long) delay); 180 | 181 | ngx_msleep((ngx_msec_t) delay); 182 | 183 | return NGX_OK; 184 | } 185 | 186 | 187 | static void 188 | ngx_http_echo_sleep_cleanup(void *data) 189 | { 190 | ngx_http_request_t *r = data; 191 | ngx_http_echo_ctx_t *ctx; 192 | 193 | dd("echo sleep cleanup"); 194 | 195 | ctx = ngx_http_get_module_ctx(r, ngx_http_echo_module); 196 | if (ctx == NULL) { 197 | return; 198 | } 199 | 200 | if (ctx->sleep.timer_set) { 201 | dd("cleanup: deleting timer for echo_sleep"); 202 | 203 | ngx_del_timer(&ctx->sleep); 204 | return; 205 | } 206 | 207 | dd("cleanup: timer not set"); 208 | } 209 | -------------------------------------------------------------------------------- /t/echo-before-body.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | plan tests => 2 * blocks(); 7 | 8 | #$Test::Nginx::LWP::LogLevel = 'debug'; 9 | 10 | run_tests(); 11 | 12 | __DATA__ 13 | 14 | === TEST 1: sanity 15 | --- config 16 | location /echo { 17 | echo_before_body hello; 18 | echo world; 19 | } 20 | --- request 21 | GET /echo 22 | --- response_body 23 | hello 24 | world 25 | 26 | 27 | 28 | === TEST 2: echo before proxy 29 | --- config 30 | location /echo { 31 | echo_before_body hello; 32 | proxy_pass $scheme://127.0.0.1:$server_port$request_uri/more; 33 | } 34 | location /echo/more { 35 | echo world; 36 | } 37 | --- request 38 | GET /echo 39 | --- response_body 40 | hello 41 | world 42 | 43 | 44 | 45 | === TEST 3: with variables 46 | --- config 47 | location /echo { 48 | echo_before_body $request_method; 49 | echo world; 50 | } 51 | --- request 52 | GET /echo 53 | --- response_body 54 | GET 55 | world 56 | 57 | 58 | 59 | === TEST 4: w/o args 60 | --- config 61 | location /echo { 62 | echo_before_body; 63 | echo world; 64 | } 65 | --- request 66 | GET /echo 67 | --- response_body eval 68 | "\nworld\n" 69 | 70 | 71 | 72 | === TEST 5: order is not important 73 | --- config 74 | location /reversed { 75 | echo world; 76 | echo_before_body hello; 77 | } 78 | --- request 79 | GET /reversed 80 | --- response_body 81 | hello 82 | world 83 | 84 | 85 | 86 | === TEST 6: multiple echo_before_body instances 87 | --- config 88 | location /echo { 89 | echo_before_body hello; 90 | echo_before_body world; 91 | echo !; 92 | } 93 | --- request 94 | GET /echo 95 | --- response_body 96 | hello 97 | world 98 | ! 99 | 100 | 101 | 102 | === TEST 7: multiple echo_before_body instances with multiple echo cmds 103 | --- config 104 | location /echo { 105 | echo_before_body hello; 106 | echo_before_body world; 107 | echo i; 108 | echo say; 109 | } 110 | --- request 111 | GET /echo 112 | --- response_body 113 | hello 114 | world 115 | i 116 | say 117 | 118 | 119 | 120 | === TEST 8: with $echo_response_status 121 | --- config 122 | location /status { 123 | echo_before_body "status: $echo_response_status"; 124 | return 404; 125 | } 126 | --- request 127 | GET /status 128 | --- response_body_like 129 | status: 404 130 | .*404 Not Found.*$ 131 | --- error_code: 404 132 | 133 | 134 | 135 | === TEST 9: $echo_response_status in echo_before_body in subrequests 136 | --- config 137 | location /main { 138 | echo_location '/status?val=403'; 139 | echo_location '/status?val=500'; 140 | } 141 | location /status { 142 | if ($arg_val = 500) { 143 | echo_before_body "status: $echo_response_status"; 144 | return 500; 145 | break; 146 | } 147 | if ($arg_val = 403) { 148 | echo_before_body "status: $echo_response_status"; 149 | return 403; 150 | break; 151 | } 152 | return 200; 153 | } 154 | --- request 155 | GET /main 156 | --- response_body_like 157 | ^status: 403.*?status: 500.*$ 158 | 159 | 160 | 161 | === TEST 10: echo -n 162 | --- config 163 | location /echo { 164 | echo_before_body -n hello; 165 | echo_before_body -n world; 166 | echo ==; 167 | } 168 | --- request 169 | GET /echo 170 | --- response_body 171 | helloworld== 172 | 173 | 174 | 175 | === TEST 11: echo a -n 176 | --- config 177 | location /echo { 178 | echo_before_body a -n hello; 179 | echo_before_body b -n world; 180 | echo ==; 181 | } 182 | --- request 183 | GET /echo 184 | --- response_body 185 | a -n hello 186 | b -n world 187 | == 188 | 189 | 190 | 191 | === TEST 12: -n in a var 192 | --- config 193 | location /echo { 194 | set $opt -n; 195 | echo_before_body $opt hello; 196 | echo_before_body $opt world; 197 | echo ==; 198 | } 199 | --- request 200 | GET /echo 201 | --- response_body 202 | -n hello 203 | -n world 204 | == 205 | 206 | 207 | 208 | === TEST 13: -n only 209 | --- config 210 | location /echo { 211 | echo_before_body -n; 212 | echo_before_body -n; 213 | echo ==; 214 | } 215 | --- request 216 | GET /echo 217 | --- response_body 218 | == 219 | 220 | 221 | 222 | === TEST 14: -n with an empty string 223 | --- config 224 | location /echo { 225 | echo_before_body -n ""; 226 | set $empty ""; 227 | echo_before_body -n $empty; 228 | echo ==; 229 | } 230 | --- request 231 | GET /echo 232 | --- response_body 233 | == 234 | 235 | 236 | 237 | === TEST 15: -- -n 238 | --- config 239 | location /echo { 240 | echo_before_body -- -n hello; 241 | echo_before_body -- -n world; 242 | echo ==; 243 | } 244 | --- request 245 | GET /echo 246 | --- response_body 247 | -n hello 248 | -n world 249 | == 250 | 251 | 252 | 253 | === TEST 16: -n -n 254 | --- config 255 | location /echo { 256 | echo_before_body -n -n hello; 257 | echo_before_body -n -n world; 258 | echo ==; 259 | } 260 | --- request 261 | GET /echo 262 | --- response_body 263 | helloworld== 264 | 265 | 266 | 267 | === TEST 17: -n -- -n 268 | --- config 269 | location /echo { 270 | echo_before_body -n -- -n hello; 271 | echo_before_body -n -- -n world; 272 | echo ==; 273 | } 274 | --- request 275 | GET /echo 276 | --- response_body 277 | -n hello-n world== 278 | 279 | -------------------------------------------------------------------------------- /src/ngx_http_echo_foreach.c: -------------------------------------------------------------------------------- 1 | #ifndef DDEBUG 2 | #define DDEBUG 0 3 | #endif 4 | #include "ddebug.h" 5 | 6 | #include "ngx_http_echo_foreach.h" 7 | #include "ngx_http_echo_util.h" 8 | 9 | #include 10 | 11 | 12 | ngx_int_t 13 | ngx_http_echo_it_variable(ngx_http_request_t *r, 14 | ngx_http_variable_value_t *v, uintptr_t data) 15 | { 16 | ngx_http_echo_ctx_t *ctx; 17 | ngx_uint_t i; 18 | ngx_array_t *choices; 19 | ngx_str_t *choice_elts, *choice; 20 | 21 | ctx = ngx_http_get_module_ctx(r, ngx_http_echo_module); 22 | 23 | if (ctx && ctx->foreach != NULL) { 24 | 25 | choices = ctx->foreach->choices; 26 | i = ctx->foreach->next_choice; 27 | 28 | if (i < choices->nelts) { 29 | choice_elts = choices->elts; 30 | choice = &choice_elts[i]; 31 | 32 | v->len = choice->len; 33 | v->data = choice->data; 34 | v->valid = 1; 35 | v->no_cacheable = 1; 36 | v->not_found = 0; 37 | 38 | return NGX_OK; 39 | } 40 | } 41 | 42 | v->not_found = 1; 43 | 44 | return NGX_OK; 45 | } 46 | 47 | 48 | ngx_int_t 49 | ngx_http_echo_exec_echo_foreach_split(ngx_http_request_t *r, 50 | ngx_http_echo_ctx_t *ctx, ngx_array_t *computed_args) 51 | { 52 | ngx_http_echo_loc_conf_t *elcf; 53 | ngx_str_t *delimiter, *compound; 54 | u_char *pos, *last, *end; 55 | ngx_str_t *choice; 56 | ngx_str_t *computed_arg_elts; 57 | ngx_array_t *cmds; 58 | ngx_http_echo_cmd_t *cmd; 59 | ngx_http_echo_cmd_t *cmd_elts; 60 | 61 | if (ctx->foreach != NULL) { 62 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 63 | "Nested echo_foreach not supported yet."); 64 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 65 | } 66 | 67 | if (computed_args->nelts < 2) { 68 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 69 | "echo_foreach should take at least two arguments. " 70 | "(if your delimiter starts with \"-\", preceding it " 71 | "with a \"--\".)"); 72 | 73 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 74 | } 75 | 76 | computed_arg_elts = computed_args->elts; 77 | 78 | compound = &computed_arg_elts[1]; 79 | 80 | dd("HEY coumpound len: %u", (int) compound->len); 81 | 82 | ctx->foreach = ngx_palloc(r->pool, sizeof(ngx_http_echo_foreach_ctx_t)); 83 | 84 | if (ctx->foreach == NULL) { 85 | return NGX_ERROR; 86 | } 87 | 88 | ctx->foreach->cmd_index = ctx->next_handler_cmd; 89 | 90 | ctx->foreach->next_choice = 0; 91 | 92 | ctx->foreach->choices = ngx_array_create(r->pool, 10, sizeof(ngx_str_t)); 93 | if (ctx->foreach->choices == NULL) { 94 | return NGX_ERROR; 95 | } 96 | 97 | delimiter = &computed_arg_elts[0]; 98 | 99 | pos = compound->data; 100 | end = compound->data + compound->len; 101 | 102 | while ((last = ngx_http_echo_strlstrn(pos, end, delimiter->data, 103 | delimiter->len - 1)) != NULL) 104 | { 105 | dd("entered the loop"); 106 | 107 | if (last == pos) { 108 | dd("!!! len == 0"); 109 | pos = last + delimiter->len; 110 | continue; 111 | } 112 | 113 | choice = ngx_array_push(ctx->foreach->choices); 114 | if (choice == NULL) { 115 | return NGX_ERROR; 116 | } 117 | 118 | choice->data = pos; 119 | choice->len = last - pos; 120 | pos = last + delimiter->len; 121 | } 122 | 123 | if (pos < end) { 124 | choice = ngx_array_push(ctx->foreach->choices); 125 | if (choice == NULL) { 126 | return NGX_ERROR; 127 | } 128 | 129 | choice->data = pos; 130 | choice->len = end - pos; 131 | } 132 | 133 | if (ctx->foreach->choices->nelts == 0) { 134 | /* skip the foreach body entirely */ 135 | elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module); 136 | cmds = elcf->handler_cmds; 137 | cmd_elts = cmds->elts; 138 | for (/* void */; ctx->next_handler_cmd < cmds->nelts; 139 | ctx->next_handler_cmd++) 140 | { 141 | cmd = &cmd_elts[ctx->next_handler_cmd + 1]; 142 | if (cmd->opcode == echo_opcode_echo_end) { 143 | return NGX_OK; 144 | } 145 | } 146 | 147 | } 148 | 149 | return NGX_OK; 150 | } 151 | 152 | 153 | ngx_int_t 154 | ngx_http_echo_exec_echo_end(ngx_http_request_t *r, 155 | ngx_http_echo_ctx_t *ctx) 156 | { 157 | if (ctx->foreach == NULL) { 158 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 159 | "Found a echo_end that has no corresponding echo_foreach " 160 | "before it."); 161 | return NGX_ERROR; 162 | } 163 | 164 | ctx->foreach->next_choice++; 165 | 166 | if (ctx->foreach->next_choice >= ctx->foreach->choices->nelts) { 167 | /* TODO We need to explicitly free the foreach ctx from 168 | * the pool */ 169 | ctx->foreach = NULL; 170 | 171 | return NGX_OK; 172 | } 173 | 174 | dd("echo_end: ++ next_choice (total: %u): %u", 175 | (unsigned) ctx->foreach->choices->nelts, 176 | (unsigned) ctx->foreach->next_choice); 177 | 178 | /* the main handler dispatcher loop will increment 179 | * ctx->next_handler_cmd for us anyway. */ 180 | ctx->next_handler_cmd = ctx->foreach->cmd_index; 181 | 182 | return NGX_OK; 183 | } 184 | -------------------------------------------------------------------------------- /t/foreach-split.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * 2 * blocks(); 9 | 10 | #$Test::Nginx::LWP::LogLevel = 'debug'; 11 | 12 | run_tests(); 13 | 14 | __DATA__ 15 | 16 | === TEST 1: sanity 17 | --- config 18 | location /main { 19 | echo_foreach_split '&' $query_string; 20 | echo_location_async $echo_it; 21 | echo '/* end */'; 22 | echo_end; 23 | } 24 | location /sub/1.css { 25 | echo "body { font-size: 12pt; }"; 26 | } 27 | location /sub/2.css { 28 | echo "table { color: 'red'; }"; 29 | } 30 | --- request 31 | GET /main?/sub/1.css&/sub/2.css 32 | --- response_body 33 | body { font-size: 12pt; } 34 | /* end */ 35 | table { color: 'red'; } 36 | /* end */ 37 | 38 | 39 | 40 | === TEST 2: split in a url argument (echo_location_async) 41 | --- config 42 | location /main_async { 43 | echo_foreach_split ',' $arg_cssfiles; 44 | echo_location_async $echo_it; 45 | echo_end; 46 | } 47 | location /foo.css { 48 | echo foo; 49 | } 50 | location /bar.css { 51 | echo bar; 52 | } 53 | location /baz.css { 54 | echo baz; 55 | } 56 | --- request 57 | GET /main_async?cssfiles=/foo.css,/bar.css,/baz.css 58 | --- response_body 59 | foo 60 | bar 61 | baz 62 | 63 | 64 | 65 | === TEST 3: split in a url argument (echo_location) 66 | --- config 67 | location /main_sync { 68 | echo_foreach_split ',' $arg_cssfiles; 69 | echo_location $echo_it; 70 | echo_end; 71 | } 72 | location /foo.css { 73 | echo foo; 74 | } 75 | location /bar.css { 76 | echo bar; 77 | } 78 | location /baz.css { 79 | echo baz; 80 | } 81 | --- request 82 | GET /main_sync?cssfiles=/foo.css,/bar.css,/baz.css 83 | --- response_body 84 | foo 85 | bar 86 | baz 87 | --- SKIP 88 | 89 | 90 | 91 | === TEST 4: empty loop 92 | --- config 93 | location /main { 94 | echo "start"; 95 | echo_foreach_split ',' $arg_cssfiles; 96 | echo_end; 97 | echo "end"; 98 | } 99 | --- request 100 | GET /main?cssfiles=/foo.css,/bar.css,/baz.css 101 | --- response_body 102 | start 103 | end 104 | 105 | 106 | 107 | === TEST 5: trailing delimiter 108 | --- config 109 | location /main_t { 110 | echo_foreach_split ',' $arg_cssfiles; 111 | echo_location_async $echo_it; 112 | echo_end; 113 | } 114 | location /foo.css { 115 | echo foo; 116 | } 117 | --- request 118 | GET /main_t?cssfiles=/foo.css, 119 | --- response_body 120 | foo 121 | 122 | 123 | 124 | === TEST 6: multi-char delimiter 125 | --- config 126 | location /main_sleep { 127 | echo_foreach_split '-a-' $arg_list; 128 | echo $echo_it; 129 | echo_end; 130 | } 131 | --- request 132 | GET /main_sleep?list=foo-a-bar-a-baz 133 | --- error_code: 500 134 | --- response_body_like: 500 Internal Server Error 135 | 136 | 137 | 138 | === TEST 7: multi-char delimiter (the right way) 139 | --- config 140 | location /main_sleep { 141 | echo_foreach_split -- '-a-' $arg_list; 142 | echo $echo_it; 143 | echo_end; 144 | } 145 | --- request 146 | GET /main_sleep?list=foo-a-bar-a-baz 147 | --- response_body 148 | foo 149 | bar 150 | baz 151 | 152 | 153 | 154 | === TEST 8: loop with sleep 155 | --- config 156 | location /main_sleep { 157 | echo_foreach_split '-' $arg_list; 158 | echo_sleep 0.001; 159 | echo $echo_it; 160 | echo_end; 161 | } 162 | --- request 163 | GET /main_sleep?list=foo-a-bar-A-baz 164 | --- response_body 165 | foo 166 | a 167 | bar 168 | A 169 | baz 170 | 171 | 172 | 173 | === TEST 9: empty 174 | --- config 175 | location /merge { 176 | default_type 'text/javascript'; 177 | echo_foreach_split '&' $query_string; 178 | echo "/* JS File $echo_it */"; 179 | echo_location_async $echo_it; 180 | echo; 181 | echo_end; 182 | } 183 | --- request 184 | GET /merge 185 | --- response_body 186 | 187 | 188 | 189 | === TEST 10: single & 190 | --- config 191 | location /merge { 192 | default_type 'text/javascript'; 193 | echo_foreach_split '&' $query_string; 194 | echo "/* JS File $echo_it */"; 195 | echo_location_async $echo_it; 196 | echo; 197 | echo_end; 198 | } 199 | --- request 200 | GET /merge?& 201 | --- response_body 202 | 203 | 204 | 205 | === TEST 11: pure &'s 206 | --- config 207 | location /merge { 208 | default_type 'text/javascript'; 209 | echo_foreach_split '&' $query_string; 210 | echo "/* JS File $echo_it */"; 211 | echo_location_async $echo_it; 212 | echo; 213 | echo_end; 214 | } 215 | --- request 216 | GET /merge?&&& 217 | --- response_body 218 | 219 | 220 | 221 | === TEST 12: pure & and spaces 222 | TODO: needs to uri_decode $echo_it... 223 | --- config 224 | location /merge { 225 | default_type 'text/javascript'; 226 | echo_foreach_split '&' $query_string; 227 | echo "/* JS File $echo_it */"; 228 | echo_location_async $echo_it; 229 | echo; 230 | echo_end; 231 | } 232 | --- request 233 | GET /merge?&%20&%20& 234 | --- response_body 235 | --- SKIP 236 | 237 | 238 | 239 | === TEST 13: multiple foreach_split 240 | --- config 241 | location /multi { 242 | echo_foreach_split '&' $query_string; 243 | echo [$echo_it]; 244 | echo_end; 245 | 246 | echo '...'; 247 | 248 | echo_foreach_split '-' $query_string; 249 | echo [$echo_it]; 250 | echo_end; 251 | } 252 | --- request 253 | GET /multi?a-b&c-d 254 | --- response_body 255 | [a-b] 256 | [c-d] 257 | ... 258 | [a] 259 | [b&c] 260 | [d] 261 | 262 | 263 | 264 | === TEST 14: github issue #2: setting a variable from $echo_it results to crashing 265 | --- config 266 | location = /getFile { 267 | set $filelist "a,b,c"; 268 | echo_foreach_split ',' $filelist; 269 | set $file $echo_it; 270 | echo_subrequest GET '/getFile2' -q 'sha256=$file'; 271 | echo_end; 272 | } 273 | 274 | location = /getFile2 { 275 | echo "sha256: $arg_sha256"; 276 | } 277 | --- request 278 | GET /getFile 279 | --- response_body 280 | sha256: 281 | sha256: 282 | sha256: 283 | 284 | -------------------------------------------------------------------------------- /src/ngx_http_echo_util.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Yichun Zhang (agentzh) 4 | */ 5 | 6 | 7 | #ifndef DDEBUG 8 | #define DDEBUG 0 9 | #endif 10 | #include "ddebug.h" 11 | 12 | 13 | #include "ngx_http_echo_util.h" 14 | #include "ngx_http_echo_sleep.h" 15 | 16 | 17 | ngx_uint_t ngx_http_echo_content_length_hash = 0; 18 | 19 | 20 | ngx_http_echo_ctx_t * 21 | ngx_http_echo_create_ctx(ngx_http_request_t *r) 22 | { 23 | ngx_http_echo_ctx_t *ctx; 24 | 25 | ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_echo_ctx_t)); 26 | if (ctx == NULL) { 27 | return NULL; 28 | } 29 | 30 | ctx->sleep.handler = ngx_http_echo_sleep_event_handler; 31 | ctx->sleep.data = r; 32 | ctx->sleep.log = r->connection->log; 33 | 34 | return ctx; 35 | } 36 | 37 | 38 | ngx_int_t 39 | ngx_http_echo_eval_cmd_args(ngx_http_request_t *r, 40 | ngx_http_echo_cmd_t *cmd, ngx_array_t *computed_args, 41 | ngx_array_t *opts) 42 | { 43 | unsigned expecting_opts = 1; 44 | ngx_uint_t i; 45 | ngx_array_t *args = cmd->args; 46 | ngx_str_t *arg, *raw, *opt; 47 | ngx_http_echo_arg_template_t *value; 48 | 49 | value = args->elts; 50 | 51 | for (i = 0; i < args->nelts; i++) { 52 | raw = &value[i].raw_value; 53 | 54 | if (value[i].lengths == NULL && raw->len > 0) { 55 | if (expecting_opts) { 56 | if (raw->len == 1 || raw->data[0] != '-') { 57 | expecting_opts = 0; 58 | 59 | } else if (raw->data[1] == '-') { 60 | expecting_opts = 0; 61 | continue; 62 | 63 | } else { 64 | opt = ngx_array_push(opts); 65 | if (opt == NULL) { 66 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 67 | } 68 | 69 | opt->len = raw->len - 1; 70 | opt->data = raw->data + 1; 71 | 72 | dd("pushing opt: %.*s", (int) opt->len, opt->data); 73 | 74 | continue; 75 | } 76 | } 77 | 78 | } else { 79 | expecting_opts = 0; 80 | } 81 | 82 | arg = ngx_array_push(computed_args); 83 | if (arg == NULL) { 84 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 85 | } 86 | 87 | if (value[i].lengths == NULL) { /* does not contain vars */ 88 | dd("Using raw value \"%.*s\"", (int) raw->len, raw->data); 89 | *arg = *raw; 90 | 91 | } else { 92 | if (ngx_http_script_run(r, arg, value[i].lengths->elts, 93 | 0, value[i].values->elts) 94 | == NULL) 95 | { 96 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 97 | } 98 | } 99 | 100 | dd("pushed arg: %.*s", (int) arg->len, arg->data); 101 | } 102 | 103 | return NGX_OK; 104 | } 105 | 106 | 107 | ngx_int_t 108 | ngx_http_echo_send_chain_link(ngx_http_request_t *r, 109 | ngx_http_echo_ctx_t *ctx, ngx_chain_t *in) 110 | { 111 | ngx_int_t rc; 112 | 113 | rc = ngx_http_echo_send_header_if_needed(r, ctx); 114 | 115 | if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { 116 | return rc; 117 | } 118 | 119 | if (in == NULL) { 120 | 121 | #if defined(nginx_version) && nginx_version <= 8004 122 | 123 | /* earlier versions of nginx does not allow subrequests 124 | to send last_buf themselves */ 125 | if (r != r->main) { 126 | return NGX_OK; 127 | } 128 | 129 | #endif 130 | 131 | rc = ngx_http_send_special(r, NGX_HTTP_LAST); 132 | if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { 133 | return rc; 134 | } 135 | 136 | return NGX_OK; 137 | } 138 | 139 | /* FIXME we should udpate chains to recycle chain links and bufs */ 140 | return ngx_http_output_filter(r, in); 141 | } 142 | 143 | 144 | ngx_int_t 145 | ngx_http_echo_send_header_if_needed(ngx_http_request_t *r, 146 | ngx_http_echo_ctx_t *ctx) 147 | { 148 | ngx_int_t rc; 149 | ngx_http_echo_loc_conf_t *elcf; 150 | 151 | if (!r->header_sent && !ctx->header_sent) { 152 | elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module); 153 | 154 | r->headers_out.status = (ngx_uint_t) elcf->status; 155 | 156 | if (ngx_http_set_content_type(r) != NGX_OK) { 157 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 158 | } 159 | 160 | ngx_http_clear_content_length(r); 161 | ngx_http_clear_accept_ranges(r); 162 | 163 | rc = ngx_http_send_header(r); 164 | ctx->header_sent = 1; 165 | return rc; 166 | } 167 | 168 | return NGX_OK; 169 | } 170 | 171 | 172 | ssize_t 173 | ngx_http_echo_atosz(u_char *line, size_t n) 174 | { 175 | ssize_t value; 176 | 177 | if (n == 0) { 178 | return NGX_ERROR; 179 | } 180 | 181 | for (value = 0; n--; line++) { 182 | if (*line == '_') { /* we ignore undercores */ 183 | continue; 184 | } 185 | 186 | if (*line < '0' || *line > '9') { 187 | return NGX_ERROR; 188 | } 189 | 190 | value = value * 10 + (*line - '0'); 191 | } 192 | 193 | if (value < 0) { 194 | return NGX_ERROR; 195 | } 196 | 197 | return value; 198 | } 199 | 200 | 201 | /* Modified from the ngx_strlcasestrn function in ngx_string.h 202 | * Copyright (C) by Igor Sysoev */ 203 | u_char * 204 | ngx_http_echo_strlstrn(u_char *s1, u_char *last, u_char *s2, size_t n) 205 | { 206 | ngx_uint_t c1, c2; 207 | 208 | c2 = (ngx_uint_t) *s2++; 209 | last -= n; 210 | 211 | do { 212 | do { 213 | if (s1 >= last) { 214 | return NULL; 215 | } 216 | 217 | c1 = (ngx_uint_t) *s1++; 218 | 219 | } while (c1 != c2); 220 | 221 | } while (ngx_strncmp(s1, s2, n) != 0); 222 | 223 | return --s1; 224 | } 225 | 226 | 227 | ngx_int_t 228 | ngx_http_echo_post_request_at_head(ngx_http_request_t *r, 229 | ngx_http_posted_request_t *pr) 230 | { 231 | dd_enter(); 232 | 233 | if (pr == NULL) { 234 | pr = ngx_palloc(r->pool, sizeof(ngx_http_posted_request_t)); 235 | if (pr == NULL) { 236 | return NGX_ERROR; 237 | } 238 | } 239 | 240 | pr->request = r; 241 | pr->next = r->main->posted_requests; 242 | r->main->posted_requests = pr; 243 | 244 | return NGX_OK; 245 | } 246 | 247 | 248 | u_char * 249 | ngx_http_echo_rebase_path(ngx_pool_t *pool, u_char *src, size_t osize, 250 | size_t *nsize) 251 | { 252 | u_char *p, *dst; 253 | 254 | if (osize == 0) { 255 | return NULL; 256 | } 257 | 258 | if (src[0] == '/') { 259 | /* being an absolute path already, just add a trailing '\0' */ 260 | *nsize = osize; 261 | 262 | dst = ngx_palloc(pool, *nsize + 1); 263 | if (dst == NULL) { 264 | *nsize = 0; 265 | return NULL; 266 | } 267 | 268 | p = ngx_copy(dst, src, osize); 269 | *p = '\0'; 270 | 271 | return dst; 272 | } 273 | 274 | *nsize = ngx_cycle->prefix.len + osize; 275 | 276 | dst = ngx_palloc(pool, *nsize + 1); 277 | if (dst == NULL) { 278 | *nsize = 0; 279 | return NULL; 280 | } 281 | 282 | p = ngx_copy(dst, ngx_cycle->prefix.data, ngx_cycle->prefix.len); 283 | p = ngx_copy(p, src, osize); 284 | 285 | *p = '\0'; 286 | 287 | return dst; 288 | } 289 | 290 | 291 | ngx_int_t 292 | ngx_http_echo_flush_postponed_outputs(ngx_http_request_t *r) 293 | { 294 | if (r == r->connection->data && r->postponed) { 295 | /* notify the downstream postpone filter to flush the postponed 296 | * outputs of the current request */ 297 | return ngx_http_output_filter(r, NULL); 298 | } 299 | 300 | /* do nothing */ 301 | return NGX_OK; 302 | } 303 | -------------------------------------------------------------------------------- /t/echo.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (2 * blocks() + 6); 9 | 10 | #$Test::Nginx::LWP::LogLevel = 'debug'; 11 | 12 | run_tests(); 13 | 14 | __DATA__ 15 | 16 | === TEST 1: sanity 17 | --- config 18 | location /echo { 19 | echo hello; 20 | } 21 | --- request 22 | GET /echo 23 | --- response_body 24 | hello 25 | 26 | 27 | 28 | === TEST 2: multiple args 29 | --- config 30 | location /echo { 31 | echo say hello world; 32 | } 33 | --- request 34 | GET /echo 35 | --- response_body 36 | say hello world 37 | 38 | 39 | 40 | === TEST 3: multiple directive instances 41 | --- config 42 | location /echo { 43 | echo say that; 44 | echo hello; 45 | echo world !; 46 | } 47 | --- request 48 | GET /echo 49 | --- response_body 50 | say that 51 | hello 52 | world ! 53 | 54 | 55 | 56 | === TEST 4: echo without arguments 57 | --- config 58 | location /echo { 59 | echo; 60 | echo; 61 | } 62 | --- request 63 | GET /echo 64 | --- response_body eval 65 | "\n\n" 66 | 67 | 68 | 69 | === TEST 5: escaped newline 70 | --- config 71 | location /echo { 72 | echo "hello\nworld"; 73 | } 74 | --- request 75 | GET /echo 76 | --- response_body 77 | hello 78 | world 79 | 80 | 81 | 82 | === TEST 6: escaped tabs and \r and " wihtin "..." 83 | --- config 84 | location /echo { 85 | echo "i say \"hello\tworld\"\r"; 86 | } 87 | --- request 88 | GET /echo 89 | --- response_body eval: "i say \"hello\tworld\"\r\n" 90 | 91 | 92 | 93 | === TEST 7: escaped tabs and \r and " in single quotes 94 | --- config 95 | location /echo { 96 | echo 'i say \"hello\tworld\"\r'; 97 | } 98 | --- request 99 | GET /echo 100 | --- response_body eval: "i say \"hello\tworld\"\r\n" 101 | 102 | 103 | 104 | === TEST 8: escaped tabs and \r and " w/o any quotes 105 | --- config 106 | location /echo { 107 | echo i say \"hello\tworld\"\r; 108 | } 109 | --- request 110 | GET /echo 111 | --- response_body eval: "i say \"hello\tworld\"\r\n" 112 | 113 | 114 | 115 | === TEST 9: escaping $ 116 | As of Nginx 0.8.20, there's still no way to escape the '$' character. 117 | --- config 118 | location /echo { 119 | echo \$; 120 | } 121 | --- request 122 | GET /echo 123 | --- response_body 124 | $ 125 | --- SKIP 126 | 127 | 128 | 129 | === TEST 10: XSS 130 | --- config 131 | location /blah { 132 | echo_duplicate 1 "$arg_callback("; 133 | echo_location_async "/data?$uri"; 134 | echo_duplicate 1 ")"; 135 | } 136 | location /data { 137 | echo_duplicate 1 '{"dog":"$query_string"}'; 138 | } 139 | --- request 140 | GET /blah/9999999.json?callback=ding1111111 141 | --- response_body chomp 142 | ding1111111({"dog":"/blah/9999999.json"}) 143 | 144 | 145 | 146 | === TEST 11: XSS - filter version 147 | --- config 148 | location /blah { 149 | echo_before_body "$arg_callback("; 150 | 151 | echo_duplicate 1 '{"dog":"$uri"}'; 152 | 153 | echo_after_body ")"; 154 | } 155 | --- request 156 | GET /blah/9999999.json?callback=ding1111111 157 | --- response_body 158 | ding1111111( 159 | {"dog":"/blah/9999999.json"}) 160 | 161 | 162 | 163 | === TEST 12: if 164 | --- config 165 | location /first { 166 | echo "before"; 167 | echo_location_async /second $request_uri; 168 | echo "after"; 169 | } 170 | 171 | location = /second { 172 | if ($query_string ~ '([^?]+)') { 173 | set $memcached_key $1; # needing this to be keyed on the request_path, not the entire uri 174 | echo $memcached_key; 175 | } 176 | } 177 | --- request 178 | GET /first/9999999.json?callback=ding1111111 179 | --- response_body 180 | before 181 | /first/9999999.json 182 | after 183 | 184 | 185 | 186 | === TEST 13: echo -n 187 | --- config 188 | location /echo { 189 | echo -n hello; 190 | echo -n world; 191 | } 192 | --- request 193 | GET /echo 194 | --- response_body chop 195 | helloworld 196 | 197 | 198 | 199 | === TEST 14: echo a -n 200 | --- config 201 | location /echo { 202 | echo a -n hello; 203 | echo b -n world; 204 | } 205 | --- request 206 | GET /echo 207 | --- response_body 208 | a -n hello 209 | b -n world 210 | 211 | 212 | 213 | === TEST 15: -n in a var 214 | --- config 215 | location /echo { 216 | set $opt -n; 217 | echo $opt hello; 218 | echo $opt world; 219 | } 220 | --- request 221 | GET /echo 222 | --- response_body 223 | -n hello 224 | -n world 225 | 226 | 227 | 228 | === TEST 16: -n only 229 | --- config 230 | location /echo { 231 | echo -n; 232 | echo -n; 233 | } 234 | --- request 235 | GET /echo 236 | --- response_body chop 237 | 238 | 239 | 240 | === TEST 17: -n with an empty string 241 | --- config 242 | location /echo { 243 | echo -n ""; 244 | set $empty ""; 245 | echo -n $empty; 246 | } 247 | --- request 248 | GET /echo 249 | --- response_body chop 250 | 251 | 252 | 253 | === TEST 18: -- -n 254 | --- config 255 | location /echo { 256 | echo -- -n hello; 257 | echo -- -n world; 258 | } 259 | --- request 260 | GET /echo 261 | --- response_body 262 | -n hello 263 | -n world 264 | 265 | 266 | 267 | === TEST 19: -n -n 268 | --- config 269 | location /echo { 270 | echo -n -n hello; 271 | echo -n -n world; 272 | } 273 | --- request 274 | GET /echo 275 | --- response_body chop 276 | helloworld 277 | 278 | 279 | 280 | === TEST 20: -n -- -n 281 | --- config 282 | location /echo { 283 | echo -n -- -n hello; 284 | echo -n -- -n world; 285 | } 286 | --- request 287 | GET /echo 288 | --- response_body chop 289 | -n hello-n world 290 | 291 | 292 | 293 | === TEST 21: proxy 294 | --- config 295 | location /main { 296 | proxy_pass http://127.0.0.1:$server_port/echo; 297 | } 298 | location /echo { 299 | echo hello; 300 | echo world; 301 | } 302 | --- request 303 | GET /main 304 | --- response_headers 305 | !Content-Length 306 | --- response_body 307 | hello 308 | world 309 | 310 | 311 | 312 | === TEST 22: if is evil 313 | --- config 314 | location /test { 315 | set $a 3; 316 | set_by_lua $a ' 317 | if ngx.var.a == "3" then 318 | return 4 319 | end 320 | '; 321 | echo $a; 322 | } 323 | --- request 324 | GET /test 325 | --- response_body 326 | 4 327 | --- SKIP 328 | 329 | 330 | 331 | === TEST 23: HEAD 332 | --- config 333 | location /echo { 334 | echo hello; 335 | echo world; 336 | } 337 | --- request 338 | HEAD /echo 339 | --- response_body 340 | 341 | 342 | 343 | === TEST 24: POST 344 | --- config 345 | location /echo { 346 | echo hello; 347 | echo world; 348 | } 349 | --- pipelined_requests eval 350 | ["POST /echo 351 | blah blah", "POST /echo 352 | foo bar baz"] 353 | --- response_body eval 354 | ["hello\nworld\n","hello\nworld\n"] 355 | 356 | 357 | 358 | === TEST 25: POST 359 | --- config 360 | location /echo { 361 | echo_sleep 0.001; 362 | echo hello; 363 | echo world; 364 | } 365 | --- pipelined_requests eval 366 | ["POST /echo 367 | blah blah", "POST /echo 368 | foo bar baz"] 369 | --- response_body eval 370 | ["hello\nworld\n","hello\nworld\n"] 371 | 372 | 373 | 374 | === TEST 26: empty arg after -n (github issue #33) 375 | --- config 376 | location = /t { 377 | set $empty ""; 378 | echo -n $empty hello world; 379 | } 380 | --- request 381 | GET /t 382 | --- response_body chop 383 | hello world 384 | 385 | 386 | 387 | === TEST 27: image filter 388 | --- config 389 | location = /gif { 390 | empty_gif; 391 | } 392 | 393 | location = /t { 394 | default_type image/gif; 395 | image_filter resize 10 10; 396 | set $gif1 ''; 397 | set $gif2 ''; 398 | rewrite_by_lua ' 399 | local res = ngx.location.capture("/gif") 400 | local data = res.body 401 | ngx.var.gif1 = string.sub(data, 1, #data - 1) 402 | ngx.var.gif2 = string.sub(data, #data) 403 | '; 404 | echo -n $gif1; 405 | echo -n $gif2; 406 | } 407 | --- request 408 | GET /t 409 | --- stap 410 | F(ngx_http_image_header_filter) { 411 | println("image header filter") 412 | } 413 | --- stap_out 414 | image header filter 415 | --- response_body_like: . 416 | 417 | -------------------------------------------------------------------------------- /src/ngx_http_echo_filter.c: -------------------------------------------------------------------------------- 1 | #ifndef DDEBUG 2 | #define DDEBUG 0 3 | #endif 4 | #include "ddebug.h" 5 | 6 | #include "ngx_http_echo_filter.h" 7 | #include "ngx_http_echo_util.h" 8 | #include "ngx_http_echo_echo.h" 9 | 10 | #include 11 | 12 | 13 | 14 | ngx_http_output_header_filter_pt ngx_http_echo_next_header_filter; 15 | 16 | ngx_http_output_body_filter_pt ngx_http_echo_next_body_filter; 17 | 18 | static ngx_int_t ngx_http_echo_header_filter(ngx_http_request_t *r); 19 | 20 | static ngx_int_t ngx_http_echo_body_filter(ngx_http_request_t *r, 21 | ngx_chain_t *in); 22 | 23 | /* filter handlers */ 24 | static ngx_int_t ngx_http_echo_exec_filter_cmds(ngx_http_request_t *r, 25 | ngx_http_echo_ctx_t *ctx, ngx_array_t *cmds, ngx_uint_t *iterator); 26 | 27 | 28 | static volatile ngx_cycle_t *ngx_http_echo_prev_cycle = NULL; 29 | 30 | 31 | ngx_int_t 32 | ngx_http_echo_filter_init(ngx_conf_t *cf) 33 | { 34 | int multi_http_blocks; 35 | ngx_http_echo_main_conf_t *emcf; 36 | 37 | emcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_echo_module); 38 | 39 | if (ngx_http_echo_prev_cycle != ngx_cycle) { 40 | ngx_http_echo_prev_cycle = ngx_cycle; 41 | multi_http_blocks = 0; 42 | 43 | } else { 44 | multi_http_blocks = 1; 45 | } 46 | 47 | if (multi_http_blocks || emcf->requires_filter) { 48 | dd("top header filter: %ld", 49 | (unsigned long) ngx_http_top_header_filter); 50 | 51 | ngx_http_echo_next_header_filter = ngx_http_top_header_filter; 52 | ngx_http_top_header_filter = ngx_http_echo_header_filter; 53 | 54 | dd("top body filter: %ld", (unsigned long) ngx_http_top_body_filter); 55 | 56 | ngx_http_echo_next_body_filter = ngx_http_top_body_filter; 57 | ngx_http_top_body_filter = ngx_http_echo_body_filter; 58 | } 59 | 60 | return NGX_OK; 61 | } 62 | 63 | 64 | static ngx_int_t 65 | ngx_http_echo_header_filter(ngx_http_request_t *r) 66 | { 67 | ngx_http_echo_loc_conf_t *conf; 68 | ngx_http_echo_ctx_t *ctx; 69 | 70 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 71 | "echo header filter, uri \"%V?%V\"", &r->uri, &r->args); 72 | 73 | ctx = ngx_http_get_module_ctx(r, ngx_http_echo_module); 74 | 75 | /* XXX we should add option to insert contents for responses 76 | * of non-200 status code here... */ 77 | /* 78 | if (r->headers_out.status != NGX_HTTP_OK) { 79 | if (ctx != NULL) { 80 | ctx->skip_filter = 1; 81 | } 82 | return ngx_http_echo_next_header_filter(r); 83 | } 84 | */ 85 | 86 | conf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module); 87 | if (conf->before_body_cmds == NULL && conf->after_body_cmds == NULL) { 88 | if (ctx != NULL) { 89 | ctx->skip_filter = 1; 90 | } 91 | return ngx_http_echo_next_header_filter(r); 92 | } 93 | 94 | if (ctx == NULL) { 95 | ctx = ngx_http_echo_create_ctx(r); 96 | if (ctx == NULL) { 97 | return NGX_ERROR; 98 | } 99 | 100 | ngx_http_set_ctx(r, ctx, ngx_http_echo_module); 101 | } 102 | 103 | /* enable streaming here (use chunked encoding) */ 104 | ngx_http_clear_content_length(r); 105 | ngx_http_clear_accept_ranges(r); 106 | 107 | return ngx_http_echo_next_header_filter(r); 108 | } 109 | 110 | 111 | static ngx_int_t 112 | ngx_http_echo_body_filter(ngx_http_request_t *r, ngx_chain_t *in) 113 | { 114 | ngx_http_echo_ctx_t *ctx; 115 | ngx_int_t rc; 116 | ngx_http_echo_loc_conf_t *conf; 117 | unsigned last; 118 | ngx_chain_t *cl; 119 | ngx_buf_t *b; 120 | 121 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 122 | "echo body filter, uri \"%V?%V\"", &r->uri, &r->args); 123 | 124 | if (in == NULL || r->header_only) { 125 | return ngx_http_echo_next_body_filter(r, in); 126 | } 127 | 128 | ctx = ngx_http_get_module_ctx(r, ngx_http_echo_module); 129 | 130 | if (ctx == NULL || ctx->skip_filter) { 131 | return ngx_http_echo_next_body_filter(r, in); 132 | } 133 | 134 | conf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module); 135 | 136 | if (!ctx->before_body_sent) { 137 | ctx->before_body_sent = 1; 138 | 139 | if (conf->before_body_cmds != NULL) { 140 | rc = ngx_http_echo_exec_filter_cmds(r, ctx, conf->before_body_cmds, 141 | &ctx->next_before_body_cmd); 142 | if (rc != NGX_OK) { 143 | return NGX_ERROR; 144 | } 145 | } 146 | } 147 | 148 | if (conf->after_body_cmds == NULL) { 149 | ctx->skip_filter = 1; 150 | return ngx_http_echo_next_body_filter(r, in); 151 | } 152 | 153 | last = 0; 154 | 155 | for (cl = in; cl; cl = cl->next) { 156 | dd("cl %p, special %d", cl, ngx_buf_special(cl->buf)); 157 | 158 | if (cl->buf->last_buf || cl->buf->last_in_chain) { 159 | cl->buf->last_buf = 0; 160 | cl->buf->last_in_chain = 0; 161 | cl->buf->sync = 1; 162 | last = 1; 163 | } 164 | } 165 | 166 | dd("in %p, last %d", in, (int) last); 167 | 168 | if (in) { 169 | rc = ngx_http_echo_next_body_filter(r, in); 170 | 171 | #if 0 172 | if (rc == NGX_AGAIN) { 173 | return NGX_ERROR; 174 | } 175 | #endif 176 | 177 | dd("next filter returns %d, last %d", (int) rc, (int) last); 178 | 179 | if (rc == NGX_ERROR || rc > NGX_OK || !last) { 180 | return rc; 181 | } 182 | } 183 | 184 | dd("exec filter cmds for after body cmds"); 185 | 186 | rc = ngx_http_echo_exec_filter_cmds(r, ctx, conf->after_body_cmds, 187 | &ctx->next_after_body_cmd); 188 | if (rc == NGX_ERROR || rc > NGX_OK) { 189 | dd("FAILED: exec filter cmds for after body cmds"); 190 | return NGX_ERROR; 191 | } 192 | 193 | ctx->skip_filter = 1; 194 | 195 | dd("after body cmds executed...terminating..."); 196 | 197 | /* XXX we can NOT use 198 | * ngx_http_send_special(r, NGX_HTTP_LAST) here 199 | * because we should bypass the upstream filters. */ 200 | 201 | b = ngx_calloc_buf(r->pool); 202 | if (b == NULL) { 203 | return NGX_ERROR; 204 | } 205 | 206 | if (r == r->main && !r->post_action) { 207 | b->last_buf = 1; 208 | 209 | } else { 210 | b->sync = 1; 211 | b->last_in_chain = 1; 212 | } 213 | 214 | cl = ngx_alloc_chain_link(r->pool); 215 | if (cl == NULL) { 216 | return NGX_ERROR; 217 | } 218 | 219 | cl->next = NULL; 220 | cl->buf = b; 221 | 222 | return ngx_http_echo_next_body_filter(r, cl); 223 | } 224 | 225 | 226 | static ngx_int_t 227 | ngx_http_echo_exec_filter_cmds(ngx_http_request_t *r, 228 | ngx_http_echo_ctx_t *ctx, ngx_array_t *cmds, ngx_uint_t *iterator) 229 | { 230 | ngx_int_t rc; 231 | ngx_array_t *opts = NULL; 232 | ngx_array_t *computed_args = NULL; 233 | ngx_http_echo_cmd_t *cmd; 234 | ngx_http_echo_cmd_t *cmd_elts; 235 | 236 | for (cmd_elts = cmds->elts; *iterator < cmds->nelts; (*iterator)++) { 237 | cmd = &cmd_elts[*iterator]; 238 | 239 | /* evaluate arguments for the current cmd (if any) */ 240 | if (cmd->args) { 241 | computed_args = ngx_array_create(r->pool, cmd->args->nelts, 242 | sizeof(ngx_str_t)); 243 | if (computed_args == NULL) { 244 | return NGX_ERROR; 245 | } 246 | 247 | opts = ngx_array_create(r->pool, 1, sizeof(ngx_str_t)); 248 | if (opts == NULL) { 249 | return NGX_ERROR; 250 | } 251 | 252 | rc = ngx_http_echo_eval_cmd_args(r, cmd, computed_args, opts); 253 | 254 | if (rc != NGX_OK) { 255 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 256 | "Failed to evaluate arguments for " 257 | "the directive."); 258 | return rc; 259 | } 260 | } 261 | 262 | /* do command dispatch based on the opcode */ 263 | switch (cmd->opcode) { 264 | case echo_opcode_echo_before_body: 265 | case echo_opcode_echo_after_body: 266 | dd("exec echo_before_body or echo_after_body..."); 267 | 268 | rc = ngx_http_echo_exec_echo(r, ctx, computed_args, 269 | 1 /* in filter */, opts); 270 | 271 | if (rc == NGX_ERROR || rc > NGX_OK) { 272 | return rc; 273 | } 274 | 275 | break; 276 | default: 277 | break; 278 | } 279 | } 280 | 281 | return NGX_OK; 282 | } 283 | -------------------------------------------------------------------------------- /t/location-async.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (2 * blocks() + 1); 9 | 10 | run_tests(); 11 | 12 | __DATA__ 13 | 14 | === TEST 1: sanity 15 | --- config 16 | location /main { 17 | echo_location_async /sub; 18 | } 19 | location /sub { 20 | echo hello; 21 | } 22 | --- request 23 | GET /main 24 | --- response_body 25 | hello 26 | 27 | 28 | 29 | === TEST 2: trailing echo 30 | --- config 31 | location /main { 32 | echo_location_async /sub; 33 | echo after subrequest; 34 | } 35 | location /sub { 36 | echo hello; 37 | } 38 | --- request 39 | GET /main 40 | --- response_body 41 | hello 42 | after subrequest 43 | 44 | 45 | 46 | === TEST 3: leading echo 47 | --- config 48 | location /main { 49 | echo before subrequest; 50 | echo_location_async /sub; 51 | } 52 | location /sub { 53 | echo hello; 54 | } 55 | --- request 56 | GET /main 57 | --- response_body 58 | before subrequest 59 | hello 60 | 61 | 62 | 63 | === TEST 4: leading & trailing echo 64 | --- config 65 | location /main { 66 | echo before subrequest; 67 | echo_location_async /sub; 68 | echo after subrequest; 69 | } 70 | location /sub { 71 | echo hello; 72 | } 73 | --- request 74 | GET /main 75 | --- response_body 76 | before subrequest 77 | hello 78 | after subrequest 79 | 80 | 81 | 82 | === TEST 5: multiple subrequests 83 | --- config 84 | location /main { 85 | echo before sr 1; 86 | echo_location_async /sub; 87 | echo after sr 1; 88 | echo before sr 2; 89 | echo_location_async /sub; 90 | echo after sr 2; 91 | } 92 | location /sub { 93 | echo hello; 94 | } 95 | --- request 96 | GET /main 97 | --- response_body 98 | before sr 1 99 | hello 100 | after sr 1 101 | before sr 2 102 | hello 103 | after sr 2 104 | 105 | 106 | 107 | === TEST 6: timed multiple subrequests (blocking sleep) 108 | --- config 109 | location /main { 110 | echo_reset_timer; 111 | echo_location_async /sub1; 112 | echo_location_async /sub2; 113 | echo "took $echo_timer_elapsed sec for total."; 114 | } 115 | location /sub1 { 116 | echo_blocking_sleep 0.02; 117 | echo hello; 118 | } 119 | location /sub2 { 120 | echo_blocking_sleep 0.01; 121 | echo world; 122 | } 123 | 124 | --- request 125 | GET /main 126 | --- response_body_like 127 | ^hello 128 | world 129 | took 0\.00[0-5] sec for total\.$ 130 | 131 | 132 | 133 | === TEST 7: timed multiple subrequests (non-blocking sleep) 134 | --- config 135 | location /main { 136 | echo_reset_timer; 137 | echo_location_async /sub1; 138 | echo_location_async /sub2; 139 | echo "took $echo_timer_elapsed sec for total."; 140 | } 141 | location /sub1 { 142 | echo_sleep 0.02; 143 | echo hello; 144 | } 145 | location /sub2 { 146 | echo_sleep 0.01; 147 | echo world; 148 | } 149 | 150 | --- request 151 | GET /main 152 | --- response_body_like 153 | ^hello 154 | world 155 | took 0\.00[0-5] sec for total\.$ 156 | 157 | 158 | 159 | === TEST 8: location with args 160 | --- config 161 | location /main { 162 | echo_location_async /sub 'foo=Foo&bar=Bar'; 163 | } 164 | location /sub { 165 | echo $arg_foo $arg_bar; 166 | } 167 | --- request 168 | GET /main 169 | --- response_body 170 | Foo Bar 171 | 172 | 173 | 174 | === TEST 9: encoded chars in query strings 175 | --- config 176 | location /main { 177 | echo_location_async /sub 'foo=a%20b&bar=Bar'; 178 | } 179 | location /sub { 180 | echo $arg_foo $arg_bar; 181 | } 182 | --- request 183 | GET /main 184 | --- response_body 185 | a%20b Bar 186 | 187 | 188 | 189 | === TEST 10: UTF-8 chars in query strings 190 | --- config 191 | location /main { 192 | echo_location_async /sub 'foo=你好'; 193 | } 194 | location /sub { 195 | echo $arg_foo; 196 | } 197 | --- request 198 | GET /main 199 | --- response_body 200 | 你好 201 | 202 | 203 | 204 | === TEST 11: encoded chars in location url 205 | --- config 206 | location /main { 207 | echo_location_async /sub%31 'foo=Foo&bar=Bar'; 208 | } 209 | location /sub%31 { 210 | echo 'sub%31'; 211 | } 212 | location /sub1 { 213 | echo 'sub1'; 214 | } 215 | --- request 216 | GET /main 217 | --- response_body 218 | sub1 219 | 220 | 221 | 222 | === TEST 12: querystring in url 223 | --- config 224 | location /main { 225 | echo_location_async /sub?foo=Foo&bar=Bar; 226 | } 227 | location /sub { 228 | echo $arg_foo $arg_bar; 229 | } 230 | --- request 231 | GET /main 232 | --- response_body 233 | Foo Bar 234 | 235 | 236 | 237 | === TEST 13: querystring in url *AND* an explicit querystring 238 | --- config 239 | location /main { 240 | echo_location_async /sub?foo=Foo&bar=Bar blah=Blah; 241 | } 242 | location /sub { 243 | echo $arg_foo $arg_bar $arg_blah; 244 | } 245 | --- request 246 | GET /main 247 | --- response_body 248 | Blah 249 | 250 | 251 | 252 | === TEST 14: explicit flush in main request 253 | flush won't really flush the buffer... 254 | --- config 255 | location /main_flush { 256 | echo 'pre main'; 257 | echo_location_async /sub; 258 | echo 'post main'; 259 | echo_flush; 260 | } 261 | 262 | location /sub { 263 | echo_sleep 0.02; 264 | echo 'sub'; 265 | } 266 | --- request 267 | GET /main_flush 268 | --- response_body 269 | pre main 270 | sub 271 | post main 272 | 273 | 274 | 275 | === TEST 15: no varaiable inheritance 276 | --- config 277 | location /main { 278 | echo $echo_cacheable_request_uri; 279 | echo_location_async /sub; 280 | echo_location_async /sub2; 281 | } 282 | location /sub { 283 | echo $echo_cacheable_request_uri; 284 | } 285 | location /sub2 { 286 | echo $echo_cacheable_request_uri; 287 | } 288 | 289 | --- request 290 | GET /main 291 | --- response_body 292 | /main 293 | /sub 294 | /sub2 295 | 296 | 297 | 298 | === TEST 16: unsafe uri 299 | --- config 300 | location /unsafe { 301 | echo_location_async '/../foo'; 302 | } 303 | --- request 304 | GET /unsafe 305 | --- stap2 306 | F(ngx_http_send_header) { 307 | printf("send header on req %p (header sent: %d)\n", $r, $r->header_sent) 308 | print_ubacktrace() 309 | } 310 | --- ignore_response 311 | --- error_log 312 | echo_location_async sees unsafe uri: "/../foo" 313 | --- no_error_log 314 | [error] 315 | [alert] 316 | 317 | 318 | 319 | === TEST 17: access/deny (access phase handlers skipped in subrequests) 320 | --- config 321 | location /main { 322 | echo_location_async /denied; 323 | } 324 | location /denied { 325 | deny all; 326 | echo No no no; 327 | } 328 | --- request 329 | GET /main 330 | --- error_code: 200 331 | --- response_body 332 | No no no 333 | 334 | 335 | 336 | === TEST 18: rewrite is honored. 337 | --- config 338 | location /main { 339 | echo_location_async /rewrite; 340 | } 341 | location /rewrite { 342 | rewrite ^ /foo break; 343 | echo $uri; 344 | } 345 | --- request 346 | GET /main 347 | --- response_body 348 | /foo 349 | 350 | 351 | 352 | === TEST 19: let subrequest to read the main request's request body 353 | --- SKIP 354 | --- config 355 | location /main { 356 | echo_location_async /sub; 357 | } 358 | location /sub { 359 | echo_read_request_body; 360 | echo_request_body; 361 | } 362 | --- request 363 | POST /main 364 | hello, body! 365 | --- response_body chomp 366 | hello, body! 367 | 368 | 369 | 370 | === TEST 20: leading subrequest & echo_before_body 371 | --- config 372 | location /main { 373 | echo_before_body hello; 374 | echo_location_async /foo; 375 | } 376 | location /foo { 377 | echo world; 378 | } 379 | --- request 380 | GET /main 381 | --- response_body 382 | hello 383 | world 384 | 385 | 386 | 387 | === TEST 21: leading subrequest & xss 388 | --- config 389 | location /main { 390 | default_type 'application/json'; 391 | xss_get on; 392 | xss_callback_arg c; 393 | echo_location_async /foo; 394 | } 395 | location /foo { 396 | echo -n world; 397 | } 398 | --- request 399 | GET /main?c=hi 400 | --- response_body chop 401 | hi(world); 402 | 403 | 404 | 405 | === TEST 22: multiple leading subrequest & xss 406 | --- config 407 | location /main { 408 | default_type 'application/json'; 409 | xss_get on; 410 | xss_callback_arg c; 411 | echo_location_async /foo; 412 | echo_location_async /bar; 413 | } 414 | location /foo { 415 | echo -n world; 416 | } 417 | location /bar { 418 | echo -n ' people'; 419 | } 420 | --- request 421 | GET /main?c=hi 422 | --- response_body chop 423 | hi(world people); 424 | 425 | 426 | 427 | === TEST 23: sanity (HEAD) 428 | --- config 429 | location /main { 430 | echo_location_async /sub; 431 | echo_location_async /sub; 432 | } 433 | location /sub { 434 | echo hello; 435 | } 436 | --- request 437 | HEAD /main 438 | --- response_body 439 | 440 | -------------------------------------------------------------------------------- /src/ngx_http_echo_echo.c: -------------------------------------------------------------------------------- 1 | #ifndef DDEBUG 2 | #define DDEBUG 0 3 | #endif 4 | #include "ddebug.h" 5 | 6 | #include "ngx_http_echo_echo.h" 7 | #include "ngx_http_echo_util.h" 8 | #include "ngx_http_echo_filter.h" 9 | 10 | #include 11 | 12 | static ngx_buf_t ngx_http_echo_space_buf; 13 | 14 | static ngx_buf_t ngx_http_echo_newline_buf; 15 | 16 | 17 | ngx_int_t 18 | ngx_http_echo_echo_init(ngx_conf_t *cf) 19 | { 20 | static u_char space_str[] = " "; 21 | static u_char newline_str[] = "\n"; 22 | 23 | dd("global init..."); 24 | 25 | ngx_memzero(&ngx_http_echo_space_buf, sizeof(ngx_buf_t)); 26 | 27 | ngx_http_echo_space_buf.memory = 1; 28 | 29 | ngx_http_echo_space_buf.start = 30 | ngx_http_echo_space_buf.pos = 31 | space_str; 32 | 33 | ngx_http_echo_space_buf.end = 34 | ngx_http_echo_space_buf.last = 35 | space_str + sizeof(space_str) - 1; 36 | 37 | ngx_memzero(&ngx_http_echo_newline_buf, sizeof(ngx_buf_t)); 38 | 39 | ngx_http_echo_newline_buf.memory = 1; 40 | 41 | ngx_http_echo_newline_buf.start = 42 | ngx_http_echo_newline_buf.pos = 43 | newline_str; 44 | 45 | ngx_http_echo_newline_buf.end = 46 | ngx_http_echo_newline_buf.last = 47 | newline_str + sizeof(newline_str) - 1; 48 | 49 | return NGX_OK; 50 | } 51 | 52 | 53 | ngx_int_t 54 | ngx_http_echo_exec_echo_sync(ngx_http_request_t *r, 55 | ngx_http_echo_ctx_t *ctx) 56 | { 57 | ngx_buf_t *buf; 58 | ngx_chain_t *cl = NULL; /* the head of the chain link */ 59 | 60 | buf = ngx_calloc_buf(r->pool); 61 | if (buf == NULL) { 62 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 63 | } 64 | 65 | buf->sync = 1; 66 | 67 | cl = ngx_alloc_chain_link(r->pool); 68 | if (cl == NULL) { 69 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 70 | } 71 | 72 | cl->buf = buf; 73 | cl->next = NULL; 74 | 75 | return ngx_http_echo_send_chain_link(r, ctx, cl); 76 | } 77 | 78 | 79 | ngx_int_t 80 | ngx_http_echo_exec_echo(ngx_http_request_t *r, 81 | ngx_http_echo_ctx_t *ctx, ngx_array_t *computed_args, 82 | ngx_flag_t in_filter, ngx_array_t *opts) 83 | { 84 | ngx_uint_t i; 85 | 86 | ngx_buf_t *space_buf; 87 | ngx_buf_t *newline_buf; 88 | ngx_buf_t *buf; 89 | 90 | ngx_str_t *computed_arg; 91 | ngx_str_t *computed_arg_elts; 92 | ngx_str_t *opt; 93 | 94 | ngx_chain_t *cl = NULL; /* the head of the chain link */ 95 | ngx_chain_t **ll = &cl; /* always point to the address of the last link */ 96 | 97 | dd_enter(); 98 | 99 | if (computed_args == NULL) { 100 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 101 | } 102 | 103 | computed_arg_elts = computed_args->elts; 104 | for (i = 0; i < computed_args->nelts; i++) { 105 | computed_arg = &computed_arg_elts[i]; 106 | 107 | if (computed_arg->len == 0) { 108 | buf = NULL; 109 | 110 | } else { 111 | buf = ngx_calloc_buf(r->pool); 112 | if (buf == NULL) { 113 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 114 | } 115 | 116 | buf->start = buf->pos = computed_arg->data; 117 | buf->last = buf->end = computed_arg->data + 118 | computed_arg->len; 119 | 120 | buf->memory = 1; 121 | } 122 | 123 | if (cl == NULL) { 124 | cl = ngx_alloc_chain_link(r->pool); 125 | if (cl == NULL) { 126 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 127 | } 128 | cl->buf = buf; 129 | cl->next = NULL; 130 | ll = &cl->next; 131 | 132 | } else { 133 | /* append a space first */ 134 | *ll = ngx_alloc_chain_link(r->pool); 135 | 136 | if (*ll == NULL) { 137 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 138 | } 139 | 140 | space_buf = ngx_calloc_buf(r->pool); 141 | 142 | if (space_buf == NULL) { 143 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 144 | } 145 | 146 | /* nginx clears buf flags at the end of each request handling, 147 | * so we have to make a clone here. */ 148 | *space_buf = ngx_http_echo_space_buf; 149 | 150 | (*ll)->buf = space_buf; 151 | (*ll)->next = NULL; 152 | 153 | ll = &(*ll)->next; 154 | 155 | /* then append the buf only if it's non-empty */ 156 | if (buf) { 157 | *ll = ngx_alloc_chain_link(r->pool); 158 | if (*ll == NULL) { 159 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 160 | } 161 | (*ll)->buf = buf; 162 | (*ll)->next = NULL; 163 | 164 | ll = &(*ll)->next; 165 | } 166 | } 167 | } /* end for */ 168 | 169 | if (cl && cl->buf == NULL) { 170 | cl = cl->next; 171 | } 172 | 173 | if (opts && opts->nelts > 0) { 174 | opt = opts->elts; 175 | /* FIXME handle other unrecognized options here */ 176 | if (opt[0].len == 1 && opt[0].data[0] == 'n') { 177 | goto done; 178 | } 179 | } 180 | 181 | /* append the newline character */ 182 | 183 | newline_buf = ngx_calloc_buf(r->pool); 184 | 185 | if (newline_buf == NULL) { 186 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 187 | } 188 | 189 | *newline_buf = ngx_http_echo_newline_buf; 190 | 191 | if (cl == NULL) { 192 | cl = ngx_alloc_chain_link(r->pool); 193 | 194 | if (cl == NULL) { 195 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 196 | } 197 | 198 | cl->buf = newline_buf; 199 | cl->next = NULL; 200 | /* ll = &cl->next; */ 201 | 202 | } else { 203 | *ll = ngx_alloc_chain_link(r->pool); 204 | 205 | if (*ll == NULL) { 206 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 207 | } 208 | 209 | (*ll)->buf = newline_buf; 210 | (*ll)->next = NULL; 211 | /* ll = &(*ll)->next; */ 212 | } 213 | 214 | done: 215 | 216 | if (cl == NULL || cl->buf == NULL) { 217 | return NGX_OK; 218 | } 219 | 220 | if (in_filter) { 221 | return ngx_http_echo_next_body_filter(r, cl); 222 | } 223 | 224 | return ngx_http_echo_send_chain_link(r, ctx, cl); 225 | } 226 | 227 | 228 | ngx_int_t 229 | ngx_http_echo_exec_echo_flush(ngx_http_request_t *r, ngx_http_echo_ctx_t *ctx) 230 | { 231 | return ngx_http_send_special(r, NGX_HTTP_FLUSH); 232 | } 233 | 234 | 235 | ngx_int_t 236 | ngx_http_echo_exec_echo_request_body(ngx_http_request_t *r, 237 | ngx_http_echo_ctx_t *ctx) 238 | { 239 | ngx_buf_t *b; 240 | ngx_chain_t *out, *cl, **ll; 241 | 242 | if (r->request_body == NULL || r->request_body->bufs == NULL) { 243 | return NGX_OK; 244 | } 245 | 246 | out = NULL; 247 | ll = &out; 248 | 249 | for (cl = r->request_body->bufs; cl; cl = cl->next) { 250 | if (ngx_buf_special(cl->buf)) { 251 | /* we do not want to create zero-size bufs */ 252 | continue; 253 | } 254 | 255 | *ll = ngx_alloc_chain_link(r->pool); 256 | if (*ll == NULL) { 257 | return NGX_ERROR; 258 | } 259 | 260 | b = ngx_alloc_buf(r->pool); 261 | if (b == NULL) { 262 | return NGX_ERROR; 263 | } 264 | 265 | (*ll)->buf = b; 266 | (*ll)->next = NULL; 267 | 268 | ngx_memcpy(b, cl->buf, sizeof(ngx_buf_t)); 269 | b->tag = (ngx_buf_tag_t) &ngx_http_echo_exec_echo_request_body; 270 | b->last_buf = 0; 271 | b->last_in_chain = 0; 272 | 273 | ll = &(*ll)->next; 274 | } 275 | 276 | if (out == NULL) { 277 | return NGX_OK; 278 | } 279 | 280 | return ngx_http_echo_send_chain_link(r, ctx, out); 281 | } 282 | 283 | 284 | ngx_int_t 285 | ngx_http_echo_exec_echo_duplicate(ngx_http_request_t *r, 286 | ngx_http_echo_ctx_t *ctx, ngx_array_t *computed_args) 287 | { 288 | ngx_str_t *computed_arg; 289 | ngx_str_t *computed_arg_elts; 290 | ssize_t i, count; 291 | ngx_str_t *str; 292 | u_char *p; 293 | ngx_int_t rc; 294 | ngx_buf_t *buf; 295 | ngx_chain_t *cl; 296 | 297 | dd_enter(); 298 | 299 | computed_arg_elts = computed_args->elts; 300 | 301 | computed_arg = &computed_arg_elts[0]; 302 | 303 | count = ngx_http_echo_atosz(computed_arg->data, computed_arg->len); 304 | 305 | if (count == NGX_ERROR) { 306 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 307 | "invalid size specified: \"%V\"", computed_arg); 308 | 309 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 310 | } 311 | 312 | str = &computed_arg_elts[1]; 313 | 314 | if (count == 0 || str->len == 0) { 315 | rc = ngx_http_echo_send_header_if_needed(r, ctx); 316 | if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { 317 | return rc; 318 | } 319 | 320 | return NGX_OK; 321 | } 322 | 323 | buf = ngx_create_temp_buf(r->pool, count * str->len); 324 | if (buf == NULL) { 325 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 326 | } 327 | 328 | p = buf->pos; 329 | for (i = 0; i < count; i++) { 330 | p = ngx_copy(p, str->data, str->len); 331 | } 332 | buf->last = p; 333 | 334 | cl = ngx_alloc_chain_link(r->pool); 335 | if (cl == NULL) { 336 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 337 | } 338 | cl->next = NULL; 339 | cl->buf = buf; 340 | 341 | return ngx_http_echo_send_chain_link(r, ctx, cl); 342 | } 343 | -------------------------------------------------------------------------------- /t/location.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | 5 | use Test::Nginx::Socket; 6 | 7 | repeat_each(2); 8 | 9 | plan tests => repeat_each() * (2 * blocks() + 2); 10 | 11 | #$Test::Nginx::LWP::LogLevel = 'debug'; 12 | 13 | #no_diff(); 14 | 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: sanity 20 | --- config 21 | location /main { 22 | echo_location /sub; 23 | } 24 | location /sub { 25 | echo hello; 26 | } 27 | --- request 28 | GET /main 29 | --- response_body 30 | hello 31 | 32 | 33 | 34 | === TEST 2: sanity with proxy in the middle 35 | --- config 36 | location /main { 37 | echo_location /proxy; 38 | } 39 | location /proxy { 40 | proxy_pass $scheme://127.0.0.1:$server_port/sub; 41 | } 42 | location /sub { 43 | echo hello; 44 | } 45 | --- request 46 | GET /main 47 | --- response_body 48 | hello 49 | 50 | 51 | 52 | === TEST 3: trailing echo 53 | --- config 54 | location /main { 55 | echo_location /sub; 56 | echo after subrequest; 57 | } 58 | location /sub { 59 | echo hello; 60 | } 61 | --- request 62 | GET /main 63 | --- response_body 64 | hello 65 | after subrequest 66 | 67 | 68 | 69 | === TEST 4: leading echo 70 | --- config 71 | location /main { 72 | echo before subrequest; 73 | echo_location /sub; 74 | } 75 | location /sub { 76 | echo hello; 77 | } 78 | --- request 79 | GET /main 80 | --- response_body 81 | before subrequest 82 | hello 83 | 84 | 85 | 86 | === TEST 5: leading & trailing echo 87 | --- config 88 | location /main { 89 | echo before subrequest; 90 | echo_location /sub; 91 | echo after subrequest; 92 | } 93 | location /sub { 94 | echo hello; 95 | } 96 | --- request 97 | GET /main 98 | --- response_body 99 | before subrequest 100 | hello 101 | after subrequest 102 | 103 | 104 | 105 | === TEST 6: multiple subrequests 106 | --- config 107 | location /main { 108 | echo before sr 1; 109 | echo_location /sub; 110 | echo after sr 1; 111 | echo before sr 2; 112 | echo_location /sub; 113 | echo after sr 2; 114 | } 115 | location /sub { 116 | echo hello; 117 | } 118 | --- request 119 | GET /main 120 | --- response_body 121 | before sr 1 122 | hello 123 | after sr 1 124 | before sr 2 125 | hello 126 | after sr 2 127 | 128 | 129 | 130 | === TEST 7: timed multiple subrequests (blocking sleep) 131 | --- config 132 | location /main { 133 | echo_reset_timer; 134 | echo_location /sub1; 135 | echo_location /sub2; 136 | echo "took $echo_timer_elapsed sec for total."; 137 | } 138 | location /sub1 { 139 | echo_blocking_sleep 0.02; 140 | echo hello; 141 | } 142 | location /sub2 { 143 | echo_blocking_sleep 0.01; 144 | echo world; 145 | } 146 | 147 | --- request 148 | GET /main 149 | --- response_body_like 150 | ^hello 151 | world 152 | took 0\.0(?:2[5-9]|3[0-6]) sec for total\.$ 153 | 154 | 155 | 156 | === TEST 8: timed multiple subrequests (non-blocking sleep) 157 | --- config 158 | location /main { 159 | echo_reset_timer; 160 | echo_location /sub1; 161 | echo_location /sub2; 162 | echo "took $echo_timer_elapsed sec for total."; 163 | } 164 | location /sub1 { 165 | echo_sleep 0.02; 166 | echo hello; 167 | } 168 | location /sub2 { 169 | echo_sleep 0.01; 170 | echo world; 171 | } 172 | 173 | --- request 174 | GET /main 175 | --- response_body_like 176 | ^hello 177 | world 178 | took 0\.0(?:2[5-9]|3[0-6]) sec for total\.$ 179 | 180 | 181 | 182 | === TEST 9: location with args 183 | --- config 184 | location /main { 185 | echo_location /sub 'foo=Foo&bar=Bar'; 186 | } 187 | location /sub { 188 | echo $arg_foo $arg_bar; 189 | } 190 | --- request 191 | GET /main 192 | --- response_body 193 | Foo Bar 194 | 195 | 196 | 197 | === TEST 10: chained subrequests 198 | --- config 199 | location /main { 200 | echo 'pre main'; 201 | echo_location /sub; 202 | echo 'post main'; 203 | } 204 | 205 | location /sub { 206 | echo 'pre sub'; 207 | echo_location /subsub; 208 | echo 'post sub'; 209 | } 210 | 211 | location /subsub { 212 | echo 'subsub'; 213 | } 214 | --- request 215 | GET /main 216 | --- response_body 217 | pre main 218 | pre sub 219 | subsub 220 | post sub 221 | post main 222 | 223 | 224 | 225 | === TEST 11: chained subrequests using named locations 226 | as of 0.8.20, ngx_http_subrequest still does not support 227 | named location. sigh. this case is a TODO. 228 | --- config 229 | location /main { 230 | echo 'pre main'; 231 | echo_location @sub; 232 | echo 'post main'; 233 | } 234 | 235 | location @sub { 236 | echo 'pre sub'; 237 | echo_location @subsub; 238 | echo 'post sub'; 239 | } 240 | 241 | location @subsub { 242 | echo 'subsub'; 243 | } 244 | --- request 245 | GET /main 246 | --- response_body 247 | pre main 248 | pre sub 249 | subsub 250 | post sub 251 | post main 252 | --- SKIP 253 | 254 | 255 | 256 | === TEST 12: explicit flush in main request 257 | --- config 258 | location /main { 259 | echo 'pre main'; 260 | echo_location /sub; 261 | echo 'post main'; 262 | echo_flush; 263 | } 264 | 265 | location /sub { 266 | echo_sleep 0.02; 267 | echo 'sub'; 268 | } 269 | --- request 270 | GET /main 271 | --- response_body 272 | pre main 273 | sub 274 | post main 275 | 276 | 277 | 278 | === TEST 13: no varaiable inheritance 279 | --- config 280 | location /main { 281 | echo $echo_cacheable_request_uri; 282 | echo_location /sub; 283 | echo_location /sub2; 284 | } 285 | location /sub { 286 | echo $echo_cacheable_request_uri; 287 | } 288 | location /sub2 { 289 | echo $echo_cacheable_request_uri; 290 | } 291 | 292 | --- request 293 | GET /main 294 | --- response_body 295 | /main 296 | /sub 297 | /sub2 298 | 299 | 300 | 301 | === TEST 14: unsafe uri 302 | --- config 303 | location /unsafe { 304 | echo_location '/../foo'; 305 | } 306 | --- request 307 | GET /unsafe 308 | --- ignore_response 309 | --- error_log 310 | echo_location sees unsafe uri: "/../foo" 311 | --- no_error_log 312 | [error] 313 | [alert] 314 | 315 | 316 | 317 | === TEST 15: querystring in url 318 | --- config 319 | location /main { 320 | echo_location /sub?foo=Foo&bar=Bar; 321 | } 322 | location /sub { 323 | echo $arg_foo $arg_bar; 324 | } 325 | --- request 326 | GET /main 327 | --- response_body 328 | Foo Bar 329 | 330 | 331 | 332 | === TEST 16: querystring in url *AND* an explicit querystring 333 | --- config 334 | location /main { 335 | echo_location /sub?foo=Foo&bar=Bar blah=Blah; 336 | } 337 | location /sub { 338 | echo $arg_foo $arg_bar $arg_blah; 339 | } 340 | --- request 341 | GET /main 342 | --- response_body 343 | Blah 344 | 345 | 346 | 347 | === TEST 17: let subrequest to read the main request's request body 348 | --- SKIP 349 | --- config 350 | location /main { 351 | echo_location /sub; 352 | } 353 | location /sub { 354 | echo_read_request_body; 355 | echo_request_body; 356 | } 357 | --- request 358 | POST /main 359 | hello, body! 360 | --- response_body chomp 361 | hello, body! 362 | 363 | 364 | 365 | === TEST 18: sleep after location 366 | --- config 367 | location /main { 368 | echo_location /sub; 369 | echo_sleep 0.001; 370 | echo_location /sub; 371 | } 372 | location /sub { 373 | echo_sleep 0.001; 374 | echo sub; 375 | } 376 | --- request 377 | GET /main 378 | --- response_body 379 | sub 380 | sub 381 | --- skip_nginx: 2: < 0.8.11 382 | 383 | 384 | 385 | === TEST 19: deep nested echo_location/echo_location_async 386 | --- config 387 | location /main { 388 | echo_location /bar; 389 | echo_location_async /bar; 390 | echo_location_async /bar; 391 | echo_location /group; 392 | echo_location_async /group; 393 | } 394 | 395 | location /group { 396 | echo_location /bar; 397 | echo_location_async /bar; 398 | } 399 | 400 | location /bar { 401 | #echo_sleep 0.001; 402 | echo $echo_incr; 403 | } 404 | --- request 405 | GET /main 406 | --- response_body 407 | 1 408 | 2 409 | 3 410 | 4 411 | 5 412 | 6 413 | 7 414 | --- timeout: 2 415 | 416 | 417 | 418 | === TEST 20: deep nested echo_location/echo_location_async (with sleep) 419 | --- config 420 | location /main { 421 | echo_location /bar; 422 | echo_location_async /bar; 423 | echo_location_async /bar; 424 | echo_location /group; 425 | echo_location_async /group; 426 | } 427 | 428 | location /group { 429 | echo_location /baz; 430 | echo_location_async /bah; 431 | } 432 | 433 | location ~ '^/ba[rzh]' { 434 | echo_sleep 0.001; 435 | echo $echo_incr; 436 | } 437 | --- request 438 | GET /main 439 | --- response_body 440 | 1 441 | 2 442 | 3 443 | 4 444 | 5 445 | 6 446 | 7 447 | --- timeout: 2 448 | 449 | 450 | 451 | === TEST 21: deep nested echo_location (with sleep) 452 | --- config 453 | location /main { 454 | echo_location /bar; 455 | echo_location /bar; 456 | echo_location /bar; 457 | echo_location /group; 458 | echo_location /group; 459 | } 460 | 461 | location /group { 462 | echo_location /bar; 463 | echo_location /bar; 464 | } 465 | 466 | location /incr { 467 | echo_sleep 0.001; 468 | echo $echo_incr; 469 | } 470 | 471 | location /bar { 472 | proxy_pass $scheme://127.0.0.1:$server_port/incr; 473 | } 474 | --- request 475 | GET /main 476 | --- response_body 477 | 1 478 | 1 479 | 1 480 | 1 481 | 1 482 | 1 483 | 1 484 | --- timeout: 5 485 | --- no_error_log 486 | [error] 487 | 488 | 489 | 490 | === TEST 22: leading subrequest & echo_before_body 491 | --- config 492 | location /main { 493 | echo_before_body hello; 494 | echo_location /foo; 495 | } 496 | location /foo { 497 | echo world; 498 | } 499 | --- request 500 | GET /main 501 | --- response_body 502 | hello 503 | world 504 | 505 | 506 | 507 | === TEST 23: leading subrequest & xss 508 | --- config 509 | location /main { 510 | default_type 'application/json'; 511 | xss_get on; 512 | xss_callback_arg c; 513 | echo_location /foo; 514 | } 515 | location /foo { 516 | echo -n world; 517 | } 518 | --- request 519 | GET /main?c=hi 520 | --- response_body chop 521 | hi(world); 522 | 523 | 524 | 525 | === TEST 24: multiple leading subrequest & xss 526 | --- config 527 | location /main { 528 | default_type 'application/json'; 529 | xss_get on; 530 | xss_callback_arg c; 531 | echo_location /foo; 532 | echo_location /bar; 533 | } 534 | location /main2 { 535 | content_by_lua ' 536 | local res = ngx.location.capture("/foo") 537 | local res2 = ngx.location.capture("/bar") 538 | ngx.say(res.body) 539 | ngx.say(res2.body) 540 | '; 541 | } 542 | location /foo { 543 | echo -n world; 544 | } 545 | location /bar { 546 | echo -n ' people'; 547 | } 548 | --- request 549 | GET /main?c=hi 550 | --- response_body chop 551 | hi(world people); 552 | 553 | 554 | 555 | === TEST 25: sanity (HEAD) 556 | --- config 557 | location /main { 558 | echo_location /sub; 559 | echo_location /sub; 560 | } 561 | location /sub { 562 | echo hello; 563 | } 564 | --- request 565 | HEAD /main 566 | --- response_body 567 | 568 | -------------------------------------------------------------------------------- /src/ngx_http_echo_handler.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Yichun Zhang (agentzh) 4 | */ 5 | 6 | 7 | #ifndef DDEBUG 8 | #define DDEBUG 0 9 | #endif 10 | #include "ddebug.h" 11 | 12 | 13 | #include "ngx_http_echo_filter.h" 14 | #include "ngx_http_echo_handler.h" 15 | #include "ngx_http_echo_echo.h" 16 | #include "ngx_http_echo_util.h" 17 | #include "ngx_http_echo_sleep.h" 18 | #include "ngx_http_echo_var.h" 19 | #include "ngx_http_echo_timer.h" 20 | #include "ngx_http_echo_location.h" 21 | #include "ngx_http_echo_subrequest.h" 22 | #include "ngx_http_echo_request_info.h" 23 | #include "ngx_http_echo_foreach.h" 24 | 25 | #include 26 | #include 27 | 28 | 29 | void 30 | ngx_http_echo_wev_handler(ngx_http_request_t *r) 31 | { 32 | ngx_int_t rc; 33 | ngx_http_echo_ctx_t *ctx; 34 | 35 | dd("wev handler"); 36 | 37 | ctx = ngx_http_get_module_ctx(r, ngx_http_echo_module); 38 | 39 | if (ctx == NULL) { 40 | ngx_http_finalize_request(r, NGX_ERROR); 41 | return; 42 | } 43 | 44 | dd("waiting: %d, done: %d", (int) ctx->waiting, (int) ctx->done); 45 | 46 | if (ctx->waiting && ! ctx->done) { 47 | 48 | if (r == r->connection->data && r->postponed) { 49 | 50 | if (r->postponed->request) { 51 | r->connection->data = r->postponed->request; 52 | 53 | #if defined(nginx_version) && nginx_version >= 8012 54 | ngx_http_post_request(r->postponed->request, NULL); 55 | #else 56 | ngx_http_post_request(r->postponed->request); 57 | #endif 58 | 59 | } else { 60 | ngx_http_echo_flush_postponed_outputs(r); 61 | } 62 | } 63 | 64 | return; 65 | } 66 | 67 | ctx->done = 0; 68 | 69 | ctx->next_handler_cmd++; 70 | 71 | rc = ngx_http_echo_run_cmds(r); 72 | 73 | dd("rc: %d", (int) rc); 74 | 75 | if (rc == NGX_ERROR || rc == NGX_DONE) { 76 | ngx_http_finalize_request(r, rc); 77 | return; 78 | } 79 | 80 | if (rc == NGX_AGAIN) { 81 | dd("mark busy %d for %.*s", (int) ctx->next_handler_cmd, 82 | (int) r->uri.len, 83 | r->uri.data); 84 | 85 | ctx->waiting = 1; 86 | ctx->done = 0; 87 | 88 | } else { 89 | dd("mark ready %d", (int) ctx->next_handler_cmd); 90 | ctx->waiting = 0; 91 | ctx->done = 1; 92 | 93 | dd("finalizing with rc %d", (int) rc); 94 | 95 | dd("finalize request %.*s with %d", (int) r->uri.len, r->uri.data, 96 | (int) rc); 97 | 98 | ngx_http_finalize_request(r, rc); 99 | } 100 | } 101 | 102 | 103 | ngx_int_t 104 | ngx_http_echo_handler(ngx_http_request_t *r) 105 | { 106 | ngx_int_t rc; 107 | ngx_http_echo_ctx_t *ctx; 108 | 109 | dd("subrequest in memory: %d", (int) r->subrequest_in_memory); 110 | 111 | rc = ngx_http_echo_run_cmds(r); 112 | 113 | dd("run cmds returned %d", (int) rc); 114 | 115 | if (rc == NGX_ERROR 116 | || rc == NGX_OK 117 | || rc == NGX_DONE 118 | || rc == NGX_DECLINED) 119 | { 120 | return rc; 121 | } 122 | 123 | ctx = ngx_http_get_module_ctx(r, ngx_http_echo_module); 124 | 125 | if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { 126 | if (ctx && r->header_sent) { 127 | return NGX_ERROR; 128 | } 129 | 130 | return rc; 131 | } 132 | 133 | /* rc == NGX_AGAIN */ 134 | 135 | #if defined(nginx_version) && nginx_version >= 8011 136 | r->main->count++; 137 | #endif 138 | 139 | dd("%d", r->connection->destroyed); 140 | dd("%d", r->done); 141 | 142 | if (ctx) { 143 | dd("mark busy %d for %.*s", (int) ctx->next_handler_cmd, 144 | (int) r->uri.len, 145 | r->uri.data); 146 | 147 | ctx->waiting = 1; 148 | ctx->done = 0; 149 | } 150 | 151 | return NGX_DONE; 152 | } 153 | 154 | 155 | ngx_int_t 156 | ngx_http_echo_run_cmds(ngx_http_request_t *r) 157 | { 158 | ngx_http_echo_loc_conf_t *elcf; 159 | ngx_http_echo_ctx_t *ctx; 160 | ngx_int_t rc; 161 | ngx_array_t *cmds; 162 | ngx_array_t *computed_args = NULL; 163 | ngx_http_echo_cmd_t *cmd; 164 | ngx_http_echo_cmd_t *cmd_elts; 165 | ngx_array_t *opts = NULL; 166 | 167 | elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module); 168 | cmds = elcf->handler_cmds; 169 | if (cmds == NULL) { 170 | return NGX_DECLINED; 171 | } 172 | 173 | ctx = ngx_http_get_module_ctx(r, ngx_http_echo_module); 174 | if (ctx == NULL) { 175 | ctx = ngx_http_echo_create_ctx(r); 176 | if (ctx == NULL) { 177 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 178 | } 179 | 180 | ngx_http_set_ctx(r, ctx, ngx_http_echo_module); 181 | } 182 | 183 | dd("exec handler: %.*s: %i", (int) r->uri.len, r->uri.data, 184 | (int) ctx->next_handler_cmd); 185 | 186 | cmd_elts = cmds->elts; 187 | 188 | for (; ctx->next_handler_cmd < cmds->nelts; ctx->next_handler_cmd++) { 189 | 190 | cmd = &cmd_elts[ctx->next_handler_cmd]; 191 | 192 | /* evaluate arguments for the current cmd (if any) */ 193 | if (cmd->args) { 194 | computed_args = ngx_array_create(r->pool, cmd->args->nelts, 195 | sizeof(ngx_str_t)); 196 | 197 | if (computed_args == NULL) { 198 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 199 | } 200 | 201 | opts = ngx_array_create(r->pool, 1, sizeof(ngx_str_t)); 202 | 203 | if (opts == NULL) { 204 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 205 | } 206 | 207 | rc = ngx_http_echo_eval_cmd_args(r, cmd, computed_args, opts); 208 | if (rc != NGX_OK) { 209 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 210 | "Failed to evaluate arguments for " 211 | "the directive."); 212 | return rc; 213 | } 214 | } 215 | 216 | if (computed_args == NULL) { 217 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 218 | } 219 | 220 | /* do command dispatch based on the opcode */ 221 | 222 | switch (cmd->opcode) { 223 | 224 | case echo_opcode_echo_sync: 225 | rc = ngx_http_echo_exec_echo_sync(r, ctx); 226 | break; 227 | 228 | case echo_opcode_echo: 229 | /* XXX moved the following code to a separate 230 | * function */ 231 | dd("found echo opcode"); 232 | rc = ngx_http_echo_exec_echo(r, ctx, computed_args, 233 | 0 /* in filter */, opts); 234 | break; 235 | 236 | case echo_opcode_echo_request_body: 237 | rc = ngx_http_echo_exec_echo_request_body(r, ctx); 238 | break; 239 | 240 | case echo_opcode_echo_location_async: 241 | if (!r->request_body) { 242 | /* we require reading the request body before doing 243 | * subrequests */ 244 | 245 | ctx->next_handler_cmd--; /* re-run the current cmd */ 246 | goto read_request_body; 247 | } 248 | 249 | dd("found opcode echo location async..."); 250 | rc = ngx_http_echo_exec_echo_location_async(r, ctx, 251 | computed_args); 252 | break; 253 | 254 | case echo_opcode_echo_location: 255 | if (!r->request_body) { 256 | /* we require reading the request body before doing 257 | * subrequests */ 258 | 259 | ctx->next_handler_cmd--; /* re-run the current cmd */ 260 | goto read_request_body; 261 | } 262 | 263 | return ngx_http_echo_exec_echo_location(r, ctx, computed_args); 264 | 265 | case echo_opcode_echo_subrequest_async: 266 | if (!r->request_body) { 267 | /* we require reading the request body before doing 268 | * subrequests */ 269 | 270 | ctx->next_handler_cmd--; /* re-run the current cmd */ 271 | goto read_request_body; 272 | } 273 | 274 | dd("found opcode echo subrequest async..."); 275 | rc = ngx_http_echo_exec_echo_subrequest_async(r, ctx, 276 | computed_args); 277 | break; 278 | 279 | case echo_opcode_echo_subrequest: 280 | if (!r->request_body) { 281 | /* we require reading the request body before doing 282 | * subrequests */ 283 | 284 | ctx->next_handler_cmd--; /* re-run the current cmd */ 285 | goto read_request_body; 286 | } 287 | 288 | return ngx_http_echo_exec_echo_subrequest(r, ctx, computed_args); 289 | 290 | case echo_opcode_echo_sleep: 291 | return ngx_http_echo_exec_echo_sleep(r, ctx, computed_args); 292 | 293 | case echo_opcode_echo_flush: 294 | rc = ngx_http_echo_exec_echo_flush(r, ctx); 295 | break; 296 | 297 | case echo_opcode_echo_blocking_sleep: 298 | rc = ngx_http_echo_exec_echo_blocking_sleep(r, ctx, 299 | computed_args); 300 | break; 301 | 302 | case echo_opcode_echo_reset_timer: 303 | rc = ngx_http_echo_exec_echo_reset_timer(r, ctx); 304 | break; 305 | 306 | case echo_opcode_echo_duplicate: 307 | rc = ngx_http_echo_exec_echo_duplicate(r, ctx, computed_args); 308 | break; 309 | 310 | case echo_opcode_echo_read_request_body: 311 | 312 | read_request_body: 313 | 314 | ctx->wait_read_request_body = 0; 315 | 316 | rc = ngx_http_echo_exec_echo_read_request_body(r, ctx); 317 | 318 | if (rc == NGX_ERROR) { 319 | return NGX_ERROR; 320 | } 321 | 322 | if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { 323 | #if (nginx_version >= 8011 && nginx_version < 1002006) \ 324 | || (nginx_version >= 1003000 && nginx_version < 1003009) 325 | r->main->count--; 326 | #endif 327 | return rc; 328 | } 329 | 330 | #if nginx_version >= 8011 331 | r->main->count--; 332 | #endif 333 | dd("read request body: %d", (int) rc); 334 | 335 | if (rc == NGX_OK) { 336 | continue; 337 | } 338 | 339 | /* rc == NGX_AGAIN */ 340 | ctx->wait_read_request_body = 1; 341 | return NGX_AGAIN; 342 | 343 | case echo_opcode_echo_foreach_split: 344 | rc = ngx_http_echo_exec_echo_foreach_split(r, ctx, computed_args); 345 | break; 346 | 347 | case echo_opcode_echo_end: 348 | rc = ngx_http_echo_exec_echo_end(r, ctx); 349 | break; 350 | 351 | case echo_opcode_echo_exec: 352 | dd("echo_exec"); 353 | return ngx_http_echo_exec_exec(r, ctx, computed_args); 354 | 355 | default: 356 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 357 | "unknown opcode: %d", cmd->opcode); 358 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 359 | } 360 | 361 | if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { 362 | return rc; 363 | } 364 | } 365 | 366 | rc = ngx_http_echo_send_chain_link(r, ctx, NULL /* indicate LAST */); 367 | 368 | if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { 369 | return rc; 370 | } 371 | 372 | if (!r->request_body) { 373 | if (ngx_http_discard_request_body(r) != NGX_OK) { 374 | return NGX_ERROR; 375 | } 376 | } 377 | 378 | return NGX_OK; 379 | } 380 | 381 | 382 | ngx_int_t 383 | ngx_http_echo_post_subrequest(ngx_http_request_t *r, 384 | void *data, ngx_int_t rc) 385 | { 386 | ngx_http_echo_ctx_t *ctx = data; 387 | ngx_http_request_t *pr; 388 | ngx_http_echo_ctx_t *pr_ctx; 389 | 390 | dd("echo post_subrequest: %.*s", (int) r->uri.len, r->uri.data); 391 | 392 | if (ctx->run_post_subrequest) { 393 | dd("already run post_subrequest: %p: %.*s", ctx, 394 | (int) r->uri.len, r->uri.data); 395 | 396 | return rc; 397 | } 398 | 399 | dd("setting run_post_subrequest to 1 for %p for %.*s", ctx, 400 | (int) r->uri.len, r->uri.data); 401 | 402 | ctx->run_post_subrequest = 1; 403 | 404 | pr = r->parent; 405 | 406 | pr_ctx = ngx_http_get_module_ctx(pr, ngx_http_echo_module); 407 | if (pr_ctx == NULL) { 408 | return NGX_ERROR; 409 | } 410 | 411 | dd("mark ready %d", (int) pr_ctx->next_handler_cmd); 412 | 413 | pr_ctx->waiting = 0; 414 | pr_ctx->done = 1; 415 | 416 | pr->write_event_handler = ngx_http_echo_wev_handler; 417 | 418 | /* work-around issues in nginx's event module */ 419 | 420 | if (r != r->connection->data 421 | && r->postponed 422 | && (r->main->posted_requests == NULL 423 | || r->main->posted_requests->request != pr)) 424 | { 425 | #if defined(nginx_version) && nginx_version >= 8012 426 | ngx_http_post_request(pr, NULL); 427 | #else 428 | ngx_http_post_request(pr); 429 | #endif 430 | } 431 | 432 | return rc; 433 | } 434 | -------------------------------------------------------------------------------- /t/subrequest-async.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (blocks() * 2 + 1); 9 | 10 | #$Test::Nginx::LWP::LogLevel = 'debug'; 11 | 12 | $ENV{TEST_NGINX_HTML_DIR} = html_dir; 13 | 14 | run_tests(); 15 | 16 | __DATA__ 17 | 18 | === TEST 1: sanity - GET 19 | --- config 20 | location /main { 21 | echo_subrequest_async GET /sub; 22 | } 23 | location /sub { 24 | echo "sub method: $echo_request_method"; 25 | echo "main method: $echo_client_request_method"; 26 | } 27 | --- request 28 | GET /main 29 | --- response_body 30 | sub method: GET 31 | main method: GET 32 | 33 | 34 | 35 | === TEST 2: sanity - DELETE 36 | --- config 37 | location /main { 38 | echo_subrequest_async DELETE /sub; 39 | } 40 | location /sub { 41 | echo "sub method: $echo_request_method"; 42 | echo "main method: $echo_client_request_method"; 43 | } 44 | --- request 45 | GET /main 46 | --- response_body 47 | sub method: DELETE 48 | main method: GET 49 | 50 | 51 | 52 | === TEST 3: trailing echo 53 | --- config 54 | location /main { 55 | echo_subrequest_async GET /sub; 56 | echo after subrequest; 57 | } 58 | location /sub { 59 | echo hello; 60 | } 61 | --- request 62 | GET /main 63 | --- response_body 64 | hello 65 | after subrequest 66 | 67 | 68 | 69 | === TEST 4: leading echo 70 | --- config 71 | location /main { 72 | echo before subrequest; 73 | echo_subrequest_async GET /sub; 74 | } 75 | location /sub { 76 | echo hello; 77 | } 78 | --- request 79 | GET /main 80 | --- response_body 81 | before subrequest 82 | hello 83 | 84 | 85 | 86 | === TEST 5: leading & trailing echo 87 | --- config 88 | location /main { 89 | echo before subrequest; 90 | echo_subrequest_async GET /sub; 91 | echo after subrequest; 92 | } 93 | location /sub { 94 | echo hello; 95 | } 96 | --- request 97 | GET /main 98 | --- response_body 99 | before subrequest 100 | hello 101 | after subrequest 102 | 103 | 104 | 105 | === TEST 6: multiple subrequests 106 | --- config 107 | location /main { 108 | echo before sr 1; 109 | echo_subrequest_async GET /sub; 110 | echo after sr 1; 111 | echo before sr 2; 112 | echo_subrequest_async GET /sub; 113 | echo after sr 2; 114 | } 115 | location /sub { 116 | echo hello; 117 | } 118 | --- request 119 | GET /main 120 | --- response_body 121 | before sr 1 122 | hello 123 | after sr 1 124 | before sr 2 125 | hello 126 | after sr 2 127 | 128 | 129 | 130 | === TEST 7: timed multiple subrequests (blocking sleep) 131 | --- config 132 | location /main { 133 | echo_reset_timer; 134 | echo_subrequest_async GET /sub1; 135 | echo_subrequest_async GET /sub2; 136 | echo "took $echo_timer_elapsed sec for total."; 137 | } 138 | location /sub1 { 139 | echo_blocking_sleep 0.02; 140 | echo hello; 141 | } 142 | location /sub2 { 143 | echo_blocking_sleep 0.01; 144 | echo world; 145 | } 146 | 147 | --- request 148 | GET /main 149 | --- response_body_like 150 | ^hello 151 | world 152 | took 0\.00[0-5] sec for total\.$ 153 | 154 | 155 | 156 | === TEST 8: timed multiple subrequests (non-blocking sleep) 157 | --- config 158 | location /main { 159 | echo_reset_timer; 160 | echo_subrequest_async GET /sub1; 161 | echo_subrequest_async GET /sub2; 162 | echo "took $echo_timer_elapsed sec for total."; 163 | } 164 | location /sub1 { 165 | echo_sleep 0.02; 166 | echo hello; 167 | } 168 | location /sub2 { 169 | echo_sleep 0.01; 170 | echo world; 171 | } 172 | 173 | --- request 174 | GET /main 175 | --- response_body_like 176 | ^hello 177 | world 178 | took 0\.00[0-5] sec for total\.$ 179 | 180 | 181 | 182 | === TEST 9: location with args 183 | --- config 184 | location /main { 185 | echo_subrequest_async GET /sub -q 'foo=Foo&bar=Bar'; 186 | } 187 | location /sub { 188 | echo $arg_foo $arg_bar; 189 | } 190 | --- request 191 | GET /main 192 | --- response_body 193 | Foo Bar 194 | 195 | 196 | 197 | === TEST 10: encoded chars in query strings 198 | --- config 199 | location /main { 200 | echo_subrequest_async GET /sub -q 'foo=a%20b&bar=Bar'; 201 | } 202 | location /sub { 203 | echo $arg_foo $arg_bar; 204 | } 205 | --- request 206 | GET /main 207 | --- response_body 208 | a%20b Bar 209 | 210 | 211 | 212 | === TEST 11: UTF-8 chars in query strings 213 | --- config 214 | location /main { 215 | echo_subrequest_async GET /sub -q 'foo=你好'; 216 | } 217 | location /sub { 218 | echo $arg_foo; 219 | } 220 | --- request 221 | GET /main 222 | --- response_body 223 | 你好 224 | 225 | 226 | 227 | === TEST 12: encoded chars in location url 228 | --- config 229 | location /main { 230 | echo_subrequest_async GET /sub%31 -q 'foo=Foo&bar=Bar'; 231 | } 232 | location /sub%31 { 233 | echo 'sub%31'; 234 | } 235 | location /sub1 { 236 | echo 'sub1'; 237 | } 238 | --- request 239 | GET /main 240 | --- response_body 241 | sub1 242 | 243 | 244 | 245 | === TEST 13: querystring in url 246 | --- config 247 | location /main { 248 | echo_subrequest_async GET /sub?foo=Foo&bar=Bar; 249 | } 250 | location /sub { 251 | echo $arg_foo $arg_bar; 252 | } 253 | --- request 254 | GET /main 255 | --- response_body 256 | Foo Bar 257 | 258 | 259 | 260 | === TEST 14: querystring in url *AND* an explicit querystring 261 | --- config 262 | location /main { 263 | echo_subrequest_async GET /sub?foo=Foo&bar=Bar -q blah=Blah; 264 | } 265 | location /sub { 266 | echo $arg_foo $arg_bar $arg_blah; 267 | } 268 | --- request 269 | GET /main 270 | --- response_body 271 | Blah 272 | 273 | 274 | 275 | === TEST 15: explicit flush in main request 276 | flush won't really flush the buffer... 277 | --- config 278 | location /main_flush { 279 | echo 'pre main'; 280 | echo_subrequest_async GET /sub; 281 | echo 'post main'; 282 | echo_flush; 283 | } 284 | 285 | location /sub { 286 | echo_sleep 0.02; 287 | echo 'sub'; 288 | } 289 | --- request 290 | GET /main_flush 291 | --- response_body 292 | pre main 293 | sub 294 | post main 295 | 296 | 297 | 298 | === TEST 16: POST subrequest with body (with proxy in the middle) and without read body explicitly 299 | --- config 300 | location /main { 301 | echo_subrequest_async POST /proxy -b 'hello, world'; 302 | } 303 | location /proxy { 304 | proxy_pass $scheme://127.0.0.1:$server_port/sub; 305 | } 306 | location /sub { 307 | echo "sub method: $echo_request_method."; 308 | # we need to read body explicitly here...or $echo_request_body 309 | # will evaluate to empty ("") 310 | echo "sub body: $echo_request_body."; 311 | } 312 | --- request 313 | GET /main 314 | --- response_body 315 | sub method: POST. 316 | sub body: . 317 | 318 | 319 | 320 | === TEST 17: POST subrequest with body (with proxy in the middle) and read body explicitly 321 | --- config 322 | location /main { 323 | echo_subrequest_async POST /proxy -b 'hello, world'; 324 | } 325 | location /proxy { 326 | proxy_pass $scheme://127.0.0.1:$server_port/sub; 327 | } 328 | location /sub { 329 | echo "sub method: $echo_request_method."; 330 | # we need to read body explicitly here...or $echo_request_body 331 | # will evaluate to empty ("") 332 | echo_read_request_body; 333 | echo "sub body: $echo_request_body."; 334 | } 335 | --- request 336 | GET /main 337 | --- response_body 338 | sub method: POST. 339 | sub body: hello, world. 340 | 341 | 342 | 343 | === TEST 18: multiple subrequests 344 | --- config 345 | location /multi { 346 | echo_subrequest_async POST '/sub' -q 'foo=Foo' -b 'hi'; 347 | echo_subrequest_async PUT '/sub' -q 'bar=Bar' -b 'hello'; 348 | } 349 | location /sub { 350 | echo "querystring: $query_string"; 351 | echo "method: $echo_request_method"; 352 | echo "body: $echo_request_body"; 353 | echo "content length: $http_content_length"; 354 | echo '///'; 355 | } 356 | --- request 357 | GET /multi 358 | --- response_body 359 | querystring: foo=Foo 360 | method: POST 361 | body: hi 362 | content length: 2 363 | /// 364 | querystring: bar=Bar 365 | method: PUT 366 | body: hello 367 | content length: 5 368 | /// 369 | 370 | 371 | 372 | === TEST 19: no varaiable inheritance 373 | --- config 374 | location /main { 375 | echo $echo_cacheable_request_uri; 376 | echo_subrequest_async GET /sub; 377 | echo_subrequest_async GET /sub2; 378 | } 379 | location /sub { 380 | echo $echo_cacheable_request_uri; 381 | } 382 | location /sub2 { 383 | echo $echo_cacheable_request_uri; 384 | } 385 | 386 | --- request 387 | GET /main 388 | --- response_body 389 | /main 390 | /sub 391 | /sub2 392 | 393 | 394 | 395 | === TEST 20: unsafe uri 396 | --- config 397 | location /unsafe { 398 | echo_subrequest_async GET '/../foo'; 399 | } 400 | --- request 401 | GET /unsafe 402 | --- ignore_response 403 | --- error_log 404 | echo_subrequest_async sees unsafe uri: "/../foo" 405 | --- no_error_log 406 | [error] 407 | [alert] 408 | 409 | 410 | 411 | === TEST 21: let subrequest to read the main request's request body 412 | --- SKIP 413 | --- config 414 | location /main { 415 | echo_subrequest_async POST /sub; 416 | } 417 | location /sub { 418 | echo_read_request_body; 419 | echo_request_body; 420 | } 421 | --- request 422 | POST /main 423 | hello, body! 424 | --- response_body chomp 425 | hello, body! 426 | 427 | 428 | 429 | === TEST 22: POST subrequest with file body (relative paths) 430 | --- config 431 | location /main { 432 | echo_subrequest_async POST /sub -f html/blah.txt; 433 | } 434 | location /sub { 435 | echo "sub method: $echo_request_method"; 436 | # we don't need to call echo_read_client_body explicitly here 437 | echo_request_body; 438 | } 439 | --- user_files 440 | >>> blah.txt 441 | Hello, world 442 | --- request 443 | GET /main 444 | --- response_body 445 | sub method: POST 446 | Hello, world 447 | 448 | 449 | 450 | === TEST 23: POST subrequest with file body (absolute paths) 451 | --- config 452 | location /main { 453 | echo_subrequest_async POST /sub -f $TEST_NGINX_HTML_DIR/blah.txt; 454 | } 455 | location /sub { 456 | echo "sub method: $echo_request_method"; 457 | # we don't need to call echo_read_client_body explicitly here 458 | echo_request_body; 459 | } 460 | --- user_files 461 | >>> blah.txt 462 | Hello, world! 463 | Haha 464 | --- request 465 | GET /main 466 | --- response_body 467 | sub method: POST 468 | Hello, world! 469 | Haha 470 | 471 | 472 | 473 | === TEST 24: POST subrequest with file body (file not found) 474 | --- config 475 | location /main { 476 | echo_subrequest_async POST /sub -f html/blah/blah.txt; 477 | } 478 | location /sub { 479 | echo "sub method: $echo_request_method"; 480 | # we don't need to call echo_read_client_body explicitly here 481 | echo_request_body; 482 | } 483 | --- user_files 484 | >>> blah.txt 485 | Hello, world 486 | --- request 487 | GET /main 488 | --- ignore_response 489 | --- error_log eval 490 | qr/open\(\) ".*?" failed/ 491 | --- no_error_log 492 | [alert] 493 | 494 | 495 | 496 | === TEST 25: POST subrequest with file body (absolute paths in vars) 497 | --- config 498 | location /main { 499 | set $path $TEST_NGINX_HTML_DIR/blah.txt; 500 | echo_subrequest_async POST /sub -f $path; 501 | } 502 | location /sub { 503 | echo "sub method: $echo_request_method"; 504 | # we don't need to call echo_read_client_body explicitly here 505 | echo_request_body; 506 | } 507 | --- user_files 508 | >>> blah.txt 509 | Hello, world! 510 | Haha 511 | --- request 512 | GET /main 513 | --- response_body 514 | sub method: POST 515 | Hello, world! 516 | Haha 517 | 518 | 519 | 520 | === TEST 26: leading subrequest & echo_before_body 521 | --- config 522 | location /main { 523 | echo_before_body hello; 524 | echo_subrequest_async GET /foo; 525 | } 526 | location /foo { 527 | echo world; 528 | } 529 | --- request 530 | GET /main 531 | --- response_body 532 | hello 533 | world 534 | 535 | 536 | 537 | === TEST 27: leading subrequest & xss 538 | --- config 539 | location /main { 540 | default_type 'application/json'; 541 | xss_get on; 542 | xss_callback_arg c; 543 | echo_subrequest_async GET /foo; 544 | } 545 | location /foo { 546 | echo -n world; 547 | } 548 | --- request 549 | GET /main?c=hi 550 | --- response_body chop 551 | hi(world); 552 | 553 | 554 | 555 | === TEST 28: multiple leading subrequest & xss 556 | --- config 557 | location /main { 558 | default_type 'application/json'; 559 | xss_get on; 560 | xss_callback_arg c; 561 | echo_subrequest_async GET /foo; 562 | echo_subrequest_async GET /bar; 563 | } 564 | location /foo { 565 | echo -n world; 566 | } 567 | location /bar { 568 | echo -n ' people'; 569 | } 570 | --- request 571 | GET /main?c=hi 572 | --- response_body chop 573 | hi(world people); 574 | 575 | 576 | 577 | === TEST 29: sanity (HEAD) 578 | --- config 579 | location /main { 580 | echo_subrequest_async GET /sub; 581 | echo_subrequest_async GET /sub; 582 | } 583 | location /sub { 584 | echo hello; 585 | } 586 | --- request 587 | HEAD /main 588 | --- response_body 589 | 590 | 591 | 592 | === TEST 30: HEAD subrequest 593 | --- config 594 | location /main { 595 | echo_subrequest_async HEAD /sub; 596 | echo_subrequest_async HEAD /sub; 597 | } 598 | location /sub { 599 | echo hello; 600 | } 601 | --- request 602 | GET /main 603 | --- response_body 604 | 605 | -------------------------------------------------------------------------------- /src/ngx_http_echo_request_info.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Yichun Zhang (agentzh) 4 | */ 5 | 6 | 7 | #ifndef DDEBUG 8 | #define DDEBUG 0 9 | #endif 10 | #include "ddebug.h" 11 | 12 | #include "ngx_http_echo_request_info.h" 13 | #include "ngx_http_echo_util.h" 14 | #include "ngx_http_echo_handler.h" 15 | 16 | #include 17 | 18 | 19 | static void ngx_http_echo_post_read_request_body(ngx_http_request_t *r); 20 | #if nginx_version >= 1011011 21 | void ngx_http_echo_request_headers_cleanup(void *data); 22 | #endif 23 | 24 | 25 | ngx_int_t 26 | ngx_http_echo_exec_echo_read_request_body(ngx_http_request_t *r, 27 | ngx_http_echo_ctx_t *ctx) 28 | { 29 | return ngx_http_read_client_request_body(r, 30 | ngx_http_echo_post_read_request_body); 31 | } 32 | 33 | 34 | static void 35 | ngx_http_echo_post_read_request_body(ngx_http_request_t *r) 36 | { 37 | ngx_http_echo_ctx_t *ctx; 38 | 39 | ctx = ngx_http_get_module_ctx(r, ngx_http_echo_module); 40 | 41 | dd("wait read request body %d", (int) ctx->wait_read_request_body); 42 | 43 | if (ctx->wait_read_request_body) { 44 | ctx->waiting = 0; 45 | ctx->done = 1; 46 | 47 | r->write_event_handler = ngx_http_echo_wev_handler; 48 | 49 | ngx_http_echo_wev_handler(r); 50 | } 51 | } 52 | 53 | 54 | /* this function's implementation is borrowed from nginx 0.8.20 55 | * and modified a bit to work with subrequests. 56 | * Copyrighted (C) by Igor Sysoev */ 57 | ngx_int_t 58 | ngx_http_echo_request_method_variable(ngx_http_request_t *r, 59 | ngx_http_variable_value_t *v, uintptr_t data) 60 | { 61 | if (r->method_name.data) { 62 | v->len = r->method_name.len; 63 | v->valid = 1; 64 | v->no_cacheable = 0; 65 | v->not_found = 0; 66 | v->data = r->method_name.data; 67 | 68 | } else { 69 | v->not_found = 1; 70 | } 71 | 72 | return NGX_OK; 73 | } 74 | 75 | 76 | /* this function's implementation is borrowed from nginx 0.8.20 77 | * and modified a bit to work with subrequests. 78 | * Copyrighted (C) by Igor Sysoev */ 79 | ngx_int_t 80 | ngx_http_echo_client_request_method_variable(ngx_http_request_t *r, 81 | ngx_http_variable_value_t *v, uintptr_t data) 82 | { 83 | if (r->main->method_name.data) { 84 | v->len = r->main->method_name.len; 85 | v->valid = 1; 86 | v->no_cacheable = 0; 87 | v->not_found = 0; 88 | v->data = r->main->method_name.data; 89 | 90 | } else { 91 | v->not_found = 1; 92 | } 93 | 94 | return NGX_OK; 95 | } 96 | 97 | 98 | /* this function's implementation is borrowed from nginx 0.8.20 99 | * and modified a bit to work with subrequests. 100 | * Copyrighted (C) by Igor Sysoev */ 101 | ngx_int_t 102 | ngx_http_echo_request_body_variable(ngx_http_request_t *r, 103 | ngx_http_variable_value_t *v, uintptr_t data) 104 | { 105 | u_char *p; 106 | size_t len; 107 | ngx_buf_t *b; 108 | ngx_chain_t *cl; 109 | ngx_chain_t *in; 110 | 111 | if (r->request_body == NULL 112 | || r->request_body->bufs == NULL 113 | || r->request_body->temp_file) 114 | { 115 | v->not_found = 1; 116 | 117 | return NGX_OK; 118 | } 119 | 120 | in = r->request_body->bufs; 121 | 122 | len = 0; 123 | for (cl = in; cl; cl = cl->next) { 124 | b = cl->buf; 125 | 126 | if (!ngx_buf_in_memory(b)) { 127 | if (b->in_file) { 128 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 129 | "variable echo_request_body sees in-file only " 130 | "buffers and discard the whole body data"); 131 | 132 | v->not_found = 1; 133 | 134 | return NGX_OK; 135 | } 136 | 137 | } else { 138 | len += b->last - b->pos; 139 | } 140 | } 141 | 142 | p = ngx_pnalloc(r->pool, len); 143 | if (p == NULL) { 144 | return NGX_ERROR; 145 | } 146 | 147 | v->data = p; 148 | 149 | for (cl = in; cl; cl = cl->next) { 150 | b = cl->buf; 151 | 152 | if (ngx_buf_in_memory(b)) { 153 | p = ngx_copy(p, b->pos, b->last - b->pos); 154 | } 155 | } 156 | 157 | if (p - v->data != (ssize_t) len) { 158 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 159 | "variable echo_request_body: buffer error"); 160 | 161 | v->not_found = 1; 162 | 163 | return NGX_OK; 164 | } 165 | 166 | v->len = len; 167 | v->valid = 1; 168 | v->no_cacheable = 0; 169 | v->not_found = 0; 170 | 171 | return NGX_OK; 172 | } 173 | 174 | 175 | ngx_int_t 176 | ngx_http_echo_client_request_headers_variable(ngx_http_request_t *r, 177 | ngx_http_variable_value_t *v, uintptr_t data) 178 | { 179 | int line_break_len; 180 | size_t size; 181 | u_char *p, *last, *pos; 182 | ngx_int_t i, j; 183 | ngx_buf_t *b, *first = NULL; 184 | unsigned found; 185 | #if nginx_version >= 1011011 186 | ngx_buf_t **bb; 187 | ngx_chain_t *cl; 188 | ngx_http_echo_main_conf_t *emcf; 189 | #endif 190 | ngx_connection_t *c; 191 | ngx_http_request_t *mr; 192 | ngx_http_connection_t *hc; 193 | 194 | mr = r->main; 195 | hc = r->main->http_connection; 196 | c = mr->connection; 197 | 198 | #if (NGX_HTTP_V2) 199 | /* TODO */ 200 | if (mr->stream) { 201 | v->not_found = 1; 202 | return NGX_OK; 203 | } 204 | #endif 205 | 206 | #if nginx_version >= 1011011 207 | emcf = ngx_http_get_module_main_conf(r, ngx_http_echo_module); 208 | #endif 209 | 210 | size = 0; 211 | b = c->buffer; 212 | 213 | if (mr->request_line.data[mr->request_line.len] == CR) { 214 | line_break_len = 2; 215 | 216 | } else { 217 | line_break_len = 1; 218 | } 219 | 220 | if (mr->request_line.data >= b->start 221 | && mr->request_line.data + mr->request_line.len + line_break_len 222 | <= b->pos) 223 | { 224 | first = b; 225 | size += b->pos - mr->request_line.data; 226 | } 227 | 228 | if (hc->nbusy) { 229 | b = NULL; 230 | 231 | #if nginx_version >= 1011011 232 | if (hc->nbusy > emcf->busy_buf_ptr_count) { 233 | if (emcf->busy_buf_ptrs) { 234 | ngx_free(emcf->busy_buf_ptrs); 235 | } 236 | 237 | emcf->busy_buf_ptrs = ngx_alloc(hc->nbusy * sizeof(ngx_buf_t *), 238 | r->connection->log); 239 | 240 | if (emcf->busy_buf_ptrs == NULL) { 241 | return NGX_ERROR; 242 | } 243 | 244 | emcf->busy_buf_ptr_count = hc->nbusy; 245 | } 246 | 247 | bb = emcf->busy_buf_ptrs; 248 | for (cl = hc->busy; cl; cl = cl->next) { 249 | *bb++ = cl->buf; 250 | } 251 | 252 | bb = emcf->busy_buf_ptrs; 253 | for (i = hc->nbusy; i > 0; i--) { 254 | b = bb[i - 1]; 255 | #else 256 | for (i = 0; i < hc->nbusy; i++) { 257 | b = hc->busy[i]; 258 | #endif 259 | 260 | if (first == NULL) { 261 | if (mr->request_line.data >= b->pos 262 | || mr->request_line.data + mr->request_line.len 263 | + line_break_len <= b->start) 264 | { 265 | continue; 266 | } 267 | 268 | dd("found first at %d", (int) i); 269 | first = b; 270 | } 271 | 272 | size += b->pos - b->start; 273 | } 274 | } 275 | 276 | 277 | size++; /* plus the null terminator, as required by the later 278 | ngx_strstr() call */ 279 | 280 | v->data = ngx_palloc(r->pool, size); 281 | if (v->data == NULL) { 282 | return NGX_ERROR; 283 | } 284 | 285 | last = v->data; 286 | 287 | b = c->buffer; 288 | found = 0; 289 | 290 | if (first == b) { 291 | found = 1; 292 | pos = b->pos; 293 | 294 | last = ngx_copy(v->data, mr->request_line.data, 295 | pos - mr->request_line.data); 296 | 297 | if (b != mr->header_in) { 298 | /* skip truncated header entries (if any) */ 299 | while (last > v->data && last[-1] != LF) { 300 | last--; 301 | } 302 | } 303 | 304 | i = 0; 305 | for (p = v->data; p != last; p++) { 306 | if (*p == '\0') { 307 | i++; 308 | if (p + 1 != last && *(p + 1) == LF) { 309 | *p = CR; 310 | 311 | } else if (i % 2 == 1) { 312 | *p = ':'; 313 | 314 | } else { 315 | *p = LF; 316 | } 317 | } 318 | } 319 | } 320 | 321 | if (hc->nbusy) { 322 | 323 | #if nginx_version >= 1011011 324 | bb = emcf->busy_buf_ptrs; 325 | for (i = hc->nbusy; i > 0; i--) { 326 | b = bb[i - 1]; 327 | #else 328 | for (i = 0; i < hc->nbusy; i++) { 329 | b = hc->busy[i]; 330 | #endif 331 | 332 | if (!found) { 333 | if (b != first) { 334 | continue; 335 | } 336 | 337 | dd("found first"); 338 | found = 1; 339 | } 340 | 341 | p = last; 342 | 343 | pos = b->pos; 344 | 345 | if (b == first) { 346 | dd("request line: %.*s", (int) mr->request_line.len, 347 | mr->request_line.data); 348 | 349 | last = ngx_copy(last, 350 | mr->request_line.data, 351 | pos - mr->request_line.data); 352 | 353 | } else { 354 | last = ngx_copy(last, b->start, pos - b->start); 355 | } 356 | 357 | #if 1 358 | /* skip truncated header entries (if any) */ 359 | while (last > p && last[-1] != LF) { 360 | last--; 361 | } 362 | #endif 363 | 364 | j = 0; 365 | for (; p != last; p++) { 366 | if (*p == '\0') { 367 | j++; 368 | if (p + 1 == last) { 369 | /* XXX this should not happen */ 370 | dd("found string end!!"); 371 | 372 | } else if (*(p + 1) == LF) { 373 | *p = CR; 374 | 375 | } else if (j % 2 == 1) { 376 | *p = ':'; 377 | 378 | } else { 379 | *p = LF; 380 | } 381 | } 382 | } 383 | 384 | if (b == mr->header_in) { 385 | break; 386 | } 387 | } 388 | } 389 | 390 | *last++ = '\0'; 391 | 392 | if (last - v->data > (ssize_t) size) { 393 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 394 | "buffer error when evaluating " 395 | "$echo_client__request_headers: \"%V\"", 396 | (ngx_int_t) (last - v->data - size)); 397 | 398 | return NGX_ERROR; 399 | } 400 | 401 | /* strip the leading part (if any) of the request body in our header. 402 | * the first part of the request body could slip in because nginx core's 403 | * ngx_http_request_body_length_filter and etc can move r->header_in->pos 404 | * in case that some of the body data has been preread into r->header_in. 405 | */ 406 | 407 | if ((p = (u_char *) ngx_strstr(v->data, CRLF CRLF)) != NULL) { 408 | last = p + sizeof(CRLF CRLF) - 1; 409 | 410 | } else if ((p = (u_char *) ngx_strstr(v->data, CRLF "\n")) != NULL) { 411 | last = p + sizeof(CRLF "\n") - 1; 412 | 413 | } else if ((p = (u_char *) ngx_strstr(v->data, "\n" CRLF)) != NULL) { 414 | last = p + sizeof("\n" CRLF) - 1; 415 | 416 | } else { 417 | for (p = last - 1; p - v->data >= 2; p--) { 418 | if (p[0] == LF && p[-1] == CR) { 419 | p[-1] = LF; 420 | last = p + 1; 421 | break; 422 | } 423 | 424 | if (p[0] == LF && p[-1] == LF) { 425 | last = p + 1; 426 | break; 427 | } 428 | } 429 | } 430 | 431 | v->len = last - v->data; 432 | v->valid = 1; 433 | v->no_cacheable = 0; 434 | v->not_found = 0; 435 | 436 | return NGX_OK; 437 | } 438 | 439 | 440 | ngx_int_t 441 | ngx_http_echo_cacheable_request_uri_variable(ngx_http_request_t *r, 442 | ngx_http_variable_value_t *v, uintptr_t data) 443 | { 444 | if (r->uri.len) { 445 | v->len = r->uri.len; 446 | v->valid = 1; 447 | v->no_cacheable = 0; 448 | v->not_found = 0; 449 | v->data = r->uri.data; 450 | 451 | } else { 452 | v->not_found = 1; 453 | } 454 | 455 | return NGX_OK; 456 | } 457 | 458 | 459 | ngx_int_t 460 | ngx_http_echo_request_uri_variable(ngx_http_request_t *r, 461 | ngx_http_variable_value_t *v, uintptr_t data) 462 | { 463 | if (r->uri.len) { 464 | v->len = r->uri.len; 465 | v->valid = 1; 466 | v->no_cacheable = 1; 467 | v->not_found = 0; 468 | v->data = r->uri.data; 469 | 470 | } else { 471 | v->not_found = 1; 472 | } 473 | 474 | return NGX_OK; 475 | } 476 | 477 | 478 | ngx_int_t 479 | ngx_http_echo_response_status_variable(ngx_http_request_t *r, 480 | ngx_http_variable_value_t *v, uintptr_t data) 481 | { 482 | u_char *p; 483 | 484 | if (r->headers_out.status) { 485 | dd("headers out status: %d", (int) r->headers_out.status); 486 | 487 | p = ngx_palloc(r->pool, NGX_INT_T_LEN); 488 | if (p == NULL) { 489 | return NGX_ERROR; 490 | } 491 | 492 | v->len = ngx_sprintf(p, "%ui", r->headers_out.status) - p; 493 | v->data = p; 494 | 495 | v->valid = 1; 496 | v->no_cacheable = 1; 497 | v->not_found = 0; 498 | 499 | } else { 500 | v->not_found = 1; 501 | } 502 | 503 | return NGX_OK; 504 | } 505 | 506 | 507 | #if nginx_version >= 1011011 508 | void 509 | ngx_http_echo_request_headers_cleanup(void *data) 510 | { 511 | ngx_http_echo_main_conf_t *emcf; 512 | 513 | emcf = (ngx_http_echo_main_conf_t *) data; 514 | 515 | if (emcf->busy_buf_ptrs) { 516 | ngx_free(emcf->busy_buf_ptrs); 517 | emcf->busy_buf_ptrs = NULL; 518 | } 519 | } 520 | #endif 521 | 522 | /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ 523 | -------------------------------------------------------------------------------- /t/subrequest.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | 5 | use Test::Nginx::Socket; 6 | 7 | repeat_each(2); 8 | 9 | plan tests => repeat_each() * (2 * blocks() + 1); 10 | 11 | $ENV{TEST_NGINX_HTML_DIR} = html_dir; 12 | $ENV{TEST_NGINX_CLIENT_PORT} ||= server_port(); 13 | 14 | run_tests(); 15 | 16 | __DATA__ 17 | 18 | === TEST 1: sanity 19 | --- config 20 | location /main { 21 | echo_subrequest GET /sub; 22 | } 23 | location /sub { 24 | echo hello; 25 | } 26 | --- request 27 | GET /main 28 | --- response_body 29 | hello 30 | 31 | 32 | 33 | === TEST 2: trailing echo 34 | --- config 35 | location /main { 36 | echo_subrequest GET /sub; 37 | echo after subrequest; 38 | } 39 | location /sub { 40 | echo hello; 41 | } 42 | --- request 43 | GET /main 44 | --- response_body 45 | hello 46 | after subrequest 47 | 48 | 49 | 50 | === TEST 3: leading echo 51 | --- config 52 | location /main { 53 | echo before subrequest; 54 | echo_subrequest GET /sub; 55 | } 56 | location /sub { 57 | echo hello; 58 | } 59 | --- request 60 | GET /main 61 | --- response_body 62 | before subrequest 63 | hello 64 | 65 | 66 | 67 | === TEST 4: leading & trailing echo 68 | --- config 69 | location /main { 70 | echo before subrequest; 71 | echo_subrequest GET /sub; 72 | echo after subrequest; 73 | } 74 | location /sub { 75 | echo hello; 76 | } 77 | --- request 78 | GET /main 79 | --- response_body 80 | before subrequest 81 | hello 82 | after subrequest 83 | 84 | 85 | 86 | === TEST 5: multiple subrequests 87 | --- config 88 | location /main { 89 | echo before sr 1; 90 | echo_subrequest GET /sub; 91 | echo after sr 1; 92 | echo before sr 2; 93 | echo_subrequest GET /sub; 94 | echo after sr 2; 95 | } 96 | location /sub { 97 | echo hello; 98 | } 99 | --- request 100 | GET /main 101 | --- response_body 102 | before sr 1 103 | hello 104 | after sr 1 105 | before sr 2 106 | hello 107 | after sr 2 108 | 109 | 110 | 111 | === TEST 6: timed multiple subrequests (blocking sleep) 112 | --- config 113 | location /main { 114 | echo_reset_timer; 115 | echo_subrequest GET /sub1; 116 | echo_subrequest GET /sub2; 117 | echo "took $echo_timer_elapsed sec for total."; 118 | } 119 | location /sub1 { 120 | echo_blocking_sleep 0.02; 121 | echo hello; 122 | } 123 | location /sub2 { 124 | echo_blocking_sleep 0.01; 125 | echo world; 126 | } 127 | 128 | --- request 129 | GET /main 130 | --- response_body_like 131 | ^hello 132 | world 133 | took 0\.0(?:2[5-9]|3[0-6]) sec for total\.$ 134 | 135 | 136 | 137 | === TEST 7: timed multiple subrequests (non-blocking sleep) 138 | --- config 139 | location /main { 140 | echo_reset_timer; 141 | echo_subrequest GET /sub1; 142 | echo_subrequest GET /sub2; 143 | echo "took $echo_timer_elapsed sec for total."; 144 | } 145 | location /sub1 { 146 | echo_sleep 0.02; 147 | echo hello; 148 | } 149 | location /sub2 { 150 | echo_sleep 0.01; 151 | echo world; 152 | } 153 | 154 | --- request 155 | GET /main 156 | --- response_body_like 157 | ^hello 158 | world 159 | took 0\.0(?:2[5-9]|3[0-6]) sec for total\.$ 160 | 161 | 162 | 163 | === TEST 8: location with args 164 | --- config 165 | location /main { 166 | echo_subrequest GET /sub -q 'foo=Foo&bar=Bar'; 167 | } 168 | location /sub { 169 | echo $arg_foo $arg_bar; 170 | } 171 | --- request 172 | GET /main 173 | --- response_body 174 | Foo Bar 175 | 176 | 177 | 178 | === TEST 9: chained subrequests 179 | --- config 180 | location /main { 181 | echo 'pre main'; 182 | echo_subrequest GET /sub; 183 | echo 'post main'; 184 | } 185 | 186 | location /sub { 187 | echo 'pre sub'; 188 | echo_subrequest GET /subsub; 189 | echo 'post sub'; 190 | } 191 | 192 | location /subsub { 193 | echo 'subsub'; 194 | } 195 | --- request 196 | GET /main 197 | --- response_body 198 | pre main 199 | pre sub 200 | subsub 201 | post sub 202 | post main 203 | 204 | 205 | 206 | === TEST 10: chained subrequests using named locations 207 | as of 0.8.20, ngx_http_subrequest still does not support 208 | named location. sigh. this case is a TODO. 209 | --- config 210 | location /main { 211 | echo 'pre main'; 212 | echo_subrequest GET @sub; 213 | echo 'post main'; 214 | } 215 | 216 | location @sub { 217 | echo 'pre sub'; 218 | echo_subrequest GET @subsub; 219 | echo 'post sub'; 220 | } 221 | 222 | location @subsub { 223 | echo 'subsub'; 224 | } 225 | --- request 226 | GET /main 227 | --- response_body 228 | pre main 229 | pre sub 230 | subsub 231 | post sub 232 | post main 233 | --- SKIP 234 | 235 | 236 | 237 | === TEST 11: explicit flush in main request 238 | --- config 239 | location /main { 240 | echo 'pre main'; 241 | echo_subrequest GET /sub; 242 | echo 'post main'; 243 | echo_flush; 244 | } 245 | 246 | location /sub { 247 | echo_sleep 0.02; 248 | echo 'sub'; 249 | } 250 | --- request 251 | GET /main 252 | --- response_body 253 | pre main 254 | sub 255 | post main 256 | 257 | 258 | 259 | === TEST 12: DELETE subrequest 260 | --- config 261 | location /main { 262 | echo_subrequest DELETE /sub; 263 | } 264 | location /sub { 265 | echo "sub method: $echo_request_method"; 266 | echo "main method: $echo_client_request_method"; 267 | } 268 | --- request 269 | GET /main 270 | --- response_body 271 | sub method: DELETE 272 | main method: GET 273 | 274 | 275 | 276 | === TEST 13: DELETE subrequest 277 | --- config 278 | location /main { 279 | echo "main method: $echo_client_request_method"; 280 | echo_subrequest GET /proxy; 281 | echo_subrequest DELETE /proxy; 282 | } 283 | location /proxy { 284 | proxy_pass $scheme://127.0.0.1:$server_port/sub; 285 | } 286 | location /sub { 287 | echo "sub method: $echo_request_method"; 288 | } 289 | --- request 290 | GET /main 291 | --- response_body 292 | main method: GET 293 | sub method: GET 294 | sub method: DELETE 295 | 296 | 297 | 298 | === TEST 14: POST subrequest with body 299 | --- config 300 | location /main { 301 | echo_subrequest POST /sub -b 'hello, world'; 302 | } 303 | location /sub { 304 | echo "sub method: $echo_request_method"; 305 | # we don't need to call echo_read_client_body explicitly here 306 | echo "sub body: $echo_request_body"; 307 | } 308 | --- request 309 | GET /main 310 | --- response_body 311 | sub method: POST 312 | sub body: hello, world 313 | 314 | 315 | 316 | === TEST 15: POST subrequest with body (explicitly read the body) 317 | --- config 318 | location /main { 319 | echo_subrequest POST /sub -b 'hello, world'; 320 | } 321 | location /sub { 322 | echo "sub method: $echo_request_method"; 323 | # we call echo_read_client_body explicitly here even 324 | # though it's not necessary. 325 | echo_read_request_body; 326 | echo "sub body: $echo_request_body"; 327 | } 328 | --- request 329 | GET /main 330 | --- response_body 331 | sub method: POST 332 | sub body: hello, world 333 | 334 | 335 | 336 | === TEST 16: POST subrequest with body (with proxy in the middle) and without read body explicitly 337 | --- config 338 | location /main { 339 | echo_subrequest POST /proxy -b 'hello, world'; 340 | } 341 | location /proxy { 342 | proxy_pass $scheme://127.0.0.1:$server_port/sub; 343 | } 344 | location /sub { 345 | echo "sub method: $echo_request_method."; 346 | # we need to read body explicitly here...or $echo_request_body 347 | # will evaluate to empty ("") 348 | echo "sub body: $echo_request_body."; 349 | } 350 | --- request 351 | GET /main 352 | --- response_body 353 | sub method: POST. 354 | sub body: . 355 | 356 | 357 | 358 | === TEST 17: POST subrequest with body (with proxy in the middle) and read body explicitly 359 | --- config 360 | location /main { 361 | echo_subrequest POST /proxy -b 'hello, world'; 362 | } 363 | location /proxy { 364 | proxy_pass $scheme://127.0.0.1:$server_port/sub; 365 | } 366 | location /sub { 367 | echo "sub method: $echo_request_method."; 368 | # we need to read body explicitly here...or $echo_request_body 369 | # will evaluate to empty ("") 370 | echo_read_request_body; 371 | echo "sub body: $echo_request_body."; 372 | } 373 | --- request 374 | GET /main 375 | --- response_body 376 | sub method: POST. 377 | sub body: hello, world. 378 | 379 | 380 | 381 | === TEST 18: multiple subrequests 382 | --- config 383 | location /multi { 384 | echo_subrequest POST '/sub' -q 'foo=Foo' -b 'hi'; 385 | echo_subrequest PUT '/sub' -q 'bar=Bar' -b 'hello'; 386 | } 387 | location /sub { 388 | echo "querystring: $query_string"; 389 | echo "method: $echo_request_method"; 390 | echo "body: $echo_request_body"; 391 | echo "content length: $http_content_length"; 392 | echo '///'; 393 | } 394 | --- request 395 | GET /multi 396 | --- response_body 397 | querystring: foo=Foo 398 | method: POST 399 | body: hi 400 | content length: 2 401 | /// 402 | querystring: bar=Bar 403 | method: PUT 404 | body: hello 405 | content length: 5 406 | /// 407 | 408 | 409 | 410 | === TEST 19: unsafe uri 411 | --- config 412 | location /unsafe { 413 | echo_subrequest GET '/../foo'; 414 | } 415 | --- request 416 | GET /unsafe 417 | --- ignore_response 418 | --- error_log 419 | echo_subrequest sees unsafe uri: "/../foo" 420 | --- no_error_log 421 | [error] 422 | 423 | 424 | 425 | === TEST 20: querystring in url 426 | --- config 427 | location /main { 428 | echo_subrequest GET /sub?foo=Foo&bar=Bar; 429 | } 430 | location /sub { 431 | echo $arg_foo $arg_bar; 432 | } 433 | --- request 434 | GET /main 435 | --- response_body 436 | Foo Bar 437 | 438 | 439 | 440 | === TEST 21: querystring in url *AND* an explicit querystring 441 | --- config 442 | location /main { 443 | echo_subrequest GET /sub?foo=Foo&bar=Bar -q blah=Blah; 444 | } 445 | location /sub { 446 | echo $arg_foo $arg_bar $arg_blah; 447 | } 448 | --- request 449 | GET /main 450 | --- response_body 451 | Blah 452 | 453 | 454 | 455 | === TEST 22: let subrequest to read the main request's request body 456 | --- SKIP 457 | --- config 458 | location /main { 459 | echo_subrequest POST /sub; 460 | } 461 | location /sub { 462 | echo_read_request_body; 463 | echo_request_body; 464 | } 465 | --- request 466 | POST /main 467 | hello, body! 468 | --- response_body chomp 469 | hello, body! 470 | 471 | 472 | 473 | === TEST 23: deep nested echo_subrequest/echo_subrequest_async 474 | --- config 475 | location /main { 476 | echo_subrequest GET /bar; 477 | echo_subrequest_async GET /bar; 478 | echo_subrequest_async GET /bar; 479 | echo_subrequest GET /group; 480 | echo_subrequest_async GET /group; 481 | } 482 | 483 | location /group { 484 | echo_subrequest GET /bar; 485 | echo_subrequest_async GET /bar; 486 | } 487 | 488 | location /bar { 489 | echo $echo_incr; 490 | } 491 | --- request 492 | GET /main 493 | --- response_body 494 | 1 495 | 2 496 | 3 497 | 4 498 | 5 499 | 6 500 | 7 501 | 502 | 503 | 504 | === TEST 24: deep nested echo_subrequest/echo_subrequest_async 505 | --- config 506 | location /main { 507 | echo_subrequest GET /bar?a; 508 | echo_subrequest_async GET /bar?b; 509 | echo_subrequest_async GET /bar?c; 510 | echo_subrequest GET /group?a=d&b=e; 511 | echo_subrequest_async GET /group?a=f&b=g; 512 | } 513 | 514 | location /group { 515 | echo_subrequest GET /bar?$arg_a; 516 | echo_subrequest_async GET /bar?$arg_b; 517 | } 518 | 519 | location /bar { 520 | echo -n $query_string; 521 | } 522 | --- request 523 | GET /main 524 | --- response_body: abcdefg 525 | --- timeout: 2 526 | 527 | 528 | 529 | === TEST 25: POST subrequest with file body (relative paths) 530 | --- config 531 | location /main { 532 | echo_subrequest POST /sub -f html/blah.txt; 533 | } 534 | location /sub { 535 | echo "sub method: $echo_request_method"; 536 | # we don't need to call echo_read_client_body explicitly here 537 | echo_request_body; 538 | } 539 | --- user_files 540 | >>> blah.txt 541 | Hello, world 542 | --- request 543 | GET /main 544 | --- response_body 545 | sub method: POST 546 | Hello, world 547 | 548 | 549 | 550 | === TEST 26: POST subrequest with file body (absolute paths) 551 | --- config 552 | location /main { 553 | echo_subrequest POST /sub -f $TEST_NGINX_HTML_DIR/blah.txt; 554 | } 555 | location /sub { 556 | echo "sub method: $echo_request_method"; 557 | # we don't need to call echo_read_client_body explicitly here 558 | echo_request_body; 559 | } 560 | --- user_files 561 | >>> blah.txt 562 | Hello, world! 563 | Haha 564 | --- request 565 | GET /main 566 | --- response_body 567 | sub method: POST 568 | Hello, world! 569 | Haha 570 | 571 | 572 | 573 | === TEST 27: POST subrequest with file body (file not found) 574 | --- config 575 | location /main { 576 | echo_subrequest POST /sub -f html/blah/blah.txt; 577 | } 578 | location /sub { 579 | echo "sub method: $echo_request_method"; 580 | # we don't need to call echo_read_client_body explicitly here 581 | echo_request_body; 582 | } 583 | --- user_files 584 | >>> blah.txt 585 | Hello, world 586 | --- request 587 | GET /main 588 | --- ignore_response 589 | --- error_log eval 590 | qr/open\(\) ".*?" failed/ 591 | --- no_error_log 592 | [alert] 593 | 594 | 595 | 596 | === TEST 28: leading subrequest & echo_before_body 597 | --- config 598 | location /main { 599 | echo_before_body hello; 600 | echo_subrequest GET /foo; 601 | } 602 | location /foo { 603 | echo world; 604 | } 605 | --- request 606 | GET /main 607 | --- response_body 608 | hello 609 | world 610 | 611 | 612 | 613 | === TEST 29: leading subrequest & xss 614 | --- config 615 | location /main { 616 | default_type 'application/json'; 617 | xss_get on; 618 | xss_callback_arg c; 619 | echo_subrequest GET /foo; 620 | } 621 | location /foo { 622 | echo -n world; 623 | } 624 | --- request 625 | GET /main?c=hi 626 | --- response_body chop 627 | hi(world); 628 | 629 | 630 | 631 | === TEST 30: multiple leading subrequest & xss 632 | --- config 633 | location /main { 634 | default_type 'application/json'; 635 | xss_get on; 636 | xss_callback_arg c; 637 | echo_subrequest GET /foo; 638 | echo_subrequest GET /bar; 639 | } 640 | location /foo { 641 | echo -n world; 642 | } 643 | location /bar { 644 | echo -n ' people'; 645 | } 646 | --- request 647 | GET /main?c=hi 648 | --- response_body chop 649 | hi(world people); 650 | 651 | 652 | 653 | === TEST 31: sanity (HEAD) 654 | --- config 655 | location /main { 656 | echo_subrequest GET /sub; 657 | echo_subrequest GET /sub; 658 | } 659 | location /sub { 660 | echo hello; 661 | } 662 | --- request 663 | HEAD /main 664 | --- response_body 665 | 666 | 667 | 668 | === TEST 32: POST subrequest to ngx_proxy 669 | --- config 670 | location /hello { 671 | default_type text/plain; 672 | echo_subrequest POST '/proxy' -q 'foo=Foo&bar=baz' -b 'request_body=test&test=3'; 673 | } 674 | 675 | location /proxy { 676 | proxy_pass http://127.0.0.1:$TEST_NGINX_CLIENT_PORT/sub; 677 | #proxy_pass http://127.0.0.1:1113/sub; 678 | } 679 | 680 | location /sub { 681 | echo_read_request_body; 682 | echo "sub method: $echo_request_method"; 683 | # we don't need to call echo_read_client_body explicitly here 684 | echo "sub body: $echo_request_body"; 685 | } 686 | --- request 687 | GET /hello 688 | --- response_body 689 | sub method: POST 690 | sub body: request_body=test&test=3 691 | 692 | 693 | 694 | === TEST 33: HEAD subrequest 695 | --- config 696 | location /main { 697 | echo_subrequest HEAD /sub; 698 | echo_subrequest HEAD /sub; 699 | } 700 | location /sub { 701 | echo hello; 702 | } 703 | --- request 704 | GET /main 705 | --- response_body 706 | 707 | 708 | 709 | === TEST 34: method name as an nginx variable (github issue #34) 710 | --- config 711 | location ~ ^/delay/(?[0-9.]+)/(?.*)$ { 712 | # echo_blocking_sleep $delay; 713 | echo_subrequest '$echo_request_method' '/$originalURL' -q '$args'; 714 | } 715 | 716 | location /api { 717 | echo "args: $args"; 718 | } 719 | --- request 720 | GET /delay/0.343/api/?a=b 721 | --- response_body 722 | args: a=b 723 | --- no_error_log 724 | [error] 725 | 726 | --------------------------------------------------------------------------------