├── .gitattributes ├── util ├── update-readme.sh ├── releng ├── build.sh └── wiki2pod.pl ├── src ├── ngx_http_rds_json_handler.h ├── ngx_http_rds.h ├── ngx_http_rds_json_util.h ├── ngx_http_rds_json_processor.h ├── ngx_http_rds_json_output.h ├── ddebug.h ├── resty_dbd_stream.h ├── ngx_http_rds_json_filter_module.h ├── ngx_http_rds_json_util.c ├── ngx_http_rds_utils.h ├── ngx_http_rds_json_handler.c └── ngx_http_rds_json_processor.c ├── .gitignore ├── t ├── form.t ├── ret.t ├── escape.t ├── unused.t ├── pg.t ├── 000_init.t ├── buf.t ├── compact │ ├── buf.t │ ├── sanity.t │ ├── sanity-stream.t │ └── openresty.t ├── sanity-stream.t ├── sanity.t ├── property.t └── openresty.t ├── config ├── .github └── workflows │ └── coverity.yml ├── .travis.yml ├── valgrind.suppress └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.t linguist-language=Text 2 | -------------------------------------------------------------------------------- /util/update-readme.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | perl util/wiki2pod.pl doc/manpage.wiki > /tmp/a.pod && pod2text /tmp/a.pod > README 4 | 5 | -------------------------------------------------------------------------------- /src/ngx_http_rds_json_handler.h: -------------------------------------------------------------------------------- 1 | #ifndef NGX_HTTP_RDS_JSON_HANDLER_H 2 | #define NGX_HTTP_RDS_JSON_HANDLER_H 3 | 4 | #include "ngx_http_rds_json_filter_module.h" 5 | 6 | ngx_int_t ngx_http_rds_json_ret_handler(ngx_http_request_t *r); 7 | 8 | #endif /* NGX_HTTP_RDS_JSON_HANDLER_H */ 9 | 10 | -------------------------------------------------------------------------------- /util/releng: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #./update-readme 4 | ack '(?<=\#define)\s*DDEBUG\s*1' src 5 | echo ==================================================== 6 | ack '(?<=_version_string) "\d+\.\d+\.\d+"' src 7 | ack '(?<=This document describes rds-json-nginx-module v)\d+\.\d+' README 8 | ack '.{81}' src/ngx_http_*.[ch] 9 | 10 | -------------------------------------------------------------------------------- /src/ngx_http_rds.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) agentzh 4 | */ 5 | 6 | #ifndef NGX_HTTP_RDS_H 7 | #define NGX_HTTP_RDS_H 8 | 9 | 10 | #include "resty_dbd_stream.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | typedef struct { 18 | uint16_t std_errcode; 19 | uint16_t drv_errcode; 20 | ngx_str_t errstr; 21 | 22 | uint64_t affected_rows; 23 | uint64_t insert_id; 24 | uint16_t col_count; 25 | 26 | } ngx_http_rds_header_t; 27 | 28 | 29 | typedef struct ngx_http_rds_column_s { 30 | rds_col_type_t std_type; 31 | uint16_t drv_type; 32 | 33 | ngx_str_t name; 34 | 35 | } ngx_http_rds_column_t; 36 | 37 | 38 | 39 | 40 | 41 | #endif /* NGX_HTTP_RDS_H */ 42 | 43 | -------------------------------------------------------------------------------- /src/ngx_http_rds_json_util.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Yichun Zhang (agentzh) 4 | */ 5 | 6 | 7 | #ifndef NGX_HTTP_RDS_JSON_UTIL_H 8 | #define NGX_HTTP_RDS_JSON_UTIL_H 9 | 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | #ifndef NGX_UINT64_LEN 18 | #define NGX_UINT64_LEN (sizeof("18446744073709551615") - 1) 19 | #endif 20 | 21 | #ifndef NGX_UINT16_LEN 22 | #define NGX_UINT16_LEN (sizeof("65535") - 1) 23 | #endif 24 | 25 | #ifndef ngx_copy_literal 26 | #define ngx_copy_literal(p, s) ngx_copy(p, s, sizeof(s) - 1) 27 | #endif 28 | 29 | 30 | uintptr_t ngx_http_rds_json_escape_json_str(u_char *dst, u_char *src, 31 | size_t size); 32 | 33 | ngx_int_t ngx_http_rds_json_test_content_type(ngx_http_request_t *r); 34 | 35 | void ngx_http_rds_json_discard_bufs(ngx_pool_t *pool, ngx_chain_t *in); 36 | 37 | 38 | #endif /* NGX_HTTP_RDS_JSON_UTIL_H */ 39 | -------------------------------------------------------------------------------- /src/ngx_http_rds_json_processor.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) agentzh 4 | */ 5 | 6 | #ifndef NGX_HTTP_RDS_JSON_PROCESSOR_H 7 | #define NGX_HTTP_RDS_JSON_PROCESSOR_H 8 | 9 | 10 | #include "ngx_http_rds_json_filter_module.h" 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | ngx_int_t ngx_http_rds_json_process_header(ngx_http_request_t *r, 18 | ngx_chain_t *in, ngx_http_rds_json_ctx_t *ctx); 19 | 20 | ngx_int_t ngx_http_rds_json_process_col(ngx_http_request_t *r, 21 | ngx_chain_t *in, ngx_http_rds_json_ctx_t *ctx); 22 | 23 | ngx_int_t ngx_http_rds_json_process_row(ngx_http_request_t *r, 24 | ngx_chain_t *in, ngx_http_rds_json_ctx_t *ctx); 25 | 26 | ngx_int_t ngx_http_rds_json_process_field(ngx_http_request_t *r, 27 | ngx_chain_t *in, ngx_http_rds_json_ctx_t *ctx); 28 | 29 | ngx_int_t ngx_http_rds_json_process_more_field_data(ngx_http_request_t *r, 30 | ngx_chain_t *in, ngx_http_rds_json_ctx_t *ctx); 31 | 32 | 33 | #endif /* NGX_HTTP_RDS_JSON_PROCESSOR_H */ 34 | 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.mobi 2 | genmobi.sh 3 | .libs 4 | *.swp 5 | *.slo 6 | *.la 7 | *.swo 8 | *.lo 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 | go 24 | t/t.sh 25 | test/t/servroot/ 26 | releng 27 | reset 28 | *.t_ 29 | src/handler.h 30 | src/util.c 31 | src/module.h 32 | src/module.c 33 | src/drizzle.c 34 | src/processor.h 35 | src/handler.c 36 | src/util.h 37 | src/drizzle.h 38 | src/processor.c 39 | src/output.c 40 | src/output.h 41 | libdrizzle 42 | ctags 43 | src/stream.h 44 | nginx 45 | keepalive 46 | reindex 47 | src/keepalive.c 48 | src/keepalive.h 49 | src/checker.h 50 | src/checker.c 51 | src/quoting.h 52 | src/quoting.c 53 | src/module.h 54 | src/module.c 55 | src/util.h 56 | src/util.c 57 | src/processor.h 58 | src/processor.c 59 | src/rds.h 60 | src/utils.h 61 | src/handler.c 62 | src/handler.h 63 | util/bench 64 | pack 65 | restart 66 | misc/ 67 | t/servroot/ 68 | all 69 | build1[0-9] 70 | buildroot/ 71 | *.html 72 | *.plist 73 | Makefile 74 | *.patch 75 | -------------------------------------------------------------------------------- /t/form.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 | $ENV{TEST_NGINX_MYSQL_HOST} ||= '127.0.0.1'; 11 | $ENV{TEST_NGINX_MYSQL_PORT} ||= 3306; 12 | 13 | our $http_config = <<'_EOC_'; 14 | upstream backend { 15 | drizzle_server $TEST_NGINX_MYSQL_HOST:$TEST_NGINX_MYSQL_PORT protocol=mysql 16 | dbname=ngx_test user=ngx_test password=ngx_test; 17 | } 18 | _EOC_ 19 | 20 | no_diff(); 21 | 22 | run_tests(); 23 | 24 | __DATA__ 25 | 26 | === TEST 1: sanity 27 | --- http_config eval: $::http_config 28 | --- config 29 | location /mysql { 30 | set_form_input $sql 'sql'; 31 | set_unescape_uri $sql; 32 | #echo $sql; 33 | drizzle_query $sql; 34 | drizzle_pass backend; 35 | rds_json on; 36 | } 37 | --- more_headers 38 | Content-Type: application/x-www-form-urlencoded 39 | --- request 40 | POST /mysql 41 | sql=select%20*%20from%20cats; 42 | --- response_body chomp 43 | [{"id":2,"name":null},{"id":3,"name":"bob"}] 44 | 45 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_rds_json_filter_module 2 | 3 | RDS_JSON_FILTER_SRCS=" \ 4 | $ngx_addon_dir/src/ngx_http_rds_json_filter_module.c \ 5 | $ngx_addon_dir/src/ngx_http_rds_json_processor.c \ 6 | $ngx_addon_dir/src/ngx_http_rds_json_util.c \ 7 | $ngx_addon_dir/src/ngx_http_rds_json_output.c \ 8 | $ngx_addon_dir/src/ngx_http_rds_json_handler.c \ 9 | " 10 | 11 | RDS_JSON_FILTER_DEPS=" \ 12 | $ngx_addon_dir/src/ddebug.h \ 13 | $ngx_addon_dir/src/resty_dbd_stream.h \ 14 | $ngx_addon_dir/src/ngx_http_rds_json_filter_module.h \ 15 | $ngx_addon_dir/src/ngx_http_rds_json_processor.h \ 16 | $ngx_addon_dir/src/ngx_http_rds_json_util.h \ 17 | $ngx_addon_dir/src/ngx_http_rds.h \ 18 | $ngx_addon_dir/src/resty_dbd_stream.h \ 19 | $ngx_addon_dir/src/ngx_http_rds_json_output.h \ 20 | $ngx_addon_dir/src/ngx_http_rds_utils.h \ 21 | $ngx_addon_dir/src/ngx_http_rds_json_handler.h \ 22 | " 23 | 24 | 25 | ngx_module_type=HTTP_AUX_FILTER 26 | ngx_module_name=$ngx_addon_name 27 | ngx_module_srcs="$RDS_JSON_FILTER_SRCS" 28 | ngx_module_deps="$RDS_JSON_FILTER_DEPS" 29 | 30 | . auto/module 31 | -------------------------------------------------------------------------------- /src/ngx_http_rds_json_output.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) agentzh 4 | */ 5 | 6 | 7 | #ifndef NGX_HTTP_RDS_JSON_OUTPUT_H 8 | #define NGX_HTTP_RDS_JSON_OUTPUT_H 9 | 10 | 11 | #include "ngx_http_rds_json_filter_module.h" 12 | #include "ngx_http_rds.h" 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | 19 | ngx_int_t ngx_http_rds_json_output_header(ngx_http_request_t *r, 20 | ngx_http_rds_json_ctx_t *ctx, ngx_http_rds_header_t *header); 21 | ngx_int_t ngx_http_rds_json_output_cols(ngx_http_request_t *r, 22 | ngx_http_rds_json_ctx_t *ctx); 23 | ngx_int_t ngx_http_rds_json_output_literal(ngx_http_request_t *r, 24 | ngx_http_rds_json_ctx_t *ctx, u_char *data, size_t len, int last_buf); 25 | ngx_int_t ngx_http_rds_json_output_bufs(ngx_http_request_t *r, 26 | ngx_http_rds_json_ctx_t *ctx); 27 | ngx_int_t ngx_http_rds_json_output_field(ngx_http_request_t *r, 28 | ngx_http_rds_json_ctx_t *ctx, u_char *data, size_t len, int is_null); 29 | ngx_int_t ngx_http_rds_json_output_more_field_data(ngx_http_request_t *r, 30 | ngx_http_rds_json_ctx_t *ctx, u_char *data, size_t len); 31 | ngx_int_t ngx_http_rds_json_output_props(ngx_http_request_t *r, 32 | ngx_http_rds_json_ctx_t *ctx, ngx_http_rds_json_loc_conf_t *conf); 33 | 34 | 35 | #endif /* NGX_HTTP_RDS_JSON_OUTPUT_H */ 36 | -------------------------------------------------------------------------------- /t/ret.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | repeat_each(3); 7 | #repeat_each(1); 8 | 9 | plan tests => repeat_each() * 3 * blocks(); 10 | 11 | no_long_string(); 12 | 13 | run_tests(); 14 | 15 | __DATA__ 16 | 17 | === TEST 1: sanity 18 | --- config 19 | location /foo { 20 | rds_json_ret 400 "Bad request"; 21 | } 22 | --- request 23 | GET /foo 24 | --- response_headers 25 | Content-Type: application/json 26 | --- response_body chop 27 | {"errcode":400,"errstr":"Bad request"} 28 | 29 | 30 | 31 | === TEST 2: empty errstr 32 | --- config 33 | location /foo { 34 | rds_json_ret 400 ""; 35 | } 36 | --- request 37 | GET /foo 38 | --- response_headers 39 | Content-Type: application/json 40 | --- response_body chop 41 | {"errcode":400} 42 | 43 | 44 | 45 | === TEST 3: set content type 46 | --- config 47 | rds_json_content_type 'text/javascript'; 48 | location /foo { 49 | rds_json_ret 400 "Bad request"; 50 | } 51 | --- request 52 | GET /foo 53 | --- response_headers 54 | Content-Type: text/javascript 55 | --- response_body chop 56 | {"errcode":400,"errstr":"Bad request"} 57 | 58 | 59 | 60 | === TEST 4: JSON escaping 61 | --- config 62 | location /foo { 63 | if ($arg_limit !~ '^\d+$') { 64 | rds_json_ret 400 'Invalid "limit" argument.'; 65 | } 66 | echo done; 67 | } 68 | --- request 69 | GET /foo 70 | --- response_headers 71 | Content-Type: application/json 72 | --- response_body chop 73 | {"errcode":400,"errstr":"Invalid \"limit\" argument."} 74 | 75 | -------------------------------------------------------------------------------- /t/escape.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 | $ENV{TEST_NGINX_MYSQL_HOST} ||= '127.0.0.1'; 11 | $ENV{TEST_NGINX_MYSQL_PORT} ||= 3306; 12 | 13 | our $http_config = <<'_EOC_'; 14 | upstream backend { 15 | drizzle_server $TEST_NGINX_MYSQL_HOST:$TEST_NGINX_MYSQL_PORT protocol=mysql 16 | dbname=ngx_test user=ngx_test password=ngx_test; 17 | } 18 | _EOC_ 19 | 20 | no_long_string(); 21 | no_diff(); 22 | 23 | run_tests(); 24 | 25 | __DATA__ 26 | 27 | === TEST 1: escaping column names (normal mode) 28 | --- http_config eval: $::http_config 29 | --- config 30 | location /mysql { 31 | drizzle_query " 32 | select `\"name\"`, height from birds order by height; 33 | "; 34 | drizzle_pass backend; 35 | rds_json on; 36 | } 37 | --- request 38 | GET /mysql 39 | --- response_body chomp 40 | [{"\"name\"":"hi,ya","height":-3},{"\"name\"":"\rkay","height":0.005},{"\"name\"":"ab;c","height":0.005},{"\"name\"":"hello \"tom","height":3.14},{"\"name\"":"hey\ndad","height":7},{"\"name\"":"foo\tbar","height":21}] 41 | 42 | 43 | 44 | === TEST 2: escaping column names (compact mode) 45 | --- http_config eval: $::http_config 46 | --- config 47 | location /mysql { 48 | drizzle_query " 49 | select `\"name\"`, height from birds order by height; 50 | "; 51 | drizzle_pass backend; 52 | rds_json on; 53 | rds_json_format compact; 54 | } 55 | --- request 56 | GET /mysql 57 | --- response_body chomp 58 | [["\"name\"","height"],["hi,ya",-3],["\rkay",0.005],["ab;c",0.005],["hello \"tom",3.14],["hey\ndad",7],["foo\tbar",21]] 59 | 60 | -------------------------------------------------------------------------------- /util/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # this file is mostly meant to be used by the author himself. 4 | 5 | root=`pwd` 6 | home=~ 7 | version=$1 8 | force=$2 9 | #opts=$2 10 | 11 | ngx-build $force $version \ 12 | --with-cc-opt="-O1" \ 13 | $NGX_EXTRA_OPT \ 14 | --with-ld-opt="-Wl,-rpath,/opt/drizzle/lib:/opt/pg9/lib" \ 15 | --without-mail_pop3_module \ 16 | --without-mail_imap_module \ 17 | --without-mail_smtp_module \ 18 | --without-http_upstream_ip_hash_module \ 19 | --without-http_empty_gif_module \ 20 | --without-http_memcached_module \ 21 | --without-http_referer_module \ 22 | --without-http_autoindex_module \ 23 | --without-http_auth_basic_module \ 24 | --without-http_userid_module \ 25 | --add-module=$root/../eval-nginx-module \ 26 | --add-module=$root/../echo-nginx-module \ 27 | --add-module=$root/../xss-nginx-module \ 28 | --add-module=$root/../ndk-nginx-module \ 29 | --add-module=$root/../set-misc-nginx-module \ 30 | --add-module=$root/../array-var-nginx-module \ 31 | --add-module=$root $opts \ 32 | --add-module=$root/../drizzle-nginx-module \ 33 | --add-module=$root/../form-input-nginx-module \ 34 | --add-module=$root/../postgres-nginx-module \ 35 | --with-debug 36 | #--add-module=$root/../lua-nginx-module \ 37 | #--add-module=$home/work/ngx_http_auth_request-0.1 #\ 38 | #--with-rtsig_module 39 | #--with-cc-opt="-g3 -O0" 40 | #--add-module=$root/../echo-nginx-module \ 41 | #--without-http_ssi_module # we cannot disable ssi because echo_location_async depends on it (i dunno why?!) 42 | 43 | -------------------------------------------------------------------------------- /t/unused.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | repeat_each(2); 7 | #repeat_each(1); 8 | 9 | plan tests => repeat_each() * (5 * blocks()); 10 | 11 | log_level('debug'); 12 | 13 | no_long_string(); 14 | 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: not using this module's directives at all 20 | --- config 21 | location /foo { 22 | echo Hello; 23 | } 24 | --- request 25 | GET /foo 26 | --- response_headers 27 | Content-Type: text/plain 28 | --- response_body 29 | Hello 30 | --- no_error_log 31 | rds json header filter, "/foo" 32 | rds json body filter, "/foo" 33 | 34 | 35 | 36 | === TEST 2: using rds_json_ret, but not rds_json 37 | --- config 38 | location /foo { 39 | rds_json_ret 400 "Bad request"; 40 | } 41 | --- request 42 | GET /foo 43 | --- response_headers 44 | Content-Type: application/json 45 | --- response_body chop 46 | {"errcode":400,"errstr":"Bad request"} 47 | --- no_error_log 48 | rds json header filter, "/foo" 49 | rds json body filter, "/foo" 50 | 51 | 52 | 53 | === TEST 3: using rds_json 54 | --- config 55 | location /foo { 56 | echo Hello; 57 | rds_json on; 58 | } 59 | --- request 60 | GET /foo 61 | --- response_headers 62 | Content-Type: text/plain 63 | --- response_body 64 | Hello 65 | --- error_log 66 | rds json header filter, "/foo" 67 | rds json body filter, "/foo" 68 | 69 | 70 | 71 | === TEST 4: multiple http {} blocks 72 | This test case won't run with nginx 1.9.3+ since duplicate http {} blocks 73 | have been prohibited since then. 74 | --- SKIP 75 | --- config 76 | location /foo { 77 | echo Hello; 78 | rds_json on; 79 | } 80 | 81 | --- post_main_config 82 | http { 83 | } 84 | 85 | --- request 86 | GET /foo 87 | --- response_headers 88 | Content-Type: text/plain 89 | --- response_body 90 | Hello 91 | --- error_log 92 | rds json header filter, "/foo" 93 | rds json body filter, "/foo" 94 | 95 | -------------------------------------------------------------------------------- /src/ddebug.h: -------------------------------------------------------------------------------- 1 | #ifndef DDEBUG_H 2 | #define DDEBUG_H 3 | 4 | #include 5 | #include 6 | 7 | #if defined(DDEBUG) && (DDEBUG) 8 | 9 | # define dd_dump_chain_size() { \ 10 | int n; \ 11 | ngx_chain_t *cl; \ 12 | \ 13 | for (n = 0, cl = ctx->out; cl; cl = cl->next, n++) { \ 14 | } \ 15 | \ 16 | dd("chain size: %d", n); \ 17 | } 18 | 19 | # if (NGX_HAVE_VARIADIC_MACROS) 20 | 21 | # define dd(...) fprintf(stderr, "rds-json *** %s: ", __func__); \ 22 | fprintf(stderr, __VA_ARGS__); \ 23 | fprintf(stderr, " at %s line %d.\n", __FILE__, __LINE__) 24 | 25 | # else 26 | 27 | #include 28 | #include 29 | 30 | #include 31 | 32 | static ngx_inline void 33 | dd(const char * fmt, ...) { 34 | } 35 | 36 | # endif 37 | 38 | #else 39 | 40 | # define dd_dump_chain_size() 41 | 42 | # if (NGX_HAVE_VARIADIC_MACROS) 43 | 44 | # define dd(...) 45 | 46 | # else 47 | 48 | #include 49 | 50 | static ngx_inline void 51 | dd(const char * fmt, ...) { 52 | } 53 | 54 | # endif 55 | 56 | #endif 57 | 58 | #if defined(DDEBUG) && (DDEBUG) 59 | 60 | #define dd_check_read_event_handler(r) \ 61 | dd("r->read_event_handler = %s", \ 62 | r->read_event_handler == ngx_http_block_reading ? \ 63 | "ngx_http_block_reading" : \ 64 | r->read_event_handler == ngx_http_test_reading ? \ 65 | "ngx_http_test_reading" : \ 66 | r->read_event_handler == ngx_http_request_empty_handler ? \ 67 | "ngx_http_request_empty_handler" : "UNKNOWN") 68 | 69 | #define dd_check_write_event_handler(r) \ 70 | dd("r->write_event_handler = %s", \ 71 | r->write_event_handler == ngx_http_handler ? \ 72 | "ngx_http_handler" : \ 73 | r->write_event_handler == ngx_http_core_run_phases ? \ 74 | "ngx_http_core_run_phases" : \ 75 | r->write_event_handler == ngx_http_request_empty_handler ? \ 76 | "ngx_http_request_empty_handler" : "UNKNOWN") 77 | 78 | #else 79 | 80 | #define dd_check_read_event_handler(r) 81 | #define dd_check_write_event_handler(r) 82 | 83 | #endif 84 | 85 | #endif /* DDEBUG_H */ 86 | 87 | -------------------------------------------------------------------------------- /src/resty_dbd_stream.h: -------------------------------------------------------------------------------- 1 | #ifndef RESTY_DBD_STREAME_H 2 | #define RESTY_DBD_STREAME_H 3 | 4 | #define resty_dbd_stream_version 3 5 | #define resty_dbd_stream_version_string "0.0.3" 6 | 7 | #define rds_content_type \ 8 | "application/x-resty-dbd-stream" 9 | 10 | #define rds_content_type_len \ 11 | (sizeof(rds_content_type) - 1) 12 | 13 | 14 | typedef enum { 15 | rds_rough_col_type_int = 0 << 14, 16 | rds_rough_col_type_float = 1 << 14, 17 | rds_rough_col_type_str = 2 << 14, 18 | rds_rough_col_type_bool = 3 << 14 19 | 20 | } rds_rough_col_type_t; 21 | 22 | 23 | /* The following types (or spellings thereof) are specified 24 | * by SQL: 25 | * bigint, bit, bit varying, boolean, char, character varying, 26 | * character, varchar, date, double precision, integer, 27 | * interval, numeric, decimal, real, smallint, 28 | * time (with or without time zone), 29 | * timestamp (with or without time zone), xml */ 30 | 31 | typedef enum { 32 | rds_col_type_unknown = 0 | rds_rough_col_type_str, 33 | rds_col_type_bigint = 1 | rds_rough_col_type_int, 34 | rds_col_type_bit = 2 | rds_rough_col_type_str, 35 | rds_col_type_bit_varying = 3 | rds_rough_col_type_str, 36 | 37 | rds_col_type_bool = 4 | rds_rough_col_type_bool, 38 | rds_col_type_char = 5 | rds_rough_col_type_str, 39 | rds_col_type_varchar = 6 | rds_rough_col_type_str, 40 | rds_col_type_date = 7 | rds_rough_col_type_str, 41 | rds_col_type_double = 8 | rds_rough_col_type_float, 42 | rds_col_type_integer = 9 | rds_rough_col_type_int, 43 | rds_col_type_interval = 10 | rds_rough_col_type_float, 44 | rds_col_type_decimal = 11 | rds_rough_col_type_float, 45 | rds_col_type_real = 12 | rds_rough_col_type_float, 46 | rds_col_type_smallint = 13 | rds_rough_col_type_int, 47 | rds_col_type_time_with_time_zone = 14 | rds_rough_col_type_str, 48 | rds_col_type_time = 15 | rds_rough_col_type_str, 49 | rds_col_type_timestamp_with_time_zone = 16 | rds_rough_col_type_str, 50 | rds_col_type_timestamp = 17 | rds_rough_col_type_str, 51 | rds_col_type_xml = 18 | rds_rough_col_type_str, 52 | 53 | /* our additions */ 54 | rds_col_type_blob = 19 | rds_rough_col_type_str 55 | 56 | } rds_col_type_t; 57 | 58 | #endif /* RESTY_DBD_STREAME_H */ 59 | 60 | -------------------------------------------------------------------------------- /.github/workflows/coverity.yml: -------------------------------------------------------------------------------- 1 | name: Coverity 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | 7 | jobs: 8 | scan: 9 | runs-on: ubuntu-18.04 10 | if: ${{ github.repository_owner == 'openresty' }} 11 | env: 12 | COVERITY_SCAN_PROJECT_NAME: 'rds-json-nginx-module' 13 | COVERITY_SCAN_BRANCH_PATTERN: '*' 14 | COVERITY_SCAN_NOTIFICATION_EMAIL: 'chipitsine@gmail.com' 15 | LUAJIT_PREFIX: '/opt/luajit21' 16 | LUAJIT_LIB: '/opt/luajit21/lib' 17 | LUAJIT_INC: '/opt/luajit21/include/luajit-2.1' 18 | LUA_INCLUDE_DIR: '/opt/luajit21/include/luajit-2.1' 19 | LUA_CMODULE_DIR: '/lib' 20 | JOBS: 3 21 | NGX_BUILD_JOBS: 3 22 | NGINX_VERSION: 1.19.9 23 | CC: gcc 24 | steps: 25 | - uses: actions/checkout@v3 26 | - name: Install apt dependencies 27 | run: | 28 | sudo apt-get update 29 | sudo apt-get install -y axel libgd-dev 30 | - name: clone OpenResty satellites 31 | run: | 32 | git clone https://github.com/openresty/nginx-devel-utils.git 33 | git clone https://github.com/openresty/openresty.git ../openresty 34 | git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx 35 | git clone https://github.com/simpl/ngx_devel_kit.git ../ndk-nginx-module 36 | git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module 37 | git clone https://github.com/openresty/lua-resty-core.git ../lua-resty-core 38 | git clone https://github.com/openresty/lua-resty-lrucache.git ../lua-resty-lrucache 39 | git clone https://github.com/openresty/nginx-eval-module.git ../eval-nginx-module 40 | git clone https://github.com/openresty/xss-nginx-module.git ../xss-nginx-module 41 | git clone https://github.com/openresty/set-misc-nginx-module.git ../set-misc-nginx-module 42 | git clone https://github.com/openresty/headers-more-nginx-module.git ../headers-more-nginx-module 43 | git clone https://github.com/openresty/drizzle-nginx-module.git ../drizzle-nginx-module 44 | git clone https://github.com/calio/form-input-nginx-module.git ../form-input-nginx-module 45 | git clone https://github.com/openresty/ngx_postgres.git ../postgres-nginx-module 46 | git clone https://github.com/openresty/openresty.git ../ngx_openresty 47 | git clone https://github.com/openresty/echo-nginx-module.git ../echo-nginx-module 48 | git clone https://github.com/openresty/array-var-nginx-module.git ../array-var-nginx-module 49 | - name: Install libdrizzle 50 | run: | 51 | wget http://openresty.org/download/drizzle7-2011.07.21.tar.gz 52 | tar xzf drizzle7-2011.07.21.tar.gz && cd drizzle7-2011.07.21 53 | ./configure --prefix=/usr --without-server 54 | sudo PATH=$PATH make libdrizzle-1.0 install-libdrizzle-1.0 55 | - name: Install luajit2 56 | run: | 57 | git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git 58 | cd luajit2 59 | make -j$JOBS CCDEBUG=-g Q= PREFIX=$LUAJIT_PREFIX CC=$CC XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT' 60 | sudo make install PREFIX=$LUAJIT_PREFIX 61 | - name: Run Coverity Scan 62 | env: 63 | COVERITY_SCAN_TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} 64 | run: | 65 | export COVERITY_SCAN_BUILD_COMMAND="sh util/build.sh $NGINX_VERSION" 66 | export PATH=$PWD/work/nginx/sbin:$PWD/nginx-devel-utils:$PATH 67 | export NGX_BUILD_CC=gcc 68 | curl -fsSL "https://scan.coverity.com/scripts/travisci_build_coverity_scan.sh" | bash || true 69 | -------------------------------------------------------------------------------- /src/ngx_http_rds_json_filter_module.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) agentzh 4 | */ 5 | 6 | #ifndef NGX_HTTP_RDS_JSON_FILTER_MODULE_H 7 | #define NGX_HTTP_RDS_JSON_FILTER_MODULE_H 8 | 9 | #include "ngx_http_rds.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | #ifndef NGX_HTTP_RESET_CONTENT 18 | #define NGX_HTTP_RESET_CONTENT 205 19 | #endif 20 | 21 | 22 | extern ngx_module_t ngx_http_rds_json_filter_module; 23 | 24 | extern ngx_http_output_header_filter_pt ngx_http_rds_json_next_header_filter; 25 | 26 | extern ngx_http_output_body_filter_pt ngx_http_rds_json_next_body_filter; 27 | 28 | 29 | typedef enum { 30 | json_format_normal, 31 | json_format_compact, 32 | json_format_pretty /* TODO */ 33 | 34 | } ngx_http_rds_json_format_t; 35 | 36 | 37 | typedef struct { 38 | ngx_str_t key; 39 | ngx_http_complex_value_t value; 40 | } ngx_http_rds_json_property_t; 41 | 42 | 43 | typedef struct { 44 | unsigned requires_filters; /* :1 */ 45 | 46 | } ngx_http_rds_json_main_conf_t; 47 | 48 | 49 | typedef struct { 50 | ngx_flag_t enabled; 51 | ngx_uint_t format; 52 | ngx_str_t content_type; 53 | ngx_str_t root; /* rds_json_root key */ 54 | ngx_str_t success; /* rds_json_success_property 55 | * key */ 56 | ngx_array_t *user_props; /* rds_json_user_property */ 57 | 58 | ngx_str_t errcode_key; 59 | ngx_str_t errstr_key; 60 | 61 | size_t buf_size; 62 | 63 | /* for rds_json_ret */ 64 | ngx_str_t errcode; 65 | ngx_http_complex_value_t *errstr; 66 | 67 | } ngx_http_rds_json_loc_conf_t; 68 | 69 | 70 | typedef enum { 71 | state_expect_header, 72 | state_expect_col, 73 | state_expect_row, 74 | state_expect_field, 75 | state_expect_more_field_data, 76 | state_done 77 | 78 | } ngx_http_rds_json_state_t; 79 | 80 | 81 | typedef struct { 82 | ngx_http_rds_json_state_t state; 83 | 84 | ngx_str_t *col_name; 85 | ngx_uint_t col_count; 86 | ngx_uint_t cur_col; 87 | 88 | ngx_http_rds_column_t *cols; 89 | size_t row; 90 | 91 | uint32_t field_offset; 92 | uint32_t field_total; 93 | 94 | ngx_buf_tag_t tag; 95 | 96 | ngx_chain_t *out; 97 | ngx_chain_t **last_out; 98 | ngx_chain_t *busy_bufs; 99 | ngx_chain_t *free_bufs; 100 | 101 | ngx_buf_t *out_buf; 102 | ngx_buf_t cached; 103 | ngx_buf_t postponed; 104 | 105 | size_t avail_out; 106 | 107 | uint32_t field_data_rest; 108 | 109 | ngx_uint_t header_sent:1; 110 | ngx_uint_t seen_stream_end:1; 111 | ngx_uint_t generated_col_names:1; 112 | } ngx_http_rds_json_ctx_t; 113 | 114 | 115 | #endif /* NGX_HTTP_RDS_JSON_FILTER_MODULE_H */ 116 | 117 | -------------------------------------------------------------------------------- /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/pg.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 | $ENV{TEST_NGINX_POSTGRESQL_HOST} ||= '127.0.0.1'; 11 | $ENV{TEST_NGINX_POSTGRESQL_PORT} ||= 5432; 12 | 13 | no_long_string(); 14 | 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: bool blob field (keepalive off) 20 | --- http_config 21 | upstream backend { 22 | postgres_server $TEST_NGINX_POSTGRESQL_HOST:$TEST_NGINX_POSTGRESQL_PORT 23 | dbname=ngx_test user=ngx_test password=ngx_test; 24 | postgres_keepalive off; 25 | } 26 | --- config 27 | location /test { 28 | echo_location /pg "drop table if exists foo"; 29 | echo; 30 | echo_location /pg "create table foo (id serial, flag bool);"; 31 | echo; 32 | echo_location /pg "insert into foo (flag) values (true);"; 33 | echo; 34 | echo_location /pg "insert into foo (flag) values (false);"; 35 | echo; 36 | echo_location /pg "select * from foo order by id;"; 37 | echo; 38 | } 39 | location /pg { 40 | postgres_pass backend; 41 | postgres_query $query_string; 42 | rds_json on; 43 | } 44 | location = /pgignore { 45 | postgres_pass backend; 46 | postgres_query $query_string; 47 | rds_json on; 48 | error_page 500 = /ignore; 49 | } 50 | location /ignore { echo "ignore"; } 51 | --- request 52 | GET /test 53 | --- response_body 54 | {"errcode":0} 55 | {"errcode":0} 56 | {"errcode":0,"affected_rows":1} 57 | {"errcode":0,"affected_rows":1} 58 | [{"id":1,"flag":true},{"id":2,"flag":false}] 59 | --- skip_nginx: 2: < 0.7.46 60 | 61 | 62 | 63 | === TEST 2: bool blob field (keepalive on) 64 | --- http_config 65 | upstream backend { 66 | postgres_server $TEST_NGINX_POSTGRESQL_HOST:$TEST_NGINX_POSTGRESQL_PORT 67 | dbname=ngx_test user=ngx_test password=ngx_test; 68 | } 69 | --- config 70 | location /test { 71 | echo_location /pgignore "drop table if exists foo"; 72 | echo; 73 | echo_location /pg "create table foo (id serial, flag bool);"; 74 | echo; 75 | echo_location /pg "insert into foo (flag) values (true);"; 76 | echo; 77 | echo_location /pg "insert into foo (flag) values (false);"; 78 | echo; 79 | echo_location /pg "select * from foo order by id;"; 80 | echo; 81 | } 82 | location /pg { 83 | postgres_pass backend; 84 | postgres_query $query_string; 85 | rds_json on; 86 | } 87 | location = /pgignore { 88 | postgres_pass backend; 89 | postgres_query $query_string; 90 | rds_json on; 91 | error_page 500 = /ignore; 92 | } 93 | location /ignore { echo "ignore"; } 94 | --- request 95 | GET /test 96 | --- response_body 97 | {"errcode":0} 98 | {"errcode":0} 99 | {"errcode":0,"affected_rows":1} 100 | {"errcode":0,"affected_rows":1} 101 | [{"id":1,"flag":true},{"id":2,"flag":false}] 102 | --- skip_nginx: 2: < 0.7.46 103 | 104 | 105 | 106 | === TEST 3: sanity (github issue #2) 107 | --- http_config 108 | upstream backend { 109 | postgres_server $TEST_NGINX_POSTGRESQL_HOST:$TEST_NGINX_POSTGRESQL_PORT 110 | dbname=ngx_test user=ngx_test password=ngx_test; 111 | } 112 | --- config 113 | location /pg { 114 | rds_json on; 115 | rds_json_root url; 116 | 117 | postgres_pass backend; 118 | postgres_query GET "select * from cats order by id"; 119 | postgres_output rds; 120 | } 121 | --- request 122 | GET /pg 123 | --- response_headers 124 | Content-Type: application/json 125 | --- response_body chop 126 | {"url":[{"id":2,"name":null},{"id":3,"name":"bob"}]} 127 | --- skip_nginx: 2: < 0.7.46 128 | 129 | -------------------------------------------------------------------------------- /src/ngx_http_rds_json_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 "resty_dbd_stream.h" 14 | #include "ngx_http_rds_json_util.h" 15 | 16 | 17 | uintptr_t 18 | ngx_http_rds_json_escape_json_str(u_char *dst, u_char *src, size_t size) 19 | { 20 | ngx_uint_t n; 21 | 22 | static u_char hex[] = "0123456789abcdef"; 23 | 24 | if (dst == NULL) { 25 | /* find the number of characters to be escaped */ 26 | 27 | n = 0; 28 | 29 | while (size) { 30 | /* UTF-8 char has high bit of 1 */ 31 | if ((*src & 0x80) == 0) { 32 | switch (*src) { 33 | case '\r': 34 | case '\n': 35 | case '\\': 36 | case '"': 37 | case '\f': 38 | case '\b': 39 | case '\t': 40 | n++; 41 | break; 42 | default: 43 | if (*src < 32) { 44 | n += sizeof("\\u00xx") - 2; 45 | } 46 | 47 | break; 48 | } 49 | } 50 | 51 | src++; 52 | size--; 53 | } 54 | 55 | return (uintptr_t) n; 56 | } 57 | 58 | while (size) { 59 | if ((*src & 0x80) == 0) { 60 | switch (*src) { 61 | case '\r': 62 | *dst++ = '\\'; 63 | *dst++ = 'r'; 64 | break; 65 | 66 | case '\n': 67 | *dst++ = '\\'; 68 | *dst++ = 'n'; 69 | break; 70 | 71 | case '\\': 72 | *dst++ = '\\'; 73 | *dst++ = '\\'; 74 | break; 75 | 76 | case '"': 77 | *dst++ = '\\'; 78 | *dst++ = '"'; 79 | break; 80 | 81 | case '\f': 82 | *dst++ = '\\'; 83 | *dst++ = 'f'; 84 | break; 85 | 86 | case '\b': 87 | *dst++ = '\\'; 88 | *dst++ = 'b'; 89 | break; 90 | 91 | case '\t': 92 | *dst++ = '\\'; 93 | *dst++ = 't'; 94 | break; 95 | 96 | default: 97 | if (*src < 32) { /* control chars */ 98 | *dst++ = '\\'; 99 | *dst++ = 'u'; 100 | *dst++ = '0'; 101 | *dst++ = '0'; 102 | *dst++ = hex[*src >> 4]; 103 | *dst++ = hex[*src & 0x0f]; 104 | } else { 105 | *dst++ = *src; 106 | } 107 | break; 108 | } /* switch */ 109 | 110 | src++; 111 | 112 | } else { 113 | *dst++ = *src++; 114 | } 115 | 116 | size--; 117 | } 118 | 119 | return (uintptr_t) dst; 120 | } 121 | 122 | 123 | ngx_int_t 124 | ngx_http_rds_json_test_content_type(ngx_http_request_t *r) 125 | { 126 | ngx_str_t *type; 127 | 128 | type = &r->headers_out.content_type; 129 | if (type->len != rds_content_type_len 130 | || ngx_strncmp(type->data, rds_content_type, rds_content_type_len) 131 | != 0) 132 | { 133 | return NGX_DECLINED; 134 | } 135 | 136 | return NGX_OK; 137 | } 138 | 139 | 140 | void 141 | ngx_http_rds_json_discard_bufs(ngx_pool_t *pool, ngx_chain_t *in) 142 | { 143 | ngx_chain_t *cl; 144 | 145 | for (cl = in; cl; cl = cl->next) { 146 | #if 0 147 | if (cl->buf->temporary 148 | && ngx_buf_size(cl->buf) > 0) 149 | { 150 | ngx_pfree(pool, cl->buf->start); 151 | } 152 | #endif 153 | 154 | cl->buf->pos = cl->buf->last; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: focal 3 | 4 | os: linux 5 | 6 | branches: 7 | only: 8 | - "master" 9 | language: c 10 | 11 | addons: 12 | apt: 13 | packages: 14 | - axel 15 | - cpanminus 16 | - libtest-base-perl 17 | - libtext-diff-perl 18 | - liburi-perl 19 | - libwww-perl 20 | - libtest-longstring-perl 21 | - liblist-moreutils-perl 22 | - libgd-dev 23 | postgresql: "13" 24 | 25 | cache: 26 | apt: true 27 | directories: 28 | - download-cache 29 | 30 | compiler: 31 | - gcc 32 | 33 | env: 34 | global: 35 | - LUAJIT_PREFIX=/opt/luajit21 36 | - LUAJIT_LIB=$LUAJIT_PREFIX/lib 37 | - LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH 38 | - LUAJIT_INC=$LUAJIT_PREFIX/include/luajit-2.1 39 | - LUA_INCLUDE_DIR=$LUAJIT_INC 40 | - LUA_CMODULE_DIR=/lib 41 | - JOBS=3 42 | - DRIZZLE_VER=2011.07.21 43 | - NGX_BUILD_JOBS=$JOBS 44 | - TEST_NGINX_SLEEP=0.006 45 | matrix: 46 | - NGINX_VERSION=1.29.2 NGX_EXTRA_OPT=--without-pcre2 47 | 48 | services: 49 | - mysql 50 | - postgresql 51 | 52 | install: 53 | - if [ ! -f download-cache/drizzle7-$DRIZZLE_VER.tar.gz ]; then wget -P download-cache https://github.com/openresty/openresty-deps-prebuild/releases/download/v20230902/drizzle7-$DRIZZLE_VER.tar.gz; fi 54 | - git clone https://github.com/openresty/openresty-devel-utils.git 55 | - git clone https://github.com/openresty/openresty.git ../openresty 56 | - git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx 57 | - git clone https://github.com/simpl/ngx_devel_kit.git ../ndk-nginx-module 58 | - git clone https://github.com/openresty/test-nginx.git 59 | - git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git 60 | - git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module 61 | - git clone https://github.com/openresty/lua-resty-core.git ../lua-resty-core 62 | - git clone https://github.com/openresty/lua-resty-lrucache.git ../lua-resty-lrucache 63 | - git clone https://github.com/openresty/echo-nginx-module.git ../echo-nginx-module 64 | - git clone https://github.com/openresty/nginx-eval-module.git ../eval-nginx-module 65 | - git clone https://github.com/openresty/xss-nginx-module.git ../xss-nginx-module 66 | - git clone https://github.com/openresty/set-misc-nginx-module.git ../set-misc-nginx-module 67 | - git clone https://github.com/openresty/array-var-nginx-module.git ../array-var-nginx-module 68 | - git clone https://github.com/openresty/drizzle-nginx-module.git ../drizzle-nginx-module 69 | - git clone https://github.com/calio/form-input-nginx-module.git ../form-input-nginx-module 70 | - git clone https://github.com/openresty/ngx_postgres.git ../postgres-nginx-module 71 | - git clone https://github.com/openresty/openresty.git ../ngx_openresty 72 | 73 | before_script: 74 | - mysql -uroot -e "create database ngx_test; CREATE USER 'ngx_test'@'%' IDENTIFIED WITH mysql_native_password BY 'ngx_test'; grant all on ngx_test.* to 'ngx_test'@'%'; flush privileges;" 75 | - mysql -uroot -e 'alter database ngx_test character set utf8mb4 collate utf8mb4_unicode_ci;' 76 | - psql -c "create database ngx_test;" -U postgres 77 | - psql -c "create user ngx_test with password 'ngx_test';" -U postgres 78 | - psql -c "grant all privileges on database ngx_test to ngx_test;" -U postgres 79 | 80 | script: 81 | - tar xzf download-cache/drizzle7-2011.07.21.tar.gz && cd drizzle7-2011.07.21 82 | - ./configure --prefix=/usr --without-server > build.log 2>&1 || (cat build.log && exit 1) 83 | - sudo PATH=$PATH make libdrizzle-1.0 install-libdrizzle-1.0 > build.log 2>&1 || (cat build.log && exit 1) 84 | - cd ../luajit2 85 | - make -j$JOBS CCDEBUG=-g Q= PREFIX=$LUAJIT_PREFIX CC=$CC XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT' > build.log 2>&1 || (cat build.log && exit 1) 86 | - sudo make install PREFIX=$LUAJIT_PREFIX > build.log 2>&1 || (cat build.log && exit 1) 87 | - cd ../test-nginx && sudo cpanm . && cd .. 88 | - export PATH=$PWD/work/nginx/sbin:$PWD/openresty-devel-utils:$PATH 89 | - export NGX_BUILD_CC=$CC 90 | - sh util/build.sh $NGINX_VERSION > build.log 2>&1 || (cat build.log && exit 1) 91 | - nginx -V 92 | - prove -I. -r t 93 | -------------------------------------------------------------------------------- /t/000_init.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() * blocks(); 9 | 10 | $ENV{TEST_NGINX_POSTGRESQL_PORT} ||= 5432; 11 | $ENV{TEST_NGINX_POSTGRESQL_HOST} ||= '127.0.0.1'; 12 | 13 | $ENV{TEST_NGINX_MYSQL_HOST} ||= '127.0.0.1'; 14 | $ENV{TEST_NGINX_MYSQL_PORT} ||= 3306; 15 | 16 | our $http_config = <<'_EOC_'; 17 | upstream database { 18 | drizzle_server $TEST_NGINX_MYSQL_HOST:$TEST_NGINX_MYSQL_PORT protocol=mysql 19 | dbname=ngx_test user=ngx_test password=ngx_test; 20 | } 21 | _EOC_ 22 | 23 | our $http_config2 = <<'_EOC_'; 24 | upstream database { 25 | postgres_server $TEST_NGINX_POSTGRESQL_HOST:$TEST_NGINX_POSTGRESQL_PORT 26 | dbname=ngx_test user=ngx_test password=ngx_test; 27 | } 28 | _EOC_ 29 | 30 | 31 | worker_connections(128); 32 | no_shuffle(); 33 | run_tests(); 34 | 35 | no_diff(); 36 | 37 | __DATA__ 38 | 39 | === TEST 1: cats - drop table 40 | --- http_config eval: $::http_config 41 | --- config 42 | location = /init { 43 | drizzle_pass database; 44 | drizzle_query "DROP TABLE IF EXISTS cats"; 45 | } 46 | --- request 47 | GET /init 48 | --- error_code: 200 49 | --- timeout: 10 50 | 51 | 52 | 53 | === TEST 2: cats - create table 54 | --- http_config eval: $::http_config 55 | --- config 56 | location = /init { 57 | drizzle_pass database; 58 | drizzle_query "CREATE TABLE cats (id integer, name text)"; 59 | } 60 | --- request 61 | GET /init 62 | --- error_code: 200 63 | --- timeout: 10 64 | 65 | 66 | 67 | === TEST 3: cats - insert value 68 | --- http_config eval: $::http_config 69 | --- config 70 | location = /init { 71 | drizzle_pass database; 72 | drizzle_query "INSERT INTO cats (id) VALUES (2)"; 73 | } 74 | --- request 75 | GET /init 76 | --- error_code: 200 77 | --- timeout: 10 78 | 79 | 80 | 81 | === TEST 4: cats - insert value 82 | --- http_config eval: $::http_config 83 | --- config 84 | location = /init { 85 | drizzle_pass database; 86 | drizzle_query "INSERT INTO cats (id, name) VALUES (3, 'bob')"; 87 | } 88 | --- request 89 | GET /init 90 | --- error_code: 200 91 | --- timeout: 10 92 | 93 | 94 | 95 | === TEST 5: birds - drop table 96 | --- http_config eval: $::http_config 97 | --- config 98 | location = /init { 99 | drizzle_pass database; 100 | drizzle_query "DROP TABLE IF EXISTS birds"; 101 | } 102 | --- request 103 | GET /init 104 | --- error_code: 200 105 | --- timeout: 10 106 | 107 | 108 | 109 | === TEST 6: birds - create table 110 | --- http_config eval: $::http_config 111 | --- config 112 | location = /init { 113 | drizzle_pass database; 114 | drizzle_query "CREATE TABLE birds (`\"name\"` text, height real)"; 115 | } 116 | --- request 117 | GET /init 118 | --- error_code: 200 119 | --- timeout: 10 120 | 121 | 122 | 123 | === TEST 7: birds - insert values 124 | --- http_config eval: $::http_config 125 | --- config 126 | location = /init { 127 | drizzle_pass database; 128 | drizzle_query " 129 | INSERT INTO birds (`\"name\"`, height) 130 | VALUES 131 | ('hello \"tom', 3.14), 132 | ('hi,ya', -3), 133 | ('hey\\ndad', 7), 134 | ('\\rkay', 0.005), 135 | ('ab;c', 0.005), 136 | ('foo\\tbar', 21);"; 137 | } 138 | --- request 139 | GET /init 140 | --- error_code: 200 141 | --- timeout: 10 142 | 143 | 144 | 145 | === TEST 8: cats - drop table - pg 146 | --- http_config eval: $::http_config2 147 | --- config 148 | location = /init { 149 | postgres_pass database; 150 | postgres_query "DROP TABLE cats"; 151 | error_page 500 = /ignore; 152 | } 153 | 154 | location /ignore { echo "ignore"; } 155 | --- request 156 | GET /init 157 | --- error_code: 200 158 | --- timeout: 10 159 | 160 | 161 | 162 | === TEST 9: cats - create table - pg 163 | --- http_config eval: $::http_config2 164 | --- config 165 | location = /init { 166 | postgres_pass database; 167 | postgres_query "CREATE TABLE cats (id integer, name text)"; 168 | } 169 | --- request 170 | GET /init 171 | --- error_code: 200 172 | --- timeout: 10 173 | 174 | 175 | 176 | === TEST 10: cats - insert value - pg 177 | --- http_config eval: $::http_config2 178 | --- config 179 | location = /init { 180 | postgres_pass database; 181 | postgres_query "INSERT INTO cats (id) VALUES (2)"; 182 | } 183 | --- request 184 | GET /init 185 | --- error_code: 200 186 | --- timeout: 10 187 | 188 | 189 | 190 | === TEST 11: cats - insert value - pg 191 | --- http_config eval: $::http_config2 192 | --- config 193 | location = /init { 194 | postgres_pass database; 195 | postgres_query "INSERT INTO cats (id, name) VALUES (3, 'bob')"; 196 | } 197 | --- request 198 | GET /init 199 | --- error_code: 200 200 | --- timeout: 10 201 | 202 | -------------------------------------------------------------------------------- /src/ngx_http_rds_utils.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) agentzh 4 | */ 5 | 6 | 7 | #ifndef NGX_HTTP_RDS_UTILS_H 8 | #define NGX_HTTP_RDS_UTILS_H 9 | 10 | 11 | static ngx_inline ngx_int_t 12 | ngx_http_rds_parse_header(ngx_http_request_t *r, ngx_buf_t *b, 13 | ngx_http_rds_header_t *header) 14 | { 15 | ssize_t rest; 16 | 17 | rest = sizeof(uint8_t) /* endian type */ 18 | + sizeof(uint32_t) /* format version */ 19 | + sizeof(uint8_t) /* result type */ 20 | 21 | + sizeof(uint16_t) /* standard error code */ 22 | + sizeof(uint16_t) /* driver-specific error code */ 23 | 24 | + sizeof(uint16_t) /* driver-specific errstr len */ 25 | + 0 /* driver-specific errstr data */ 26 | + sizeof(uint64_t) /* affected rows */ 27 | + sizeof(uint64_t) /* insert id */ 28 | + sizeof(uint16_t) /* column count */ 29 | ; 30 | 31 | if (b->last - b->pos < rest) { 32 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 33 | "rds: header is incomplete in the buf"); 34 | return NGX_ERROR; 35 | } 36 | 37 | /* check endian type */ 38 | 39 | if (*(uint8_t *) b->pos != 40 | #if (NGX_HAVE_LITTLE_ENDIAN) 41 | 0 42 | #else /* big endian */ 43 | 1 44 | #endif 45 | ) 46 | { 47 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 48 | "rds: endian type in the header differ"); 49 | return NGX_ERROR; 50 | } 51 | 52 | b->pos += sizeof(uint8_t); 53 | 54 | /* check RDS format version number */ 55 | 56 | if (*(uint32_t *) b->pos != (uint32_t) resty_dbd_stream_version) { 57 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 58 | "rds: RDS format version differ"); 59 | return NGX_ERROR; 60 | } 61 | 62 | dd("RDS format version: %d", (int) *(uint32_t *) b->pos); 63 | 64 | b->pos += sizeof(uint32_t); 65 | 66 | /* check RDS result type */ 67 | 68 | if (*b->pos != 0) { 69 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 70 | "rds: RDS result type must be 0 for now"); 71 | return NGX_ERROR; 72 | } 73 | 74 | b->pos++; 75 | 76 | /* save the standard error code */ 77 | 78 | header->std_errcode = *(uint16_t *) b->pos; 79 | 80 | b->pos += sizeof(uint16_t); 81 | 82 | /* save the driver-specific error code */ 83 | 84 | header->drv_errcode = *(uint16_t *) b->pos; 85 | 86 | b->pos += sizeof(uint16_t); 87 | 88 | /* save the error string length */ 89 | 90 | header->errstr.len = *(uint16_t *) b->pos; 91 | 92 | b->pos += sizeof(uint16_t); 93 | 94 | dd("errstr len: %d", (int) header->errstr.len); 95 | 96 | /* check the rest data's size */ 97 | 98 | rest = header->errstr.len 99 | + sizeof(uint64_t) /* affected rows */ 100 | + sizeof(uint64_t) /* insert id */ 101 | + sizeof(uint16_t) /* column count */ 102 | ; 103 | 104 | if (b->last - b->pos < rest) { 105 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 106 | "rds: header is incomplete in the buf"); 107 | return NGX_ERROR; 108 | } 109 | 110 | /* save the error string data */ 111 | 112 | header->errstr.data = b->pos; 113 | 114 | b->pos += header->errstr.len; 115 | 116 | /* save affected rows */ 117 | 118 | header->affected_rows = *(uint64_t *) b->pos; 119 | 120 | b->pos += sizeof(uint64_t); 121 | 122 | /* save insert id */ 123 | 124 | header->insert_id = *(uint64_t *)b->pos; 125 | 126 | b->pos += sizeof(uint64_t); 127 | 128 | /* save column count */ 129 | 130 | header->col_count = *(uint16_t *) b->pos; 131 | 132 | b->pos += sizeof(uint16_t); 133 | 134 | dd("saved column count: %d", (int) header->col_count); 135 | 136 | return NGX_OK; 137 | } 138 | 139 | 140 | static ngx_inline ngx_int_t 141 | ngx_http_rds_parse_col(ngx_http_request_t *r, ngx_buf_t *b, 142 | ngx_http_rds_column_t *col) 143 | { 144 | ssize_t rest; 145 | 146 | rest = sizeof(uint16_t) /* std col type */ 147 | + sizeof(uint16_t) /* driver col type */ 148 | + sizeof(uint16_t) /* col name str len */ 149 | ; 150 | 151 | if (b->last - b->pos < rest) { 152 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 153 | "rds: column spec is incomplete in the buf"); 154 | return NGX_ERROR; 155 | } 156 | 157 | /* save standard column type */ 158 | col->std_type = *(uint16_t *) b->pos; 159 | b->pos += sizeof(uint16_t); 160 | 161 | /* save driver-specific column type */ 162 | col->drv_type = *(uint16_t *) b->pos; 163 | b->pos += sizeof(uint16_t); 164 | 165 | /* read column name string length */ 166 | 167 | col->name.len = *(uint16_t *) b->pos; 168 | b->pos += sizeof(uint16_t); 169 | 170 | if (col->name.len == 0) { 171 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 172 | "rds_json: column name empty"); 173 | return NGX_ERROR; 174 | } 175 | 176 | rest = col->name.len; 177 | 178 | if (b->last - b->pos < rest) { 179 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 180 | "rds: column name string is incomplete in the buf"); 181 | return NGX_ERROR; 182 | } 183 | 184 | /* save the column name string data */ 185 | 186 | col->name.data = ngx_palloc(r->pool, col->name.len); 187 | if (col->name.data == NULL) { 188 | return NGX_ERROR; 189 | } 190 | 191 | ngx_memcpy(col->name.data, b->pos, col->name.len); 192 | b->pos += col->name.len; 193 | 194 | dd("saved column name \"%.*s\" (len %d, offset %d)", 195 | (int) col->name.len, col->name.data, 196 | (int) col->name.len, (int) (b->pos - b->start)); 197 | 198 | return NGX_OK; 199 | } 200 | 201 | 202 | #endif /* NGX_HTTP_RDS_UTILS_H */ 203 | -------------------------------------------------------------------------------- /src/ngx_http_rds_json_handler.c: -------------------------------------------------------------------------------- 1 | #ifndef DDEBUG 2 | #define DDEBUG 0 3 | #endif 4 | #include "ddebug.h" 5 | 6 | #include "ngx_http_rds_json_handler.h" 7 | #include "ngx_http_rds_json_util.h" 8 | 9 | 10 | ngx_int_t 11 | ngx_http_rds_json_ret_handler(ngx_http_request_t *r) 12 | { 13 | ngx_chain_t *cl; 14 | ngx_buf_t *b; 15 | size_t len; 16 | ngx_str_t errstr; 17 | ngx_int_t rc; 18 | uintptr_t escape = 0; 19 | ngx_str_t *values = NULL; 20 | uintptr_t *escapes = NULL; 21 | ngx_uint_t i; 22 | ngx_http_rds_json_property_t *prop = NULL; 23 | ngx_http_rds_json_loc_conf_t *conf; 24 | 25 | dd("entered ret handler"); 26 | 27 | conf = ngx_http_get_module_loc_conf(r, ngx_http_rds_json_filter_module); 28 | 29 | /* evaluate the final value of conf->errstr */ 30 | 31 | if (ngx_http_complex_value(r, conf->errstr, &errstr) != NGX_OK) { 32 | return NGX_ERROR; 33 | } 34 | 35 | /* calculate the buffer size */ 36 | 37 | len = sizeof("{") - 1 38 | + conf->errcode_key.len 39 | + sizeof(":") - 1 40 | + conf->errcode.len 41 | + sizeof("}") - 1 42 | ; 43 | 44 | if (errstr.len) { 45 | escape = ngx_http_rds_json_escape_json_str(NULL, errstr.data, 46 | errstr.len); 47 | 48 | len += sizeof(",") - 1 49 | + conf->errstr_key.len 50 | + sizeof(":") - 1 51 | + sizeof("\"") - 1 52 | + errstr.len 53 | + escape 54 | + sizeof("\"") - 1 55 | ; 56 | } 57 | 58 | if (conf->success.len) { 59 | len += conf->success.len + sizeof(":,") - 1; 60 | 61 | if (ngx_atoi(conf->errcode.data, conf->errcode.len) == 0) { 62 | len += sizeof("true") - 1; 63 | 64 | } else { 65 | len += sizeof("false") - 1; 66 | } 67 | } 68 | 69 | if (conf->user_props) { 70 | values = ngx_pnalloc(r->pool, 71 | conf->user_props->nelts 72 | * (sizeof(ngx_str_t) + sizeof(uintptr_t))); 73 | 74 | if (values == NULL) { 75 | return NGX_ERROR; 76 | } 77 | 78 | escapes = (uintptr_t *) ((u_char *) values + 79 | conf->user_props->nelts * sizeof(ngx_str_t)); 80 | 81 | prop = conf->user_props->elts; 82 | for (i = 0; i < conf->user_props->nelts; i++) { 83 | if (ngx_http_complex_value(r, &prop[i].value, &values[i]) 84 | != NGX_OK) 85 | { 86 | return NGX_ERROR; 87 | } 88 | 89 | escapes[i] = ngx_http_rds_json_escape_json_str(NULL, values[i].data, 90 | values[i].len); 91 | 92 | len += sizeof(":\"\",") - 1 + prop[i].key.len + values[i].len 93 | + escapes[i]; 94 | } 95 | } 96 | 97 | 98 | /* create the buffer */ 99 | 100 | cl = ngx_alloc_chain_link(r->pool); 101 | if (cl == NULL) { 102 | return NGX_ERROR; 103 | } 104 | 105 | b = ngx_create_temp_buf(r->pool, len); 106 | if (b == NULL) { 107 | return NGX_ERROR; 108 | } 109 | 110 | if (r == r->main) { 111 | b->last_buf = 1; 112 | } 113 | 114 | cl->buf = b; 115 | cl->next = NULL; 116 | 117 | /* copy data over to the buffer */ 118 | 119 | *b->last++ = '{'; 120 | 121 | b->last = ngx_copy(b->last, conf->errcode_key.data, conf->errcode_key.len); 122 | *b->last++ = ':'; 123 | b->last = ngx_copy(b->last, conf->errcode.data, conf->errcode.len); 124 | 125 | if (errstr.len) { 126 | *b->last++ = ','; 127 | b->last = ngx_copy(b->last, conf->errstr_key.data, 128 | conf->errstr_key.len); 129 | *b->last++ = ':'; 130 | *b->last++ = '"'; 131 | 132 | if (escape == 0) { 133 | b->last = ngx_copy(b->last, errstr.data, errstr.len); 134 | } else { 135 | b->last = (u_char *) ngx_http_rds_json_escape_json_str(b->last, 136 | errstr.data, errstr.len); 137 | } 138 | 139 | *b->last++ = '"'; 140 | } 141 | 142 | if (conf->success.len) { 143 | *b->last++ = ','; 144 | b->last = ngx_copy(b->last, conf->success.data, conf->success.len); 145 | 146 | if (ngx_atoi(conf->errcode.data, conf->errcode.len) == 0) { 147 | b->last = ngx_copy_literal(b->last, ":true"); 148 | 149 | } else { 150 | b->last = ngx_copy_literal(b->last, ":false"); 151 | } 152 | } 153 | 154 | if (conf->user_props) { 155 | for (i = 0; i < conf->user_props->nelts; i++) { 156 | *b->last++ = ','; 157 | b->last = ngx_copy(b->last, prop[i].key.data, prop[i].key.len); 158 | *b->last++ = ':'; 159 | *b->last++ = '"'; 160 | 161 | if (escapes[i] == 0) { 162 | b->last = ngx_copy(b->last, values[i].data, values[i].len); 163 | 164 | } else { 165 | b->last = (u_char *) ngx_http_rds_json_escape_json_str(b->last, 166 | values[i].data, values[i].len); 167 | } 168 | 169 | *b->last++ = '"'; 170 | } 171 | } 172 | 173 | *b->last++ = '}'; 174 | 175 | if (b->last != b->end) { 176 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 177 | "rds_json: rds_json_ret: buffer error"); 178 | 179 | return NGX_ERROR; 180 | } 181 | 182 | /* send headers */ 183 | 184 | r->headers_out.status = NGX_HTTP_OK; 185 | r->headers_out.content_type = conf->content_type; 186 | r->headers_out.content_type_len = conf->content_type.len; 187 | 188 | rc = ngx_http_send_header(r); 189 | if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { 190 | return rc; 191 | } 192 | 193 | dd("output filter..."); 194 | 195 | return ngx_http_output_filter(r, cl); 196 | } 197 | 198 | -------------------------------------------------------------------------------- /t/buf.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 | no_long_string(); 11 | 12 | run_tests(); 13 | 14 | #no_diff(); 15 | 16 | __DATA__ 17 | 18 | === TEST 1: rds in a single buf (empty result set) 19 | --- config 20 | location = /single { 21 | default_type 'application/x-resty-dbd-stream'; 22 | set_unescape_uri $rds $arg_rds; 23 | echo_duplicate 1 $rds; 24 | rds_json on; 25 | } 26 | --- request eval 27 | my $rds = "\x{00}". # endian 28 | "\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3 29 | "\x{00}". # result type 30 | "\x{00}\x{00}". # std errcode 31 | "\x{00}\x{00}" . # driver errcode 32 | "\x{00}\x{00}". # driver errstr len 33 | "". # driver errstr data 34 | "\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected 35 | "\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id 36 | "\x{02}\x{00}". # col count 37 | "\x{01}\x{00}". # std col type (bigint/int) 38 | "\x{03}\x{00}". # drizzle col type 39 | "\x{02}\x{00}". # col name len 40 | "id". # col name data 41 | "\x{13}\x{80}". # std col type (blob/str) 42 | "\x{fc}\x{00}". # drizzle col type 43 | "\x{04}\x{00}". # col name len 44 | "name". # col name data 45 | "\x{00}"; # row list terminator 46 | 47 | use URI::Escape; 48 | $rds = uri_escape($rds); 49 | "GET /single?rds=$rds" 50 | --- response_body chop 51 | [] 52 | 53 | 54 | 55 | === TEST 2: rds in a single buf (non-empty result set) 56 | --- config 57 | location = /single { 58 | default_type 'application/x-resty-dbd-stream'; 59 | set_unescape_uri $rds $arg_rds; 60 | echo_duplicate 1 $rds; 61 | rds_json on; 62 | } 63 | --- request eval 64 | my $rds = 65 | "\x{00}". # endian 66 | "\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3 67 | "\x{00}". # result type 68 | "\x{00}\x{00}". # std errcode 69 | "\x{00}\x{00}" . # driver errcode 70 | "\x{00}\x{00}". # driver errstr len 71 | "". # driver errstr data 72 | "\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected 73 | "\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id 74 | "\x{02}\x{00}". # col count 75 | "\x{01}\x{00}". # std col type (bigint/int) 76 | "\x{03}\x{00}". # drizzle col type 77 | "\x{02}\x{00}". # col name len 78 | "id". # col name data 79 | "\x{13}\x{80}". # std col type (blob/str) 80 | "\x{fc}\x{00}". # drizzle col type 81 | "\x{04}\x{00}". # col name len 82 | "name". # col name data 83 | "\x{01}". # valid row flag 84 | "\x{01}\x{00}\x{00}\x{00}". # field len 85 | "2". # field data 86 | "\x{ff}\x{ff}\x{ff}\x{ff}". # field len 87 | "". # field data 88 | "\x{01}". # valid row flag 89 | "\x{01}\x{00}\x{00}\x{00}". # field len 90 | "3". # field data 91 | "\x{03}\x{00}\x{00}\x{00}". # field len 92 | "bob". # field data 93 | "\x{00}"; # row list terminator 94 | 95 | use URI::Escape; 96 | $rds = uri_escape($rds); 97 | "GET /single?rds=$rds" 98 | --- response_body chop 99 | [{"id":2,"name":null},{"id":3,"name":"bob"}] 100 | 101 | 102 | 103 | === TEST 3: rds in a single buf (non-empty result set, and each row in a single buf) 104 | --- config 105 | location = /single { 106 | default_type 'application/x-resty-dbd-stream'; 107 | 108 | set_unescape_uri $a $arg_a; 109 | set_unescape_uri $b $arg_b; 110 | set_unescape_uri $c $arg_c; 111 | set_unescape_uri $d $arg_d; 112 | 113 | echo_duplicate 1 $a; 114 | echo_duplicate 1 $b; 115 | echo_duplicate 1 $c; 116 | echo_duplicate 1 $d; 117 | 118 | rds_json on; 119 | } 120 | --- request eval 121 | my $a = 122 | "\x{00}". # endian 123 | "\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3 124 | "\x{00}". # result type 125 | "\x{00}\x{00}". # std errcode 126 | "\x{00}\x{00}" . # driver errcode 127 | "\x{00}\x{00}". # driver errstr len 128 | "". # driver errstr data 129 | "\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected 130 | "\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id 131 | "\x{02}\x{00}". # col count 132 | "\x{01}\x{00}". # std col type (bigint/int) 133 | "\x{03}\x{00}". # drizzle col type 134 | "\x{02}\x{00}". # col name len 135 | "id". # col name data 136 | "\x{13}\x{80}". # std col type (blob/str) 137 | "\x{fc}\x{00}". # drizzle col type 138 | "\x{04}\x{00}". # col name len 139 | "name"; # col name data 140 | 141 | my $b = 142 | "\x{01}". # valid row flag 143 | "\x{01}\x{00}\x{00}\x{00}". # field len 144 | "2". # field data 145 | "\x{ff}\x{ff}\x{ff}\x{ff}". # field len 146 | ""; # field data 147 | 148 | my $c = 149 | "\x{01}". # valid row flag 150 | "\x{01}\x{00}\x{00}\x{00}". # field len 151 | "3". # field data 152 | "\x{03}\x{00}\x{00}\x{00}". # field len 153 | "bob"; # field data 154 | 155 | my $d = 156 | "\x{00}"; # row list terminator 157 | 158 | use URI::Escape; 159 | 160 | $a = uri_escape($a); 161 | $b = uri_escape($b); 162 | $c = uri_escape($c); 163 | $d = uri_escape($d); 164 | 165 | "GET /single?a=$a&b=$b&c=$c&d=$d" 166 | --- response_body chop 167 | [{"id":2,"name":null},{"id":3,"name":"bob"}] 168 | 169 | 170 | 171 | === TEST 4: rds in a single buf (non-empty result set, and each row in a single buf) 172 | --- config 173 | location = /single { 174 | default_type 'application/x-resty-dbd-stream'; 175 | 176 | set_unescape_uri $a $arg_a; 177 | set_unescape_uri $b $arg_b; 178 | set_unescape_uri $c $arg_c; 179 | 180 | echo -n $a; 181 | echo -n $b; 182 | echo -n $c; 183 | 184 | rds_json on; 185 | } 186 | --- request eval 187 | my $a = 188 | "\x{00}". # endian 189 | "\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3 190 | "\x{00}". # result type 191 | "\x{00}\x{00}". # std errcode 192 | "\x{00}\x{00}" . # driver errcode 193 | "\x{00}\x{00}". # driver errstr len 194 | "". # driver errstr data 195 | "\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected 196 | "\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id 197 | "\x{02}\x{00}". # col count 198 | "\x{01}\x{00}". # std col type (bigint/int) 199 | "\x{03}\x{00}". # drizzle col type 200 | "\x{02}\x{00}". # col name len 201 | "id". # col name data 202 | "\x{13}\x{80}". # std col type (blob/str) 203 | "\x{fc}\x{00}". # drizzle col type 204 | "\x{04}\x{00}". # col name len 205 | "name"; # col name data 206 | 207 | my $b = 208 | "\x{01}". # valid row flag 209 | "\x{01}\x{00}\x{00}\x{00}". # field len 210 | "2". # field data 211 | "\x{ff}\x{ff}\x{ff}\x{ff}". # field len 212 | ""; # field data 213 | 214 | my $c = 215 | "\x{01}". # valid row flag 216 | "\x{01}\x{00}\x{00}\x{00}". # field len 217 | "3". # field data 218 | "\x{03}\x{00}\x{00}\x{00}". # field len 219 | "bob". # field data 220 | "\x{00}"; # row list terminator 221 | 222 | use URI::Escape; 223 | 224 | $a = uri_escape($a); 225 | $b = uri_escape($b); 226 | $c = uri_escape($c); 227 | 228 | "GET /single?a=$a&b=$b&c=$c" 229 | --- response_body chop 230 | [{"id":2,"name":null},{"id":3,"name":"bob"}] 231 | 232 | -------------------------------------------------------------------------------- /t/compact/buf.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 | no_long_string(); 11 | 12 | run_tests(); 13 | 14 | #no_diff(); 15 | 16 | __DATA__ 17 | 18 | === TEST 1: rds in a single buf (empty result set) 19 | --- config 20 | location = /single { 21 | default_type 'application/x-resty-dbd-stream'; 22 | set_unescape_uri $rds $arg_rds; 23 | echo_duplicate 1 $rds; 24 | rds_json on; 25 | rds_json_format compact; 26 | } 27 | --- request eval 28 | my $rds = "\x{00}". # endian 29 | "\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3 30 | "\x{00}". # result type 31 | "\x{00}\x{00}". # std errcode 32 | "\x{00}\x{00}" . # driver errcode 33 | "\x{00}\x{00}". # driver errstr len 34 | "". # driver errstr data 35 | "\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected 36 | "\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id 37 | "\x{02}\x{00}". # col count 38 | "\x{01}\x{00}". # std col type (bigint/int) 39 | "\x{03}\x{00}". # drizzle col type 40 | "\x{02}\x{00}". # col name len 41 | "id". # col name data 42 | "\x{13}\x{80}". # std col type (blob/str) 43 | "\x{fc}\x{00}". # drizzle col type 44 | "\x{04}\x{00}". # col name len 45 | "name". # col name data 46 | "\x{00}"; # row list terminator 47 | 48 | use URI::Escape; 49 | $rds = uri_escape($rds); 50 | "GET /single?rds=$rds" 51 | --- response_body chop 52 | [["id","name"]] 53 | 54 | 55 | 56 | === TEST 2: rds in a single buf (non-empty result set) 57 | --- config 58 | location = /single { 59 | default_type 'application/x-resty-dbd-stream'; 60 | set_unescape_uri $rds $arg_rds; 61 | echo_duplicate 1 $rds; 62 | rds_json on; 63 | rds_json_format compact; 64 | } 65 | --- request eval 66 | my $rds = 67 | "\x{00}". # endian 68 | "\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3 69 | "\x{00}". # result type 70 | "\x{00}\x{00}". # std errcode 71 | "\x{00}\x{00}" . # driver errcode 72 | "\x{00}\x{00}". # driver errstr len 73 | "". # driver errstr data 74 | "\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected 75 | "\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id 76 | "\x{02}\x{00}". # col count 77 | "\x{01}\x{00}". # std col type (bigint/int) 78 | "\x{03}\x{00}". # drizzle col type 79 | "\x{02}\x{00}". # col name len 80 | "id". # col name data 81 | "\x{13}\x{80}". # std col type (blob/str) 82 | "\x{fc}\x{00}". # drizzle col type 83 | "\x{04}\x{00}". # col name len 84 | "name". # col name data 85 | "\x{01}". # valid row flag 86 | "\x{01}\x{00}\x{00}\x{00}". # field len 87 | "2". # field data 88 | "\x{ff}\x{ff}\x{ff}\x{ff}". # field len 89 | "". # field data 90 | "\x{01}". # valid row flag 91 | "\x{01}\x{00}\x{00}\x{00}". # field len 92 | "3". # field data 93 | "\x{03}\x{00}\x{00}\x{00}". # field len 94 | "bob". # field data 95 | "\x{00}"; # row list terminator 96 | 97 | use URI::Escape; 98 | $rds = uri_escape($rds); 99 | "GET /single?rds=$rds" 100 | --- response_body chop 101 | [["id","name"],[2,null],[3,"bob"]] 102 | 103 | 104 | 105 | === TEST 3: rds in a single buf (non-empty result set, and each row in a single buf) 106 | --- config 107 | location = /single { 108 | default_type 'application/x-resty-dbd-stream'; 109 | 110 | set_unescape_uri $a $arg_a; 111 | set_unescape_uri $b $arg_b; 112 | set_unescape_uri $c $arg_c; 113 | set_unescape_uri $d $arg_d; 114 | 115 | echo_duplicate 1 $a; 116 | echo_duplicate 1 $b; 117 | echo_duplicate 1 $c; 118 | echo_duplicate 1 $d; 119 | 120 | rds_json on; 121 | rds_json_format compact; 122 | } 123 | --- request eval 124 | my $a = 125 | "\x{00}". # endian 126 | "\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3 127 | "\x{00}". # result type 128 | "\x{00}\x{00}". # std errcode 129 | "\x{00}\x{00}" . # driver errcode 130 | "\x{00}\x{00}". # driver errstr len 131 | "". # driver errstr data 132 | "\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected 133 | "\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id 134 | "\x{02}\x{00}". # col count 135 | "\x{01}\x{00}". # std col type (bigint/int) 136 | "\x{03}\x{00}". # drizzle col type 137 | "\x{02}\x{00}". # col name len 138 | "id". # col name data 139 | "\x{13}\x{80}". # std col type (blob/str) 140 | "\x{fc}\x{00}". # drizzle col type 141 | "\x{04}\x{00}". # col name len 142 | "name"; # col name data 143 | 144 | my $b = 145 | "\x{01}". # valid row flag 146 | "\x{01}\x{00}\x{00}\x{00}". # field len 147 | "2". # field data 148 | "\x{ff}\x{ff}\x{ff}\x{ff}". # field len 149 | ""; # field data 150 | 151 | my $c = 152 | "\x{01}". # valid row flag 153 | "\x{01}\x{00}\x{00}\x{00}". # field len 154 | "3". # field data 155 | "\x{03}\x{00}\x{00}\x{00}". # field len 156 | "bob"; # field data 157 | 158 | my $d = 159 | "\x{00}"; # row list terminator 160 | 161 | use URI::Escape; 162 | 163 | $a = uri_escape($a); 164 | $b = uri_escape($b); 165 | $c = uri_escape($c); 166 | $d = uri_escape($d); 167 | 168 | "GET /single?a=$a&b=$b&c=$c&d=$d" 169 | --- response_body chop 170 | [["id","name"],[2,null],[3,"bob"]] 171 | 172 | 173 | 174 | === TEST 4: rds in a single buf (non-empty result set, and each row in a single buf) 175 | --- config 176 | location = /single { 177 | default_type 'application/x-resty-dbd-stream'; 178 | 179 | set_unescape_uri $a $arg_a; 180 | set_unescape_uri $b $arg_b; 181 | set_unescape_uri $c $arg_c; 182 | 183 | echo -n $a; 184 | echo -n $b; 185 | echo -n $c; 186 | 187 | rds_json on; 188 | rds_json_format compact; 189 | } 190 | --- request eval 191 | my $a = 192 | "\x{00}". # endian 193 | "\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3 194 | "\x{00}". # result type 195 | "\x{00}\x{00}". # std errcode 196 | "\x{00}\x{00}" . # driver errcode 197 | "\x{00}\x{00}". # driver errstr len 198 | "". # driver errstr data 199 | "\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected 200 | "\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id 201 | "\x{02}\x{00}". # col count 202 | "\x{01}\x{00}". # std col type (bigint/int) 203 | "\x{03}\x{00}". # drizzle col type 204 | "\x{02}\x{00}". # col name len 205 | "id". # col name data 206 | "\x{13}\x{80}". # std col type (blob/str) 207 | "\x{fc}\x{00}". # drizzle col type 208 | "\x{04}\x{00}". # col name len 209 | "name"; # col name data 210 | 211 | my $b = 212 | "\x{01}". # valid row flag 213 | "\x{01}\x{00}\x{00}\x{00}". # field len 214 | "2". # field data 215 | "\x{ff}\x{ff}\x{ff}\x{ff}". # field len 216 | ""; # field data 217 | 218 | my $c = 219 | "\x{01}". # valid row flag 220 | "\x{01}\x{00}\x{00}\x{00}". # field len 221 | "3". # field data 222 | "\x{03}\x{00}\x{00}\x{00}". # field len 223 | "bob". # field data 224 | "\x{00}"; # row list terminator 225 | 226 | use URI::Escape; 227 | 228 | $a = uri_escape($a); 229 | $b = uri_escape($b); 230 | $c = uri_escape($c); 231 | 232 | "GET /single?a=$a&b=$b&c=$c" 233 | --- response_body chop 234 | [["id","name"],[2,null],[3,"bob"]] 235 | 236 | -------------------------------------------------------------------------------- /valgrind.suppress: -------------------------------------------------------------------------------- 1 | { 2 | 3 | Memcheck:Leak 4 | fun:malloc 5 | fun:ngx_alloc 6 | obj:* 7 | } 8 | { 9 | 10 | Memcheck:Param 11 | epoll_ctl(event) 12 | fun:epoll_ctl 13 | } 14 | { 15 | 16 | Memcheck:Leak 17 | fun:malloc 18 | fun:ngx_alloc 19 | fun:ngx_event_process_init 20 | fun:ngx_single_process_cycle 21 | } 22 | { 23 | nginx-core-process-init 24 | Memcheck:Leak 25 | fun:malloc 26 | fun:ngx_alloc 27 | fun:ngx_event_process_init 28 | fun:ngx_single_process_cycle 29 | fun:main 30 | } 31 | { 32 | nginx-core-crc32-init 33 | Memcheck:Leak 34 | fun:malloc 35 | fun:ngx_alloc 36 | fun:ngx_crc32_table_init 37 | fun:main 38 | } 39 | { 40 | palloc_large_for_init_request 41 | Memcheck:Leak 42 | fun:malloc 43 | fun:ngx_alloc 44 | fun:ngx_palloc_large 45 | fun:ngx_palloc 46 | fun:ngx_pcalloc 47 | fun:ngx_http_init_request 48 | fun:ngx_epoll_process_events 49 | fun:ngx_process_events_and_timers 50 | fun:ngx_single_process_cycle 51 | fun:main 52 | } 53 | { 54 | palloc_large_for_create_temp_buf 55 | Memcheck:Leak 56 | fun:malloc 57 | fun:ngx_alloc 58 | fun:ngx_palloc_large 59 | fun:ngx_palloc 60 | fun:ngx_create_temp_buf 61 | fun:ngx_http_init_request 62 | fun:ngx_epoll_process_events 63 | fun:ngx_process_events_and_timers 64 | fun:ngx_single_process_cycle 65 | fun:main 66 | } 67 | { 68 | accept_create_pool 69 | Memcheck:Leak 70 | fun:memalign 71 | fun:posix_memalign 72 | fun:ngx_memalign 73 | fun:ngx_create_pool 74 | fun:ngx_event_accept 75 | fun:ngx_epoll_process_events 76 | fun:ngx_process_events_and_timers 77 | fun:ngx_single_process_cycle 78 | fun:main 79 | } 80 | { 81 | create_pool_for_init_req 82 | Memcheck:Leak 83 | fun:memalign 84 | fun:posix_memalign 85 | fun:ngx_memalign 86 | fun:ngx_create_pool 87 | fun:ngx_http_init_request 88 | fun:ngx_epoll_process_events 89 | fun:ngx_process_events_and_timers 90 | fun:ngx_single_process_cycle 91 | fun:main 92 | } 93 | { 94 | create_pool_posix_memalign 95 | Memcheck:Leak 96 | fun:memalign 97 | fun:posix_memalign 98 | fun:ngx_memalign 99 | fun:ngx_create_pool 100 | fun:main 101 | } 102 | { 103 | spawn_process_alloc 104 | Memcheck:Leak 105 | fun:malloc 106 | fun:ngx_alloc 107 | fun:ngx_event_process_init 108 | fun:ngx_worker_process_init 109 | fun:ngx_worker_process_cycle 110 | fun:ngx_spawn_process 111 | fun:ngx_start_worker_processes 112 | fun:ngx_master_process_cycle 113 | fun:main 114 | } 115 | { 116 | 117 | Memcheck:Leak 118 | fun:malloc 119 | fun:ngx_alloc 120 | fun:ngx_palloc_large 121 | fun:ngx_palloc 122 | fun:ngx_array_push 123 | fun:ngx_hash_add_key 124 | fun:ngx_http_variables_add_core_vars 125 | fun:ngx_http_core_preconfiguration 126 | fun:ngx_http_block 127 | fun:ngx_conf_parse 128 | fun:ngx_init_cycle 129 | fun:main 130 | } 131 | { 132 | 133 | Memcheck:Leak 134 | fun:malloc 135 | fun:ngx_alloc 136 | fun:ngx_palloc_large 137 | fun:ngx_palloc 138 | fun:ngx_pcalloc 139 | fun:ngx_hash_init 140 | fun:ngx_http_variables_init_vars 141 | fun:ngx_http_block 142 | fun:ngx_conf_parse 143 | fun:ngx_init_cycle 144 | fun:main 145 | } 146 | { 147 | 148 | Memcheck:Leak 149 | fun:malloc 150 | fun:ngx_alloc 151 | fun:ngx_palloc_large 152 | fun:ngx_palloc 153 | fun:ngx_pcalloc 154 | fun:ngx_hash_keys_array_init 155 | fun:ngx_http_variables_add_core_vars 156 | fun:ngx_http_core_preconfiguration 157 | fun:ngx_http_block 158 | fun:ngx_conf_parse 159 | fun:ngx_init_cycle 160 | fun:main 161 | } 162 | { 163 | 164 | Memcheck:Leak 165 | fun:malloc 166 | fun:ngx_alloc 167 | fun:ngx_palloc_large 168 | fun:ngx_palloc 169 | fun:ngx_array_push 170 | fun:ngx_hash_add_key 171 | fun:ngx_http_add_variable 172 | fun:ngx_http_ssi_preconfiguration 173 | fun:ngx_http_block 174 | fun:ngx_conf_parse 175 | fun:ngx_init_cycle 176 | fun:main 177 | } 178 | { 179 | 180 | Memcheck:Leak 181 | fun:malloc 182 | fun:ngx_alloc 183 | fun:ngx_palloc_large 184 | fun:ngx_palloc 185 | fun:ngx_pcalloc 186 | fun:ngx_http_upstream_drizzle_create_srv_conf 187 | fun:ngx_http_core_server 188 | fun:ngx_conf_parse 189 | fun:ngx_http_block 190 | fun:ngx_conf_parse 191 | fun:ngx_init_cycle 192 | fun:main 193 | } 194 | { 195 | 196 | Memcheck:Leak 197 | fun:malloc 198 | fun:ngx_alloc 199 | fun:ngx_palloc_large 200 | fun:ngx_palloc 201 | fun:ngx_pcalloc 202 | fun:ngx_http_upstream_drizzle_create_srv_conf 203 | fun:ngx_http_upstream 204 | fun:ngx_conf_parse 205 | fun:ngx_http_block 206 | fun:ngx_conf_parse 207 | fun:ngx_init_cycle 208 | fun:main 209 | } 210 | { 211 | 212 | Memcheck:Leak 213 | fun:malloc 214 | fun:ngx_alloc 215 | fun:ngx_palloc_large 216 | fun:ngx_palloc 217 | fun:ngx_pcalloc 218 | fun:ngx_http_upstream_drizzle_create_srv_conf 219 | fun:ngx_http_block 220 | fun:ngx_conf_parse 221 | fun:ngx_init_cycle 222 | fun:main 223 | } 224 | { 225 | 226 | Memcheck:Leak 227 | fun:malloc 228 | fun:ngx_alloc 229 | fun:ngx_palloc_large 230 | fun:ngx_palloc 231 | fun:ngx_pcalloc 232 | fun:ngx_init_cycle 233 | fun:main 234 | } 235 | { 236 | 237 | Memcheck:Leak 238 | fun:malloc 239 | fun:ngx_alloc 240 | fun:ngx_palloc_large 241 | fun:ngx_palloc 242 | fun:ngx_hash_init 243 | fun:ngx_http_upstream_init_main_conf 244 | fun:ngx_http_block 245 | fun:ngx_conf_parse 246 | fun:ngx_init_cycle 247 | fun:main 248 | } 249 | { 250 | 251 | Memcheck:Leak 252 | fun:malloc 253 | fun:ngx_alloc 254 | fun:ngx_palloc_large 255 | fun:ngx_palloc 256 | fun:ngx_array_push 257 | fun:ngx_http_get_variable_index 258 | fun:ngx_http_script_compile 259 | fun:ngx_http_rewrite_value 260 | fun:ndk_set_var_value_core 261 | fun:ndk_set_var_value 262 | fun:ngx_conf_parse 263 | fun:ngx_http_core_location 264 | } 265 | { 266 | 267 | Memcheck:Leak 268 | fun:malloc 269 | fun:ngx_alloc 270 | fun:ngx_palloc_large 271 | fun:ngx_palloc 272 | fun:ngx_array_push_n 273 | fun:ngx_http_script_add_code 274 | fun:ngx_http_script_compile 275 | fun:ngx_http_rewrite_value 276 | fun:ndk_set_var_value_core 277 | fun:ndk_set_var_value 278 | fun:ngx_conf_parse 279 | fun:ngx_http_core_location 280 | } 281 | { 282 | 283 | Memcheck:Leak 284 | fun:malloc 285 | fun:ngx_alloc 286 | fun:ngx_palloc_large 287 | fun:ngx_palloc 288 | fun:ngx_array_push 289 | fun:ngx_http_get_variable_index 290 | fun:ngx_http_rewrite_set 291 | fun:ngx_conf_parse 292 | fun:ngx_http_core_location 293 | fun:ngx_conf_parse 294 | fun:ngx_http_core_server 295 | fun:ngx_conf_parse 296 | } 297 | { 298 | 299 | Memcheck:Cond 300 | fun:index 301 | fun:expand_dynamic_string_token 302 | fun:_dl_map_object 303 | fun:map_doit 304 | fun:_dl_catch_error 305 | fun:do_preload 306 | fun:dl_main 307 | } 308 | { 309 | 310 | Memcheck:Leak 311 | match-leak-kinds: definite 312 | fun:malloc 313 | fun:ngx_alloc 314 | fun:ngx_set_environment 315 | fun:ngx_single_process_cycle 316 | } 317 | { 318 | 319 | Memcheck:Leak 320 | match-leak-kinds: definite 321 | fun:malloc 322 | fun:ngx_alloc 323 | fun:ngx_set_environment 324 | fun:ngx_worker_process_init 325 | fun:ngx_worker_process_cycle 326 | } 327 | -------------------------------------------------------------------------------- /t/compact/sanity.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | #repeat_each(10); 7 | no_shuffle(); 8 | 9 | repeat_each(2); 10 | 11 | plan tests => repeat_each() * 2 * blocks() + 2 * repeat_each() * 3; 12 | 13 | $ENV{TEST_NGINX_MYSQL_HOST} ||= '127.0.0.1'; 14 | $ENV{TEST_NGINX_MYSQL_PORT} ||= 3306; 15 | 16 | our $http_config = <<'_EOC_'; 17 | upstream backend { 18 | drizzle_server $TEST_NGINX_MYSQL_HOST:$TEST_NGINX_MYSQL_PORT protocol=mysql 19 | dbname=ngx_test user=ngx_test password=ngx_test; 20 | } 21 | _EOC_ 22 | 23 | #no_long_string(); 24 | 25 | run_tests(); 26 | 27 | #no_diff(); 28 | 29 | __DATA__ 30 | 31 | === TEST 1: sanity 32 | --- http_config eval: $::http_config 33 | --- config 34 | location /mysql { 35 | drizzle_pass backend; 36 | #drizzle_dbname $dbname; 37 | drizzle_query 'select * from cats'; 38 | rds_json on; 39 | rds_json_format compact; 40 | } 41 | --- request 42 | GET /mysql 43 | --- response_headers_like 44 | X-Resty-DBD-Module: ngx_drizzle \d+\.\d+\.\d+ 45 | Content-Type: application/json 46 | --- response_body chomp 47 | [["id","name"],[2,null],[3,"bob"]] 48 | --- timeout: 15 49 | 50 | 51 | 52 | === TEST 2: keep-alive 53 | --- http_config eval: $::http_config 54 | --- config 55 | location /mysql { 56 | drizzle_pass backend; 57 | #drizzle_dbname $dbname; 58 | drizzle_query 'select * from cats'; 59 | rds_json on; 60 | rds_json_format compact; 61 | } 62 | --- request 63 | GET /mysql 64 | --- response_body chop 65 | [["id","name"],[2,null],[3,"bob"]] 66 | 67 | 68 | 69 | === TEST 3: update 70 | --- http_config eval: $::http_config 71 | --- config 72 | location /mysql { 73 | drizzle_pass backend; 74 | #drizzle_dbname $dbname; 75 | drizzle_query "update cats set name='bob' where name='bob'"; 76 | rds_json on; 77 | rds_json_format compact; 78 | } 79 | --- request 80 | GET /mysql 81 | --- response_body chop 82 | {"errcode":0,"errstr":"Rows matched: 1 Changed: 0 Warnings: 0"} 83 | 84 | 85 | 86 | === TEST 4: select empty result 87 | --- http_config eval: $::http_config 88 | --- config 89 | location /mysql { 90 | drizzle_pass backend; 91 | drizzle_query "select * from cats where name='tom'"; 92 | rds_json on; 93 | rds_json_format compact; 94 | } 95 | --- request 96 | GET /mysql 97 | --- response_body chop 98 | [["id","name"]] 99 | 100 | 101 | 102 | === TEST 5: update & no module header 103 | --- http_config eval: $::http_config 104 | --- config 105 | location /mysql { 106 | if ($arg_name ~ '[^A-Za-z0-9]') { 107 | return 400; 108 | } 109 | 110 | drizzle_pass backend; 111 | drizzle_module_header off; 112 | drizzle_query "update cats set name='$arg_name' where name='$arg_name'"; 113 | 114 | rds_json on; 115 | rds_json_format compact; 116 | } 117 | --- request 118 | GET /mysql?name=bob 119 | --- response_headers 120 | X-Resty-DBD-Module: 121 | Content-Type: application/json 122 | --- response_body chop 123 | {"errcode":0,"errstr":"Rows matched: 1 Changed: 0 Warnings: 0"} 124 | 125 | 126 | 127 | === TEST 6: invalid SQL 128 | --- http_config eval: $::http_config 129 | --- config 130 | location /mysql { 131 | drizzle_pass backend; 132 | drizzle_module_header off; 133 | drizzle_query "select '32"; 134 | rds_json on; 135 | rds_json_format compact; 136 | } 137 | --- response_headers 138 | X-Resty-DBD-Module: 139 | Content-Type: text/html 140 | --- request 141 | GET /mysql 142 | --- error_code: 500 143 | --- response_body_like: 500 Internal Server Error 144 | 145 | 146 | 147 | === TEST 7: single row, single col 148 | --- http_config eval: $::http_config 149 | --- config 150 | location /test { 151 | echo_location /mysql "drop table if exists singles"; 152 | echo; 153 | echo_location /mysql "create table singles (name varchar(15));"; 154 | echo; 155 | echo_location /mysql "insert into singles values ('marry');"; 156 | echo; 157 | echo_location /mysql "select * from singles;"; 158 | echo; 159 | } 160 | location /mysql { 161 | drizzle_pass backend; 162 | drizzle_module_header off; 163 | drizzle_query $query_string; 164 | rds_json on; 165 | rds_json_format compact; 166 | } 167 | --- request 168 | GET /test 169 | --- response_body 170 | {"errcode":0} 171 | {"errcode":0} 172 | {"errcode":0,"affected_rows":1} 173 | [["name"],["marry"]] 174 | --- skip_nginx: 2: < 0.7.46 175 | --- timeout: 5 176 | 177 | 178 | 179 | === TEST 8: floating number and insert id 180 | --- http_config eval: $::http_config 181 | --- config 182 | location /test { 183 | echo_location /mysql "drop table if exists foo"; 184 | echo; 185 | echo_location /mysql "create table foo (id serial not null, primary key (id), val real);"; 186 | echo; 187 | echo_location /mysql "insert into foo (val) values (3.1415926);"; 188 | echo; 189 | echo_location /mysql "select * from foo;"; 190 | echo; 191 | } 192 | location /mysql { 193 | drizzle_pass backend; 194 | drizzle_module_header off; 195 | drizzle_query $query_string; 196 | rds_json on; 197 | rds_json_format compact; 198 | } 199 | --- request 200 | GET /test 201 | --- response_body 202 | {"errcode":0} 203 | {"errcode":0} 204 | {"errcode":0,"insert_id":1,"affected_rows":1} 205 | [["id","val"],[1,3.1415926]] 206 | --- skip_nginx: 2: < 0.7.46 207 | 208 | 209 | 210 | === TEST 9: text blob field 211 | --- http_config eval: $::http_config 212 | --- config 213 | location /test { 214 | echo_location /mysql "drop table if exists foo"; 215 | echo; 216 | echo_location /mysql "create table foo (id serial, body text);"; 217 | echo; 218 | echo_location /mysql "insert into foo (body) values ('hello');"; 219 | echo; 220 | echo_location /mysql "select * from foo;"; 221 | echo; 222 | } 223 | location /mysql { 224 | drizzle_pass backend; 225 | drizzle_module_header off; 226 | drizzle_query $query_string; 227 | rds_json on; 228 | rds_json_format compact; 229 | } 230 | --- request 231 | GET /test 232 | --- response_body 233 | {"errcode":0} 234 | {"errcode":0} 235 | {"errcode":0,"insert_id":1,"affected_rows":1} 236 | [["id","body"],[1,"hello"]] 237 | --- skip_nginx: 2: < 0.7.46 238 | 239 | 240 | 241 | === TEST 10: bool blob field 242 | --- http_config eval: $::http_config 243 | --- config 244 | location /test { 245 | echo_location /mysql "drop table if exists foo"; 246 | echo; 247 | echo_location /mysql "create table foo (id serial, flag bool);"; 248 | echo; 249 | echo_location /mysql "insert into foo (flag) values (true);"; 250 | echo; 251 | echo_location /mysql "insert into foo (flag) values (false);"; 252 | echo; 253 | echo_location /mysql "select * from foo order by id;"; 254 | echo; 255 | } 256 | location /mysql { 257 | drizzle_pass backend; 258 | drizzle_module_header off; 259 | drizzle_query $query_string; 260 | rds_json on; 261 | rds_json_format compact; 262 | } 263 | --- request 264 | GET /test 265 | --- response_body 266 | {"errcode":0} 267 | {"errcode":0} 268 | {"errcode":0,"insert_id":1,"affected_rows":1} 269 | {"errcode":0,"insert_id":2,"affected_rows":1} 270 | [["id","flag"],[1,1],[2,0]] 271 | --- skip_nginx: 2: < 0.7.46 272 | --- timeout: 10 273 | 274 | 275 | 276 | === TEST 11: bit field 277 | --- http_config eval: $::http_config 278 | --- config 279 | location /test { 280 | echo_location /mysql "drop table if exists foo"; 281 | echo; 282 | echo_location /mysql "create table foo (id serial, flag bit);"; 283 | echo; 284 | echo_location /mysql "insert into foo (flag) values (1);"; 285 | echo; 286 | echo_location /mysql "insert into foo (flag) values (0);"; 287 | echo; 288 | echo_location /mysql "select * from foo order by id;"; 289 | echo; 290 | } 291 | location /mysql { 292 | drizzle_pass backend; 293 | drizzle_module_header off; 294 | drizzle_query $query_string; 295 | rds_json on; 296 | rds_json_format compact; 297 | } 298 | --- request 299 | GET /test 300 | --- response_body 301 | {"errcode":0} 302 | {"errcode":0} 303 | {"errcode":0,"insert_id":1,"affected_rows":1} 304 | {"errcode":0,"insert_id":2,"affected_rows":1} 305 | [["id","flag"],[1,"\u0001"],[2,"\u0000"]] 306 | --- skip_nginx: 2: < 0.7.46 307 | --- timeout: 10 308 | 309 | 310 | 311 | === TEST 12: date type 312 | --- http_config eval: $::http_config 313 | --- config 314 | location /test { 315 | echo_location /mysql "drop table if exists foo"; 316 | echo; 317 | echo_location /mysql "create table foo (id serial, created date);"; 318 | echo; 319 | echo_location /mysql "insert into foo (created) values ('2007-05-24');"; 320 | echo; 321 | echo_location /mysql "select * from foo"; 322 | echo; 323 | } 324 | location /mysql { 325 | drizzle_pass backend; 326 | drizzle_module_header off; 327 | drizzle_query $query_string; 328 | rds_json on; 329 | rds_json_format compact; 330 | } 331 | --- request 332 | GET /test 333 | --- response_body 334 | {"errcode":0} 335 | {"errcode":0} 336 | {"errcode":0,"insert_id":1,"affected_rows":1} 337 | [["id","created"],[1,"2007-05-24"]] 338 | 339 | 340 | 341 | === TEST 13: strings need to be escaped 342 | --- http_config eval: $::http_config 343 | --- config 344 | location /test { 345 | echo_location /mysql "drop table if exists foo"; 346 | echo; 347 | echo_location /mysql "create table foo (id serial, body char(25));"; 348 | echo; 349 | echo_location /mysql "insert into foo (body) values ('a\\r\\nb\\bhello\Z');"; 350 | echo; 351 | echo_location /mysql "select * from foo"; 352 | echo; 353 | } 354 | location /mysql { 355 | drizzle_pass backend; 356 | drizzle_module_header off; 357 | drizzle_query $query_string; 358 | rds_json on; 359 | rds_json_format compact; 360 | } 361 | --- request 362 | GET /test 363 | --- response_body 364 | {"errcode":0} 365 | {"errcode":0} 366 | {"errcode":0,"insert_id":1,"affected_rows":1} 367 | [["id","body"],[1,"a\r\nb\bhello\u001a"]] 368 | 369 | 370 | 371 | === TEST 14: null values 372 | --- http_config eval: $::http_config 373 | --- config 374 | location /test { 375 | echo_location /mysql "drop table if exists foo"; 376 | echo; 377 | echo_location /mysql "create table foo (id serial, name char(10), age integer);"; 378 | echo; 379 | echo_location /mysql "insert into foo (name, age) values ('', null);"; 380 | echo; 381 | echo_location /mysql "insert into foo (name, age) values (null, 0);"; 382 | echo; 383 | echo_location /mysql "select * from foo order by id"; 384 | echo; 385 | } 386 | location /mysql { 387 | drizzle_pass backend; 388 | drizzle_module_header off; 389 | drizzle_query $query_string; 390 | rds_json on; 391 | rds_json_format compact; 392 | } 393 | --- request 394 | GET /test 395 | --- response_body 396 | {"errcode":0} 397 | {"errcode":0} 398 | {"errcode":0,"insert_id":1,"affected_rows":1} 399 | {"errcode":0,"insert_id":2,"affected_rows":1} 400 | [["id","name","age"],[1,"",null],[2,null,0]] 401 | --- timeout: 10 402 | 403 | -------------------------------------------------------------------------------- /t/sanity-stream.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | #repeat_each(10); 7 | no_shuffle(); 8 | 9 | repeat_each(2); 10 | 11 | plan tests => repeat_each() * 2 * blocks() + 2 * repeat_each() * 3; 12 | 13 | $ENV{TEST_NGINX_MYSQL_HOST} ||= '127.0.0.1'; 14 | $ENV{TEST_NGINX_MYSQL_PORT} ||= 3306; 15 | 16 | our $http_config = <<'_EOC_'; 17 | upstream backend { 18 | drizzle_server $TEST_NGINX_MYSQL_HOST:$TEST_NGINX_MYSQL_PORT protocol=mysql 19 | dbname=ngx_test user=ngx_test password=ngx_test; 20 | } 21 | _EOC_ 22 | 23 | #no_long_string(); 24 | 25 | run_tests(); 26 | 27 | #no_diff(); 28 | 29 | __DATA__ 30 | 31 | === TEST 1: sanity 32 | --- http_config eval: $::http_config 33 | --- config 34 | location /mysql { 35 | drizzle_pass backend; 36 | #drizzle_dbname $dbname; 37 | drizzle_query 'select * from cats'; 38 | rds_json on; 39 | rds_json_format compact; 40 | drizzle_buffer_size 1; 41 | } 42 | --- request 43 | GET /mysql 44 | --- response_headers_like 45 | X-Resty-DBD-Module: ngx_drizzle \d+\.\d+\.\d+ 46 | Content-Type: application/json 47 | --- response_body chomp 48 | [["id","name"],[2,null],[3,"bob"]] 49 | --- timeout: 15 50 | 51 | 52 | 53 | === TEST 2: keep-alive 54 | --- http_config eval: $::http_config 55 | --- config 56 | location /mysql { 57 | drizzle_pass backend; 58 | #drizzle_dbname $dbname; 59 | drizzle_query 'select * from cats'; 60 | rds_json on; 61 | rds_json_format compact; 62 | drizzle_buffer_size 1; 63 | } 64 | --- request 65 | GET /mysql 66 | --- response_body chop 67 | [["id","name"],[2,null],[3,"bob"]] 68 | 69 | 70 | 71 | === TEST 3: update 72 | --- http_config eval: $::http_config 73 | --- config 74 | location /mysql { 75 | drizzle_pass backend; 76 | #drizzle_dbname $dbname; 77 | drizzle_query "update cats set name='bob' where name='bob'"; 78 | rds_json on; 79 | rds_json_format compact; 80 | drizzle_buffer_size 1; 81 | } 82 | --- request 83 | GET /mysql 84 | --- response_body chop 85 | {"errcode":0,"errstr":"Rows matched: 1 Changed: 0 Warnings: 0"} 86 | 87 | 88 | 89 | === TEST 4: select empty result 90 | --- http_config eval: $::http_config 91 | --- config 92 | location /mysql { 93 | drizzle_pass backend; 94 | drizzle_query "select * from cats where name='tom'"; 95 | rds_json on; 96 | rds_json_format compact; 97 | drizzle_buffer_size 1; 98 | } 99 | --- request 100 | GET /mysql 101 | --- response_body chop 102 | [["id","name"]] 103 | 104 | 105 | 106 | === TEST 5: update & no module header 107 | --- http_config eval: $::http_config 108 | --- config 109 | location /mysql { 110 | if ($arg_name ~ '[^A-Za-z0-9]') { 111 | return 400; 112 | } 113 | 114 | drizzle_pass backend; 115 | drizzle_module_header off; 116 | drizzle_query "update cats set name='$arg_name' where name='$arg_name'"; 117 | 118 | rds_json on; 119 | rds_json_format compact; 120 | drizzle_buffer_size 1; 121 | } 122 | --- request 123 | GET /mysql?name=bob 124 | --- response_headers 125 | X-Resty-DBD-Module: 126 | Content-Type: application/json 127 | --- response_body chop 128 | {"errcode":0,"errstr":"Rows matched: 1 Changed: 0 Warnings: 0"} 129 | 130 | 131 | 132 | === TEST 6: invalid SQL 133 | --- http_config eval: $::http_config 134 | --- config 135 | location /mysql { 136 | drizzle_pass backend; 137 | drizzle_module_header off; 138 | drizzle_query "select '32"; 139 | rds_json on; 140 | rds_json_format compact; 141 | drizzle_buffer_size 1; 142 | } 143 | --- response_headers 144 | X-Resty-DBD-Module: 145 | Content-Type: text/html 146 | --- request 147 | GET /mysql 148 | --- error_code: 500 149 | --- response_body_like: 500 Internal Server Error 150 | 151 | 152 | 153 | === TEST 7: single row, single col 154 | --- http_config eval: $::http_config 155 | --- config 156 | location /test { 157 | echo_location /mysql "drop table if exists singles"; 158 | echo; 159 | echo_location /mysql "create table singles (name varchar(15));"; 160 | echo; 161 | echo_location /mysql "insert into singles values ('marry');"; 162 | echo; 163 | echo_location /mysql "select * from singles;"; 164 | echo; 165 | } 166 | location /mysql { 167 | drizzle_pass backend; 168 | drizzle_module_header off; 169 | drizzle_query $query_string; 170 | rds_json on; 171 | rds_json_format compact; 172 | drizzle_buffer_size 1; 173 | } 174 | --- request 175 | GET /test 176 | --- response_body 177 | {"errcode":0} 178 | {"errcode":0} 179 | {"errcode":0,"affected_rows":1} 180 | [["name"],["marry"]] 181 | --- skip_nginx: 2: < 0.7.46 182 | --- timeout: 5 183 | 184 | 185 | 186 | === TEST 8: floating number and insert id 187 | --- http_config eval: $::http_config 188 | --- config 189 | location /test { 190 | echo_location /mysql "drop table if exists foo"; 191 | echo; 192 | echo_location /mysql "create table foo (id serial not null, primary key (id), val real);"; 193 | echo; 194 | echo_location /mysql "insert into foo (val) values (3.1415926);"; 195 | echo; 196 | echo_location /mysql "select * from foo;"; 197 | echo; 198 | } 199 | location /mysql { 200 | drizzle_pass backend; 201 | drizzle_module_header off; 202 | drizzle_query $query_string; 203 | rds_json on; 204 | rds_json_format compact; 205 | drizzle_buffer_size 1; 206 | } 207 | --- request 208 | GET /test 209 | --- response_body 210 | {"errcode":0} 211 | {"errcode":0} 212 | {"errcode":0,"insert_id":1,"affected_rows":1} 213 | [["id","val"],[1,3.1415926]] 214 | --- skip_nginx: 2: < 0.7.46 215 | 216 | 217 | 218 | === TEST 9: text blob field 219 | --- http_config eval: $::http_config 220 | --- config 221 | location /test { 222 | echo_location /mysql "drop table if exists foo"; 223 | echo; 224 | echo_location /mysql "create table foo (id serial, body text);"; 225 | echo; 226 | echo_location /mysql "insert into foo (body) values ('hello');"; 227 | echo; 228 | echo_location /mysql "select * from foo;"; 229 | echo; 230 | } 231 | location /mysql { 232 | drizzle_pass backend; 233 | drizzle_module_header off; 234 | drizzle_query $query_string; 235 | rds_json on; 236 | rds_json_format compact; 237 | drizzle_buffer_size 1; 238 | } 239 | --- request 240 | GET /test 241 | --- response_body 242 | {"errcode":0} 243 | {"errcode":0} 244 | {"errcode":0,"insert_id":1,"affected_rows":1} 245 | [["id","body"],[1,"hello"]] 246 | --- skip_nginx: 2: < 0.7.46 247 | 248 | 249 | 250 | === TEST 10: bool blob field 251 | --- http_config eval: $::http_config 252 | --- config 253 | location /test { 254 | echo_location /mysql "drop table if exists foo"; 255 | echo; 256 | echo_location /mysql "create table foo (id serial, flag bool);"; 257 | echo; 258 | echo_location /mysql "insert into foo (flag) values (true);"; 259 | echo; 260 | echo_location /mysql "insert into foo (flag) values (false);"; 261 | echo; 262 | echo_location /mysql "select * from foo order by id;"; 263 | echo; 264 | } 265 | location /mysql { 266 | drizzle_pass backend; 267 | drizzle_module_header off; 268 | drizzle_query $query_string; 269 | rds_json on; 270 | rds_json_format compact; 271 | drizzle_buffer_size 1; 272 | } 273 | --- request 274 | GET /test 275 | --- response_body 276 | {"errcode":0} 277 | {"errcode":0} 278 | {"errcode":0,"insert_id":1,"affected_rows":1} 279 | {"errcode":0,"insert_id":2,"affected_rows":1} 280 | [["id","flag"],[1,1],[2,0]] 281 | --- skip_nginx: 2: < 0.7.46 282 | --- timeout: 10 283 | 284 | 285 | 286 | === TEST 11: bit field 287 | --- http_config eval: $::http_config 288 | --- config 289 | location /test { 290 | echo_location /mysql "drop table if exists foo"; 291 | echo; 292 | echo_location /mysql "create table foo (id serial, flag bit);"; 293 | echo; 294 | echo_location /mysql "insert into foo (flag) values (1);"; 295 | echo; 296 | echo_location /mysql "insert into foo (flag) values (0);"; 297 | echo; 298 | echo_location /mysql "select * from foo order by id;"; 299 | echo; 300 | } 301 | location /mysql { 302 | drizzle_pass backend; 303 | drizzle_module_header off; 304 | drizzle_query $query_string; 305 | rds_json on; 306 | rds_json_format compact; 307 | drizzle_buffer_size 1; 308 | } 309 | --- request 310 | GET /test 311 | --- response_body 312 | {"errcode":0} 313 | {"errcode":0} 314 | {"errcode":0,"insert_id":1,"affected_rows":1} 315 | {"errcode":0,"insert_id":2,"affected_rows":1} 316 | [["id","flag"],[1,"\u0001"],[2,"\u0000"]] 317 | --- skip_nginx: 2: < 0.7.46 318 | --- timeout: 10 319 | 320 | 321 | 322 | === TEST 12: date type 323 | --- http_config eval: $::http_config 324 | --- config 325 | location /test { 326 | echo_location /mysql "drop table if exists foo"; 327 | echo; 328 | echo_location /mysql "create table foo (id serial, created date);"; 329 | echo; 330 | echo_location /mysql "insert into foo (created) values ('2007-05-24');"; 331 | echo; 332 | echo_location /mysql "select * from foo"; 333 | echo; 334 | } 335 | location /mysql { 336 | drizzle_pass backend; 337 | drizzle_module_header off; 338 | drizzle_query $query_string; 339 | rds_json on; 340 | rds_json_format compact; 341 | drizzle_buffer_size 1; 342 | } 343 | --- request 344 | GET /test 345 | --- response_body 346 | {"errcode":0} 347 | {"errcode":0} 348 | {"errcode":0,"insert_id":1,"affected_rows":1} 349 | [["id","created"],[1,"2007-05-24"]] 350 | 351 | 352 | 353 | === TEST 13: strings need to be escaped 354 | --- http_config eval: $::http_config 355 | --- config 356 | location /test { 357 | echo_location /mysql "drop table if exists foo"; 358 | echo; 359 | echo_location /mysql "create table foo (id serial, body char(25));"; 360 | echo; 361 | echo_location /mysql "insert into foo (body) values ('a\\r\\nb\\bhello\Z');"; 362 | echo; 363 | echo_location /mysql "select * from foo"; 364 | echo; 365 | } 366 | location /mysql { 367 | drizzle_pass backend; 368 | drizzle_module_header off; 369 | drizzle_query $query_string; 370 | rds_json on; 371 | rds_json_format compact; 372 | drizzle_buffer_size 1; 373 | } 374 | --- request 375 | GET /test 376 | --- response_body 377 | {"errcode":0} 378 | {"errcode":0} 379 | {"errcode":0,"insert_id":1,"affected_rows":1} 380 | [["id","body"],[1,"a\r\nb\bhello\u001a"]] 381 | 382 | 383 | 384 | === TEST 14: null values 385 | --- http_config eval: $::http_config 386 | --- config 387 | location /test { 388 | echo_location /mysql "drop table if exists foo"; 389 | echo; 390 | echo_location /mysql "create table foo (id serial, name char(10), age integer);"; 391 | echo; 392 | echo_location /mysql "insert into foo (name, age) values ('', null);"; 393 | echo; 394 | echo_location /mysql "insert into foo (name, age) values (null, 0);"; 395 | echo; 396 | echo_location /mysql "select * from foo order by id"; 397 | echo; 398 | } 399 | location /mysql { 400 | drizzle_pass backend; 401 | drizzle_module_header off; 402 | drizzle_query $query_string; 403 | rds_json on; 404 | rds_json_format compact; 405 | drizzle_buffer_size 1; 406 | } 407 | --- request 408 | GET /test 409 | --- response_body 410 | {"errcode":0} 411 | {"errcode":0} 412 | {"errcode":0,"insert_id":1,"affected_rows":1} 413 | {"errcode":0,"insert_id":2,"affected_rows":1} 414 | [["id","name","age"],[1,"",null],[2,null,0]] 415 | --- timeout: 10 416 | 417 | -------------------------------------------------------------------------------- /t/compact/sanity-stream.t: -------------------------------------------------------------------------------- 1 | # vi:filetype=perl 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | #repeat_each(10); 7 | no_shuffle(); 8 | 9 | repeat_each(2); 10 | 11 | plan tests => repeat_each() * 2 * blocks() + 2 * repeat_each() * 3; 12 | 13 | $ENV{TEST_NGINX_MYSQL_HOST} ||= '127.0.0.1'; 14 | $ENV{TEST_NGINX_MYSQL_PORT} ||= 3306; 15 | 16 | our $http_config = <<'_EOC_'; 17 | upstream backend { 18 | drizzle_server $TEST_NGINX_MYSQL_HOST:$TEST_NGINX_MYSQL_PORT protocol=mysql 19 | dbname=ngx_test user=ngx_test password=ngx_test; 20 | } 21 | _EOC_ 22 | 23 | #no_long_string(); 24 | 25 | run_tests(); 26 | 27 | #no_diff(); 28 | 29 | __DATA__ 30 | 31 | === TEST 1: sanity 32 | --- http_config eval: $::http_config 33 | --- config 34 | location /mysql { 35 | drizzle_pass backend; 36 | #drizzle_dbname $dbname; 37 | drizzle_query 'select * from cats'; 38 | rds_json on; 39 | rds_json_format compact; 40 | drizzle_buffer_size 1; 41 | } 42 | --- request 43 | GET /mysql 44 | --- response_headers_like 45 | X-Resty-DBD-Module: ngx_drizzle \d+\.\d+\.\d+ 46 | Content-Type: application/json 47 | --- response_body chomp 48 | [["id","name"],[2,null],[3,"bob"]] 49 | --- timeout: 15 50 | 51 | 52 | 53 | === TEST 2: keep-alive 54 | --- http_config eval: $::http_config 55 | --- config 56 | location /mysql { 57 | drizzle_pass backend; 58 | #drizzle_dbname $dbname; 59 | drizzle_query 'select * from cats'; 60 | rds_json on; 61 | rds_json_format compact; 62 | drizzle_buffer_size 1; 63 | } 64 | --- request 65 | GET /mysql 66 | --- response_body chop 67 | [["id","name"],[2,null],[3,"bob"]] 68 | 69 | 70 | 71 | === TEST 3: update 72 | --- http_config eval: $::http_config 73 | --- config 74 | location /mysql { 75 | drizzle_pass backend; 76 | #drizzle_dbname $dbname; 77 | drizzle_query "update cats set name='bob' where name='bob'"; 78 | rds_json on; 79 | rds_json_format compact; 80 | drizzle_buffer_size 1; 81 | } 82 | --- request 83 | GET /mysql 84 | --- response_body chop 85 | {"errcode":0,"errstr":"Rows matched: 1 Changed: 0 Warnings: 0"} 86 | 87 | 88 | 89 | === TEST 4: select empty result 90 | --- http_config eval: $::http_config 91 | --- config 92 | location /mysql { 93 | drizzle_pass backend; 94 | drizzle_query "select * from cats where name='tom'"; 95 | rds_json on; 96 | rds_json_format compact; 97 | drizzle_buffer_size 1; 98 | } 99 | --- request 100 | GET /mysql 101 | --- response_body chop 102 | [["id","name"]] 103 | 104 | 105 | 106 | === TEST 5: update & no module header 107 | --- http_config eval: $::http_config 108 | --- config 109 | location /mysql { 110 | if ($arg_name ~ '[^A-Za-z0-9]') { 111 | return 400; 112 | } 113 | 114 | drizzle_pass backend; 115 | drizzle_module_header off; 116 | drizzle_query "update cats set name='$arg_name' where name='$arg_name'"; 117 | 118 | rds_json on; 119 | rds_json_format compact; 120 | drizzle_buffer_size 1; 121 | } 122 | --- request 123 | GET /mysql?name=bob 124 | --- response_headers 125 | X-Resty-DBD-Module: 126 | Content-Type: application/json 127 | --- response_body chop 128 | {"errcode":0,"errstr":"Rows matched: 1 Changed: 0 Warnings: 0"} 129 | 130 | 131 | 132 | === TEST 6: invalid SQL 133 | --- http_config eval: $::http_config 134 | --- config 135 | location /mysql { 136 | drizzle_pass backend; 137 | drizzle_module_header off; 138 | drizzle_query "select '32"; 139 | rds_json on; 140 | rds_json_format compact; 141 | drizzle_buffer_size 1; 142 | } 143 | --- response_headers 144 | X-Resty-DBD-Module: 145 | Content-Type: text/html 146 | --- request 147 | GET /mysql 148 | --- error_code: 500 149 | --- response_body_like: 500 Internal Server Error 150 | 151 | 152 | 153 | === TEST 7: single row, single col 154 | --- http_config eval: $::http_config 155 | --- config 156 | location /test { 157 | echo_location /mysql "drop table if exists singles"; 158 | echo; 159 | echo_location /mysql "create table singles (name varchar(15));"; 160 | echo; 161 | echo_location /mysql "insert into singles values ('marry');"; 162 | echo; 163 | echo_location /mysql "select * from singles;"; 164 | echo; 165 | } 166 | location /mysql { 167 | drizzle_pass backend; 168 | drizzle_module_header off; 169 | drizzle_query $query_string; 170 | rds_json on; 171 | rds_json_format compact; 172 | drizzle_buffer_size 1; 173 | } 174 | --- request 175 | GET /test 176 | --- response_body 177 | {"errcode":0} 178 | {"errcode":0} 179 | {"errcode":0,"affected_rows":1} 180 | [["name"],["marry"]] 181 | --- skip_nginx: 2: < 0.7.46 182 | --- timeout: 5 183 | 184 | 185 | 186 | === TEST 8: floating number and insert id 187 | --- http_config eval: $::http_config 188 | --- config 189 | location /test { 190 | echo_location /mysql "drop table if exists foo"; 191 | echo; 192 | echo_location /mysql "create table foo (id serial not null, primary key (id), val real);"; 193 | echo; 194 | echo_location /mysql "insert into foo (val) values (3.1415926);"; 195 | echo; 196 | echo_location /mysql "select * from foo;"; 197 | echo; 198 | } 199 | location /mysql { 200 | drizzle_pass backend; 201 | drizzle_module_header off; 202 | drizzle_query $query_string; 203 | rds_json on; 204 | rds_json_format compact; 205 | drizzle_buffer_size 1; 206 | } 207 | --- request 208 | GET /test 209 | --- response_body 210 | {"errcode":0} 211 | {"errcode":0} 212 | {"errcode":0,"insert_id":1,"affected_rows":1} 213 | [["id","val"],[1,3.1415926]] 214 | --- skip_nginx: 2: < 0.7.46 215 | 216 | 217 | 218 | === TEST 9: text blob field 219 | --- http_config eval: $::http_config 220 | --- config 221 | location /test { 222 | echo_location /mysql "drop table if exists foo"; 223 | echo; 224 | echo_location /mysql "create table foo (id serial, body text);"; 225 | echo; 226 | echo_location /mysql "insert into foo (body) values ('hello');"; 227 | echo; 228 | echo_location /mysql "select * from foo;"; 229 | echo; 230 | } 231 | location /mysql { 232 | drizzle_pass backend; 233 | drizzle_module_header off; 234 | drizzle_query $query_string; 235 | rds_json on; 236 | rds_json_format compact; 237 | drizzle_buffer_size 1; 238 | } 239 | --- request 240 | GET /test 241 | --- response_body 242 | {"errcode":0} 243 | {"errcode":0} 244 | {"errcode":0,"insert_id":1,"affected_rows":1} 245 | [["id","body"],[1,"hello"]] 246 | --- skip_nginx: 2: < 0.7.46 247 | 248 | 249 | 250 | === TEST 10: bool blob field 251 | --- http_config eval: $::http_config 252 | --- config 253 | location /test { 254 | echo_location /mysql "drop table if exists foo"; 255 | echo; 256 | echo_location /mysql "create table foo (id serial, flag bool);"; 257 | echo; 258 | echo_location /mysql "insert into foo (flag) values (true);"; 259 | echo; 260 | echo_location /mysql "insert into foo (flag) values (false);"; 261 | echo; 262 | echo_location /mysql "select * from foo order by id;"; 263 | echo; 264 | } 265 | location /mysql { 266 | drizzle_pass backend; 267 | drizzle_module_header off; 268 | drizzle_query $query_string; 269 | rds_json on; 270 | rds_json_format compact; 271 | drizzle_buffer_size 1; 272 | } 273 | --- request 274 | GET /test 275 | --- response_body 276 | {"errcode":0} 277 | {"errcode":0} 278 | {"errcode":0,"insert_id":1,"affected_rows":1} 279 | {"errcode":0,"insert_id":2,"affected_rows":1} 280 | [["id","flag"],[1,1],[2,0]] 281 | --- skip_nginx: 2: < 0.7.46 282 | --- timeout: 10 283 | 284 | 285 | 286 | === TEST 11: bit field 287 | --- http_config eval: $::http_config 288 | --- config 289 | location /test { 290 | echo_location /mysql "drop table if exists foo"; 291 | echo; 292 | echo_location /mysql "create table foo (id serial, flag bit);"; 293 | echo; 294 | echo_location /mysql "insert into foo (flag) values (1);"; 295 | echo; 296 | echo_location /mysql "insert into foo (flag) values (0);"; 297 | echo; 298 | echo_location /mysql "select * from foo order by id;"; 299 | echo; 300 | } 301 | location /mysql { 302 | drizzle_pass backend; 303 | drizzle_module_header off; 304 | drizzle_query $query_string; 305 | rds_json on; 306 | rds_json_format compact; 307 | drizzle_buffer_size 1; 308 | } 309 | --- request 310 | GET /test 311 | --- response_body 312 | {"errcode":0} 313 | {"errcode":0} 314 | {"errcode":0,"insert_id":1,"affected_rows":1} 315 | {"errcode":0,"insert_id":2,"affected_rows":1} 316 | [["id","flag"],[1,"\u0001"],[2,"\u0000"]] 317 | --- skip_nginx: 2: < 0.7.46 318 | --- timeout: 10 319 | 320 | 321 | 322 | === TEST 12: date type 323 | --- http_config eval: $::http_config 324 | --- config 325 | location /test { 326 | echo_location /mysql "drop table if exists foo"; 327 | echo; 328 | echo_location /mysql "create table foo (id serial, created date);"; 329 | echo; 330 | echo_location /mysql "insert into foo (created) values ('2007-05-24');"; 331 | echo; 332 | echo_location /mysql "select * from foo"; 333 | echo; 334 | } 335 | location /mysql { 336 | drizzle_pass backend; 337 | drizzle_module_header off; 338 | drizzle_query $query_string; 339 | rds_json on; 340 | rds_json_format compact; 341 | drizzle_buffer_size 1; 342 | } 343 | --- request 344 | GET /test 345 | --- response_body 346 | {"errcode":0} 347 | {"errcode":0} 348 | {"errcode":0,"insert_id":1,"affected_rows":1} 349 | [["id","created"],[1,"2007-05-24"]] 350 | 351 | 352 | 353 | === TEST 13: strings need to be escaped 354 | --- http_config eval: $::http_config 355 | --- config 356 | location /test { 357 | echo_location /mysql "drop table if exists foo"; 358 | echo; 359 | echo_location /mysql "create table foo (id serial, body char(25));"; 360 | echo; 361 | echo_location /mysql "insert into foo (body) values ('a\\r\\nb\\bhello\Z');"; 362 | echo; 363 | echo_location /mysql "select * from foo"; 364 | echo; 365 | } 366 | location /mysql { 367 | drizzle_pass backend; 368 | drizzle_module_header off; 369 | drizzle_query $query_string; 370 | rds_json on; 371 | rds_json_format compact; 372 | drizzle_buffer_size 1; 373 | } 374 | --- request 375 | GET /test 376 | --- response_body 377 | {"errcode":0} 378 | {"errcode":0} 379 | {"errcode":0,"insert_id":1,"affected_rows":1} 380 | [["id","body"],[1,"a\r\nb\bhello\u001a"]] 381 | 382 | 383 | 384 | === TEST 14: null values 385 | --- http_config eval: $::http_config 386 | --- config 387 | location /test { 388 | echo_location /mysql "drop table if exists foo"; 389 | echo; 390 | echo_location /mysql "create table foo (id serial, name char(10), age integer);"; 391 | echo; 392 | echo_location /mysql "insert into foo (name, age) values ('', null);"; 393 | echo; 394 | echo_location /mysql "insert into foo (name, age) values (null, 0);"; 395 | echo; 396 | echo_location /mysql "select * from foo order by id"; 397 | echo; 398 | } 399 | location /mysql { 400 | drizzle_pass backend; 401 | drizzle_module_header off; 402 | drizzle_query $query_string; 403 | rds_json on; 404 | rds_json_format compact; 405 | drizzle_buffer_size 1; 406 | } 407 | --- request 408 | GET /test 409 | --- response_body 410 | {"errcode":0} 411 | {"errcode":0} 412 | {"errcode":0,"insert_id":1,"affected_rows":1} 413 | {"errcode":0,"insert_id":2,"affected_rows":1} 414 | [["id","name","age"],[1,"",null],[2,null,0]] 415 | --- timeout: 10 416 | 417 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Name 2 | ==== 3 | 4 | ngx_rds_json - an output filter that formats Resty DBD Streams generated by ngx_drizzle and others to JSON 5 | 6 | Table of Contents 7 | ================= 8 | 9 | * [Name](#name) 10 | * [Status](#status) 11 | * [Synopsis](#synopsis) 12 | * [Description](#description) 13 | * [Directives](#directives) 14 | * [rds_json](#rds_json) 15 | * [rds_json_buffer_size](#rds_json_buffer_size) 16 | * [rds_json_format](#rds_json_format) 17 | * [rds_json_root](#rds_json_root) 18 | * [rds_json_success_property](#rds_json_success_property) 19 | * [rds_json_user_property](#rds_json_user_property) 20 | * [rds_json_errcode_key](#rds_json_errcode_key) 21 | * [rds_json_errstr_key](#rds_json_errstr_key) 22 | * [rds_json_ret](#rds_json_ret) 23 | * [rds_json_content_type](#rds_json_content_type) 24 | * [Installation](#installation) 25 | * [Compatibility](#compatibility) 26 | * [Author](#author) 27 | * [Copyright & License](#copyright--license) 28 | * [See Also](#see-also) 29 | 30 | Status 31 | ====== 32 | 33 | This module is considered production ready. 34 | 35 | We need your help! If you find this module useful and/or interesting, please consider joining the development! 36 | Commit bit can be freely delivered at your request ;) 37 | 38 | Synopsis 39 | ======== 40 | 41 | ```nginx 42 | server { 43 | location /mysql { 44 | drizzle_query 'select * from cats'; 45 | drizzle_pass my_mysql_upstream; 46 | 47 | rds_json on; 48 | } 49 | 50 | location /foo { 51 | if ($arg_limit !~ '^\d{2}$') { 52 | rds_json_ret 400 'Bad "limit" argument'; 53 | } 54 | 55 | drizzle_query "select * from dogs limit $arg_limit"; 56 | drizzle_pass my_mysql_upstream; 57 | 58 | rds_json on; 59 | } 60 | ... 61 | } 62 | ``` 63 | 64 | Description 65 | =========== 66 | 67 | This module provides an output filter that can format the RDS outputs 68 | generated by [ngx_drizzle](https://github.com/openresty/drizzle-nginx-module) 69 | and [ngx_postgres](https://github.com/FRiCKLE/ngx_postgres/) modules to JSON. 70 | 71 | [Back to TOC](#table-of-contents) 72 | 73 | Directives 74 | ========== 75 | 76 | [Back to TOC](#table-of-contents) 77 | 78 | rds_json 79 | -------- 80 | **syntax:** *rds_json on|off* 81 | 82 | **default:** *rds_json off* 83 | 84 | **context:** *http, server, location, if location* 85 | 86 | Enables or disables the output filter of this module. 87 | 88 | [Back to TOC](#table-of-contents) 89 | 90 | rds_json_buffer_size 91 | -------------------- 92 | **syntax:** *rds_json_buffer_size <bytes>* 93 | 94 | **default:** *rds_json_buffer_size <page-size>* 95 | 96 | **context:** *http, server, location, if location* 97 | 98 | Controls the buffer size used by this module. default to the page size (4k/8k). 99 | The bigger the buffer size, the less streammy the conversion 100 | will be. But usually increasing the buffer size 101 | does help reduce CPU time. 102 | 103 | [Back to TOC](#table-of-contents) 104 | 105 | rds_json_format 106 | --------------- 107 | **syntax:** *rds_json_format normal|compact* 108 | 109 | **default:** *rds_json_format normal* 110 | 111 | **context:** *http, server, location, if location* 112 | 113 | Controls the output JSON format. A sample of the default "normal" format 114 | looks like this 115 | 116 | ```json 117 | [{"id":1,"name":"marry"},{"id":2,"name":"bob"}] 118 | ``` 119 | 120 | while it looks like below when in the "compact" format 121 | 122 | ```json 123 | [["id","name"],[1,"marry"],[2,"bob"]] 124 | ``` 125 | 126 | that is, the first row holds the column name list. 127 | 128 | [Back to TOC](#table-of-contents) 129 | 130 | rds_json_root 131 | ------------- 132 | **syntax:** *rds_json_root <key>* 133 | 134 | **default:** *no* 135 | 136 | **context:** *http, server, location, if location* 137 | 138 | Specifies the "root" key for data rows (if any). For example, 139 | 140 | ```nginx 141 | rds_json on; 142 | rds_json_root rows; 143 | ``` 144 | 145 | will return JSON output like this: 146 | 147 | ```json 148 | {"rows":[{"id":2,"name":null},{"id":3,"name":"bob"}]} 149 | ``` 150 | 151 | if `rds_json_format compact` is also specified, then the 152 | output will look like this: 153 | 154 | ```json 155 | {"rows":[["id","name"],[2,null],[3,"bob"]]} 156 | ``` 157 | 158 | Nginx variables are not supported in the argument of 159 | this directive. 160 | 161 | If this directive is not defined, neither are [rds_json_success_property](#rds_json_success_property), 162 | nor [rds_json_user_property](#rds_json_user_property), the JSON output for select queries will 163 | just be an array at the top level. 164 | 165 | When either [rds_json_success_property](#rds_json_success_property) or [rds_json_user_property](#rds_json_user_property) are specified, 166 | this directive takes a default argument of "data". 167 | 168 | [Back to TOC](#table-of-contents) 169 | 170 | rds_json_success_property 171 | ------------------------- 172 | **syntax:** *rds_json_success_property <key>* 173 | 174 | **default:** *no* 175 | 176 | **context:** *http, server, location, if location* 177 | 178 | Specifies the top-level object property name used in the JSON output 179 | for indicating success or false of the query. 180 | 181 | [Back to TOC](#table-of-contents) 182 | 183 | rds_json_user_property 184 | ----------------------- 185 | **syntax:** *rds_json_user_property <key> <value>* 186 | 187 | **default:** *no* 188 | 189 | **context:** *http, server, location, if location* 190 | 191 | Specifies additonal user properties for the top-level object 192 | of the JSON output. 193 | 194 | Multiple instances of this directives are allowed in a single scope. 195 | 196 | Nginx variables are supported in the `value` argument. 197 | 198 | Both of the `key` and `value` arguments will be automatically 199 | quoted according to JSON strings' notation. 200 | 201 | [Back to TOC](#table-of-contents) 202 | 203 | rds_json_errcode_key 204 | --------------------- 205 | **syntax:** *rds_json_errcode_key <key>* 206 | 207 | **default:** *rds_json_errcode_key errcode* 208 | 209 | **context:** *http, server, location, if location* 210 | 211 | Specifies the errcode key name used in the JSON output. 212 | 213 | [Back to TOC](#table-of-contents) 214 | 215 | rds_json_errstr_key 216 | ------------------- 217 | **syntax:** *rds_json_errstr_key <key>* 218 | 219 | **default:** *rds_json_errstr_key errstr* 220 | 221 | **context:** *http, server, location, if location* 222 | 223 | Specifies the errstr key name used in the JSON output. 224 | 225 | [Back to TOC](#table-of-contents) 226 | 227 | rds_json_ret 228 | ------------ 229 | **syntax:** *rds_json_ret <error-code> <descrption>* 230 | 231 | **default:** *no* 232 | 233 | **context:** *location, if location* 234 | 235 | This directive enables a content handler that simply emits 236 | an response body like this: 237 | 238 | ```json 239 | {"errcode":,"errstr":""} 240 | ``` 241 | 242 | while the `` string will be properly quoted as 243 | a JSON string. 244 | 245 | [Back to TOC](#table-of-contents) 246 | 247 | rds_json_content_type 248 | --------------------- 249 | **syntax:** *rds_json_content_type <mime-type>* 250 | 251 | **default:** *rds_json_content_type application/json* 252 | 253 | **context:** *http, server, location, if location* 254 | 255 | Controls the `Content-Type` header of the response generated by 256 | this module's output filter. 257 | 258 | [Back to TOC](#table-of-contents) 259 | 260 | Installation 261 | ============ 262 | 263 | You're recommended to install this module (as well as the Nginx core and many other goodies) via the [OpenResty bundle](http://openresty.org). See [the detailed instructions](http://openresty.org/#Installation) for downloading and installing OpenResty into your system. This is the easiest and most safe way to set things up. 264 | 265 | Alternatively, you can install this module manually with the Nginx source: 266 | 267 | Grab the nginx source code from [nginx.org](http://nginx.org/), for example, 268 | the version 1.13.6 (see [nginx compatibility](#compatibility)), and then build the source with this module: 269 | 270 | ```bash 271 | 272 | $ wget 'http://nginx.org/download/nginx-1.13.6.tar.gz' 273 | $ tar -xzvf nginx-1.13.6.tar.gz 274 | $ cd nginx-1.13.6/ 275 | 276 | # Here we assume you would install you nginx under /opt/nginx/. 277 | $ ./configure --prefix=/opt/nginx \ 278 | --add-module=/path/to/rds-json-nginx-module 279 | 280 | $ make -j2 281 | $ make install 282 | ``` 283 | 284 | Download the latest version of the release tarball of this module from [rds-json-nginx-module file list](https://github.com/openresty/rds-json-nginx-module/tags). 285 | 286 | Starting from NGINX 1.9.11, you can also compile this module as a dynamic module, by using the `--add-dynamic-module=PATH` option 287 | instead of `--add-module=PATH` on the `./configure` command line above. 288 | And then you can explicitly load the module in your `nginx.conf` via the [load_module](http://nginx.org/en/docs/ngx_core_module.html#load_module) directive, for example, 289 | 290 | ```nginx 291 | load_module /path/to/modules/ngx_http_rds_json_filter_module.so; 292 | ``` 293 | 294 | Also, this module is included and enabled by default in the [OpenResty bundle](http://openresty.org). 295 | 296 | [Back to TOC](#table-of-contents) 297 | 298 | Compatibility 299 | ============= 300 | The following versions of Nginx should work with this module: 301 | 302 | * **1.13.x** (last tested: 1.13.6) 303 | * **1.12.x** 304 | * **1.11.x** (last tested: 1.11.2) 305 | * **1.10.x** 306 | * **1.9.x** (last tested: 1.9.7) 307 | * **1.8.x** 308 | * **1.7.x** (last tested: 1.7.10) 309 | * **1.6.x** 310 | * **1.5.x** (last tested: 1.5.12) 311 | * **1.4.x** (last tested: 1.4.4) 312 | * **1.2.x** (last tested: 1.2.9) 313 | * **1.1.x** (last tested: 1.1.5) 314 | * **1.0.x** (last tested: 1.0.9) 315 | * **0.9.x** (last tested: 0.9.4) 316 | * **0.8.x** (last tested: 0.8.55) 317 | * **0.7.x >= 0.7.46** (last tested: 0.7.68) 318 | 319 | Earlier versions of Nginx like 0.6.x and 0.5.x will *not* work. 320 | 321 | If you find that any particular version of Nginx above 0.7.44 does not 322 | work with this module, please consider reporting a bug. 323 | 324 | [Back to TOC](#table-of-contents) 325 | 326 | Author 327 | ====== 328 | Yichun "agentzh" Zhang (章亦春) *<agentzh@gmail.com>*, OpenResty Inc. 329 | 330 | [Back to TOC](#table-of-contents) 331 | 332 | Copyright & License 333 | =================== 334 | 335 | This module is licenced under the BSD license. 336 | 337 | Copyright (C) 2009-2017, Yichun Zhang (agentzh) <agentzh@gmail.com>, OpenResty Inc. 338 | 339 | All rights reserved. 340 | 341 | Redistribution and use in source and binary forms, with or without 342 | modification, are permitted provided that the following conditions 343 | are met: 344 | 345 | * Redistributions of source code must retain the above copyright 346 | notice, this list of conditions and the following disclaimer. 347 | * Redistributions in binary form must reproduce the above copyright 348 | notice, this list of conditions and the following disclaimer in the 349 | documentation and/or other materials provided with the distribution. 350 | 351 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 352 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 353 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 354 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 355 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 356 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 357 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 358 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 359 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 360 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 361 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 362 | 363 | [Back to TOC](#table-of-contents) 364 | 365 | See Also 366 | ======== 367 | 368 | * [ngx_drizzle](https://github.com/openresty/drizzle-nginx-module) 369 | * [ngx_postgres](https://github.com/FRiCKLE/ngx_postgres/) 370 | * [ngx_rds_csv](https://github.com/openresty/rds-csv-nginx-module) 371 | 372 | [Back to TOC](#table-of-contents) 373 | 374 | -------------------------------------------------------------------------------- /t/sanity.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | #repeat_each(10); 7 | no_shuffle(); 8 | 9 | repeat_each(2); 10 | 11 | plan tests => repeat_each() * (2 * blocks() + 7); 12 | 13 | $ENV{TEST_NGINX_MYSQL_HOST} ||= '127.0.0.1'; 14 | $ENV{TEST_NGINX_MYSQL_PORT} ||= 3306; 15 | 16 | our $http_config = <<'_EOC_'; 17 | upstream backend { 18 | drizzle_server $TEST_NGINX_MYSQL_HOST:$TEST_NGINX_MYSQL_PORT protocol=mysql 19 | dbname=ngx_test user=ngx_test password=ngx_test; 20 | } 21 | _EOC_ 22 | 23 | #no_long_string(); 24 | #master_on(); 25 | 26 | run_tests(); 27 | 28 | #no_diff(); 29 | 30 | __DATA__ 31 | 32 | === TEST 1: sanity 33 | --- http_config eval: $::http_config 34 | --- config 35 | location /mysql { 36 | drizzle_pass backend; 37 | #drizzle_dbname $dbname; 38 | drizzle_query 'select * from cats order by id'; 39 | rds_json on; 40 | } 41 | --- request 42 | GET /mysql 43 | --- response_headers_like 44 | X-Resty-DBD-Module: ngx_drizzle \d+\.\d+\.\d+ 45 | Content-Type: application/json 46 | --- response_body chomp 47 | [{"id":2,"name":null},{"id":3,"name":"bob"}] 48 | --- timeout: 15 49 | 50 | 51 | 52 | === TEST 2: keep-alive 53 | --- http_config eval: $::http_config 54 | --- config 55 | location /mysql { 56 | drizzle_pass backend; 57 | #drizzle_dbname $dbname; 58 | drizzle_query 'select * from cats'; 59 | rds_json on; 60 | } 61 | --- request 62 | GET /mysql 63 | --- response_body chop 64 | [{"id":2,"name":null},{"id":3,"name":"bob"}] 65 | 66 | 67 | 68 | === TEST 3: update 69 | --- http_config eval: $::http_config 70 | --- config 71 | location /mysql { 72 | drizzle_pass backend; 73 | #drizzle_dbname $dbname; 74 | drizzle_query "update cats set name='bob' where name='bob'"; 75 | rds_json on; 76 | } 77 | --- request 78 | GET /mysql 79 | --- response_body chop 80 | {"errcode":0,"errstr":"Rows matched: 1 Changed: 0 Warnings: 0"} 81 | 82 | 83 | 84 | === TEST 4: select empty result 85 | --- http_config eval: $::http_config 86 | --- config 87 | location /mysql { 88 | drizzle_pass backend; 89 | drizzle_query "select * from cats where name='tom'"; 90 | rds_json on; 91 | } 92 | --- request 93 | GET /mysql 94 | --- response_body chop 95 | [] 96 | 97 | 98 | 99 | === TEST 5: update & no module header 100 | --- http_config eval: $::http_config 101 | --- config 102 | location /mysql { 103 | if ($arg_name ~ '[^A-Za-z0-9]') { 104 | return 400; 105 | } 106 | 107 | drizzle_pass backend; 108 | drizzle_module_header off; 109 | drizzle_query "update cats set name='$arg_name' where name='$arg_name'"; 110 | 111 | rds_json on; 112 | } 113 | --- request 114 | GET /mysql?name=bob 115 | --- response_headers 116 | X-Resty-DBD-Module: 117 | Content-Type: application/json 118 | --- response_body chop 119 | {"errcode":0,"errstr":"Rows matched: 1 Changed: 0 Warnings: 0"} 120 | 121 | 122 | 123 | === TEST 6: invalid SQL 124 | --- http_config eval: $::http_config 125 | --- config 126 | location /mysql { 127 | drizzle_pass backend; 128 | drizzle_module_header off; 129 | drizzle_query "select '32"; 130 | rds_json on; 131 | } 132 | --- response_headers 133 | X-Resty-DBD-Module: 134 | Content-Type: text/html 135 | --- request 136 | GET /mysql 137 | --- error_code: 500 138 | --- response_body_like: 500 Internal Server Error 139 | 140 | 141 | 142 | === TEST 7: single row, single col 143 | --- http_config eval: $::http_config 144 | --- config 145 | location /test { 146 | echo_location /mysql "drop table if exists singles"; 147 | echo; 148 | echo_location /mysql "create table singles (name varchar(15));"; 149 | echo; 150 | echo_location /mysql "insert into singles values ('marry');"; 151 | echo; 152 | echo_location /mysql "select * from singles;"; 153 | echo; 154 | } 155 | location /mysql { 156 | drizzle_pass backend; 157 | drizzle_module_header off; 158 | drizzle_query $query_string; 159 | rds_json on; 160 | } 161 | --- request 162 | GET /test 163 | --- response_body 164 | {"errcode":0} 165 | {"errcode":0} 166 | {"errcode":0,"affected_rows":1} 167 | [{"name":"marry"}] 168 | --- skip_nginx: 2: < 0.7.46 169 | --- timeout: 5 170 | 171 | 172 | 173 | === TEST 8: floating number and insert id 174 | --- http_config eval: $::http_config 175 | --- config 176 | location /test { 177 | echo_location /mysql "drop table if exists foo"; 178 | echo; 179 | echo_location /mysql "create table foo (id serial not null, primary key (id), val real);"; 180 | echo; 181 | echo_location /mysql "insert into foo (val) values (3.1415926);"; 182 | echo; 183 | echo_location /mysql "select * from foo;"; 184 | echo; 185 | } 186 | location /mysql { 187 | drizzle_pass backend; 188 | drizzle_module_header off; 189 | drizzle_query $query_string; 190 | rds_json on; 191 | } 192 | --- request 193 | GET /test 194 | --- response_body 195 | {"errcode":0} 196 | {"errcode":0} 197 | {"errcode":0,"insert_id":1,"affected_rows":1} 198 | [{"id":1,"val":3.1415926}] 199 | --- skip_nginx: 2: < 0.7.46 200 | 201 | 202 | 203 | === TEST 9: text blob field 204 | --- http_config eval: $::http_config 205 | --- config 206 | location /test { 207 | echo_location /mysql "drop table if exists foo"; 208 | echo; 209 | echo_location /mysql "create table foo (id serial, body text);"; 210 | echo; 211 | echo_location /mysql "insert into foo (body) values ('hello');"; 212 | echo; 213 | echo_location /mysql "select * from foo;"; 214 | echo; 215 | } 216 | location /mysql { 217 | drizzle_pass backend; 218 | drizzle_module_header off; 219 | drizzle_query $query_string; 220 | rds_json on; 221 | } 222 | --- request 223 | GET /test 224 | --- response_body 225 | {"errcode":0} 226 | {"errcode":0} 227 | {"errcode":0,"insert_id":1,"affected_rows":1} 228 | [{"id":1,"body":"hello"}] 229 | --- skip_nginx: 2: < 0.7.46 230 | 231 | 232 | 233 | === TEST 10: bool blob field 234 | --- http_config eval: $::http_config 235 | --- config 236 | location /test { 237 | echo_location /mysql "drop table if exists foo"; 238 | echo; 239 | echo_location /mysql "create table foo (id serial, flag bool);"; 240 | echo; 241 | echo_location /mysql "insert into foo (flag) values (true);"; 242 | echo; 243 | echo_location /mysql "insert into foo (flag) values (false);"; 244 | echo; 245 | echo_location /mysql "select * from foo order by id;"; 246 | echo; 247 | } 248 | location /mysql { 249 | drizzle_pass backend; 250 | drizzle_module_header off; 251 | drizzle_query $query_string; 252 | rds_json on; 253 | } 254 | --- request 255 | GET /test 256 | --- response_body 257 | {"errcode":0} 258 | {"errcode":0} 259 | {"errcode":0,"insert_id":1,"affected_rows":1} 260 | {"errcode":0,"insert_id":2,"affected_rows":1} 261 | [{"id":1,"flag":1},{"id":2,"flag":0}] 262 | --- skip_nginx: 2: < 0.7.46 263 | --- timeout: 10 264 | 265 | 266 | 267 | === TEST 11: bit field 268 | --- http_config eval: $::http_config 269 | --- config 270 | location /test { 271 | echo_location /mysql "drop table if exists foo"; 272 | echo; 273 | echo_location /mysql "create table foo (id serial, flag bit);"; 274 | echo; 275 | echo_location /mysql "insert into foo (flag) values (1);"; 276 | echo; 277 | echo_location /mysql "insert into foo (flag) values (0);"; 278 | echo; 279 | echo_location /mysql "select * from foo order by id;"; 280 | echo; 281 | } 282 | location /mysql { 283 | drizzle_pass backend; 284 | drizzle_module_header off; 285 | drizzle_query $query_string; 286 | rds_json on; 287 | } 288 | --- request 289 | GET /test 290 | --- response_body 291 | {"errcode":0} 292 | {"errcode":0} 293 | {"errcode":0,"insert_id":1,"affected_rows":1} 294 | {"errcode":0,"insert_id":2,"affected_rows":1} 295 | [{"id":1,"flag":"\u0001"},{"id":2,"flag":"\u0000"}] 296 | --- skip_nginx: 2: < 0.7.46 297 | --- timeout: 10 298 | 299 | 300 | 301 | === TEST 12: date type 302 | --- http_config eval: $::http_config 303 | --- config 304 | location /test { 305 | echo_location /mysql "drop table if exists foo"; 306 | echo; 307 | echo_location /mysql "create table foo (id serial, created date);"; 308 | echo; 309 | echo_location /mysql "insert into foo (created) values ('2007-05-24');"; 310 | echo; 311 | echo_location /mysql "select * from foo"; 312 | echo; 313 | } 314 | location /mysql { 315 | drizzle_pass backend; 316 | drizzle_module_header off; 317 | drizzle_query $query_string; 318 | rds_json on; 319 | } 320 | --- request 321 | GET /test 322 | --- response_body 323 | {"errcode":0} 324 | {"errcode":0} 325 | {"errcode":0,"insert_id":1,"affected_rows":1} 326 | [{"id":1,"created":"2007-05-24"}] 327 | 328 | 329 | 330 | === TEST 13: strings need to be escaped (forcing utf8) 331 | --- http_config 332 | upstream backend { 333 | drizzle_server $TEST_NGINX_MYSQL_HOST:$TEST_NGINX_MYSQL_PORT protocol=mysql 334 | dbname=ngx_test user=ngx_test password=ngx_test 335 | charset=utf8mb4; 336 | } 337 | 338 | --- config 339 | location /test { 340 | echo_location /mysql "drop table if exists foo"; 341 | echo; 342 | echo_location /mysql "create table foo (id serial, body char(25));"; 343 | echo; 344 | echo_location /mysql "insert into foo (body) values ('a\\r\\nb\\b你好\Z');"; 345 | echo; 346 | echo_location /mysql "select * from foo"; 347 | echo; 348 | } 349 | location /mysql { 350 | drizzle_pass backend; 351 | drizzle_module_header off; 352 | drizzle_query $query_string; 353 | rds_json on; 354 | } 355 | --- request 356 | GET /test 357 | --- response_body 358 | {"errcode":0} 359 | {"errcode":0} 360 | {"errcode":0,"insert_id":1,"affected_rows":1} 361 | [{"id":1,"body":"a\r\nb\b你好\u001a"}] 362 | --- timeout: 5 363 | 364 | 365 | 366 | === TEST 14: strings need to be escaped 367 | --- SKIP 368 | --- http_config eval: $::http_config 369 | --- config 370 | location /test { 371 | echo_location /mysql "drop table if exists foo"; 372 | echo; 373 | echo_location /mysql "create table foo (id serial, body char(25));"; 374 | echo; 375 | echo_location /mysql "insert into foo (body) values ('a\\r\\nb\\b你好\Z');"; 376 | echo; 377 | echo_location /mysql "select * from foo"; 378 | echo; 379 | } 380 | location /mysql { 381 | drizzle_pass backend; 382 | drizzle_module_header off; 383 | drizzle_query $query_string; 384 | rds_json on; 385 | } 386 | --- request 387 | GET /test 388 | --- response_body 389 | {"errcode":0} 390 | {"errcode":0} 391 | {"errcode":0,"insert_id":1,"affected_rows":1} 392 | [{"id":1,"body":"a\r\nb\b你好\u001a"}] 393 | 394 | 395 | 396 | === TEST 15: null values 397 | --- http_config eval: $::http_config 398 | --- config 399 | location /test { 400 | echo_location /mysql "drop table if exists foo"; 401 | echo; 402 | echo_location /mysql "create table foo (id serial, name char(10), age integer);"; 403 | echo; 404 | echo_location /mysql "insert into foo (name, age) values ('', null);"; 405 | echo; 406 | echo_location /mysql "insert into foo (name, age) values (null, 0);"; 407 | echo; 408 | echo_location /mysql "select * from foo order by id"; 409 | echo; 410 | } 411 | location /mysql { 412 | drizzle_pass backend; 413 | drizzle_module_header off; 414 | drizzle_query $query_string; 415 | rds_json on; 416 | } 417 | --- request 418 | GET /test 419 | --- response_body 420 | {"errcode":0} 421 | {"errcode":0} 422 | {"errcode":0,"insert_id":1,"affected_rows":1} 423 | {"errcode":0,"insert_id":2,"affected_rows":1} 424 | [{"id":1,"name":"","age":null},{"id":2,"name":null,"age":0}] 425 | --- timeout: 10 426 | 427 | 428 | 429 | === TEST 16: call proc 430 | --- http_config eval: $::http_config 431 | --- config 432 | location /mysql { 433 | drizzle_pass backend; 434 | #drizzle_dbname $dbname; 435 | drizzle_query "call myproc()"; 436 | rds_json on; 437 | } 438 | --- request 439 | GET /mysql 440 | --- response_body chop 441 | {"errcode":0,"errstr":"Rows matched: 1 Changed: 0 Warnings: 0"} 442 | --- SKIP 443 | 444 | 445 | 446 | === TEST 17: bad MIME type 447 | --- http_config eval: $::http_config 448 | --- config 449 | location /mysql { 450 | default_type "text/css"; 451 | echo hello; 452 | rds_json on; 453 | } 454 | --- request 455 | GET /mysql 456 | --- response_headers 457 | Content-Type: text/css 458 | --- response_body 459 | hello 460 | --- timeout: 15 461 | 462 | -------------------------------------------------------------------------------- /src/ngx_http_rds_json_processor.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) agentzh 4 | */ 5 | 6 | 7 | #ifndef DDEBUG 8 | #define DDEBUG 0 9 | #endif 10 | #include "ddebug.h" 11 | 12 | 13 | #include "ngx_http_rds_json_util.h" 14 | #include "ngx_http_rds_json_processor.h" 15 | #include "ngx_http_rds_json_output.h" 16 | #include "ngx_http_rds.h" 17 | #include "ngx_http_rds_utils.h" 18 | 19 | #include 20 | #include 21 | 22 | 23 | ngx_int_t 24 | ngx_http_rds_json_process_header(ngx_http_request_t *r, 25 | ngx_chain_t *in, ngx_http_rds_json_ctx_t *ctx) 26 | { 27 | ngx_buf_t *b; 28 | ngx_http_rds_header_t header; 29 | ngx_int_t rc; 30 | 31 | if (in == NULL) { 32 | return NGX_OK; 33 | } 34 | 35 | b = in->buf; 36 | 37 | if (!ngx_buf_in_memory(b)) { 38 | if (!ngx_buf_special(b)) { 39 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 40 | "rds_json: process header: buf from " 41 | "upstream not in memory"); 42 | goto invalid; 43 | } 44 | 45 | in = in->next; 46 | 47 | if (in == NULL) { 48 | return NGX_OK; 49 | } 50 | 51 | b = in->buf; 52 | } 53 | 54 | rc = ngx_http_rds_parse_header(r, b, &header); 55 | 56 | if (rc != NGX_OK) { 57 | goto invalid; 58 | } 59 | 60 | dd("col count: %d", (int) header.col_count); 61 | 62 | if (header.col_count == 0) { 63 | /* for empty result set, just return the JSON 64 | * representation of the RDS header */ 65 | 66 | dd("col count == 0"); 67 | 68 | if (b->pos != b->last) { 69 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 70 | "rds_json: header: there's unexpected remaining data " 71 | "in the buf"); 72 | goto invalid; 73 | } 74 | 75 | ctx->state = state_done; 76 | 77 | /* now we send the postponed response header */ 78 | if (!ctx->header_sent) { 79 | ctx->header_sent = 1; 80 | 81 | rc = ngx_http_rds_json_next_header_filter(r); 82 | 83 | if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { 84 | return rc; 85 | } 86 | } 87 | 88 | rc = ngx_http_rds_json_output_header(r, ctx, &header); 89 | 90 | if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { 91 | return rc; 92 | } 93 | 94 | ngx_http_rds_json_discard_bufs(r->pool, in); 95 | 96 | return rc; 97 | } 98 | 99 | ctx->cols = ngx_palloc(r->pool, 100 | header.col_count * sizeof(ngx_http_rds_column_t)); 101 | 102 | if (ctx->cols == NULL) { 103 | goto invalid; 104 | } 105 | 106 | ctx->state = state_expect_col; 107 | ctx->cur_col = 0; 108 | ctx->col_count = header.col_count; 109 | 110 | /* now we send the postponed response header */ 111 | if (!ctx->header_sent) { 112 | ctx->header_sent = 1; 113 | 114 | rc = ngx_http_rds_json_next_header_filter(r); 115 | if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { 116 | return rc; 117 | } 118 | } 119 | 120 | return ngx_http_rds_json_process_col(r, b->pos == b->last ? in->next : in, 121 | ctx); 122 | 123 | invalid: 124 | 125 | dd("return 500"); 126 | if (!ctx->header_sent) { 127 | ctx->header_sent = 1; 128 | 129 | r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR; 130 | ngx_http_send_header(r); 131 | ngx_http_send_special(r, NGX_HTTP_LAST); 132 | 133 | return NGX_ERROR; 134 | } 135 | 136 | return NGX_ERROR; 137 | } 138 | 139 | 140 | ngx_int_t 141 | ngx_http_rds_json_process_col(ngx_http_request_t *r, ngx_chain_t *in, 142 | ngx_http_rds_json_ctx_t *ctx) 143 | { 144 | ngx_buf_t *b; 145 | ngx_int_t rc; 146 | ngx_http_rds_json_loc_conf_t *conf; 147 | 148 | if (in == NULL) { 149 | return NGX_OK; 150 | } 151 | 152 | b = in->buf; 153 | 154 | if (!ngx_buf_in_memory(b)) { 155 | if (!ngx_buf_special(b)) { 156 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 157 | "rds_json: process col: buf from " 158 | "upstream not in memory"); 159 | return NGX_ERROR; 160 | } 161 | 162 | in = in->next; 163 | 164 | if (in == NULL) { 165 | return NGX_OK; 166 | } 167 | 168 | b = in->buf; 169 | } 170 | 171 | dd("parsing rds column"); 172 | 173 | rc = ngx_http_rds_parse_col(r, b, &ctx->cols[ctx->cur_col]); 174 | 175 | dd("parse col returns %d (%d)", (int) rc, (int) NGX_OK); 176 | 177 | if (rc != NGX_OK) { 178 | return NGX_ERROR; 179 | } 180 | 181 | if (b->pos == b->last) { 182 | dd("parse col buf consumed"); 183 | in = in->next; 184 | } 185 | 186 | ctx->cur_col++; 187 | 188 | if (ctx->cur_col >= ctx->col_count) { 189 | dd("end of column list"); 190 | 191 | ctx->state = state_expect_row; 192 | ctx->row = 0; 193 | 194 | dd("output \"[\""); 195 | dd("before output literal"); 196 | 197 | conf = ngx_http_get_module_loc_conf(r, ngx_http_rds_json_filter_module); 198 | 199 | if (conf->root.len) { 200 | rc = ngx_http_rds_json_output_props(r, ctx, conf); 201 | 202 | dd("after output literal"); 203 | 204 | if (rc == NGX_ERROR || rc > NGX_OK) { 205 | return rc; 206 | } 207 | } 208 | 209 | rc = ngx_http_rds_json_output_literal(r, ctx, 210 | (u_char *)"[", sizeof("[") - 1, 211 | 0 /* last buf */); 212 | 213 | dd("after output literal"); 214 | 215 | if (rc == NGX_ERROR || rc > NGX_OK) { 216 | return rc; 217 | } 218 | 219 | if (conf->format == json_format_compact) { 220 | rc = ngx_http_rds_json_output_cols(r, ctx); 221 | 222 | if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { 223 | return rc; 224 | } 225 | } 226 | 227 | dd("after output literal"); 228 | 229 | if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { 230 | return rc; 231 | } 232 | 233 | dd("process col is entering process row..."); 234 | return ngx_http_rds_json_process_row(r, in, ctx); 235 | } 236 | 237 | return ngx_http_rds_json_process_col(r, in, ctx); 238 | } 239 | 240 | 241 | ngx_int_t 242 | ngx_http_rds_json_process_row(ngx_http_request_t *r, ngx_chain_t *in, 243 | ngx_http_rds_json_ctx_t *ctx) 244 | { 245 | ngx_buf_t *b; 246 | ngx_int_t rc; 247 | 248 | ngx_http_rds_json_loc_conf_t *conf; 249 | 250 | if (in == NULL) { 251 | return NGX_OK; 252 | } 253 | 254 | dd("process row"); 255 | 256 | b = in->buf; 257 | 258 | if (!ngx_buf_in_memory(b)) { 259 | if (!ngx_buf_special(b)) { 260 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 261 | "rds_json: process row: buf from " 262 | "upstream not in memory"); 263 | return NGX_ERROR; 264 | } 265 | 266 | in = in->next; 267 | 268 | if (in == NULL) { 269 | return NGX_OK; 270 | } 271 | 272 | b = in->buf; 273 | } 274 | 275 | if (b->last - b->pos < (ssize_t) sizeof(uint8_t)) { 276 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 277 | "rds_json: row flag is incomplete in the buf"); 278 | return NGX_ERROR; 279 | } 280 | 281 | dd("row flag: %d (offset %d)", 282 | (char) *b->pos, 283 | (int) (b->pos - b->start)); 284 | 285 | if (*b->pos++ == 0) { 286 | /* end of row list */ 287 | ctx->state = state_done; 288 | 289 | if (b->pos != b->last) { 290 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 291 | "rds_json: row: there's unexpected remaining data " 292 | "in the buf"); 293 | return NGX_ERROR; 294 | } 295 | 296 | conf = ngx_http_get_module_loc_conf(r, ngx_http_rds_json_filter_module); 297 | 298 | if (conf->root.len) { 299 | rc = ngx_http_rds_json_output_literal(r, ctx, 300 | (u_char *)"]}", 301 | sizeof("]}") - 1, 302 | 1 /* last buf*/); 303 | 304 | } else { 305 | rc = ngx_http_rds_json_output_literal(r, ctx, 306 | (u_char *)"]", 307 | sizeof("]") - 1, 308 | 1 /* last buf*/); 309 | } 310 | 311 | if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { 312 | return rc; 313 | } 314 | 315 | return rc; 316 | } 317 | 318 | ctx->row++; 319 | ctx->cur_col = 0; 320 | ctx->state = state_expect_field; 321 | 322 | if (b->pos == b->last) { 323 | in = in->next; 324 | 325 | } else { 326 | dd("process row: buf not consumed completely"); 327 | } 328 | 329 | return ngx_http_rds_json_process_field(r, in, ctx); 330 | } 331 | 332 | 333 | ngx_int_t 334 | ngx_http_rds_json_process_field(ngx_http_request_t *r, ngx_chain_t *in, 335 | ngx_http_rds_json_ctx_t *ctx) 336 | { 337 | size_t total, len; 338 | ngx_buf_t *b; 339 | ngx_int_t rc; 340 | 341 | for (;;) { 342 | if (in == NULL) { 343 | return NGX_OK; 344 | } 345 | 346 | b = in->buf; 347 | 348 | if (!ngx_buf_in_memory(b)) { 349 | dd("buf not in memory"); 350 | 351 | if (!ngx_buf_special(b)) { 352 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 353 | "rds_json: process field: buf from " 354 | "upstream not in memory"); 355 | return NGX_ERROR; 356 | } 357 | 358 | in = in->next; 359 | 360 | if (in == NULL) { 361 | return NGX_OK; 362 | } 363 | 364 | b = in->buf; 365 | } 366 | 367 | dd("process field: buf size: %d", (int) ngx_buf_size(b)); 368 | 369 | if (b->last - b->pos < (ssize_t) sizeof(uint32_t)) { 370 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 371 | "rds_json: field size is incomplete in the buf: %*s " 372 | "(len: %d)", b->last - b->pos, b->pos, 373 | (int) (b->last - b->pos)); 374 | 375 | return NGX_ERROR; 376 | } 377 | 378 | total = *(uint32_t *) b->pos; 379 | 380 | dd("total: %d", (int) total); 381 | 382 | b->pos += sizeof(uint32_t); 383 | 384 | if (total == (uint32_t) -1) { 385 | /* SQL NULL found */ 386 | total = 0; 387 | len = 0; 388 | ctx->field_data_rest = 0; 389 | 390 | rc = ngx_http_rds_json_output_field(r, ctx, b->pos, len, 391 | 1 /* is null */); 392 | 393 | } else { 394 | len = (uint32_t) (b->last - b->pos); 395 | 396 | if (len >= total) { 397 | len = total; 398 | } 399 | 400 | ctx->field_data_rest = total - len; 401 | 402 | rc = ngx_http_rds_json_output_field(r, ctx, b->pos, len, 403 | 0 /* not null */); 404 | } 405 | 406 | if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { 407 | return rc; 408 | } 409 | 410 | b->pos += len; 411 | 412 | if (b->pos == b->last) { 413 | in = in->next; 414 | } 415 | 416 | if (len < total) { 417 | dd("process field: need to read more field data"); 418 | 419 | ctx->state = state_expect_more_field_data; 420 | 421 | return ngx_http_rds_json_process_more_field_data(r, in, ctx); 422 | } 423 | 424 | ctx->cur_col++; 425 | 426 | if (ctx->cur_col >= ctx->col_count) { 427 | dd("reached the end of the current row"); 428 | 429 | ctx->state = state_expect_row; 430 | 431 | return ngx_http_rds_json_process_row(r, in, ctx); 432 | } 433 | 434 | /* continue to process the next field (if any) */ 435 | } 436 | 437 | /* impossible to reach here */ 438 | } 439 | 440 | 441 | ngx_int_t 442 | ngx_http_rds_json_process_more_field_data(ngx_http_request_t *r, 443 | ngx_chain_t *in, ngx_http_rds_json_ctx_t *ctx) 444 | { 445 | ngx_int_t rc; 446 | ngx_buf_t *b; 447 | size_t len; 448 | 449 | for (;;) { 450 | if (in == NULL) { 451 | return NGX_OK; 452 | } 453 | 454 | b = in->buf; 455 | 456 | if (!ngx_buf_in_memory(b)) { 457 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 458 | "rds_json: buf from upstream not in memory"); 459 | return NGX_ERROR; 460 | } 461 | 462 | len = b->last - b->pos; 463 | 464 | if (len >= ctx->field_data_rest) { 465 | len = ctx->field_data_rest; 466 | ctx->field_data_rest = 0; 467 | 468 | } else { 469 | ctx->field_data_rest -= len; 470 | } 471 | 472 | rc = ngx_http_rds_json_output_more_field_data(r, ctx, b->pos, len); 473 | 474 | if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { 475 | return rc; 476 | } 477 | 478 | b->pos += len; 479 | 480 | if (b->pos == b->last) { 481 | in = in->next; 482 | } 483 | 484 | if (ctx->field_data_rest) { 485 | dd("process more field data: still some data remaining"); 486 | continue; 487 | } 488 | 489 | dd("process more field data: reached the end of the current field"); 490 | 491 | ctx->cur_col++; 492 | 493 | if (ctx->cur_col >= ctx->col_count) { 494 | dd("process more field data: reached the end of the current row"); 495 | 496 | ctx->state = state_expect_row; 497 | 498 | return ngx_http_rds_json_process_row(r, in, ctx); 499 | } 500 | 501 | dd("proces more field data: read the next field"); 502 | 503 | ctx->state = state_expect_field; 504 | 505 | return ngx_http_rds_json_process_field(r, in, ctx); 506 | } 507 | 508 | /* impossible to reach here */ 509 | } 510 | -------------------------------------------------------------------------------- /t/property.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 | $ENV{TEST_NGINX_MYSQL_HOST} ||= '127.0.0.1'; 11 | $ENV{TEST_NGINX_MYSQL_PORT} ||= 3306; 12 | 13 | our $http_config = <<'_EOC_'; 14 | upstream backend { 15 | drizzle_server $TEST_NGINX_MYSQL_HOST:$TEST_NGINX_MYSQL_PORT protocol=mysql 16 | dbname=ngx_test user=ngx_test password=ngx_test; 17 | } 18 | _EOC_ 19 | 20 | no_diff(); 21 | no_long_string(); 22 | 23 | run_tests(); 24 | 25 | __DATA__ 26 | 27 | === TEST 1: sanity 28 | --- http_config eval: $::http_config 29 | --- config 30 | location /mysql { 31 | drizzle_query ' 32 | select * from cats order by id asc 33 | '; 34 | drizzle_pass backend; 35 | rds_json on; 36 | rds_json_root rows; 37 | } 38 | --- request 39 | GET /mysql 40 | --- response_body chomp 41 | {"rows":[{"id":2,"name":null},{"id":3,"name":"bob"}]} 42 | 43 | 44 | 45 | === TEST 2: update (root) 46 | --- http_config eval: $::http_config 47 | --- config 48 | location /mysql { 49 | drizzle_pass backend; 50 | #drizzle_dbname $dbname; 51 | drizzle_query "update cats set name='bob' where name='bob'"; 52 | rds_json on; 53 | rds_json_root data; 54 | } 55 | --- request 56 | GET /mysql 57 | --- response_body chop 58 | {"errcode":0,"errstr":"Rows matched: 1 Changed: 0 Warnings: 0"} 59 | 60 | 61 | 62 | === TEST 3: select empty result 63 | --- http_config eval: $::http_config 64 | --- config 65 | location /mysql { 66 | drizzle_pass backend; 67 | drizzle_query "select * from cats where name='tom'"; 68 | rds_json on; 69 | rds_json_root data; 70 | } 71 | --- request 72 | GET /mysql 73 | --- response_body chop 74 | {"data":[]} 75 | 76 | 77 | 78 | === TEST 4: sanity + compact 79 | --- http_config eval: $::http_config 80 | --- config 81 | location /mysql { 82 | drizzle_query ' 83 | select * from cats order by id asc 84 | '; 85 | drizzle_pass backend; 86 | rds_json on; 87 | rds_json_root rows; 88 | rds_json_format compact; 89 | } 90 | --- request 91 | GET /mysql 92 | --- response_body chomp 93 | {"rows":[["id","name"],[2,null],[3,"bob"]]} 94 | 95 | 96 | 97 | === TEST 5: select empty result + compact 98 | --- http_config eval: $::http_config 99 | --- config 100 | location /mysql { 101 | drizzle_pass backend; 102 | drizzle_query "select * from cats where name='tom'"; 103 | rds_json on; 104 | rds_json_root data; 105 | rds_json_format compact; 106 | } 107 | --- request 108 | GET /mysql 109 | --- response_body chop 110 | {"data":[["id","name"]]} 111 | 112 | 113 | 114 | === TEST 6: select empty result + compact + escaping 115 | --- http_config eval: $::http_config 116 | --- config 117 | location /mysql { 118 | drizzle_pass backend; 119 | drizzle_query "select * from cats where name='tom'"; 120 | rds_json on; 121 | rds_json_root "'\"\\:"; 122 | rds_json_format compact; 123 | } 124 | --- request 125 | GET /mysql 126 | --- response_body chop 127 | {"'\"\\:":[["id","name"]]} 128 | 129 | 130 | 131 | === TEST 7: success property (inherited) 132 | --- http_config eval: $::http_config 133 | --- config 134 | rds_json_success_property "suc"; 135 | location /mysql { 136 | drizzle_pass backend; 137 | drizzle_query "select * from cats where name='tom'"; 138 | rds_json on; 139 | rds_json_root "rows"; 140 | rds_json_format compact; 141 | } 142 | --- request 143 | GET /mysql 144 | --- response_body chop 145 | {"suc":true,"rows":[["id","name"]]} 146 | 147 | 148 | 149 | === TEST 8: success property (root inherited) 150 | --- http_config eval: $::http_config 151 | --- config 152 | rds_json_root "rows"; 153 | location /mysql { 154 | drizzle_pass backend; 155 | drizzle_query "select * from cats where name='tom'"; 156 | rds_json on; 157 | rds_json_success_property "suc"; 158 | rds_json_format compact; 159 | } 160 | --- request 161 | GET /mysql 162 | --- response_body chop 163 | {"suc":true,"rows":[["id","name"]]} 164 | 165 | 166 | 167 | === TEST 9: success property 168 | --- http_config eval: $::http_config 169 | --- config 170 | location /mysql { 171 | drizzle_pass backend; 172 | drizzle_query "select * from cats where name='tom'"; 173 | rds_json on; 174 | #rds_json_root "data"; 175 | rds_json_success_property "suc"; 176 | rds_json_format compact; 177 | } 178 | --- request 179 | GET /mysql 180 | --- response_body chop 181 | {"suc":true,"data":[["id","name"]]} 182 | 183 | 184 | 185 | === TEST 10: success property with an odd key 186 | --- http_config eval: $::http_config 187 | --- config 188 | location /mysql { 189 | drizzle_pass backend; 190 | drizzle_query "select * from cats where name='tom'"; 191 | rds_json on; 192 | rds_json_root "data"; 193 | rds_json_success_property "a\"'\\:"; 194 | rds_json_format compact; 195 | } 196 | --- request 197 | GET /mysql 198 | --- response_body chop 199 | {"a\"'\\:":true,"data":[["id","name"]]} 200 | 201 | 202 | 203 | === TEST 11: update (root + success prop) 204 | --- http_config eval: $::http_config 205 | --- config 206 | location /mysql { 207 | drizzle_pass backend; 208 | #drizzle_dbname $dbname; 209 | drizzle_query "update cats set name='bob' where name='bob'"; 210 | rds_json on; 211 | rds_json_root rows; 212 | rds_json_success_property success; 213 | } 214 | --- request 215 | GET /mysql 216 | --- response_body chop 217 | {"success":true,"errcode":0,"errstr":"Rows matched: 1 Changed: 0 Warnings: 0"} 218 | 219 | 220 | 221 | === TEST 12: update (user property) 222 | --- http_config eval: $::http_config 223 | --- config 224 | location /mysql { 225 | drizzle_pass backend; 226 | #drizzle_dbname $dbname; 227 | drizzle_query "update cats set name='bob' where name='bob'"; 228 | rds_json on; 229 | 230 | set $name 'Jimmy'; 231 | rds_json_user_property name $name; 232 | } 233 | --- request 234 | GET /mysql 235 | --- response_body chop 236 | {"name":"Jimmy","errcode":0,"errstr":"Rows matched: 1 Changed: 0 Warnings: 0"} 237 | 238 | 239 | 240 | === TEST 13: update (multi user property) 241 | --- http_config eval: $::http_config 242 | --- config 243 | location /mysql { 244 | drizzle_pass backend; 245 | #drizzle_dbname $dbname; 246 | drizzle_query "update cats set name='bob' where name='bob'"; 247 | rds_json on; 248 | 249 | set $name 'Jimmy"'; 250 | set $age 32; 251 | rds_json_user_property name $name; 252 | rds_json_user_property age $age; 253 | } 254 | --- request 255 | GET /mysql 256 | --- response_body chop 257 | {"name":"Jimmy\"","age":"32","errcode":0,"errstr":"Rows matched: 1 Changed: 0 Warnings: 0"} 258 | 259 | 260 | 261 | === TEST 14: compact + user property 262 | --- http_config eval: $::http_config 263 | --- config 264 | location /mysql { 265 | drizzle_query ' 266 | select * from cats order by id asc 267 | '; 268 | drizzle_pass backend; 269 | rds_json on; 270 | rds_json_root rows; 271 | set $val 'bar"'; 272 | rds_json_user_property foo $val; 273 | rds_json_format compact; 274 | } 275 | --- request 276 | GET /mysql 277 | --- response_body chomp 278 | {"foo":"bar\"","rows":[["id","name"],[2,null],[3,"bob"]]} 279 | 280 | 281 | 282 | === TEST 15: compact + user property + success 283 | --- http_config eval: $::http_config 284 | --- config 285 | location /mysql { 286 | drizzle_query ' 287 | select * from cats order by id asc 288 | '; 289 | drizzle_pass backend; 290 | 291 | rds_json on; 292 | rds_json_root rows; 293 | 294 | rds_json_success_property suc; 295 | 296 | set $val 'bar"'; 297 | rds_json_user_property foo $val; 298 | 299 | set $baz '"baz"\\'; 300 | rds_json_user_property '\\bar' $baz; 301 | rds_json_format compact; 302 | } 303 | --- request 304 | GET /mysql 305 | --- response_body chomp 306 | {"suc":true,"foo":"bar\"","\\bar":"\"baz\"\\","rows":[["id","name"],[2,null],[3,"bob"]]} 307 | 308 | 309 | 310 | === TEST 16: rds_json_ret with success property 311 | --- http_config eval: $::http_config 312 | --- config 313 | location /ret { 314 | rds_json_success_property ret; 315 | 316 | rds_json_ret 400 'Non zero ret'; 317 | } 318 | --- request 319 | GET /ret 320 | --- response_body chomp 321 | {"errcode":400,"errstr":"Non zero ret","ret":false} 322 | 323 | 324 | 325 | === TEST 17: rds_json_ret with user property 326 | --- http_config eval: $::http_config 327 | --- config 328 | location /ret { 329 | set $city 'beijing'; 330 | rds_json_user_property city $city; 331 | rds_json_ret 400 'Non zero ret'; 332 | } 333 | --- request 334 | GET /ret 335 | --- response_body chomp 336 | {"errcode":400,"errstr":"Non zero ret","city":"beijing"} 337 | 338 | 339 | 340 | === TEST 18: rds_json_ret with property 341 | --- http_config eval: $::http_config 342 | --- config 343 | location /ret { 344 | rds_json_success_property ret; 345 | 346 | set $city 'beijing'; 347 | rds_json_user_property city $city; 348 | 349 | rds_json_ret 400 'Non zero ret'; 350 | } 351 | --- request 352 | GET /ret 353 | --- response_body chomp 354 | {"errcode":400,"errstr":"Non zero ret","ret":false,"city":"beijing"} 355 | 356 | 357 | 358 | === TEST 19: rds_json_ret when errcode equal 0 359 | --- http_config eval: $::http_config 360 | --- config 361 | location /ret { 362 | rds_json_success_property ret; 363 | 364 | set $city 'beijing'; 365 | rds_json_user_property city $city; 366 | 367 | rds_json_ret 0 'Zero errcode'; 368 | } 369 | --- request 370 | GET /ret 371 | --- response_body chomp 372 | {"errcode":0,"errstr":"Zero errcode","ret":true,"city":"beijing"} 373 | 374 | 375 | 376 | === TEST 20: rds_json_ret with multiple user properties 377 | --- http_config eval: $::http_config 378 | --- config 379 | location /ret { 380 | set $city 'beijing'; 381 | rds_json_user_property city $city; 382 | rds_json_user_property '"hi"' '"hello\n"'; 383 | rds_json_ret 400 'Non zero ret'; 384 | } 385 | --- request 386 | GET /ret 387 | --- response_body chomp 388 | {"errcode":400,"errstr":"Non zero ret","city":"beijing","\"hi\"":"\"hello\n\""} 389 | 390 | 391 | 392 | === TEST 21: location internal rewrite 393 | --- config 394 | location @err403 { 395 | rds_json_success_property ret; 396 | rds_json_ret 403 "Forbidden"; 397 | } 398 | 399 | location /foo { 400 | error_page 403 = @err403; 401 | return 403; 402 | } 403 | --- request 404 | GET /foo 405 | --- response_body chop 406 | {"errcode":403,"errstr":"Forbidden","ret":false} 407 | 408 | 409 | 410 | === TEST 22: rds_json_errcode_key 411 | --- config 412 | location /foo { 413 | rds_json_errcode_key "ecode"; 414 | rds_json_success_property ret; 415 | rds_json_ret 403 "Forbidden"; 416 | } 417 | --- request 418 | GET /foo 419 | --- response_body chop 420 | {"ecode":403,"errstr":"Forbidden","ret":false} 421 | 422 | 423 | 424 | === TEST 23: rds_json_errcode_key 425 | --- config 426 | rds_json_errcode_key "ecoderoot"; 427 | 428 | location /foo { 429 | rds_json_success_property ret; 430 | rds_json_ret 403 "Forbidden"; 431 | } 432 | --- request 433 | GET /foo 434 | --- response_body chop 435 | {"ecoderoot":403,"errstr":"Forbidden","ret":false} 436 | 437 | 438 | 439 | === TEST 24: rds_json_errcode_key 440 | --- config 441 | rds_json_errcode_key "ecoderoot"; 442 | 443 | location /foo { 444 | rds_json_errcode_key "ecode"; 445 | rds_json_success_property ret; 446 | rds_json_ret 403 "Forbidden"; 447 | } 448 | --- request 449 | GET /foo 450 | --- response_body chop 451 | {"ecode":403,"errstr":"Forbidden","ret":false} 452 | 453 | 454 | 455 | === TEST 25: rds_json_errstr_key 456 | --- config 457 | rds_json_errstr_key "msg"; 458 | 459 | location /foo { 460 | rds_json_success_property ret; 461 | rds_json_ret 403 "Forbidden"; 462 | } 463 | --- request 464 | GET /foo 465 | --- response_body chop 466 | {"errcode":403,"msg":"Forbidden","ret":false} 467 | 468 | 469 | 470 | === TEST 26: rds_json_errstr_key 471 | --- config 472 | rds_json_errstr_key "msg"; 473 | 474 | location /foo { 475 | rds_json_errstr_key "msg2"; 476 | rds_json_success_property ret; 477 | rds_json_ret 403 "Forbidden"; 478 | } 479 | --- request 480 | GET /foo 481 | --- response_body chop 482 | {"errcode":403,"msg2":"Forbidden","ret":false} 483 | 484 | 485 | 486 | === TEST 27: rds_json_errstr_key & rds_json_root 487 | --- config 488 | rds_json_errstr_key "msg2"; 489 | rds_json_root rows; 490 | 491 | location /foo { 492 | rds_json_success_property ret; 493 | rds_json_ret 403 "Forbidden"; 494 | } 495 | --- request 496 | GET /foo 497 | --- response_body chop 498 | {"errcode":403,"msg2":"Forbidden","ret":false} 499 | 500 | 501 | 502 | === TEST 28: rds_json_errcode_key & rds_json_root 503 | --- config 504 | rds_json_root rows; 505 | rds_json_errcode_key "code"; 506 | 507 | location /foo { 508 | rds_json_success_property ret; 509 | rds_json_ret 403 "Forbidden"; 510 | } 511 | --- request 512 | GET /foo 513 | --- response_body chop 514 | {"code":403,"errstr":"Forbidden","ret":false} 515 | 516 | 517 | 518 | === TEST 29: update - custom errstr_key 519 | --- http_config eval: $::http_config 520 | --- config 521 | location /mysql { 522 | drizzle_pass backend; 523 | #drizzle_dbname $dbname; 524 | drizzle_query "update cats set name='bob' where name='bob'"; 525 | rds_json on; 526 | rds_json_errcode_key "\"code\""; 527 | rds_json_errstr_key "\"str\""; 528 | 529 | set $name 'Jimmy"'; 530 | set $age 32; 531 | rds_json_user_property name $name; 532 | rds_json_user_property age $age; 533 | } 534 | --- request 535 | GET /mysql 536 | --- response_body chop 537 | {"name":"Jimmy\"","age":"32","\"code\"":0,"\"str\"":"Rows matched: 1 Changed: 0 Warnings: 0"} 538 | 539 | 540 | 541 | === TEST 30: select - custom errstr_key 542 | --- http_config eval: $::http_config 543 | --- config 544 | location /mysql { 545 | drizzle_pass backend; 546 | drizzle_query "select 'aaa' as a, 'bbb' as b"; 547 | rds_json on; 548 | rds_json_errcode_key "\"code\""; 549 | rds_json_errstr_key "\"str\""; 550 | 551 | rds_json_root data; 552 | 553 | set $name 'Jimmy"'; 554 | set $age 32; 555 | rds_json_user_property name $name; 556 | rds_json_user_property age $age; 557 | } 558 | --- request 559 | GET /mysql 560 | --- response_body chop 561 | {"name":"Jimmy\"","age":"32","data":[{"a":"aaa","b":"bbb"}]} 562 | -------------------------------------------------------------------------------- /t/openresty.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket skip_all => 'not working at all (missing data)'; 5 | 6 | #repeat_each(100); 7 | repeat_each(2); 8 | 9 | worker_connections(128); 10 | workers(1); 11 | #master_on; 12 | log_level('warn'); 13 | 14 | plan tests => repeat_each() * 3 * blocks(); 15 | 16 | $ENV{TEST_NGINX_MYSQL_HOST} ||= '127.0.0.1'; 17 | $ENV{TEST_NGINX_MYSQL_PORT} ||= 3306; 18 | 19 | our $http_config = <<'_EOC_'; 20 | upstream backend { 21 | drizzle_server $TEST_NGINX_MYSQL_HOST:$TEST_NGINX_MYSQL_PORT protocol=mysql 22 | dbname=ngx_test user=ngx_test password=ngx_test; 23 | } 24 | _EOC_ 25 | 26 | our $config = <<'_EOC_'; 27 | xss_get on; 28 | xss_callback_arg _callback; 29 | rds_json on; 30 | 31 | location = /auth { 32 | #internal; 33 | default_type 'application/json'; 34 | 35 | #internal; 36 | eval_subrequest_in_memory off; 37 | eval $res { 38 | set_quote_sql_str $user $arg_user; 39 | set $sql 'select count(*) res from users where name=$user'; 40 | drizzle_query $sql; 41 | drizzle_pass backend; 42 | #echo $sql; 43 | #echo hi; 44 | rds_json on; 45 | rds_json_content_type application/octet-stream; 46 | } 47 | #echo $res; 48 | if ($res ~ '"res":1') { 49 | echo pass; 50 | break; 51 | } 52 | if ($res !~ '"res":1') { 53 | return 403; 54 | break; 55 | } 56 | } 57 | 58 | # XXX we should implement these in the ngx_xss module 59 | location @err500 { rds_json_ret 500 "Internal Server Error"; } 60 | location @err404 { rds_json_ret 404 "Not Found"; } 61 | location @err400 { rds_json_ret 400 "Bad Request"; } 62 | location @err403 { rds_json_ret 403 "Forbidden"; } 63 | location @err502 { rds_json_ret 502 "Bad Gateway"; } 64 | location @err503 { rds_json_ret 503 "Service Unavailable"; } 65 | 66 | error_page 500 = @err500; 67 | error_page 404 = @err404; 68 | error_page 403 = @err403; 69 | error_page 400 = @err400; 70 | error_page 502 = @err502; 71 | error_page 503 = @err503; 72 | error_page 504 507 = @err500; 73 | 74 | location = '/=/view/PostsByMonth/~/~' { 75 | if ($arg_year !~ '^\d{4}$') { 76 | rds_json_ret 400 'Bad "year" argument'; 77 | } 78 | if ($arg_month !~ '^\d{1,2}$') { 79 | rds_json_ret 400 'Bad "month" argument'; 80 | } 81 | 82 | drizzle_query 83 | "select id, title, day(created) as day 84 | from posts 85 | where year(created) = $arg_year and month(created) = $arg_month 86 | order by created asc"; 87 | 88 | drizzle_pass backend; 89 | } 90 | 91 | location = '/=/view/RecentComments/~/~' { 92 | set $offset $arg_offset; 93 | set_if_empty $offset 0; 94 | 95 | set $limit $arg_limit; 96 | set_if_empty $limit 10; 97 | 98 | if ($offset !~ '^\d+$') { 99 | rds_json_ret 400 'Bad "offset" argument'; 100 | } 101 | if ($limit !~ '^\d{1,2}$') { 102 | rds_json_ret 400 'Bad "limit" argument'; 103 | } 104 | 105 | drizzle_query 106 | "select comments.id as id, post, sender, title 107 | from posts, comments 108 | where post = posts.id 109 | order by comments.id desc 110 | limit $offset, $limit"; 111 | 112 | drizzle_pass backend; 113 | } 114 | 115 | location = '/=/view/RecentPosts/~/~' { 116 | set $offset $arg_offset; 117 | set_if_empty $offset 0; 118 | 119 | set $limit $arg_limit; 120 | set_if_empty $limit 10; 121 | 122 | if ($offset !~ '^\d+$') { 123 | rds_json_ret 400 'Bad "offset" argument'; 124 | } 125 | if ($limit !~ '^\d{1,2}$') { 126 | rds_json_ret 400 'Bad "limit" argument'; 127 | } 128 | 129 | drizzle_query 130 | "select id, title 131 | from posts 132 | order by id desc 133 | limit $offset, $limit"; 134 | 135 | drizzle_pass backend; 136 | } 137 | 138 | location = '/=/view/PrevNextPost/~/~' { 139 | if ($arg_current !~ '^\d+$') { 140 | rds_json_ret 400 'Bad "current" argument'; 141 | } 142 | 143 | drizzle_query 144 | "(select id, title 145 | from posts 146 | where id < $arg_current 147 | order by id desc 148 | limit 1) 149 | union 150 | (select id, title 151 | from posts 152 | where id > $arg_current 153 | order by id asc 154 | limit 1)"; 155 | 156 | drizzle_pass backend; 157 | } 158 | 159 | location = '/=/view/RowCount/~/~' { 160 | if ($arg_model = 'Post') { 161 | drizzle_query "select count(*) as count from posts"; 162 | drizzle_pass backend; 163 | } 164 | if ($arg_model = 'Comment') { 165 | drizzle_query "select count(*) as count from comments"; 166 | drizzle_pass backend; 167 | } 168 | 169 | rds_json_ret 400 'Bad "model" argument'; 170 | } 171 | 172 | location = '/=/view/PostCountByMonths/~/~' { 173 | set $offset $arg_offset; 174 | set_if_empty $offset 0; 175 | 176 | set $limit $arg_limit; 177 | set_if_empty $limit 10; 178 | 179 | if ($offset !~ '^\d+$') { 180 | rds_json_ret 400 'Bad "offset" argument'; 181 | } 182 | if ($limit !~ '^\d{1,2}$') { 183 | rds_json_ret 400 'Bad "limit" argument'; 184 | } 185 | 186 | drizzle_query 187 | "select date_format(created, '%Y-%m-01') `year_month`, count(*) count 188 | from posts 189 | group by `year_month` 190 | order by `year_month` desc 191 | limit $offset, $limit"; 192 | drizzle_pass backend; 193 | } 194 | 195 | location = '/=/view/FullPostsByMonth/~/~' { 196 | set $count $arg_count; 197 | set_if_empty $count 40; 198 | 199 | if ($arg_year !~ '^(?:19|20)\d{2}$') { 200 | rds_json_ret 400 'Bad "year" argument'; 201 | } 202 | if ($arg_month !~ '^\d{1,2}$') { 203 | rds_json_ret 400 'Bad "month" argument'; 204 | } 205 | if ($arg_count !~ '^\d+$') { 206 | rds_json_ret 400 'Bad "count" argument'; 207 | } 208 | 209 | drizzle_query 210 | "select * from posts 211 | where year(created) = $arg_year and month(created) = $arg_month 212 | order by id desc 213 | limit $count"; 214 | 215 | drizzle_pass backend; 216 | } 217 | 218 | location = '/=/view/PrevNextArchive/~/~' { 219 | if ($arg_now !~ '^\d{4}-\d{1,2}(?:-\d{1,2})?$') { 220 | rds_json_ret 400 'Bad "now" argument'; 221 | } 222 | if ($arg_month !~ '^\d{1,2}$') { 223 | rds_json_ret 400 'Bad "month" argument'; 224 | } 225 | 226 | drizzle_query 227 | "(select 'next' as id, month(created) as month, year(created) as year 228 | from posts 229 | where created > $arg_now and month(created) <> $arg_month 230 | order by created asc 231 | limit 1) 232 | union 233 | (select 'prev' as id, month(created) as month, year(created) as year 234 | from posts 235 | where created < $arg_now and month(created) <> $arg_month 236 | order by created desc 237 | limit 1)"; 238 | 239 | drizzle_pass backend; 240 | } 241 | 242 | location = '/=/batch/GetSidebar/~/~' { 243 | if ($arg_year !~ '^(?:19|20)\d{2}$') { 244 | rds_json_ret 400 'Bad "year" argument'; 245 | } 246 | if ($arg_month !~ '^\d{1,2}$') { 247 | rds_json_ret 400 'Bad "month" argument'; 248 | } 249 | 250 | default_type 'application/json'; 251 | echo '['; 252 | echo_location_async '/=/view/PostsByMonth/~/~' "year=$arg_year&month=$arg_month"; 253 | echo ','; 254 | echo_location_async '/=/view/RecentPosts/~/~' "offset=0&limit=6"; 255 | echo ','; 256 | echo_location_async '/=/view/RecentComments/~/~' "offset=0&limit=6"; 257 | echo ','; 258 | echo_location_async '/=/view/PostCountByMonths/~/~' "offset=0&limit=12"; 259 | echo ']'; 260 | } 261 | 262 | location = '/=/batch/GetFullPost/~/~' { 263 | if ($arg_id !~ '^\d+$') { 264 | rds_json_ret 400 'Bad "id" argument'; 265 | } 266 | 267 | default_type 'application/json'; 268 | echo '['; 269 | echo_location_async "/=/model/Post/id/$arg_id"; 270 | echo ','; 271 | echo_location_async "/=/view/PrevNextPost/~/~" "current=$arg_id"; 272 | echo ','; 273 | echo_location_async "/=/model/Comment/post/$arg_id" "_order_by=id:desc"; 274 | echo ']'; 275 | } 276 | 277 | location ~* '^/=/model/Post/id/(.*)$' { 278 | set $id $1; 279 | if ($id !~ '^\d+$') { 280 | rds_json_ret 400 'Bad "id" value'; 281 | } 282 | 283 | drizzle_query "select * from posts where id = $id"; 284 | drizzle_pass backend; 285 | } 286 | 287 | location ~* '^/=/model/Comment/post/(.*)$' { 288 | set $post $1; 289 | if ($post !~ '^\d+$') { 290 | rds_json_ret 400 'Bad "post" value'; 291 | } 292 | 293 | drizzle_query "select * from comments where post = $post"; 294 | drizzle_pass backend; 295 | } 296 | 297 | location = '/=/model/Post/~/~' { 298 | if ($arg__offset !~ '^\d+$') { 299 | rds_json_ret 400 'Bad "_offset" argument'; 300 | } 301 | if ($arg__limit !~ '^\d{1,2}$') { 302 | rds_json_ret 400 'Bad "_limit" argument'; 303 | } 304 | if ($arg__order_by !~ '^([A-Za-z]\w*)%3A(desc|asc)$') { 305 | rds_json_ret 400 'Bad "_order_by" argument'; 306 | } 307 | set $col $1; 308 | set $order $2; 309 | drizzle_query 310 | "select * 311 | from posts 312 | order by `$col` $order 313 | limit $arg__offset, $arg__limit"; 314 | 315 | drizzle_pass backend; 316 | } 317 | 318 | location = '/=/batch/NewComment/~/~' { 319 | default_type 'application/json'; 320 | 321 | set_unescape_uri $sender $arg_sender; 322 | 323 | set_unescape_uri $email $arg_email; 324 | 325 | set_unescape_uri $url $arg_url; 326 | 327 | set_unescape_uri $body $arg_body; 328 | 329 | set_unescape_uri $post_id $arg_post_id; 330 | 331 | if ($sender !~ '\S') { 332 | rds_json_ret 400 "Bad \"sender\" argument"; 333 | } 334 | if ($email !~ '^[-A-Za-z0-9_.]+@[-A-Za-z0-9_.]+$') { 335 | rds_json_ret 400 "Bad \"email\" argument"; 336 | } 337 | if ($url !~ '^(?:\s*|https?://\S+)$') { 338 | rds_json_ret 400 "Bad \"url\" argument: $url"; 339 | } 340 | if ($body ~ '^\s*$') { 341 | rds_json_ret 400 "Bad \"body\" argument"; 342 | } 343 | if ($post_id !~ '^[1-9]\d*$') { 344 | rds_json_ret 400 "Bad \"post_id\" argument"; 345 | } 346 | 347 | set_quote_sql_str $sender; 348 | set_quote_sql_str $email; 349 | set_quote_sql_str $url; 350 | set_quote_sql_str $body; 351 | 352 | # XXX these operations should be put into a 353 | # single transaction 354 | echo '['; 355 | 356 | echo_location '/=/action/RunSQL/~/~' 357 | "insert into comments (sender, email, url, body, post) 358 | values($sender, $email, $url, $body, $post_id)"; 359 | 360 | echo ','; 361 | 362 | echo_location '/=/action/RunSQL/~/~' 363 | "update posts 364 | set comments = comments + 1 365 | where id = $post_id"; 366 | 367 | echo ']'; 368 | } 369 | 370 | location = '/=/action/RunSQL/~/~' { 371 | internal; 372 | drizzle_query $query_string; 373 | drizzle_pass backend; 374 | } 375 | _EOC_ 376 | 377 | no_long_string(); 378 | 379 | run_tests(); 380 | 381 | #no_diff(); 382 | 383 | __DATA__ 384 | 385 | === TEST 1: PostsByMonth view (no month arg) 386 | --- http_config eval: $::http_config 387 | --- config eval: $::config 388 | --- request 389 | GET /=/view/PostsByMonth/~/~?_callback=foo 390 | --- response_headers 391 | Content-Type: application/x-javascript 392 | --- response_body chop 393 | foo({"errcode":400,"errstr":"Bad \"month\" argument"}); 394 | 395 | 396 | 397 | === TEST 2: PostsByMonth view (bad month) 398 | --- http_config eval: $::http_config 399 | --- config eval: $::config 400 | --- request 401 | GET /=/view/PostsByMonth/~/~?month=1234&_callback=foo 402 | --- response_headers 403 | Content-Type: application/x-javascript 404 | --- response_body chop 405 | foo({"errcode":400,"errstr":"Bad \"month\" argument"}); 406 | 407 | 408 | 409 | === TEST 3: PostsByMonth view (emtpy result) 410 | --- http_config eval: $::http_config 411 | --- config eval: $::config 412 | --- request 413 | GET /=/view/PostsByMonth/~/~?year=1984&month=2&_callback=bar 414 | --- response_headers 415 | Content-Type: application/x-javascript 416 | --- response_body chop 417 | bar([]); 418 | 419 | 420 | 421 | === TEST 4: PostsByMonth view (non-emtpy result) 422 | --- http_config eval: $::http_config 423 | --- config eval: $::config 424 | --- request 425 | GET /=/view/PostsByMonth/~/~?year=2009&month=10&_callback=foo 426 | --- response_headers 427 | Content-Type: application/x-javascript 428 | --- response_body chop 429 | foo([{"id":114,"title":"Hacking on the Nginx echo module","day":15}]); 430 | 431 | 432 | 433 | === TEST 5: PostsByMonth view (non-emtpy result) 434 | --- http_config eval: $::http_config 435 | --- config eval: $::config 436 | --- request 437 | GET /=/view/PostsByMonth/~/~?year=2009&month=12&_callback=foo 438 | --- response_headers 439 | Content-Type: application/x-javascript 440 | --- response_body chop 441 | foo([{"id":117,"title":"Major updates to ngx_chunkin: lots of bug fixes and beginning of keep-alive support","day":4},{"id":118,"title":"ngx_memc: an extended version of ngx_memcached that supports set, add, delete, and many more commands","day":6},{"id":119,"title":"Test::Nginx::LWP and Test::Nginx::Socket are now on CPAN","day":8}]); 442 | --- timeout: 90 443 | 444 | 445 | 446 | === TEST 6: GetSideBar 447 | --- http_config eval: $::http_config 448 | --- config eval: $::config 449 | --- request 450 | GET /=/batch/GetSidebar/~/~?year=2009&month=10&_callback=foo 451 | --- response_headers 452 | Content-Type: application/x-javascript 453 | --- response_body chop 454 | foo([ 455 | [{"id":114,"title":"Hacking on the Nginx echo module","day":15}], 456 | [{"id":119,"title":"Test::Nginx::LWP and Test::Nginx::Socket are now on CPAN"},{"id":118,"title":"ngx_memc: an extended version of ngx_memcached that supports set, add, delete, and many more commands"},{"id":117,"title":"Major updates to ngx_chunkin: lots of bug fixes and beginning of keep-alive support"},{"id":116,"title":"The \"headers more\" module: scripting input and output filters in your Nginx config file"},{"id":115,"title":"The \"chunkin\" module: Experimental chunked input support for Nginx"},{"id":114,"title":"Hacking on the Nginx echo module"}], 457 | [{"id":179,"post":101,"sender":"agentzh","title":"生活搜基于 Firefox 3.1 的 List Hunter 集群"},{"id":178,"post":101,"sender":"Winter","title":"生活搜基于 Firefox 3.1 的 List Hunter 集群"},{"id":177,"post":100,"sender":"Mountain","title":"漂在北京"},{"id":176,"post":106,"sender":"agentzh","title":"Text::SmartLinks: The Perl 6 love for Perl 5"},{"id":175,"post":106,"sender":"gosber","title":"Text::SmartLinks: The Perl 6 love for Perl 5"},{"id":174,"post":105,"sender":"cnangel","title":"SSH::Batch: Treating clusters as maths sets and intervals"}], 458 | [{"year_month":"2009-12-01","count":3},{"year_month":"2009-11-01","count":2},{"year_month":"2009-10-01","count":1},{"year_month":"2009-09-01","count":5},{"year_month":"2009-05-01","count":2},{"year_month":"2009-04-01","count":3},{"year_month":"2009-02-01","count":2},{"year_month":"2008-12-01","count":3},{"year_month":"2008-11-01","count":2},{"year_month":"2008-10-01","count":2},{"year_month":"2008-09-01","count":4},{"year_month":"2008-08-01","count":2}]] 459 | ); 460 | 461 | 462 | 463 | === TEST 7: GetFullPost 464 | --- http_config eval: $::http_config 465 | --- config eval: $::config 466 | --- request 467 | GET /=/batch/GetFullPost/~/~?id=116&_user=agentzh.Public&_callback=OpenResty.callbackMap%5B1264354204389%5D 468 | --- response_headers 469 | Content-Type: application/x-javascript 470 | --- response_body chop 471 | --- SKIP 472 | 473 | 474 | 475 | === TEST 8: RowCount 476 | --- http_config eval: $::http_config 477 | --- config eval: $::config 478 | --- request 479 | GET /=/view/RowCount/~/~?model=Post&_callback=foo 480 | --- response_headers 481 | Content-Type: application/x-javascript 482 | --- response_body chop 483 | foo([{"count":118}]); 484 | 485 | 486 | 487 | === TEST 9: GetFullPost bug 488 | --- http_config eval: $::http_config 489 | --- config eval: $::config 490 | --- request 491 | GET /=/model/Comment/post/67 492 | --- response_headers 493 | Content-Type: application/json 494 | --- response_body_like: laser 495 | --- error_code: 200 496 | 497 | 498 | 499 | === TEST 10: more field data error 500 | --- http_config eval: $::http_config 501 | --- config eval: $::config 502 | --- request 503 | GET /=/model/Post/~/~?_limit=5&_order_by=id%3Adesc&_offset=100 504 | --- response_headers 505 | Content-Type: application/json 506 | --- response_body_like: 测试 507 | --- error_code: 200 508 | --- SKIP 509 | 510 | 511 | 512 | === TEST 11: default arguments 513 | --- http_config eval: $::http_config 514 | --- config eval: $::config 515 | --- request 516 | GET /=/view/RecentComments/~/~ 517 | --- response_headers 518 | Content-Type: application/json 519 | --- response_body chop 520 | [{"id":179,"post":101,"sender":"agentzh","title":"生活搜基于 Firefox 3.1 的 List Hunter 集群"},{"id":178,"post":101,"sender":"Winter","title":"生活搜基于 Firefox 3.1 的 List Hunter 集群"},{"id":177,"post":100,"sender":"Mountain","title":"漂在北京"},{"id":176,"post":106,"sender":"agentzh","title":"Text::SmartLinks: The Perl 6 love for Perl 5"},{"id":175,"post":106,"sender":"gosber","title":"Text::SmartLinks: The Perl 6 love for Perl 5"},{"id":174,"post":105,"sender":"cnangel","title":"SSH::Batch: Treating clusters as maths sets and intervals"},{"id":173,"post":106,"sender":"cnangel","title":"Text::SmartLinks: The Perl 6 love for Perl 5"},{"id":172,"post":104,"sender":"agentzh","title":"My VDOM.pm & WebKit Cluster Talk at the April Meeting of Beijing Perl Workshop"},{"id":171,"post":104,"sender":"kindy","title":"My VDOM.pm & WebKit Cluster Talk at the April Meeting of Beijing Perl Workshop"},{"id":170,"post":104,"sender":"cnangel","title":"My VDOM.pm & WebKit Cluster Talk at the April Meeting of Beijing Perl Workshop"}] 521 | --- error_code: 200 522 | --- timeout: 3 523 | 524 | 525 | 526 | === TEST 12: post a comment with empty body 527 | --- http_config eval: $::http_config 528 | --- config eval: $::config 529 | --- request 530 | GET /=/batch/NewComment/~/~?sender=agentzh&email=agentzh@gmail.com&body=&post_id=3 531 | --- response_headers 532 | Content-Type: application/json 533 | --- response_body chop 534 | {"errcode":400,"errstr":"Bad \"body\" argument"} 535 | --- error_code: 200 536 | 537 | 538 | 539 | === TEST 13: post a comment with valid gmail 540 | --- http_config eval: $::http_config 541 | --- config eval: $::config 542 | --- request 543 | GET /=/batch/NewComment/~/~?sender=agentzh&email=agentzh%40gmail.com&body=hi&post_id= 544 | --- response_headers 545 | Content-Type: application/json 546 | --- response_body chop 547 | {"errcode":400,"errstr":"Bad \"post_id\" argument"} 548 | --- error_code: 200 549 | 550 | 551 | 552 | === TEST 14: try to run the internal location 553 | --- http_config eval: $::http_config 554 | --- config eval: $::config 555 | --- request 556 | GET /=/action/RunSQL/~/~?select 557 | --- response_headers 558 | Content-Type: application/json 559 | --- response_body chop 560 | {"errcode":404,"errstr":"Not Found"} 561 | --- error_code: 200 562 | 563 | 564 | 565 | === TEST 15: auth 566 | --- http_config eval: $::http_config 567 | --- config eval: $::config 568 | --- request 569 | GET /auth?user=agentzh 570 | --- response_headers 571 | Content-Type: application/json 572 | --- response_body 573 | pass 574 | --- error_code: 200 575 | --- SKIP 576 | 577 | 578 | 579 | === TEST 16: auth 580 | 581 | init db: 582 | 583 | create table users ( 584 | id serial primary key, 585 | name text not null, 586 | password text not null 587 | ); 588 | 589 | insert into users 590 | (name, password) 591 | values ('agentzh', 'some_pass'); 592 | 593 | --- http_config eval: $::http_config 594 | --- config eval: $::config 595 | --- request 596 | GET /auth?user=john 597 | --- response_headers 598 | Content-Type: application/json 599 | --- response_body chop 600 | {"errcode":403,"errstr":"Forbidden"} 601 | --- error_code: 200 602 | --- SKIP 603 | 604 | 605 | 606 | === TEST 17: auth 607 | 608 | db init: 609 | 610 | create table users ( 611 | id serial primary key, 612 | name text not null, 613 | password text not null 614 | ); 615 | 616 | insert into users 617 | (name, password) 618 | values ('agentzh', 'some_pass'); 619 | 620 | 621 | --- http_config eval: $::http_config 622 | --- config eval: $::config 623 | --- request 624 | GET /test 625 | --- response_headers 626 | Content-Type: application/json 627 | --- response_body 628 | pass 629 | --- error_code: 200 630 | --- SKIP 631 | 632 | 633 | 634 | === TEST 18: default arguments (small pagesize) 635 | --- http_config eval: $::http_config 636 | --- config eval 637 | "rds_json_buffer_size 1; 638 | $::config" 639 | --- request 640 | GET /=/view/RecentComments/~/~ 641 | --- response_headers 642 | Content-Type: application/json 643 | --- response_body chop 644 | [{"id":179,"post":101,"sender":"agentzh","title":"生活搜基于 Firefox 3.1 的 List Hunter 集群"},{"id":178,"post":101,"sender":"Winter","title":"生活搜基于 Firefox 3.1 的 List Hunter 集群"},{"id":177,"post":100,"sender":"Mountain","title":"漂在北京"},{"id":176,"post":106,"sender":"agentzh","title":"Text::SmartLinks: The Perl 6 love for Perl 5"},{"id":175,"post":106,"sender":"gosber","title":"Text::SmartLinks: The Perl 6 love for Perl 5"},{"id":174,"post":105,"sender":"cnangel","title":"SSH::Batch: Treating clusters as maths sets and intervals"},{"id":173,"post":106,"sender":"cnangel","title":"Text::SmartLinks: The Perl 6 love for Perl 5"},{"id":172,"post":104,"sender":"agentzh","title":"My VDOM.pm & WebKit Cluster Talk at the April Meeting of Beijing Perl Workshop"},{"id":171,"post":104,"sender":"kindy","title":"My VDOM.pm & WebKit Cluster Talk at the April Meeting of Beijing Perl Workshop"},{"id":170,"post":104,"sender":"cnangel","title":"My VDOM.pm & WebKit Cluster Talk at the April Meeting of Beijing Perl Workshop"}] 645 | --- error_code: 200 646 | --- timeout: 3 647 | 648 | -------------------------------------------------------------------------------- /t/compact/openresty.t: -------------------------------------------------------------------------------- 1 | # vi:filetype=perl 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket skip_all => 'not working at all (missing data)'; 5 | 6 | #repeat_each(100); 7 | repeat_each(2); 8 | 9 | worker_connections(128); 10 | workers(1); 11 | #master_on; 12 | log_level('warn'); 13 | 14 | plan tests => repeat_each() * 3 * blocks(); 15 | 16 | $ENV{TEST_NGINX_MYSQL_HOST} ||= '127.0.0.1'; 17 | $ENV{TEST_NGINX_MYSQL_PORT} ||= 3306; 18 | 19 | our $http_config = <<'_EOC_'; 20 | upstream backend { 21 | drizzle_server $TEST_NGINX_MYSQL_HOST:$TEST_NGINX_MYSQL_PORT protocol=mysql 22 | dbname=ngx_test user=ngx_test password=ngx_test; 23 | } 24 | _EOC_ 25 | 26 | our $config = <<'_EOC_'; 27 | xss_get on; 28 | xss_callback_arg _callback; 29 | rds_json on; 30 | rds_json_format compact; 31 | 32 | location = /auth { 33 | #internal; 34 | default_type 'application/json'; 35 | 36 | #internal; 37 | eval_subrequest_in_memory off; 38 | eval $res { 39 | set_quote_sql_str $user $arg_user; 40 | set $sql 'select count(*) res from users where name=$user'; 41 | drizzle_query $sql; 42 | drizzle_pass backend; 43 | #echo $sql; 44 | #echo hi; 45 | rds_json on; 46 | rds_json_content_type application/octet-stream; 47 | } 48 | #echo $res; 49 | if ($res ~ '"res":1') { 50 | echo pass; 51 | break; 52 | } 53 | if ($res !~ '"res":1') { 54 | return 403; 55 | break; 56 | } 57 | } 58 | 59 | # XXX we should implement these in the ngx_xss module 60 | location @err500 { rds_json_ret 500 "Internal Server Error"; } 61 | location @err404 { rds_json_ret 404 "Not Found"; } 62 | location @err400 { rds_json_ret 400 "Bad Request"; } 63 | location @err403 { rds_json_ret 403 "Forbidden"; } 64 | location @err502 { rds_json_ret 502 "Bad Gateway"; } 65 | location @err503 { rds_json_ret 503 "Service Unavailable"; } 66 | 67 | error_page 500 = @err500; 68 | error_page 404 = @err404; 69 | error_page 403 = @err403; 70 | error_page 400 = @err400; 71 | error_page 502 = @err502; 72 | error_page 503 = @err503; 73 | error_page 504 507 = @err500; 74 | 75 | location = '/=/view/PostsByMonth/~/~' { 76 | if ($arg_year !~ '^\d{4}$') { 77 | rds_json_ret 400 'Bad "year" argument'; 78 | } 79 | if ($arg_month !~ '^\d{1,2}$') { 80 | rds_json_ret 400 'Bad "month" argument'; 81 | } 82 | 83 | drizzle_query 84 | "select id, title, day(created) as day 85 | from posts 86 | where year(created) = $arg_year and month(created) = $arg_month 87 | order by created asc"; 88 | 89 | drizzle_pass backend; 90 | } 91 | 92 | location = '/=/view/RecentComments/~/~' { 93 | set $offset $arg_offset; 94 | set_if_empty $offset 0; 95 | 96 | set $limit $arg_limit; 97 | set_if_empty $limit 10; 98 | 99 | if ($offset !~ '^\d+$') { 100 | rds_json_ret 400 'Bad "offset" argument'; 101 | } 102 | if ($limit !~ '^\d{1,2}$') { 103 | rds_json_ret 400 'Bad "limit" argument'; 104 | } 105 | 106 | drizzle_query 107 | "select comments.id as id, post, sender, title 108 | from posts, comments 109 | where post = posts.id 110 | order by comments.id desc 111 | limit $offset, $limit"; 112 | 113 | drizzle_pass backend; 114 | } 115 | 116 | location = '/=/view/RecentPosts/~/~' { 117 | set $offset $arg_offset; 118 | set_if_empty $offset 0; 119 | 120 | set $limit $arg_limit; 121 | set_if_empty $limit 10; 122 | 123 | if ($offset !~ '^\d+$') { 124 | rds_json_ret 400 'Bad "offset" argument'; 125 | } 126 | if ($limit !~ '^\d{1,2}$') { 127 | rds_json_ret 400 'Bad "limit" argument'; 128 | } 129 | 130 | drizzle_query 131 | "select id, title 132 | from posts 133 | order by id desc 134 | limit $offset, $limit"; 135 | 136 | drizzle_pass backend; 137 | } 138 | 139 | location = '/=/view/PrevNextPost/~/~' { 140 | if ($arg_current !~ '^\d+$') { 141 | rds_json_ret 400 'Bad "current" argument'; 142 | } 143 | 144 | drizzle_query 145 | "(select id, title 146 | from posts 147 | where id < $arg_current 148 | order by id desc 149 | limit 1) 150 | union 151 | (select id, title 152 | from posts 153 | where id > $arg_current 154 | order by id asc 155 | limit 1)"; 156 | 157 | drizzle_pass backend; 158 | } 159 | 160 | location = '/=/view/RowCount/~/~' { 161 | if ($arg_model = 'Post') { 162 | drizzle_query "select count(*) as count from posts"; 163 | drizzle_pass backend; 164 | } 165 | if ($arg_model = 'Comment') { 166 | drizzle_query "select count(*) as count from comments"; 167 | drizzle_pass backend; 168 | } 169 | 170 | rds_json_ret 400 'Bad "model" argument'; 171 | } 172 | 173 | location = '/=/view/PostCountByMonths/~/~' { 174 | set $offset $arg_offset; 175 | set_if_empty $offset 0; 176 | 177 | set $limit $arg_limit; 178 | set_if_empty $limit 10; 179 | 180 | if ($offset !~ '^\d+$') { 181 | rds_json_ret 400 'Bad "offset" argument'; 182 | } 183 | if ($limit !~ '^\d{1,2}$') { 184 | rds_json_ret 400 'Bad "limit" argument'; 185 | } 186 | 187 | drizzle_query 188 | "select date_format(created, '%Y-%m-01') `year_month`, count(*) count 189 | from posts 190 | group by `year_month` 191 | order by `year_month` desc 192 | limit $offset, $limit"; 193 | drizzle_pass backend; 194 | } 195 | 196 | location = '/=/view/FullPostsByMonth/~/~' { 197 | set $count $arg_count; 198 | set_if_empty $count 40; 199 | 200 | if ($arg_year !~ '^(?:19|20)\d{2}$') { 201 | rds_json_ret 400 'Bad "year" argument'; 202 | } 203 | if ($arg_month !~ '^\d{1,2}$') { 204 | rds_json_ret 400 'Bad "month" argument'; 205 | } 206 | if ($arg_count !~ '^\d+$') { 207 | rds_json_ret 400 'Bad "count" argument'; 208 | } 209 | 210 | drizzle_query 211 | "select * from posts 212 | where year(created) = $arg_year and month(created) = $arg_month 213 | order by id desc 214 | limit $count"; 215 | 216 | drizzle_pass backend; 217 | } 218 | 219 | location = '/=/view/PrevNextArchive/~/~' { 220 | if ($arg_now !~ '^\d{4}-\d{1,2}(?:-\d{1,2})?$') { 221 | rds_json_ret 400 'Bad "now" argument'; 222 | } 223 | if ($arg_month !~ '^\d{1,2}$') { 224 | rds_json_ret 400 'Bad "month" argument'; 225 | } 226 | 227 | drizzle_query 228 | "(select 'next' as id, month(created) as month, year(created) as year 229 | from posts 230 | where created > $arg_now and month(created) <> $arg_month 231 | order by created asc 232 | limit 1) 233 | union 234 | (select 'prev' as id, month(created) as month, year(created) as year 235 | from posts 236 | where created < $arg_now and month(created) <> $arg_month 237 | order by created desc 238 | limit 1)"; 239 | 240 | drizzle_pass backend; 241 | } 242 | 243 | location = '/=/batch/GetSidebar/~/~' { 244 | if ($arg_year !~ '^(?:19|20)\d{2}$') { 245 | rds_json_ret 400 'Bad "year" argument'; 246 | } 247 | if ($arg_month !~ '^\d{1,2}$') { 248 | rds_json_ret 400 'Bad "month" argument'; 249 | } 250 | 251 | default_type 'application/json'; 252 | echo '['; 253 | echo_location_async '/=/view/PostsByMonth/~/~' "year=$arg_year&month=$arg_month"; 254 | echo ','; 255 | echo_location_async '/=/view/RecentPosts/~/~' "offset=0&limit=6"; 256 | echo ','; 257 | echo_location_async '/=/view/RecentComments/~/~' "offset=0&limit=6"; 258 | echo ','; 259 | echo_location_async '/=/view/PostCountByMonths/~/~' "offset=0&limit=12"; 260 | echo ']'; 261 | } 262 | 263 | location = '/=/batch/GetFullPost/~/~' { 264 | if ($arg_id !~ '^\d+$') { 265 | rds_json_ret 400 'Bad "id" argument'; 266 | } 267 | 268 | default_type 'application/json'; 269 | echo '['; 270 | echo_location_async "/=/model/Post/id/$arg_id"; 271 | echo ','; 272 | echo_location_async "/=/view/PrevNextPost/~/~" "current=$arg_id"; 273 | echo ','; 274 | echo_location_async "/=/model/Comment/post/$arg_id" "_order_by=id:desc"; 275 | echo ']'; 276 | } 277 | 278 | location ~* '^/=/model/Post/id/(.*)$' { 279 | set $id $1; 280 | if ($id !~ '^\d+$') { 281 | rds_json_ret 400 'Bad "id" value'; 282 | } 283 | 284 | drizzle_query "select * from posts where id = $id"; 285 | drizzle_pass backend; 286 | } 287 | 288 | location ~* '^/=/model/Comment/post/(.*)$' { 289 | set $post $1; 290 | if ($post !~ '^\d+$') { 291 | rds_json_ret 400 'Bad "post" value'; 292 | } 293 | 294 | drizzle_query "select * from comments where post = $post"; 295 | drizzle_pass backend; 296 | } 297 | 298 | location = '/=/model/Post/~/~' { 299 | if ($arg__offset !~ '^\d+$') { 300 | rds_json_ret 400 'Bad "_offset" argument'; 301 | } 302 | if ($arg__limit !~ '^\d{1,2}$') { 303 | rds_json_ret 400 'Bad "_limit" argument'; 304 | } 305 | if ($arg__order_by !~ '^([A-Za-z]\w*)%3A(desc|asc)$') { 306 | rds_json_ret 400 'Bad "_order_by" argument'; 307 | } 308 | set $col $1; 309 | set $order $2; 310 | drizzle_query 311 | "select * 312 | from posts 313 | order by `$col` $order 314 | limit $arg__offset, $arg__limit"; 315 | 316 | drizzle_pass backend; 317 | } 318 | 319 | location = '/=/batch/NewComment/~/~' { 320 | default_type 'application/json'; 321 | 322 | set_unescape_uri $sender $arg_sender; 323 | 324 | set_unescape_uri $email $arg_email; 325 | 326 | set_unescape_uri $url $arg_url; 327 | 328 | set_unescape_uri $body $arg_body; 329 | 330 | set_unescape_uri $post_id $arg_post_id; 331 | 332 | if ($sender !~ '\S') { 333 | rds_json_ret 400 "Bad \"sender\" argument"; 334 | } 335 | if ($email !~ '^[-A-Za-z0-9_.]+@[-A-Za-z0-9_.]+$') { 336 | rds_json_ret 400 "Bad \"email\" argument"; 337 | } 338 | if ($url !~ '^(?:\s*|https?://\S+)$') { 339 | rds_json_ret 400 "Bad \"url\" argument: $url"; 340 | } 341 | if ($body ~ '^\s*$') { 342 | rds_json_ret 400 "Bad \"body\" argument"; 343 | } 344 | if ($post_id !~ '^[1-9]\d*$') { 345 | rds_json_ret 400 "Bad \"post_id\" argument"; 346 | } 347 | 348 | set_quote_sql_str $sender; 349 | set_quote_sql_str $email; 350 | set_quote_sql_str $url; 351 | set_quote_sql_str $body; 352 | 353 | # XXX these operations should be put into a 354 | # single transaction 355 | echo '['; 356 | 357 | echo_location '/=/action/RunSQL/~/~' 358 | "insert into comments (sender, email, url, body, post) 359 | values($sender, $email, $url, $body, $post_id)"; 360 | 361 | echo ','; 362 | 363 | echo_location '/=/action/RunSQL/~/~' 364 | "update posts 365 | set comments = comments + 1 366 | where id = $post_id"; 367 | 368 | echo ']'; 369 | } 370 | 371 | location = '/=/action/RunSQL/~/~' { 372 | internal; 373 | drizzle_query $query_string; 374 | drizzle_pass backend; 375 | } 376 | _EOC_ 377 | 378 | no_long_string(); 379 | 380 | run_tests(); 381 | 382 | #no_diff(); 383 | 384 | __DATA__ 385 | 386 | === TEST 1: PostsByMonth view (no month arg) 387 | --- http_config eval: $::http_config 388 | --- config eval: $::config 389 | --- request 390 | GET /=/view/PostsByMonth/~/~?_callback=foo 391 | --- response_headers 392 | Content-Type: application/x-javascript 393 | --- response_body chop 394 | foo({"errcode":400,"errstr":"Bad \"month\" argument"}); 395 | 396 | 397 | 398 | === TEST 2: PostsByMonth view (bad month) 399 | --- http_config eval: $::http_config 400 | --- config eval: $::config 401 | --- request 402 | GET /=/view/PostsByMonth/~/~?month=1234&_callback=foo 403 | --- response_headers 404 | Content-Type: application/x-javascript 405 | --- response_body chop 406 | foo({"errcode":400,"errstr":"Bad \"month\" argument"}); 407 | 408 | 409 | 410 | === TEST 3: PostsByMonth view (emtpy result) 411 | --- http_config eval: $::http_config 412 | --- config eval: $::config 413 | --- request 414 | GET /=/view/PostsByMonth/~/~?year=1984&month=2&_callback=bar 415 | --- response_headers 416 | Content-Type: application/x-javascript 417 | --- response_body chop 418 | bar([]); 419 | 420 | 421 | 422 | === TEST 4: PostsByMonth view (non-emtpy result) 423 | --- http_config eval: $::http_config 424 | --- config eval: $::config 425 | --- request 426 | GET /=/view/PostsByMonth/~/~?year=2009&month=10&_callback=foo 427 | --- response_headers 428 | Content-Type: application/x-javascript 429 | --- response_body chop 430 | foo([{"id":114,"title":"Hacking on the Nginx echo module","day":15}]); 431 | 432 | 433 | 434 | === TEST 5: PostsByMonth view (non-emtpy result) 435 | --- http_config eval: $::http_config 436 | --- config eval: $::config 437 | --- request 438 | GET /=/view/PostsByMonth/~/~?year=2009&month=12&_callback=foo 439 | --- response_headers 440 | Content-Type: application/x-javascript 441 | --- response_body chop 442 | foo([{"id":117,"title":"Major updates to ngx_chunkin: lots of bug fixes and beginning of keep-alive support","day":4},{"id":118,"title":"ngx_memc: an extended version of ngx_memcached that supports set, add, delete, and many more commands","day":6},{"id":119,"title":"Test::Nginx::LWP and Test::Nginx::Socket are now on CPAN","day":8}]); 443 | --- timeout: 90 444 | 445 | 446 | 447 | === TEST 6: GetSideBar 448 | --- http_config eval: $::http_config 449 | --- config eval: $::config 450 | --- request 451 | GET /=/batch/GetSidebar/~/~?year=2009&month=10&_callback=foo 452 | --- response_headers 453 | Content-Type: application/x-javascript 454 | --- response_body chop 455 | foo([ 456 | [{"id":114,"title":"Hacking on the Nginx echo module","day":15}], 457 | [{"id":119,"title":"Test::Nginx::LWP and Test::Nginx::Socket are now on CPAN"},{"id":118,"title":"ngx_memc: an extended version of ngx_memcached that supports set, add, delete, and many more commands"},{"id":117,"title":"Major updates to ngx_chunkin: lots of bug fixes and beginning of keep-alive support"},{"id":116,"title":"The \"headers more\" module: scripting input and output filters in your Nginx config file"},{"id":115,"title":"The \"chunkin\" module: Experimental chunked input support for Nginx"},{"id":114,"title":"Hacking on the Nginx echo module"}], 458 | [{"id":179,"post":101,"sender":"agentzh","title":"生活搜基于 Firefox 3.1 的 List Hunter 集群"},{"id":178,"post":101,"sender":"Winter","title":"生活搜基于 Firefox 3.1 的 List Hunter 集群"},{"id":177,"post":100,"sender":"Mountain","title":"漂在北京"},{"id":176,"post":106,"sender":"agentzh","title":"Text::SmartLinks: The Perl 6 love for Perl 5"},{"id":175,"post":106,"sender":"gosber","title":"Text::SmartLinks: The Perl 6 love for Perl 5"},{"id":174,"post":105,"sender":"cnangel","title":"SSH::Batch: Treating clusters as maths sets and intervals"}], 459 | [{"year_month":"2009-12-01","count":3},{"year_month":"2009-11-01","count":2},{"year_month":"2009-10-01","count":1},{"year_month":"2009-09-01","count":5},{"year_month":"2009-05-01","count":2},{"year_month":"2009-04-01","count":3},{"year_month":"2009-02-01","count":2},{"year_month":"2008-12-01","count":3},{"year_month":"2008-11-01","count":2},{"year_month":"2008-10-01","count":2},{"year_month":"2008-09-01","count":4},{"year_month":"2008-08-01","count":2}]] 460 | ); 461 | 462 | 463 | 464 | === TEST 7: GetFullPost 465 | --- http_config eval: $::http_config 466 | --- config eval: $::config 467 | --- request 468 | GET /=/batch/GetFullPost/~/~?id=116&_user=agentzh.Public&_callback=OpenResty.callbackMap%5B1264354204389%5D 469 | --- response_headers 470 | Content-Type: application/x-javascript 471 | --- response_body chop 472 | --- SKIP 473 | 474 | 475 | 476 | === TEST 8: RowCount 477 | --- http_config eval: $::http_config 478 | --- config eval: $::config 479 | --- request 480 | GET /=/view/RowCount/~/~?model=Post&_callback=foo 481 | --- response_headers 482 | Content-Type: application/x-javascript 483 | --- response_body chop 484 | foo([{"count":118}]); 485 | 486 | 487 | 488 | === TEST 9: GetFullPost bug 489 | --- http_config eval: $::http_config 490 | --- config eval: $::config 491 | --- request 492 | GET /=/model/Comment/post/67 493 | --- response_headers 494 | Content-Type: application/json 495 | --- response_body_like: laser 496 | --- error_code: 200 497 | 498 | 499 | 500 | === TEST 10: more field data error 501 | --- http_config eval: $::http_config 502 | --- config eval: $::config 503 | --- request 504 | GET /=/model/Post/~/~?_limit=5&_order_by=id%3Adesc&_offset=100 505 | --- response_headers 506 | Content-Type: application/json 507 | --- response_body_like: 测试 508 | --- error_code: 200 509 | --- SKIP 510 | 511 | 512 | 513 | === TEST 11: default arguments 514 | --- http_config eval: $::http_config 515 | --- config eval: $::config 516 | --- request 517 | GET /=/view/RecentComments/~/~ 518 | --- response_headers 519 | Content-Type: application/json 520 | --- response_body chop 521 | [{"id":179,"post":101,"sender":"agentzh","title":"生活搜基于 Firefox 3.1 的 List Hunter 集群"},{"id":178,"post":101,"sender":"Winter","title":"生活搜基于 Firefox 3.1 的 List Hunter 集群"},{"id":177,"post":100,"sender":"Mountain","title":"漂在北京"},{"id":176,"post":106,"sender":"agentzh","title":"Text::SmartLinks: The Perl 6 love for Perl 5"},{"id":175,"post":106,"sender":"gosber","title":"Text::SmartLinks: The Perl 6 love for Perl 5"},{"id":174,"post":105,"sender":"cnangel","title":"SSH::Batch: Treating clusters as maths sets and intervals"},{"id":173,"post":106,"sender":"cnangel","title":"Text::SmartLinks: The Perl 6 love for Perl 5"},{"id":172,"post":104,"sender":"agentzh","title":"My VDOM.pm & WebKit Cluster Talk at the April Meeting of Beijing Perl Workshop"},{"id":171,"post":104,"sender":"kindy","title":"My VDOM.pm & WebKit Cluster Talk at the April Meeting of Beijing Perl Workshop"},{"id":170,"post":104,"sender":"cnangel","title":"My VDOM.pm & WebKit Cluster Talk at the April Meeting of Beijing Perl Workshop"}] 522 | --- error_code: 200 523 | --- timeout: 3 524 | 525 | 526 | 527 | === TEST 12: post a comment with empty body 528 | --- http_config eval: $::http_config 529 | --- config eval: $::config 530 | --- request 531 | GET /=/batch/NewComment/~/~?sender=agentzh&email=agentzh@gmail.com&body=&post_id=3 532 | --- response_headers 533 | Content-Type: application/json 534 | --- response_body chop 535 | {"errcode":400,"errstr":"Bad \"body\" argument"} 536 | --- error_code: 200 537 | 538 | 539 | 540 | === TEST 13: post a comment with valid gmail 541 | --- http_config eval: $::http_config 542 | --- config eval: $::config 543 | --- request 544 | GET /=/batch/NewComment/~/~?sender=agentzh&email=agentzh%40gmail.com&body=hi&post_id= 545 | --- response_headers 546 | Content-Type: application/json 547 | --- response_body chop 548 | {"errcode":400,"errstr":"Bad \"post_id\" argument"} 549 | --- error_code: 200 550 | 551 | 552 | 553 | === TEST 14: try to run the internal location 554 | --- http_config eval: $::http_config 555 | --- config eval: $::config 556 | --- request 557 | GET /=/action/RunSQL/~/~?select 558 | --- response_headers 559 | Content-Type: application/json 560 | --- response_body chop 561 | {"errcode":404,"errstr":"Not Found"} 562 | --- error_code: 200 563 | 564 | 565 | 566 | === TEST 15: auth 567 | --- http_config eval: $::http_config 568 | --- config eval: $::config 569 | --- request 570 | GET /auth?user=agentzh 571 | --- response_headers 572 | Content-Type: application/json 573 | --- response_body 574 | pass 575 | --- error_code: 200 576 | --- SKIP 577 | 578 | 579 | 580 | === TEST 16: auth 581 | 582 | init db: 583 | 584 | create table users ( 585 | id serial primary key, 586 | name text not null, 587 | password text not null 588 | ); 589 | 590 | insert into users 591 | (name, password) 592 | values ('agentzh', 'some_pass'); 593 | 594 | --- http_config eval: $::http_config 595 | --- config eval: $::config 596 | --- request 597 | GET /auth?user=john 598 | --- response_headers 599 | Content-Type: application/json 600 | --- response_body chop 601 | {"errcode":403,"errstr":"Forbidden"} 602 | --- error_code: 200 603 | --- SKIP 604 | 605 | 606 | 607 | === TEST 17: auth 608 | 609 | db init: 610 | 611 | create table users ( 612 | id serial primary key, 613 | name text not null, 614 | password text not null 615 | ); 616 | 617 | insert into users 618 | (name, password) 619 | values ('agentzh', 'some_pass'); 620 | 621 | 622 | --- http_config eval: $::http_config 623 | --- config eval: $::config 624 | --- request 625 | GET /test 626 | --- response_headers 627 | Content-Type: application/json 628 | --- response_body 629 | pass 630 | --- error_code: 200 631 | --- SKIP 632 | 633 | 634 | 635 | === TEST 18: default arguments (small pagesize) 636 | --- http_config eval: $::http_config 637 | --- config eval 638 | "rds_json_buffer_size 1; 639 | $::config" 640 | --- request 641 | GET /=/view/RecentComments/~/~ 642 | --- response_headers 643 | Content-Type: application/json 644 | --- response_body chop 645 | [{"id":179,"post":101,"sender":"agentzh","title":"生活搜基于 Firefox 3.1 的 List Hunter 集群"},{"id":178,"post":101,"sender":"Winter","title":"生活搜基于 Firefox 3.1 的 List Hunter 集群"},{"id":177,"post":100,"sender":"Mountain","title":"漂在北京"},{"id":176,"post":106,"sender":"agentzh","title":"Text::SmartLinks: The Perl 6 love for Perl 5"},{"id":175,"post":106,"sender":"gosber","title":"Text::SmartLinks: The Perl 6 love for Perl 5"},{"id":174,"post":105,"sender":"cnangel","title":"SSH::Batch: Treating clusters as maths sets and intervals"},{"id":173,"post":106,"sender":"cnangel","title":"Text::SmartLinks: The Perl 6 love for Perl 5"},{"id":172,"post":104,"sender":"agentzh","title":"My VDOM.pm & WebKit Cluster Talk at the April Meeting of Beijing Perl Workshop"},{"id":171,"post":104,"sender":"kindy","title":"My VDOM.pm & WebKit Cluster Talk at the April Meeting of Beijing Perl Workshop"},{"id":170,"post":104,"sender":"cnangel","title":"My VDOM.pm & WebKit Cluster Talk at the April Meeting of Beijing Perl Workshop"}] 646 | --- error_code: 200 647 | --- timeout: 3 648 | 649 | --------------------------------------------------------------------------------