├── .gitattributes ├── .github └── workflows │ └── test.yml ├── README.md ├── config ├── doc ├── FAQ.md ├── README.md └── images │ ├── consul-template-reload-cost.png │ ├── consul-template-reload-qps.png │ ├── list_update.bmp │ ├── nginx-http-api-arch.png │ ├── nginx-upsync-arch.png │ ├── start_fllow.bmp │ ├── upsync-vs-reload-cost.png │ └── upsync-vs-reload-qps.png ├── src ├── ngx_http_json.c ├── ngx_http_json.h ├── ngx_http_parser.c ├── ngx_http_parser.h ├── ngx_http_upsync_module.c └── ngx_http_upsync_module.h └── test ├── README ├── build.sh ├── configure.sh ├── consul.sh ├── fetch.sh ├── t ├── lib │ ├── Test │ │ ├── Nginx.pm │ │ └── Nginx │ │ │ ├── IMAP.pm │ │ │ ├── POP3.pm │ │ │ └── SMTP.pm │ └── Time │ │ ├── Parse.pm │ │ └── Zone.pm └── upsync.t └── test.sh /.gitattributes: -------------------------------------------------------------------------------- 1 | src/* linguist-language=C 2 | test/* linguist-language=Text 3 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | name: ${{ matrix.config.name }} 14 | runs-on: ${{ matrix.config.os }} 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | config: 19 | - { 20 | name: "Ubuntu 22.04 GCC nginx 1.23", artifact: "Linux.tar.xz", 21 | os: ubuntu-22.04, 22 | cc: "gcc", cxx: "g++", 23 | nginx_version: "1.23.4", consul_version: "1.15.2" 24 | } 25 | - { 26 | name: "Ubuntu 22.04 Clang nginx 1.23", artifact: "Linux.tar.xz", 27 | os: ubuntu-22.04, 28 | cc: "clang", cxx: "clang++", 29 | nginx_version: "1.23.4", consul_version: "1.15.2" 30 | } 31 | # - { 32 | # name: "macOS 10.15 Clang nginx 1.23", artifact: "macOS.tar.xz", 33 | # os: macos-10.15, 34 | # cc: "clang", cxx: "clang++", 35 | # nginx_version: "1.23.4", consul_version: "1.15.2" 36 | # } 37 | - { 38 | name: "Ubuntu 20.04 GCC nginx 1.22", artifact: "Linux.tar.xz", 39 | os: ubuntu-20.04, 40 | cc: "gcc", cxx: "g++", 41 | nginx_version: "1.22.1", consul_version: "1.15.2" 42 | } 43 | - { 44 | name: "Ubuntu 20.04 Clang nginx 1.22", artifact: "Linux.tar.xz", 45 | os: ubuntu-20.04, 46 | cc: "clang", cxx: "clang++", 47 | nginx_version: "1.22.1", consul_version: "1.15.2" 48 | } 49 | # - { 50 | # name: "macOS 10.15 Clang nginx 1.22", artifact: "macOS.tar.xz", 51 | # os: macos-10.15, 52 | # cc: "clang", cxx: "clang++", 53 | # nginx_version: "1.22.1", consul_version: "1.15.2" 54 | # } 55 | 56 | steps: 57 | - uses: actions/checkout@v1 58 | 59 | - name: Dependecies 60 | run: | 61 | ./test/consul.sh ${{ matrix.config.consul_version }} > /tmp/consul.log & 62 | sleep 30 63 | shell: bash 64 | 65 | - name: Configure 66 | run: ./test/fetch.sh ${{ matrix.config.nginx_version }} && ./test/configure.sh 67 | - name: Build 68 | run: ./test/build.sh 69 | - name: Test 70 | run: ./test/test.sh 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | nginx-upsync-module 2 | =================== 3 | 4 | Nginx C module, which can sync upstreams from Consul or others. It dynamically modifies backend-servers attributes (weight, max_fails,...), without need to reload NGINX. 5 | 6 | It may not always be convenient to modify configuration files and restart NGINX. For example, if you are experiencing large amounts of traffic and high load, restarting NGINX and reloading the configuration at that point further increases load on the system and can temporarily degrade performance. 7 | 8 | The module allows to expand and scale down without affecting performance. 9 | 10 | Another module, [nginx-stream-upsync-module](https://github.com/xiaokai-wang/nginx-stream-upsync-module) supports NGINX stream module (TCP protocol), please be noticed. 11 | 12 | Table of Contents 13 | ================= 14 | 15 | * [Name](#name) 16 | * [Status](#status) 17 | * [Synopsis](#synopsis) 18 | * [Description](#description) 19 | * [Directives](#directives) 20 | * [upsync](#upsync) 21 | * [upsync_interval](#upsync_interval) 22 | * [upsync_timeout](#upsync_timeout) 23 | * [upsync_type](#upsync_type) 24 | * [strong_dependency](#strong_dependency) 25 | * [upsync_dump_path](#upsync_dump_path) 26 | * [upsync_lb](#upsync_lb) 27 | * [upstream_show](#upstream_show) 28 | * [Consul_interface](#consul_interface) 29 | * [Etcd_interface](#etcd_interface) 30 | * [Check_module](#check_module_support) 31 | * [TODO](#todo) 32 | * [Compatibility](#compatibility) 33 | * [Installation](#installation) 34 | * [Code style](#code-style) 35 | * [Author](#author) 36 | * [Copyright and License](#copyright-and-license) 37 | * [See Also](#see-also) 38 | * [Source Dependency](#source-dependency) 39 | 40 | Status 41 | ====== 42 | 43 | This module is still under active development and is considered production ready. 44 | 45 | Synopsis 46 | ======== 47 | 48 | nginx-consul: 49 | ```nginx-consul 50 | http { 51 | upstream test { 52 | upsync 127.0.0.1:8500/v1/kv/upstreams/test/ upsync_timeout=6m upsync_interval=500ms upsync_type=consul strong_dependency=off; 53 | upsync_dump_path /usr/local/nginx/conf/servers/servers_test.conf; 54 | 55 | include /usr/local/nginx/conf/servers/servers_test.conf; 56 | } 57 | 58 | upstream bar { 59 | server 127.0.0.1:8090 weight=1 fail_timeout=10 max_fails=3; 60 | } 61 | 62 | server { 63 | listen 8080; 64 | 65 | location = /proxy_test { 66 | proxy_pass http://test; 67 | } 68 | 69 | location = /bar { 70 | proxy_pass http://bar; 71 | } 72 | 73 | location = /upstream_show { 74 | upstream_show; 75 | } 76 | 77 | } 78 | } 79 | ``` 80 | nginx-etcd: 81 | ```nginx-etcd 82 | http { 83 | upstream test { 84 | upsync 127.0.0.1:2379/v2/keys/upstreams/test upsync_timeout=6m upsync_interval=500ms upsync_type=etcd strong_dependency=off; 85 | upsync_dump_path /usr/local/nginx/conf/servers/servers_test.conf; 86 | 87 | include /usr/local/nginx/conf/servers/servers_test.conf; 88 | } 89 | 90 | upstream bar { 91 | server 127.0.0.1:8090 weight=1 fail_timeout=10 max_fails=3; 92 | } 93 | 94 | server { 95 | listen 8080; 96 | 97 | location = /proxy_test { 98 | proxy_pass http://test; 99 | } 100 | 101 | location = /bar { 102 | proxy_pass http://bar; 103 | } 104 | 105 | location = /upstream_show { 106 | upstream_show; 107 | } 108 | 109 | } 110 | } 111 | ``` 112 | upsync_lb: 113 | ```upsync_lb 114 | http { 115 | upstream test { 116 | least_conn; //hash $uri consistent; 117 | 118 | upsync 127.0.0.1:8500/v1/kv/upstreams/test/ upsync_timeout=6m upsync_interval=500ms upsync_type=consul strong_dependency=off; 119 | upsync_dump_path /usr/local/nginx/conf/servers/servers_test.conf; 120 | upsync_lb least_conn; //hash_ketama; 121 | 122 | include /usr/local/nginx/conf/servers/servers_test.conf; 123 | } 124 | 125 | upstream bar { 126 | server 127.0.0.1:8090 weight=1 fail_timeout=10 max_fails=3; 127 | } 128 | 129 | server { 130 | listen 8080; 131 | 132 | location = /proxy_test { 133 | proxy_pass http://test; 134 | } 135 | 136 | location = /bar { 137 | proxy_pass http://bar; 138 | } 139 | 140 | location = /upstream_show { 141 | upstream_show; 142 | } 143 | 144 | } 145 | } 146 | ``` 147 | 148 | NOTE: recommending strong_dependency is configed off and the first time included file include all the servers. 149 | 150 | Description 151 | ====== 152 | 153 | This module provides a method to discover backend servers. Supporting dynamicly adding or deleting backend server through consul or etcd and dynamically adjusting backend servers weight, module will timely pull new backend server list from consul or etcd to upsync nginx ip router. Nginx needn't reload. Having some advantages than others: 154 | 155 | * timely 156 | 157 | module send key to consul/etcd with index, consul/etcd will compare it with its index, if index doesn't change connection will hang five minutes, in the period any operation to the key-value, will feed back rightaway. 158 | 159 | * performance 160 | 161 | Pulling from consul/etcd equal a request to nginx, updating ip router nginx needn't reload, so affecting nginx performance is little. 162 | 163 | * stability 164 | 165 | Even if one pulling failed, it will pull next upsync_interval, so guarantying backend server stably provides service. And support dumping the latest config to location, so even if consul/etcd hung up, and nginx can be reload anytime. 166 | 167 | * health_check 168 | 169 | nginx-upsync-module support adding or deleting servers health check, needing nginx_upstream_check_module. Recommending nginx-upsync-module + nginx_upstream_check_module. 170 | 171 | Directives 172 | ====== 173 | 174 | upsync 175 | ----------- 176 | ``` 177 | syntax: upsync $consul/etcd.api.com:$port/v1/kv/upstreams/$upstream_name/ [upsync_type=consul/etcd] [upsync_interval=second/minutes] [upsync_timeout=second/minutes] [strong_dependency=off/on] 178 | ``` 179 | default: none, if parameters omitted, default parameters are upsync_interval=5s upsync_timeout=6m strong_dependency=off 180 | 181 | context: upstream 182 | 183 | description: Pull upstream servers from consul/etcd... . 184 | 185 | The parameters' meanings are: 186 | 187 | * upsync_interval 188 | 189 | pulling servers from consul/etcd interval time. 190 | 191 | * upsync_timeout 192 | 193 | pulling servers from consul/etcd request timeout. 194 | 195 | * upsync_type 196 | 197 | pulling servers from conf server type. 198 | 199 | * strong_dependency 200 | 201 | when strong_dependency is on, nginx will pull servers from consul/etcd every time when nginx start up or reload. 202 | 203 | [Back to TOC](#table-of-contents) 204 | 205 | upsync_dump_path 206 | ----------- 207 | `syntax: upsync_dump_path $path` 208 | 209 | default: /tmp/servers_$host.conf 210 | 211 | context: upstream 212 | 213 | description: dump the upstream backends to the $path. 214 | 215 | [Back to TOC](#table-of-contents) 216 | 217 | upsync_lb 218 | ----------- 219 | `syntax: upsync_lb $load_balance` 220 | 221 | default: round_robin/ip_hash/hash modula 222 | 223 | context: upstream 224 | 225 | description: mainly for least_conn and hash consistent, when using one of them, you must point out using upsync_lb. 226 | 227 | [Back to TOC](#table-of-contents) 228 | 229 | upstream_show 230 | ----------- 231 | `syntax: upstream_show` 232 | 233 | default: none 234 | 235 | context: upstream 236 | 237 | description: Show specific upstream all backend servers. 238 | 239 | ```configure 240 | location /upstream_list { 241 | upstream_show; 242 | } 243 | ``` 244 | 245 | ```request1 246 | curl http://127.0.0.1:8500/upstream_list?test; 247 | ``` 248 | 249 | ```request2 250 | curl http://127.0.0.1:8500/upstream_list; 251 | 252 | show all upstreams. 253 | ``` 254 | 255 | [Back to TOC](#table-of-contents) 256 | 257 | Consul_interface 258 | ====== 259 | 260 | Data can be taken from key/value store or service catalog. In the first case parameter upsync_type of directive must be *consul*. For example 261 | 262 | ```nginx-consul 263 | upsync 127.0.0.1:8500/v1/kv/upstreams/test upsync_timeout=6m upsync_interval=500ms upsync_type=consul strong_dependency=off; 264 | ``` 265 | 266 | In the second case it must be *consul_services*. 267 | 268 | ```nginx-consul 269 | upsync 127.0.0.1:8500/v1/catalog/service/test upsync_timeout=6m upsync_interval=500ms upsync_type=consul_services strong_dependency=off; 270 | ``` 271 | 272 | In the third case, it must be *consul_health*: 273 | 274 | ```nginx-consul 275 | upsync 127.0.0.1:8500/v1/health/service/test upsync_timeout=6m upsync_interval=500ms upsync_type=consul_health strong_dependency=off; 276 | ``` 277 | 278 | Services with failing health checks are marked as down with the health api. 279 | 280 | You can add or delete backend server through consul_ui or http_interface. Below are examples for key/value store. 281 | 282 | http_interface example: 283 | 284 | * add 285 | ``` 286 | curl -X PUT http://$consul_ip:$port/v1/kv/upstreams/$upstream_name/$backend_ip:$backend_port 287 | ``` 288 | default: weight=1 max_fails=2 fail_timeout=10 down=0 backup=0; 289 | 290 | ``` 291 | curl -X PUT -d "{\"weight\":1, \"max_fails\":2, \"fail_timeout\":10}" http://$consul_ip:$port/v1/kv/$dir1/$upstream_name/$backend_ip:$backend_port 292 | or 293 | curl -X PUT -d '{"weight":1, "max_fails":2, "fail_timeout":10}' http://$consul_ip:$port/v1/kv/$dir1/$upstream_name/$backend_ip:$backend_port 294 | ``` 295 | value support json format. 296 | 297 | * delete 298 | ``` 299 | curl -X DELETE http://$consul_ip:$port/v1/kv/upstreams/$upstream_name/$backend_ip:$backend_port 300 | ``` 301 | 302 | * adjust-weight 303 | ``` 304 | curl -X PUT -d "{\"weight\":2, \"max_fails\":2, \"fail_timeout\":10}" http://$consul_ip:$port/v1/kv/$dir1/$upstream_name/$backend_ip:$backend_port 305 | or 306 | curl -X PUT -d '{"weight":2, "max_fails":2, "fail_timeout":10}' http://$consul_ip:$port/v1/kv/$dir1/$upstream_name/$backend_ip:$backend_port 307 | ``` 308 | 309 | * mark server-down 310 | ``` 311 | curl -X PUT -d "{\"weight\":2, \"max_fails\":2, \"fail_timeout\":10, \"down\":1}" http://$consul_ip:$port/v1/kv/$dir1/$upstream_name/$backend_ip:$backend_port 312 | or 313 | curl -X PUT -d '{"weight":2, "max_fails":2, "fail_timeout":10, "down":1}' http://$consul_ip:$port/v1/kv/$dir1/$upstream_name/$backend_ip:$backend_port 314 | ``` 315 | 316 | * check 317 | ``` 318 | curl http://$consul_ip:$port/v1/kv/upstreams/$upstream_name?recurse 319 | ``` 320 | 321 | [Back to TOC](#table-of-contents) 322 | 323 | Etcd_interface 324 | ====== 325 | 326 | you can add or delete backend server through http_interface. 327 | 328 | mainly like etcd, http_interface example: 329 | 330 | * add 331 | ``` 332 | curl -X PUT http://$etcd_ip:$port/v2/keys/upstreams/$upstream_name/$backend_ip:$backend_port 333 | ``` 334 | default: weight=1 max_fails=2 fail_timeout=10 down=0 backup=0; 335 | 336 | ``` 337 | curl -X PUT -d value="{\"weight\":1, \"max_fails\":2, \"fail_timeout\":10}" http://$etcd_ip:$port/v2/keys/$dir1/$upstream_name/$backend_ip:$backend_port 338 | ``` 339 | value support json format. 340 | 341 | * delete 342 | ``` 343 | curl -X DELETE http://$etcd_ip:$port/v2/keys/upstreams/$upstream_name/$backend_ip:$backend_port 344 | ``` 345 | 346 | * adjust-weight 347 | ``` 348 | curl -X PUT -d "{\"weight\":2, \"max_fails\":2, \"fail_timeout\":10}" http://$etcd_ip:$port/v2/keys/$dir1/$upstream_name/$backend_ip:$backend_port 349 | ``` 350 | 351 | * mark server-down 352 | ``` 353 | curl -X PUT -d value="{\"weight\":2, \"max_fails\":2, \"fail_timeout\":10, \"down\":1}" http://$etcd_ip:$port/v2/keys/$dir1/$upstream_name/$backend_ip:$backend_port 354 | ``` 355 | 356 | * check 357 | ``` 358 | curl http://$etcd_ip:$port/v2/keys/upstreams/$upstream_name 359 | ``` 360 | 361 | [Back to TOC](#table-of-contents) 362 | 363 | Check_module 364 | ====== 365 | 366 | check module support. 367 | 368 | check-conf: 369 | ```check-conf 370 | http { 371 | upstream test { 372 | upsync 127.0.0.1:8500/v1/kv/upstreams/test/ upsync_timeout=6m upsync_interval=500ms upsync_type=consul strong_dependency=off; 373 | upsync_dump_path /usr/local/nginx/conf/servers/servers_test.conf; 374 | 375 | check interval=1000 rise=2 fall=2 timeout=3000 type=http default_down=false; 376 | check_http_send "HEAD / HTTP/1.0\r\n\r\n"; 377 | check_http_expect_alive http_2xx http_3xx; 378 | 379 | } 380 | 381 | upstream bar { 382 | server 127.0.0.1:8090 weight=1 fail_timeout=10 max_fails=3; 383 | } 384 | 385 | server { 386 | listen 8080; 387 | 388 | location = /proxy_test { 389 | proxy_pass http://test; 390 | } 391 | 392 | location = /bar { 393 | proxy_pass http://bar; 394 | } 395 | 396 | location = /upstream_show { 397 | upstream_show; 398 | } 399 | 400 | location = /upstream_status { 401 | check_status; 402 | access_log off; 403 | } 404 | 405 | } 406 | } 407 | ``` 408 | 409 | [Back to TOC](#table-of-contents) 410 | 411 | TODO 412 | ==== 413 | 414 | * support zookeeper and so on 415 | 416 | [Back to TOC](#table-of-contents) 417 | 418 | Compatibility 419 | ============= 420 | 421 | Master branch is compatible with nginx-1.9.8+. 422 | 423 | The branch of nginx-upsync-1.8.x is compatible with Nginx-1.8.x and with tengine-2.2.0. 424 | 425 | [Back to TOC](#table-of-contents) 426 | 427 | Installation 428 | ============ 429 | 430 | This module can be used independently, can be download[Github](https://github.com/weibocom/nginx-upsync-module.git). 431 | 432 | Grab the nginx source code from [nginx.org](http://nginx.org/), for example, the version 1.8.0 (see nginx compatibility), and then build the source with this module: 433 | 434 | ```bash 435 | wget 'http://nginx.org/download/nginx-1.8.0.tar.gz' 436 | tar -xzvf nginx-1.8.0.tar.gz 437 | cd nginx-1.8.0/ 438 | ``` 439 | 440 | ```bash 441 | ./configure --add-module=/path/to/nginx-upsync-module 442 | make 443 | make install 444 | ``` 445 | 446 | if you support nginx-upstream-check-module 447 | ```bash 448 | patch -p1 < /path/to/nginx-upstream-check-module/check_1.7.5+.patch 449 | ./configure --add-module=/path/to/nginx-upstream-check-module --add-module=/path/to/nginx-upsync-module 450 | make 451 | make install 452 | ``` 453 | 454 | [Back to TOC](#table-of-contents) 455 | 456 | Code style 457 | ====== 458 | 459 | Code style is mainly based on [style](http://tengine.taobao.org/book/appendix_a.html) 460 | 461 | [Back to TOC](#table-of-contents) 462 | 463 | Author 464 | ====== 465 | 466 | Xiaokai Wang (王晓开) , Weibo Inc. 467 | 468 | [Back to TOC](#table-of-contents) 469 | 470 | Copyright and License 471 | ===================== 472 | 473 | This README template copy from agentzh. 474 | 475 | This module is licensed under the BSD license. 476 | 477 | Copyright (C) 2014 by Xiaokai Wang 478 | 479 | All rights reserved. 480 | 481 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 482 | 483 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 484 | 485 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 486 | 487 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 488 | 489 | [Back to TOC](#table-of-contents) 490 | 491 | see also 492 | ======== 493 | * the nginx_upstream_check_module: https://github.com/alibaba/tengine/blob/master/src/http/ngx_http_upstream_check_module.c 494 | * the nginx_upstream_check_module patch: https://github.com/yaoweibin/nginx_upstream_check_module 495 | * or based on https://github.com/xiaokai-wang/nginx_upstream_check_module 496 | 497 | [back to toc](#table-of-contents) 498 | 499 | source dependency 500 | ======== 501 | * Cjson: https://github.com/kbranigan/cJSON 502 | * http-parser: https://github.com/nodejs/http-parser 503 | 504 | [back to toc](#table-of-contents) 505 | 506 | 507 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_upsync_module 2 | 3 | ngx_feature_libs="-lm" 4 | 5 | ngx_module_incs=$ngx_addon_dir/src 6 | 7 | _HTTP_UPSYNC_SRCS="\ 8 | $ngx_addon_dir/src/ngx_http_upsync_module.c \ 9 | $ngx_addon_dir/src/ngx_http_json.c \ 10 | $ngx_addon_dir/src/ngx_http_parser.c \ 11 | " 12 | 13 | have=NGX_HTTP_UPSYNC . auto/have 14 | 15 | if test -n "$ngx_module_link"; then 16 | ngx_module_type=HTTP 17 | ngx_module_name=$ngx_addon_name 18 | ngx_module_srcs="$_HTTP_UPSYNC_SRCS" 19 | ngx_module_libs=$ngx_feature_libs 20 | . auto/module 21 | else 22 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $_HTTP_UPSYNC_SRCS" 23 | CORE_LIBS="$CORE_LIBS $ngx_feature_libs" 24 | CORE_INCS="$CORE_INCS $ngx_module_incs" 25 | HTTP_MODULES="$HTTP_MODULES $ngx_addon_name" 26 | fi 27 | 28 | -------------------------------------------------------------------------------- /doc/FAQ.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weibocom/nginx-upsync-module/801bc5df2edc3218b9a0adcec2b5e8b52728a217/doc/FAQ.md -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | nginx-upsync-module 2 | ==== 3 | 4 | 目录 5 | ================= 6 | 7 | * [模块背景](#模块背景) 8 | * [方案设计](#方案设计) 9 | * [业界方式](#业界方式) 10 | * [开发方案设计](#开发方案设计) 11 | * [http_api方案](#http_api方案) 12 | * [upsync方案](#upsync方案) 13 | * [方案实现](#方案实现) 14 | * [列表更新方式](#列表更新方式) 15 | * [高可用性](#高可用性) 16 | * [兼容性](#兼容性) 17 | * [性能测试](#性能测试) 18 | * [应用案例](#应用案例) 19 | 20 | 模块背景 21 | ====== 22 | 23 | 针对模块的开发背景,以微博的业务场景为例,每年的元旦、春晚、红包飞会带来巨大的流量挑战,这些业务场景的主要特点是:瞬间峰值高,互动时间短。基本上一次峰值事件,互动时间都会3小时以内,而明星突发新闻事件,红包飞这种业务,经常会遇到高达多倍的瞬间峰值。传统的应对手段,主要是提前申请足够的设备,保证冗余。 24 | 25 | 这么做除了成本高外,对系统进行水平扩容时,耗费的时间久,而且扩容缩容流程繁琐。对于明星的突发事件,流量突增几倍的情况下,进行扩容缩容的繁琐操作便会更加突出,而且这个时候对nginx 进行reload 是有一定危险的,之前的操作表明,在流量比较高时进行nginx 的reload 会出现服务的抖动,造成部分请求耗时增长20%-50%。 26 | 27 | 为了节约成本以及随着云服务的兴起,平台采用了混合云的部署方式,对资源进行弹性调度。对于第三方公有云服务,比如阿里云按照小时计费,所以需要按需部署,为了避免在弹性调度时,对服务造成不必要的抖动,以及自动化扩容缩容操作,开发了nginx-upsync-module,实现nginx 无损的扩容缩容。 28 | 29 | 方案设计 30 | ======== 31 | 32 | 方案介绍了当前业界已经存在的,分析了其相应的优缺点,并自己设计实现了两个方案,并综合对比选择了upsync方式。 33 | 34 | 业界方式 35 | ----------- 36 | 37 | 实现nginx层的弹性扩容、缩容,当前业界已存在的、不需二次开发的有基于consul的consul-template和tengine提供的基于dns的服务发现。 38 | 39 | #####两种方式的分析对比: 40 | 41 | | - | dns | consul-template | 42 | | --- | :---: | :---: | 43 | | 实时性 | 差 | 中 | 44 | | 容错性 | 强 | 强 | 45 | | 一致性 |强 |强 | 46 | | 复杂性 | 易 | 繁 | 47 | 48 | tengine团队开发了自己的模块,该模块可以动态的解析upstream conf下的域名。这种方式操作简单,只要修改dns下挂载的server列表便可;缺点是现在的第一版负载均衡策略待完善;另一点默认解析一次的时间是30s,若配置的时间过短,可能对dns server形成压力;再一点是基于dns的服务,下面不能挂过多的server,否则会发生截断。 49 | 50 | consul-template与consul作为一个组合,consul作为db,consul-template部署于nginx server上,consul-template定时向consul发起请求,发现value值有变化,便会更新本地的nginx相关配置文件,发起reload命令。但是在流量比较重的情况下,发起reload会对性能造成影响。reload的同时会引发新的work进程的创建,在一段时间内新旧work进程会同时存在,并且旧的work进程会频繁的遍历connection链表,查看是否请求已经处理结束,若结束便退出进程;另reload也会造成nginx与client和backend的长链接关闭,新的work进程需要创建新的链接。 51 | 52 | reload造成的性能影响: 53 | 54 | consul-template-reload-qps 55 | ``` 56 | 图示:reload时nginx的请求处理能力会下降(注:nginx对于握手成功的请求不会丢失) 57 | ``` 58 | consul-template-reload-cost 59 | ``` 60 | 图示:reload时耗时会发生波动,波动幅度甚至达50%+ 61 | ``` 62 | 63 | 基于上述的原因,设计并实现了另外两套方案,避免对nginx进行reload。 64 | 65 | 开发方案设计 66 | ----------- 67 | 68 | ###http_api方案 69 | 70 | 此方案提供nginx http api,添加/删除server时,通过调用api向nginx发出请求,操作简单、便利。架构图如下: 71 | 72 | nginx-http-api-arch 73 | 74 | http api除了操作简单、方便,而且实时性好;缺点是分布式一致性难于保证,如果某一条注册失败,便会造成服务配置的不一致,容错复杂;另一个就是如果扩容nginx服务器,需要重新注册server(可参考nginx-upconf-module,正在完善)。 75 | 76 | ###upsync方案 77 | 78 | upsync方式引入了第三方组件,作为nginx的upstream server配置的db,架构图如下: 79 | 80 | nginx-upsync-arch 81 | 82 | 所有的后端server列表存于consul,便于nginx横向扩展,实时拉取,容错性更好,而且可以结合db的KV服务,提高实时性。 83 | 84 | 通过上面的综合对比,选取upsync的方式,开发了nginx模块nginx-upsync-module。对于nginx配置db的选取,由于当前docker技术十分火热,选用了consul,另模块不强依赖于consul,可以横向的扩展支持etcd、zookeeper等。下面的实现基于consul进行介绍。 85 | 86 | 方案实现 87 | ====== 88 | 89 | 基于upsync方式,开发了模块nginx-upsync-module,它的功能是拉取consul的后端server的列表,并更新nginx的路由信息。此模块不依赖于任何第三方模块。 90 | 91 | 列表更新方式 92 | ------------ 93 | 94 | consul 作为nginx的db,利用consul的KV服务,每个nginx work进程独立的去拉取各个upstream的配置,并更新各自的路由。流程图如下: 95 | 96 | ![list_update](https://github.com/weibocom/nginx-upsync-module/raw/master/doc/images/list_update.bmp) 97 | 98 | 每个work进程定时的去consul拉取相应upstream的配置,定时的间隔可配;其中consul提供了time_wait机制,利用value的版本号,若consul发现对应upstream的值没有变化,便会hang住这个请求5分钟(默认),在这五分钟内对此upstream的任何操作,都会立刻返回给nginx,对相应路由进行更新。对于拉取的间隔可以结合场景的需要进行配置,基本可以实现所要求的实时性。upstream变更后,除了更新nginx的缓存路由信息,还会把本upstream的后端server列表dump到本地,保持本地server信息与consul的一致性。 99 | 100 | 除了注册/注销后端的server到consul,会更新到nginx的upstream路由信息外,对后端server属性的修改也会同步到nginx的upstream路由。当前本模块支持修改的属性有weight、max_fails、fail_timeout、down,修改server的权重可以动态的调整后端的流量,若想要临时移除server,可以把server的down属性置为1(当前down的属性暂不支持dump到本地的server列表内),流量便会停止打到该server,若要恢复流量,可重新把down置为0。 101 | 102 | 另外每个work进程各自拉取、更新各自的路由表,采用这种方式的原因:一是基于nginx的进程模型,彼此间数据独立、互不干扰;二是若采用共享内存,需要提前预分配,灵活性可能受限制,而且还需要读写锁,对性能可能存在潜在的影响;三是若采用共享内存,进程间协调去拉取配置,会增加它的复杂性,拉取的稳定性也会受到影响。基于这些原因,便采用了各自拉取的方式。 103 | 104 | 高可用性 105 | ------------ 106 | 107 | nginx的后端列表更新依赖于consul,但是不强依赖于它,表现在:一是即使中途consul意外挂了,也不会影响nginx的服务,nginx会沿用最后一次更新的服务列表继续提供服务;二是若consul重新启动提供服务,这个时候nginx会继续去consul探测,这个时候consul的后端服务列表发生了变化,也会及时的更新到nginx。 108 | 109 | 另一方面,work进程每次更新都会把后端列表dump到本地,目的是降低对consul的依赖性,即使在consul不可用之时,也可以reload nginx。nginx 启动流程图如下: 110 | 111 | ![start_fllow](https://github.com/weibocom/nginx-upsync-module/raw/master/doc/images/start_fllow.bmp) 112 | 113 | nginx启动时,master进程首先会解析本地的配置文件,解析完成功,接着进行一系列的初始化,之后便会开始work进程的初始化。work初始化时会去consul拉取配置,进行work进程upstream路由信息的更新,若拉取成功,便直接更新,若拉取失败,便会打开配置的dump后端列表的文件,提取之前dump下来的server信息,进行upstream路由的更新,之后便开始正常的提供服务。 114 | 115 | 每次去拉取consul都会设置连接超时,由于consul在无更新的情况下默认会hang五分钟,所以响应超时配置时间应大于五分钟。大于五分钟之后,consul依旧没有返回,便直接做超时处理。 116 | 117 | 兼容性 118 | ------------ 119 | 120 | 整体上讲本模块只是更新后端的upstream路由信息,不嵌入其它模块,同时也不影响其它模块的功能,亦不会影响nginx-1.9.9的几种负载均衡算法:least_conn、hash_ip等。 121 | 122 | 除此之外,模块天然支持健康监测模块,若nginx编译时包含了监测模块,会同时调用健康监测模块的接口,时时更新健康监测模块的路由表。 123 | 124 | 性能测试 125 | ====== 126 | 127 | nginx-upsync-module模块,潜在的带来额外的性能开销,比如间隔性的向consul发送请求,由于间隔比较久,且每个请求相当于nginx的一个客户端请求,所以影响有限。基于此,在相同的硬件环境下,使用此模块和不使用此模块简单做了性能对比。 128 | 129 | #####基本环境: 130 | 131 | ``` 132 | 硬件环境:Intel(R) Xeon(R) CPU E5645 @ 2.40GHz 12 核 133 | 系统环境:centos6.5; 134 | work进程数:8个; 135 | 压测工具:wrk; 136 | 压测命令:./wrk -t8 -c100 -d5m --timeout 3s http://$ip:8888/proxy_test 137 | ``` 138 | 139 | #####压测数据: 140 | 141 | | - | 总请求 | qps | 142 | | --- | --- | --- | 143 | | nginx(official) | 14802527 | 49521 | 144 | | nginx(upsync) | 14789843 | 48916 | 145 | 146 | 其中nginx(official)是官方nginx,不执行reload下的测试数据;nginx(upsync)是基于upsync模块,每隔10s钟向consul注册/注销一台机器的数据;从数据可以看出,通过upsync模块做扩缩容操作,对性能的影响有限,可以忽略不计。 147 | 148 | 应用案例 149 | ====== 150 | 151 | 本模块首先应用于平台的remind业务,qps量约为7000+左右。下面是对本业务灰度的基本数据: 152 | 153 | #####请求量变化: 154 | 155 | | - | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 156 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | 157 | | reload | 7723 | 7583 | 7833 | 7680 | 7809 | 7682 | 6924 | 7081 | 7207 | 7232 | 7486 | 7571 | 7465 | 158 | | upsync | 7782 | 7705| 7772 | 7810 | 7899 | 7978 | 7858 | 7934 | 7994 | 7731 | 7824 | 7648 | 7888 | 159 | 160 | upsync-vs-reload-qps 161 | 162 | #####平均耗时变化: 163 | 164 | | - | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 165 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | 166 | | reload | 12.102 | 15.108 | 11.443 | 9.426 | 10.178 | 10.605 | 15.253 | 14.315 | 14.762 | 8.392 | 14.385 | 32.335 | 15.277 | 167 | | upsync | 9.586 | 11.963 | 8.694 | 9.676 | 10.616 | 10.335 | 9.766 | 9.406 | 8.943 | 10.971 | 8.080 | 9.185 | 12.055 | 168 | 169 | upsync-vs-reload-cost 170 | 171 | 从数据可以得出,reload操作时造成nginx的请求处理能力下降约10%,nginx本身的耗时会增长50%+。若是频繁的扩容缩容,reload操作造成的开销会更加明显。 172 | 173 | 平台为了应对元旦期间的流量峰值,基于本平台的dcp系统,于元旦晚上批量部署阿里云实例,应用此模块进行了百余次的扩容、缩容操作,服务稳定,没有出现服务的波动。另本模块可以应用于对资源的弹性调度系统内,同时可以应用于临时流量突增的场景。 174 | 175 | #####参考附录: 176 | [1] http://tengine.taobao.org/document_cn/http_upstream_dynamic_cn.html; 177 | 178 | [2] https://www.hashicorp.com/blog/introducing-consul-template.html; 179 | 180 | [3] https://www.nginx.com/blog/dynamic-reconfiguration-with-nginx-plus; 181 | 182 | [4] https://github.com/alibaba/tengine/issues/595; 183 | 184 | [5] https://github.com/xiaokai-wang/nginx-upconf-module; 185 | -------------------------------------------------------------------------------- /doc/images/consul-template-reload-cost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weibocom/nginx-upsync-module/801bc5df2edc3218b9a0adcec2b5e8b52728a217/doc/images/consul-template-reload-cost.png -------------------------------------------------------------------------------- /doc/images/consul-template-reload-qps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weibocom/nginx-upsync-module/801bc5df2edc3218b9a0adcec2b5e8b52728a217/doc/images/consul-template-reload-qps.png -------------------------------------------------------------------------------- /doc/images/list_update.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weibocom/nginx-upsync-module/801bc5df2edc3218b9a0adcec2b5e8b52728a217/doc/images/list_update.bmp -------------------------------------------------------------------------------- /doc/images/nginx-http-api-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weibocom/nginx-upsync-module/801bc5df2edc3218b9a0adcec2b5e8b52728a217/doc/images/nginx-http-api-arch.png -------------------------------------------------------------------------------- /doc/images/nginx-upsync-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weibocom/nginx-upsync-module/801bc5df2edc3218b9a0adcec2b5e8b52728a217/doc/images/nginx-upsync-arch.png -------------------------------------------------------------------------------- /doc/images/start_fllow.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weibocom/nginx-upsync-module/801bc5df2edc3218b9a0adcec2b5e8b52728a217/doc/images/start_fllow.bmp -------------------------------------------------------------------------------- /doc/images/upsync-vs-reload-cost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weibocom/nginx-upsync-module/801bc5df2edc3218b9a0adcec2b5e8b52728a217/doc/images/upsync-vs-reload-cost.png -------------------------------------------------------------------------------- /doc/images/upsync-vs-reload-qps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weibocom/nginx-upsync-module/801bc5df2edc3218b9a0adcec2b5e8b52728a217/doc/images/upsync-vs-reload-qps.png -------------------------------------------------------------------------------- /src/ngx_http_json.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009 Dave Gamble 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | /* cJSON */ 24 | /* JSON parser in C. */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include "ngx_http_json.h" 34 | 35 | static const char *ep; 36 | 37 | const char *cJSON_GetErrorPtr(void) {return ep;} 38 | 39 | static int cJSON_strcasecmp(const char *s1,const char *s2) 40 | { 41 | if (!s1) return (s1==s2)?0:1; 42 | if (!s2) return 1; 43 | for(; tolower(*s1) == tolower(*s2); ++s1, ++s2) if(*s1 == 0) return 0; 44 | return tolower(*(const unsigned char *)s1) - tolower(*(const unsigned char *)s2); 45 | } 46 | 47 | static void *(*cJSON_malloc)(size_t sz) = malloc; 48 | static void (*cJSON_free)(void *ptr) = free; 49 | 50 | static char* cJSON_strdup(const char* str) 51 | { 52 | size_t len; 53 | char* copy; 54 | 55 | len = strlen(str) + 1; 56 | if (!(copy = (char*)cJSON_malloc(len))) return 0; 57 | memcpy(copy,str,len); 58 | return copy; 59 | } 60 | 61 | void cJSON_InitHooks(cJSON_Hooks* hooks) 62 | { 63 | if (!hooks) { /* Reset hooks */ 64 | cJSON_malloc = malloc; 65 | cJSON_free = free; 66 | return; 67 | } 68 | 69 | cJSON_malloc = (hooks->malloc_fn)?hooks->malloc_fn:malloc; 70 | cJSON_free = (hooks->free_fn)?hooks->free_fn:free; 71 | } 72 | 73 | /* Internal constructor. */ 74 | static cJSON *cJSON_New_Item(void) 75 | { 76 | cJSON* node = (cJSON*)cJSON_malloc(sizeof(cJSON)); 77 | if (node) memset(node,0,sizeof(cJSON)); 78 | return node; 79 | } 80 | 81 | /* Delete a cJSON structure. */ 82 | void cJSON_Delete(cJSON *c) 83 | { 84 | cJSON *next; 85 | while (c) 86 | { 87 | next=c->next; 88 | if (!(c->type&cJSON_IsReference) && c->child) cJSON_Delete(c->child); 89 | if (!(c->type&cJSON_IsReference) && c->valuestring) cJSON_free(c->valuestring); 90 | if (c->string) cJSON_free(c->string); 91 | cJSON_free(c); 92 | c=next; 93 | } 94 | } 95 | 96 | /* Parse the input text to generate a number, and populate the result into item. */ 97 | static const char *parse_number(cJSON *item,const char *num) 98 | { 99 | double n=0,sign=1,scale=0;int subscale=0,signsubscale=1; 100 | 101 | /* Could use sscanf for this? */ 102 | if (*num=='-') sign=-1,num++; /* Has sign? */ 103 | if (*num=='0') num++; /* is zero */ 104 | if (*num>='1' && *num<='9') do n=(n*10.0)+(*num++ -'0'); while (*num>='0' && *num<='9'); /* Number? */ 105 | if (*num=='.' && num[1]>='0' && num[1]<='9') {num++; do n=(n*10.0)+(*num++ -'0'),scale--; while (*num>='0' && *num<='9');} /* Fractional part? */ 106 | if (*num=='e' || *num=='E') /* Exponent? */ 107 | { num++;if (*num=='+') num++; else if (*num=='-') signsubscale=-1,num++; /* With sign? */ 108 | while (*num>='0' && *num<='9') subscale=(subscale*10)+(*num++ - '0'); /* Number? */ 109 | } 110 | 111 | n=sign*n*pow(10.0,(scale+subscale*signsubscale)); /* number = +/- number.fraction * 10^+/- exponent */ 112 | 113 | item->valuedouble=n; 114 | item->valueint=(int)n; 115 | item->type=cJSON_Number; 116 | return num; 117 | } 118 | 119 | /* Render the number nicely from the given item into a string. */ 120 | static char *print_number(cJSON *item) 121 | { 122 | char *str; 123 | double d=item->valuedouble; 124 | if (fabs(((double)item->valueint)-d)<=DBL_EPSILON && d<=INT_MAX && d>=INT_MIN) 125 | { 126 | str=(char*)cJSON_malloc(21); /* 2^64+1 can be represented in 21 chars. */ 127 | if (str) sprintf(str,"%d",item->valueint); 128 | } 129 | else 130 | { 131 | str=(char*)cJSON_malloc(64); /* This is a nice tradeoff. */ 132 | if (str) 133 | { 134 | if (fabs(floor(d)-d)<=DBL_EPSILON && fabs(d)<1.0e60)sprintf(str,"%.0f",d); 135 | else if (fabs(d)<1.0e-6 || fabs(d)>1.0e9) sprintf(str,"%e",d); 136 | else sprintf(str,"%f",d); 137 | } 138 | } 139 | return str; 140 | } 141 | 142 | /* Parse the input text into an unescaped cstring, and populate item. */ 143 | static const unsigned char firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; 144 | static const char *parse_string(cJSON *item,const char *str) 145 | { 146 | const char *ptr=str+1;char *ptr2;char *out;int len=0;unsigned uc,uc2; 147 | if (*str!='\"') {ep=str;return 0;} /* not a string! */ 148 | 149 | while (*ptr!='\"' && *ptr && ++len) if (*ptr++ == '\\') ptr++; /* Skip escaped quotes. */ 150 | 151 | out=(char*)cJSON_malloc(len+1); /* This is how long we need for the string, roughly. */ 152 | if (!out) return 0; 153 | 154 | ptr=str+1;ptr2=out; 155 | while (*ptr!='\"' && *ptr) 156 | { 157 | if (*ptr!='\\') *ptr2++=*ptr++; 158 | else 159 | { 160 | ptr++; 161 | switch (*ptr) 162 | { 163 | case 'b': *ptr2++='\b'; break; 164 | case 'f': *ptr2++='\f'; break; 165 | case 'n': *ptr2++='\n'; break; 166 | case 'r': *ptr2++='\r'; break; 167 | case 't': *ptr2++='\t'; break; 168 | case 'u': /* transcode utf16 to utf8. */ 169 | sscanf(ptr+1,"%4x",&uc);ptr+=4; /* get the unicode char. */ 170 | 171 | if ((uc>=0xDC00 && uc<=0xDFFF) || uc==0) break; /* check for invalid. */ 172 | 173 | if (uc>=0xD800 && uc<=0xDBFF) /* UTF16 surrogate pairs. */ 174 | { 175 | if (ptr[1]!='\\' || ptr[2]!='u') break; /* missing second-half of surrogate. */ 176 | sscanf(ptr+3,"%4x",&uc2);ptr+=6; 177 | if (uc2<0xDC00 || uc2>0xDFFF) break; /* invalid second-half of surrogate. */ 178 | uc=0x10000 + (((uc&0x3FF)<<10) | (uc2&0x3FF)); 179 | } 180 | 181 | len=4;if (uc<0x80) len=1;else if (uc<0x800) len=2;else if (uc<0x10000) len=3; ptr2+=len; 182 | 183 | switch (len) { 184 | case 4: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; /* FALLTHRU */ 185 | case 3: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; /* FALLTHRU */ 186 | case 2: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; /* FALLTHRU */ 187 | case 1: *--ptr2 =(uc | firstByteMark[len]); 188 | } 189 | ptr2+=len; 190 | break; 191 | default: *ptr2++=*ptr; break; 192 | } 193 | ptr++; 194 | } 195 | } 196 | *ptr2=0; 197 | if (*ptr=='\"') ptr++; 198 | item->valuestring=out; 199 | item->type=cJSON_String; 200 | return ptr; 201 | } 202 | 203 | /* Render the cstring provided to an escaped version that can be printed. */ 204 | static char *print_string_ptr(const char *str) 205 | { 206 | const char *ptr;char *ptr2,*out;int len=0;unsigned char token; 207 | 208 | if (!str) return cJSON_strdup(""); 209 | ptr=str;while ((token=*ptr) && ++len) {if (strchr("\"\\\b\f\n\r\t",token)) len++; else if (token<32) len+=5;ptr++;} 210 | 211 | out=(char*)cJSON_malloc(len+3); 212 | if (!out) return 0; 213 | 214 | ptr2=out;ptr=str; 215 | *ptr2++='\"'; 216 | while (*ptr) 217 | { 218 | if ((unsigned char)*ptr>31 && *ptr!='\"' && *ptr!='\\') *ptr2++=*ptr++; 219 | else 220 | { 221 | *ptr2++='\\'; 222 | switch (token=*ptr++) 223 | { 224 | case '\\': *ptr2++='\\'; break; 225 | case '\"': *ptr2++='\"'; break; 226 | case '\b': *ptr2++='b'; break; 227 | case '\f': *ptr2++='f'; break; 228 | case '\n': *ptr2++='n'; break; 229 | case '\r': *ptr2++='r'; break; 230 | case '\t': *ptr2++='t'; break; 231 | default: sprintf(ptr2,"u%04x",token);ptr2+=5; break; /* escape and print */ 232 | } 233 | } 234 | } 235 | *ptr2++='\"';*ptr2++=0; 236 | return out; 237 | } 238 | /* Invote print_string_ptr (which is useful) on an item. */ 239 | static char *print_string(cJSON *item) {return print_string_ptr(item->valuestring);} 240 | 241 | /* Predeclare these prototypes. */ 242 | static const char *parse_value(cJSON *item,const char *value); 243 | static char *print_value(cJSON *item,int depth,int fmt); 244 | static const char *parse_array(cJSON *item,const char *value); 245 | static char *print_array(cJSON *item,int depth,int fmt); 246 | static const char *parse_object(cJSON *item,const char *value); 247 | static char *print_object(cJSON *item,int depth,int fmt); 248 | 249 | /* Utility to jump whitespace and cr/lf */ 250 | static const char *skip(const char *in) {while (in && *in && (unsigned char)*in<=32) in++; return in;} 251 | 252 | /* Parse an object - create a new root, and populate. */ 253 | cJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int require_null_terminated) 254 | { 255 | const char *end=0; 256 | cJSON *c=cJSON_New_Item(); 257 | ep=0; 258 | if (!c) return 0; /* memory fail */ 259 | 260 | end=parse_value(c,skip(value)); 261 | if (!end) {cJSON_Delete(c);return 0;} /* parse failure. ep is set. */ 262 | 263 | /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ 264 | if (require_null_terminated) {end=skip(end);if (*end) {cJSON_Delete(c);ep=end;return 0;}} 265 | if (return_parse_end) *return_parse_end=end; 266 | return c; 267 | } 268 | /* Default options for cJSON_Parse */ 269 | cJSON *cJSON_Parse(const char *value) {return cJSON_ParseWithOpts(value,0,0);} 270 | 271 | /* Render a cJSON item/entity/structure to text. */ 272 | char *cJSON_Print(cJSON *item) {return print_value(item,0,1);} 273 | char *cJSON_PrintUnformatted(cJSON *item) {return print_value(item,0,0);} 274 | 275 | /* Parser core - when encountering text, process appropriately. */ 276 | static const char *parse_value(cJSON *item,const char *value) 277 | { 278 | if (!value) return 0; /* Fail on null. */ 279 | if (!strncmp(value,"null",4)) { item->type=cJSON_NULL; return value+4; } 280 | if (!strncmp(value,"false",5)) { item->type=cJSON_False; return value+5; } 281 | if (!strncmp(value,"true",4)) { item->type=cJSON_True; item->valueint=1; return value+4; } 282 | if (*value=='\"') { return parse_string(item,value); } 283 | if (*value=='-' || (*value>='0' && *value<='9')) { return parse_number(item,value); } 284 | if (*value=='[') { return parse_array(item,value); } 285 | if (*value=='{') { return parse_object(item,value); } 286 | 287 | ep=value;return 0; /* failure. */ 288 | } 289 | 290 | /* Render a value to text. */ 291 | static char *print_value(cJSON *item,int depth,int fmt) 292 | { 293 | char *out=0; 294 | if (!item) return 0; 295 | switch ((item->type)&255) 296 | { 297 | case cJSON_NULL: out=cJSON_strdup("null"); break; 298 | case cJSON_False: out=cJSON_strdup("false");break; 299 | case cJSON_True: out=cJSON_strdup("true"); break; 300 | case cJSON_Number: out=print_number(item);break; 301 | case cJSON_String: out=print_string(item);break; 302 | case cJSON_Array: out=print_array(item,depth,fmt);break; 303 | case cJSON_Object: out=print_object(item,depth,fmt);break; 304 | } 305 | return out; 306 | } 307 | 308 | /* Build an array from input text. */ 309 | static const char *parse_array(cJSON *item,const char *value) 310 | { 311 | cJSON *child; 312 | if (*value!='[') {ep=value;return 0;} /* not an array! */ 313 | 314 | item->type=cJSON_Array; 315 | value=skip(value+1); 316 | if (*value==']') return value+1; /* empty array. */ 317 | 318 | item->child=child=cJSON_New_Item(); 319 | if (!item->child) return 0; /* memory fail */ 320 | value=skip(parse_value(child,skip(value))); /* skip any spacing, get the value. */ 321 | if (!value) return 0; 322 | 323 | while (*value==',') 324 | { 325 | cJSON *new_item; 326 | if (!(new_item=cJSON_New_Item())) return 0; /* memory fail */ 327 | child->next=new_item;new_item->prev=child;child=new_item; 328 | value=skip(parse_value(child,skip(value+1))); 329 | if (!value) return 0; /* memory fail */ 330 | } 331 | 332 | if (*value==']') return value+1; /* end of array */ 333 | ep=value;return 0; /* malformed. */ 334 | } 335 | 336 | /* Render an array to text */ 337 | static char *print_array(cJSON *item,int depth,int fmt) 338 | { 339 | char **entries; 340 | char *out=0,*ptr,*ret;int len=5; 341 | cJSON *child=item->child; 342 | int numentries=0,i=0,fail=0; 343 | 344 | /* How many entries in the array? */ 345 | while (child) numentries++,child=child->next; 346 | /* Explicitly handle numentries==0 */ 347 | if (!numentries) 348 | { 349 | out=(char*)cJSON_malloc(3); 350 | if (out) strcpy(out,"[]"); 351 | return out; 352 | } 353 | /* Allocate an array to hold the values for each */ 354 | entries=(char**)cJSON_malloc(numentries*sizeof(char*)); 355 | if (!entries) return 0; 356 | memset(entries,0,numentries*sizeof(char*)); 357 | /* Retrieve all the results: */ 358 | child=item->child; 359 | while (child && !fail) 360 | { 361 | ret=print_value(child,depth+1,fmt); 362 | entries[i++]=ret; 363 | if (ret) len+=strlen(ret)+2+(fmt?1:0); else fail=1; 364 | child=child->next; 365 | } 366 | 367 | /* If we didn't fail, try to malloc the output string */ 368 | if (!fail) out=(char*)cJSON_malloc(len); 369 | /* If that fails, we fail. */ 370 | if (!out) fail=1; 371 | 372 | /* Handle failure. */ 373 | if (fail) 374 | { 375 | for (i=0;itype=cJSON_Object; 401 | value=skip(value+1); 402 | if (*value=='}') return value+1; /* empty array. */ 403 | 404 | item->child=child=cJSON_New_Item(); 405 | if (!item->child) return 0; 406 | value=skip(parse_string(child,skip(value))); 407 | if (!value) return 0; 408 | child->string=child->valuestring;child->valuestring=0; 409 | if (*value!=':') {ep=value;return 0;} /* fail! */ 410 | value=skip(parse_value(child,skip(value+1))); /* skip any spacing, get the value. */ 411 | if (!value) return 0; 412 | 413 | while (*value==',') 414 | { 415 | cJSON *new_item; 416 | if (!(new_item=cJSON_New_Item())) return 0; /* memory fail */ 417 | child->next=new_item;new_item->prev=child;child=new_item; 418 | value=skip(parse_string(child,skip(value+1))); 419 | if (!value) return 0; 420 | child->string=child->valuestring;child->valuestring=0; 421 | if (*value!=':') {ep=value;return 0;} /* fail! */ 422 | value=skip(parse_value(child,skip(value+1))); /* skip any spacing, get the value. */ 423 | if (!value) return 0; 424 | } 425 | 426 | if (*value=='}') return value+1; /* end of array */ 427 | ep=value;return 0; /* malformed. */ 428 | } 429 | 430 | /* Render an object to text. */ 431 | static char *print_object(cJSON *item,int depth,int fmt) 432 | { 433 | char **entries=0,**names=0; 434 | char *out=0,*ptr,*ret,*str;int len=7,i=0,j; 435 | cJSON *child=item->child; 436 | int numentries=0,fail=0; 437 | /* Count the number of entries. */ 438 | while (child) numentries++,child=child->next; 439 | /* Explicitly handle empty object case */ 440 | if (!numentries) 441 | { 442 | out=(char*)cJSON_malloc(fmt?depth+3:3); 443 | if (!out) return 0; 444 | ptr=out;*ptr++='{'; 445 | if (fmt) {*ptr++='\n';for (i=0;ichild;depth++;if (fmt) len+=depth; 459 | while (child) 460 | { 461 | names[i]=str=print_string_ptr(child->string); 462 | entries[i++]=ret=print_value(child,depth,fmt); 463 | if (str && ret) len+=strlen(ret)+strlen(str)+2+(fmt?2+depth:0); else fail=1; 464 | child=child->next; 465 | } 466 | 467 | /* Try to allocate the output string */ 468 | if (!fail) out=(char*)cJSON_malloc(len); 469 | if (!out) fail=1; 470 | 471 | /* Handle failure */ 472 | if (fail) 473 | { 474 | for (i=0;ichild;int i=0;while(c)i++,c=c->next;return i;} 501 | cJSON *cJSON_GetArrayItem(cJSON *array,int item) {cJSON *c=array->child; while (c && item>0) item--,c=c->next; return c;} 502 | cJSON *cJSON_GetObjectItem(cJSON *object,const char *string) {cJSON *c=object->child; while (c && cJSON_strcasecmp(c->string,string)) c=c->next; return c;} 503 | 504 | /* Utility for array list handling. */ 505 | static void suffix_object(cJSON *prev,cJSON *item) {prev->next=item;item->prev=prev;} 506 | /* Utility for handling references. */ 507 | static cJSON *create_reference(cJSON *item) {cJSON *ref=cJSON_New_Item();if (!ref) return 0;memcpy(ref,item,sizeof(cJSON));ref->string=0;ref->type|=cJSON_IsReference;ref->next=ref->prev=0;return ref;} 508 | 509 | /* Add item to array/object. */ 510 | void cJSON_AddItemToArray(cJSON *array, cJSON *item) {cJSON *c=array->child;if (!item) return; if (!c) {array->child=item;} else {while (c && c->next) c=c->next; suffix_object(c,item);}} 511 | void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item) {if (!item) return; if (item->string) cJSON_free(item->string);item->string=cJSON_strdup(string);cJSON_AddItemToArray(object,item);} 512 | void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) {cJSON_AddItemToArray(array,create_reference(item));} 513 | void cJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item) {cJSON_AddItemToObject(object,string,create_reference(item));} 514 | 515 | cJSON *cJSON_DetachItemFromArray(cJSON *array,int which) {cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) return 0; 516 | if (c->prev) c->prev->next=c->next; 517 | if (c->next) c->next->prev=c->prev; 518 | if (c==array->child) array->child=c->next; 519 | c->prev=c->next=0;return c;} 520 | void cJSON_DeleteItemFromArray(cJSON *array,int which) {cJSON_Delete(cJSON_DetachItemFromArray(array,which));} 521 | cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string) {int i=0;cJSON *c=object->child;while (c && cJSON_strcasecmp(c->string,string)) i++,c=c->next;if (c) return cJSON_DetachItemFromArray(object,i);return 0;} 522 | void cJSON_DeleteItemFromObject(cJSON *object,const char *string) {cJSON_Delete(cJSON_DetachItemFromObject(object,string));} 523 | 524 | /* Replace array/object items with new ones. */ 525 | void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem) {cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) return; 526 | newitem->next=c->next;newitem->prev=c->prev;if (newitem->next) newitem->next->prev=newitem; 527 | if (c==array->child) array->child=newitem; else newitem->prev->next=newitem;c->next=c->prev=0;cJSON_Delete(c);} 528 | void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem){int i=0;cJSON *c=object->child;while(c && cJSON_strcasecmp(c->string,string))i++,c=c->next;if(c){newitem->string=cJSON_strdup(string);cJSON_ReplaceItemInArray(object,i,newitem);}} 529 | 530 | /* Create basic types: */ 531 | cJSON *cJSON_CreateNull(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_NULL;return item;} 532 | cJSON *cJSON_CreateTrue(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_True;return item;} 533 | cJSON *cJSON_CreateFalse(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_False;return item;} 534 | cJSON *cJSON_CreateBool(int b) {cJSON *item=cJSON_New_Item();if(item)item->type=b?cJSON_True:cJSON_False;return item;} 535 | cJSON *cJSON_CreateNumber(double num) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_Number;item->valuedouble=num;item->valueint=(int)num;}return item;} 536 | cJSON *cJSON_CreateString(const char *string) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_String;item->valuestring=cJSON_strdup(string);}return item;} 537 | cJSON *cJSON_CreateArray(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_Array;return item;} 538 | cJSON *cJSON_CreateObject(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_Object;return item;} 539 | 540 | /* Create Arrays: */ 541 | cJSON *cJSON_CreateIntArray(int *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} 542 | cJSON *cJSON_CreateFloatArray(float *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} 543 | cJSON *cJSON_CreateDoubleArray(double *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} 544 | cJSON *cJSON_CreateStringArray(const char **strings,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} 545 | 546 | /* Duplication */ 547 | cJSON *cJSON_Duplicate(cJSON *item,int recurse) 548 | { 549 | cJSON *newitem,*cptr,*nptr=0,*newchild; 550 | /* Bail on bad ptr */ 551 | if (!item) return 0; 552 | /* Create new item */ 553 | newitem=cJSON_New_Item(); 554 | if (!newitem) return 0; 555 | /* Copy over all vars */ 556 | newitem->type=item->type&(~cJSON_IsReference),newitem->valueint=item->valueint,newitem->valuedouble=item->valuedouble; 557 | if (item->valuestring) {newitem->valuestring=cJSON_strdup(item->valuestring); if (!newitem->valuestring) {cJSON_Delete(newitem);return 0;}} 558 | if (item->string) {newitem->string=cJSON_strdup(item->string); if (!newitem->string) {cJSON_Delete(newitem);return 0;}} 559 | /* If non-recursive, then we're done! */ 560 | if (!recurse) return newitem; 561 | /* Walk the ->next chain for the child. */ 562 | cptr=item->child; 563 | while (cptr) 564 | { 565 | newchild=cJSON_Duplicate(cptr,1); /* Duplicate (with recurse) each item in the ->next chain */ 566 | if (!newchild) {cJSON_Delete(newitem);return 0;} 567 | if (nptr) {nptr->next=newchild,newchild->prev=nptr;nptr=newchild;} /* If newitem->child already set, then crosswire ->prev and ->next and move on */ 568 | else {newitem->child=newchild;nptr=newchild;} /* Set newitem->child and move to it */ 569 | cptr=cptr->next; 570 | } 571 | return newitem; 572 | } 573 | -------------------------------------------------------------------------------- /src/ngx_http_json.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009 Dave Gamble 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #ifndef cJSON__h 24 | #define cJSON__h 25 | 26 | #ifdef __cplusplus 27 | extern "C" 28 | { 29 | #endif 30 | 31 | /* cJSON Types: */ 32 | #define cJSON_False 0 33 | #define cJSON_True 1 34 | #define cJSON_NULL 2 35 | #define cJSON_Number 3 36 | #define cJSON_String 4 37 | #define cJSON_Array 5 38 | #define cJSON_Object 6 39 | 40 | #define cJSON_IsReference 256 41 | 42 | /* The cJSON structure: */ 43 | typedef struct cJSON { 44 | struct cJSON *next,*prev; /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ 45 | struct cJSON *child; /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ 46 | 47 | int type; /* The type of the item, as above. */ 48 | 49 | char *valuestring; /* The item's string, if type==cJSON_String */ 50 | int valueint; /* The item's number, if type==cJSON_Number */ 51 | double valuedouble; /* The item's number, if type==cJSON_Number */ 52 | 53 | char *string; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ 54 | } cJSON; 55 | 56 | typedef struct cJSON_Hooks { 57 | void *(*malloc_fn)(size_t sz); 58 | void (*free_fn)(void *ptr); 59 | } cJSON_Hooks; 60 | 61 | /* Supply malloc, realloc and free functions to cJSON */ 62 | extern void cJSON_InitHooks(cJSON_Hooks* hooks); 63 | 64 | 65 | /* Supply a block of JSON, and this returns a cJSON object you can interrogate. Call cJSON_Delete when finished. */ 66 | extern cJSON *cJSON_Parse(const char *value); 67 | /* Render a cJSON entity to text for transfer/storage. Free the char* when finished. */ 68 | extern char *cJSON_Print(cJSON *item); 69 | /* Render a cJSON entity to text for transfer/storage without any formatting. Free the char* when finished. */ 70 | extern char *cJSON_PrintUnformatted(cJSON *item); 71 | /* Delete a cJSON entity and all subentities. */ 72 | extern void cJSON_Delete(cJSON *c); 73 | 74 | /* Returns the number of items in an array (or object). */ 75 | extern int cJSON_GetArraySize(cJSON *array); 76 | /* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */ 77 | extern cJSON *cJSON_GetArrayItem(cJSON *array,int item); 78 | /* Get item "string" from object. Case insensitive. */ 79 | extern cJSON *cJSON_GetObjectItem(cJSON *object,const char *string); 80 | 81 | /* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ 82 | extern const char *cJSON_GetErrorPtr(void); 83 | 84 | /* These calls create a cJSON item of the appropriate type. */ 85 | extern cJSON *cJSON_CreateNull(void); 86 | extern cJSON *cJSON_CreateTrue(void); 87 | extern cJSON *cJSON_CreateFalse(void); 88 | extern cJSON *cJSON_CreateBool(int b); 89 | extern cJSON *cJSON_CreateNumber(double num); 90 | extern cJSON *cJSON_CreateString(const char *string); 91 | extern cJSON *cJSON_CreateArray(void); 92 | extern cJSON *cJSON_CreateObject(void); 93 | 94 | /* These utilities create an Array of count items. */ 95 | extern cJSON *cJSON_CreateIntArray(int *numbers,int count); 96 | extern cJSON *cJSON_CreateFloatArray(float *numbers,int count); 97 | extern cJSON *cJSON_CreateDoubleArray(double *numbers,int count); 98 | extern cJSON *cJSON_CreateStringArray(const char **strings,int count); 99 | 100 | /* Append item to the specified array/object. */ 101 | extern void cJSON_AddItemToArray(cJSON *array, cJSON *item); 102 | extern void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item); 103 | /* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ 104 | extern void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); 105 | extern void cJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item); 106 | 107 | /* Remove/Detatch items from Arrays/Objects. */ 108 | extern cJSON *cJSON_DetachItemFromArray(cJSON *array,int which); 109 | extern void cJSON_DeleteItemFromArray(cJSON *array,int which); 110 | extern cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string); 111 | extern void cJSON_DeleteItemFromObject(cJSON *object,const char *string); 112 | 113 | /* Update array items. */ 114 | extern void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem); 115 | extern void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); 116 | 117 | /* Duplicate a cJSON item */ 118 | extern cJSON *cJSON_Duplicate(cJSON *item,int recurse); 119 | /* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will 120 | need to be released. With recurse!=0, it will duplicate any children connected to the item. 121 | The item->next and ->prev pointers are always zero on return from Duplicate. */ 122 | 123 | /* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ 124 | extern cJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int require_null_terminated); 125 | 126 | /* Macros for creating things quickly. */ 127 | #define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull()) 128 | #define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue()) 129 | #define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse()) 130 | #define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b)) 131 | #define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n)) 132 | #define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s)) 133 | 134 | /* When assigning an integer value, it needs to be propagated to valuedouble too. */ 135 | #define cJSON_SetIntValue(object,val) ((object)?(object)->valueint=(object)->valuedouble=(val):(val)) 136 | 137 | #ifdef __cplusplus 138 | } 139 | #endif 140 | 141 | #endif 142 | -------------------------------------------------------------------------------- /src/ngx_http_parser.h: -------------------------------------------------------------------------------- 1 | /* Copyright Joyent, Inc. and other Node contributors. All rights reserved. 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to 5 | * deal in the Software without restriction, including without limitation the 6 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | * sell copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | * IN THE SOFTWARE. 20 | */ 21 | #ifndef http_parser_h 22 | #define http_parser_h 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | /* Also update SONAME in the Makefile whenever you change these. */ 28 | #define HTTP_PARSER_VERSION_MAJOR 2 29 | #define HTTP_PARSER_VERSION_MINOR 4 30 | #define HTTP_PARSER_VERSION_PATCH 2 31 | 32 | #include 33 | #if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600) 34 | #include 35 | #include 36 | typedef __int8 int8_t; 37 | typedef unsigned __int8 uint8_t; 38 | typedef __int16 int16_t; 39 | typedef unsigned __int16 uint16_t; 40 | typedef __int32 int32_t; 41 | typedef unsigned __int32 uint32_t; 42 | typedef __int64 int64_t; 43 | typedef unsigned __int64 uint64_t; 44 | #else 45 | #include 46 | #endif 47 | 48 | /* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run 49 | * faster 50 | */ 51 | #ifndef HTTP_PARSER_STRICT 52 | # define HTTP_PARSER_STRICT 1 53 | #endif 54 | 55 | /* Maximium header size allowed. If the macro is not defined 56 | * before including this header then the default is used. To 57 | * change the maximum header size, define the macro in the build 58 | * environment (e.g. -DHTTP_MAX_HEADER_SIZE=). To remove 59 | * the effective limit on the size of the header, define the macro 60 | * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff) 61 | */ 62 | #ifndef HTTP_MAX_HEADER_SIZE 63 | # define HTTP_MAX_HEADER_SIZE (80*1024) 64 | #endif 65 | 66 | typedef struct http_parser http_parser; 67 | typedef struct http_parser_settings http_parser_settings; 68 | 69 | 70 | /* Callbacks should return non-zero to indicate an error. The parser will 71 | * then halt execution. 72 | * 73 | * The one exception is on_headers_complete. In a HTTP_RESPONSE parser 74 | * returning '1' from on_headers_complete will tell the parser that it 75 | * should not expect a body. This is used when receiving a response to a 76 | * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: 77 | * chunked' headers that indicate the presence of a body. 78 | * 79 | * http_data_cb does not return data chunks. It will be called arbitrarily 80 | * many times for each string. E.G. you might get 10 callbacks for "on_url" 81 | * each providing just a few characters more data. 82 | */ 83 | typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); 84 | typedef int (*http_cb) (http_parser*); 85 | 86 | 87 | /* Request Methods */ 88 | #define HTTP_METHOD_MAP(XX) \ 89 | XX(0, DELETE, DELETE) \ 90 | XX(1, GET, GET) \ 91 | XX(2, HEAD, HEAD) \ 92 | XX(3, POST, POST) \ 93 | XX(4, PUT, PUT) \ 94 | /* pathological */ \ 95 | XX(5, CONNECT, CONNECT) \ 96 | XX(6, OPTIONS, OPTIONS) \ 97 | XX(7, TRACE, TRACE) \ 98 | /* webdav */ \ 99 | XX(8, COPY, COPY) \ 100 | XX(9, LOCK, LOCK) \ 101 | XX(10, MKCOL, MKCOL) \ 102 | XX(11, MOVE, MOVE) \ 103 | XX(12, PROPFIND, PROPFIND) \ 104 | XX(13, PROPPATCH, PROPPATCH) \ 105 | XX(14, SEARCH, SEARCH) \ 106 | XX(15, UNLOCK, UNLOCK) \ 107 | /* subversion */ \ 108 | XX(16, REPORT, REPORT) \ 109 | XX(17, MKACTIVITY, MKACTIVITY) \ 110 | XX(18, CHECKOUT, CHECKOUT) \ 111 | XX(19, MERGE, MERGE) \ 112 | /* upnp */ \ 113 | XX(20, MSEARCH, M-SEARCH) \ 114 | XX(21, NOTIFY, NOTIFY) \ 115 | XX(22, SUBSCRIBE, SUBSCRIBE) \ 116 | XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \ 117 | /* RFC-5789 */ \ 118 | XX(24, PATCH, PATCH) \ 119 | XX(25, PURGE, PURGE) \ 120 | /* CalDAV */ \ 121 | XX(26, MKCALENDAR, MKCALENDAR) \ 122 | 123 | enum http_method 124 | { 125 | #define XX(num, name, string) HTTP_##name = num, 126 | HTTP_METHOD_MAP(XX) 127 | #undef XX 128 | }; 129 | 130 | 131 | enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; 132 | 133 | 134 | /* Flag values for http_parser.flags field */ 135 | enum flags 136 | { F_CHUNKED = 1 << 0 137 | , F_CONNECTION_KEEP_ALIVE = 1 << 1 138 | , F_CONNECTION_CLOSE = 1 << 2 139 | , F_CONNECTION_UPGRADE = 1 << 3 140 | , F_TRAILING = 1 << 4 141 | , F_UPGRADE = 1 << 5 142 | , F_SKIPBODY = 1 << 6 143 | }; 144 | 145 | 146 | /* Map for errno-related constants 147 | * 148 | * The provided argument should be a macro that takes 2 arguments. 149 | */ 150 | #define HTTP_ERRNO_MAP(XX) \ 151 | /* No error */ \ 152 | XX(OK, "success") \ 153 | \ 154 | /* Callback-related errors */ \ 155 | XX(CB_message_begin, "the on_message_begin callback failed") \ 156 | XX(CB_url, "the on_url callback failed") \ 157 | XX(CB_header_field, "the on_header_field callback failed") \ 158 | XX(CB_header_value, "the on_header_value callback failed") \ 159 | XX(CB_headers_complete, "the on_headers_complete callback failed") \ 160 | XX(CB_body, "the on_body callback failed") \ 161 | XX(CB_message_complete, "the on_message_complete callback failed") \ 162 | XX(CB_status, "the on_status callback failed") \ 163 | \ 164 | /* Parsing-related errors */ \ 165 | XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ 166 | XX(HEADER_OVERFLOW, \ 167 | "too many header bytes seen; overflow detected") \ 168 | XX(CLOSED_CONNECTION, \ 169 | "data received after completed connection: close message") \ 170 | XX(INVALID_VERSION, "invalid HTTP version") \ 171 | XX(INVALID_STATUS, "invalid HTTP status code") \ 172 | XX(INVALID_METHOD, "invalid HTTP method") \ 173 | XX(INVALID_URL, "invalid URL") \ 174 | XX(INVALID_HOST, "invalid host") \ 175 | XX(INVALID_PORT, "invalid port") \ 176 | XX(INVALID_PATH, "invalid path") \ 177 | XX(INVALID_QUERY_STRING, "invalid query string") \ 178 | XX(INVALID_FRAGMENT, "invalid fragment") \ 179 | XX(LF_EXPECTED, "LF character expected") \ 180 | XX(INVALID_HEADER_TOKEN, "invalid character in header") \ 181 | XX(INVALID_CONTENT_LENGTH, \ 182 | "invalid character in content-length header") \ 183 | XX(INVALID_CHUNK_SIZE, \ 184 | "invalid character in chunk size header") \ 185 | XX(INVALID_CONSTANT, "invalid constant string") \ 186 | XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ 187 | XX(STRICT, "strict mode assertion failed") \ 188 | XX(PAUSED, "parser is paused") \ 189 | XX(UNKNOWN, "an unknown error occurred") 190 | 191 | 192 | /* Define HPE_* values for each errno value above */ 193 | #define HTTP_ERRNO_GEN(n, s) HPE_##n, 194 | enum http_errno { 195 | HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) 196 | }; 197 | #undef HTTP_ERRNO_GEN 198 | 199 | 200 | /* Get an http_errno value from an http_parser */ 201 | #define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) 202 | 203 | 204 | struct http_parser { 205 | /** PRIVATE **/ 206 | unsigned int type : 2; /* enum http_parser_type */ 207 | unsigned int flags : 7; /* F_* values from 'flags' enum; semi-public */ 208 | unsigned int state : 7; /* enum state from http_parser.c */ 209 | unsigned int header_state : 8; /* enum header_state from http_parser.c */ 210 | unsigned int index : 8; /* index into current matcher */ 211 | 212 | uint32_t nread; /* # bytes read in various scenarios */ 213 | uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ 214 | 215 | /** READ-ONLY **/ 216 | unsigned short http_major; 217 | unsigned short http_minor; 218 | unsigned int status_code : 16; /* responses only */ 219 | unsigned int method : 8; /* requests only */ 220 | unsigned int http_errno : 7; 221 | 222 | /* 1 = Upgrade header was present and the parser has exited because of that. 223 | * 0 = No upgrade header present. 224 | * Should be checked when http_parser_execute() returns in addition to 225 | * error checking. 226 | */ 227 | unsigned int upgrade : 1; 228 | 229 | /** PUBLIC **/ 230 | void *data; /* A pointer to get hook to the "connection" or "socket" object */ 231 | }; 232 | 233 | 234 | struct http_parser_settings { 235 | http_cb on_message_begin; 236 | http_data_cb on_url; 237 | http_data_cb on_status; 238 | http_data_cb on_header_field; 239 | http_data_cb on_header_value; 240 | http_cb on_headers_complete; 241 | http_data_cb on_body; 242 | http_cb on_message_complete; 243 | }; 244 | 245 | 246 | enum http_parser_url_fields 247 | { UF_SCHEMA = 0 248 | , UF_HOST = 1 249 | , UF_PORT = 2 250 | , UF_PATH = 3 251 | , UF_QUERY = 4 252 | , UF_FRAGMENT = 5 253 | , UF_USERINFO = 6 254 | , UF_MAX = 7 255 | }; 256 | 257 | 258 | /* Result structure for http_parser_parse_url(). 259 | * 260 | * Callers should index into field_data[] with UF_* values iff field_set 261 | * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and 262 | * because we probably have padding left over), we convert any port to 263 | * a uint16_t. 264 | */ 265 | struct http_parser_url { 266 | uint16_t field_set; /* Bitmask of (1 << UF_*) values */ 267 | uint16_t port; /* Converted UF_PORT string */ 268 | 269 | struct { 270 | uint16_t off; /* Offset into buffer in which field starts */ 271 | uint16_t len; /* Length of run in buffer */ 272 | } field_data[UF_MAX]; 273 | }; 274 | 275 | 276 | /* Returns the library version. Bits 16-23 contain the major version number, 277 | * bits 8-15 the minor version number and bits 0-7 the patch level. 278 | * Usage example: 279 | * 280 | * unsigned long version = http_parser_version(); 281 | * unsigned major = (version >> 16) & 255; 282 | * unsigned minor = (version >> 8) & 255; 283 | * unsigned patch = version & 255; 284 | * printf("http_parser v%u.%u.%u\n", major, minor, patch); 285 | */ 286 | unsigned long http_parser_version(void); 287 | 288 | void http_parser_init(http_parser *parser, enum http_parser_type type); 289 | 290 | 291 | /* Initialize http_parser_settings members to 0 292 | */ 293 | void http_parser_settings_init(http_parser_settings *settings); 294 | 295 | 296 | /* Executes the parser. Returns number of parsed bytes. Sets 297 | * `parser->http_errno` on error. */ 298 | size_t http_parser_execute(http_parser *parser, 299 | const http_parser_settings *settings, 300 | const char *data, 301 | size_t len); 302 | 303 | 304 | /* If http_should_keep_alive() in the on_headers_complete or 305 | * on_message_complete callback returns 0, then this should be 306 | * the last message on the connection. 307 | * If you are the server, respond with the "Connection: close" header. 308 | * If you are the client, close the connection. 309 | */ 310 | int http_should_keep_alive(const http_parser *parser); 311 | 312 | /* Returns a string version of the HTTP method. */ 313 | const char *http_method_str(enum http_method m); 314 | 315 | /* Return a string name of the given error */ 316 | const char *http_errno_name(enum http_errno err); 317 | 318 | /* Return a string description of the given error */ 319 | const char *http_errno_description(enum http_errno err); 320 | 321 | /* Parse a URL; return nonzero on failure */ 322 | int http_parser_parse_url(const char *buf, size_t buflen, 323 | int is_connect, 324 | struct http_parser_url *u); 325 | 326 | /* Pause or un-pause the parser; a nonzero value pauses */ 327 | void http_parser_pause(http_parser *parser, int paused); 328 | 329 | /* Checks if this is the final chunk of the body. */ 330 | int http_body_is_final(const http_parser *parser); 331 | 332 | #ifdef __cplusplus 333 | } 334 | #endif 335 | #endif 336 | -------------------------------------------------------------------------------- /src/ngx_http_upsync_module.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGX_HTTP_UPSYNC_MODELE_H_INCLUDED_ 2 | #define _NGX_HTTP_UPSYNC_MODELE_H_INCLUDED_ 3 | 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "ngx_http_json.h" 10 | #include "ngx_http_parser.h" 11 | 12 | #define ngx_strrchr(s1, c) strrchr((const char *) s1, (int) c) 13 | #define ngx_ftruncate(fd, offset) ftruncate(fd, offset) 14 | #define ngx_lseek(fd, offset, whence) lseek(fd, offset, whence) 15 | #define ngx_fgets(fp, offset, whence) fgets(fp, offset, whence) 16 | #define ngx_fopen(path, mode) fopen(path, mode) 17 | #define ngx_fclose(fp) fclose(fp) 18 | 19 | #define ngx_strtoull(nptr, endptr, base) strtoull((const char *) nptr, \ 20 | (char **) endptr, (int) base) 21 | 22 | #define NGX_INDEX_HEADER "X-Consul-Index" 23 | #define NGX_INDEX_HEADER_LEN 14 24 | 25 | #define NGX_INDEX_ETCD_HEADER "X-Etcd-Index" 26 | #define NGX_INDEX_ETCD_HEADER_LEN 12 27 | 28 | #define NGX_MAX_HEADERS 20 29 | #define NGX_MAX_ELEMENT_SIZE 512 30 | 31 | #define NGX_DELAY_DELETE 30 * 60 * 1000 //75 * 1000 32 | 33 | #define NGX_ADD 0 34 | #define NGX_DEL 1 35 | 36 | #define NGX_PAGE_SIZE 4 * 1024 37 | #define NGX_PAGE_NUMBER 1024 38 | 39 | #define NGX_HTTP_RETRY_TIMES 3 40 | #define NGX_HTTP_SOCKET_TIMEOUT 1 41 | 42 | #define NGX_HTTP_LB_DEFAULT 0 43 | #define NGX_HTTP_LB_ROUNDROBIN 1 44 | #define NGX_HTTP_LB_IP_HASH 2 45 | #define NGX_HTTP_LB_LEAST_CONN 4 46 | #define NGX_HTTP_LB_HASH_MODULA 8 47 | #define NGX_HTTP_LB_HASH_KETAMA 16 48 | 49 | #if (NGX_HTTP_UPSTREAM_CHECK) 50 | 51 | extern ngx_uint_t ngx_http_upstream_check_add_dynamic_peer(ngx_pool_t *pool, 52 | ngx_http_upstream_srv_conf_t *uscf, ngx_addr_t *peer_addr); 53 | extern void ngx_http_upstream_check_delete_dynamic_peer(ngx_str_t *name, 54 | ngx_addr_t *peer_addr); 55 | 56 | #endif 57 | 58 | 59 | /******************************hash*********************************/ 60 | 61 | extern ngx_module_t ngx_http_upstream_hash_module; 62 | 63 | 64 | typedef struct { 65 | uint32_t hash; 66 | ngx_str_t *server; 67 | } ngx_http_upstream_chash_point_t; 68 | 69 | 70 | typedef struct { 71 | ngx_uint_t number; 72 | ngx_http_upstream_chash_point_t point[1]; 73 | } ngx_http_upstream_chash_points_t; 74 | 75 | 76 | typedef struct { 77 | ngx_http_complex_value_t key; 78 | ngx_http_upstream_chash_points_t *points; 79 | } ngx_http_upstream_hash_srv_conf_t; 80 | 81 | /****************************hash_end*******************************/ 82 | 83 | 84 | static int ngx_libc_cdecl ngx_http_upsync_chash_cmp_points(const void *one, 85 | const void *two); 86 | static ngx_int_t ngx_http_upsync_chash_init(ngx_http_upstream_srv_conf_t *uscf, 87 | ngx_http_upstream_rr_peers_t *tmp_peers); 88 | static ngx_int_t ngx_http_upsync_del_chash_peer( 89 | ngx_http_upstream_srv_conf_t *uscf); 90 | 91 | 92 | static int ngx_libc_cdecl 93 | ngx_http_upsync_chash_cmp_points(const void *one, const void *two) 94 | { 95 | ngx_http_upstream_chash_point_t *first = 96 | (ngx_http_upstream_chash_point_t *) one; 97 | ngx_http_upstream_chash_point_t *second = 98 | (ngx_http_upstream_chash_point_t *) two; 99 | 100 | if (first->hash < second->hash) { 101 | return -1; 102 | 103 | } else if (first->hash > second->hash) { 104 | return 1; 105 | 106 | } else { 107 | return 0; 108 | } 109 | } 110 | 111 | 112 | static ngx_int_t 113 | ngx_http_upsync_chash_init(ngx_http_upstream_srv_conf_t *uscf, 114 | ngx_http_upstream_rr_peers_t *tmp_peers) 115 | { 116 | size_t new_size; 117 | size_t host_len, port_len; 118 | u_char *host, *port, c; 119 | uint32_t hash, base_hash; 120 | ngx_str_t *server; 121 | ngx_uint_t npoints, new_npoints; 122 | ngx_uint_t i, j; 123 | ngx_http_upstream_rr_peer_t *peer; 124 | ngx_http_upstream_rr_peers_t *peers; 125 | ngx_http_upstream_chash_points_t *points; 126 | ngx_http_upstream_hash_srv_conf_t *hcf; 127 | union { 128 | uint32_t value; 129 | u_char byte[4]; 130 | } prev_hash; 131 | 132 | hcf = ngx_http_conf_upstream_srv_conf(uscf, ngx_http_upstream_hash_module); 133 | if(hcf->points == NULL) { 134 | return 0; 135 | } 136 | 137 | peers = uscf->peer.data; 138 | if (tmp_peers != NULL) { 139 | new_npoints = peers->total_weight * 160; 140 | 141 | new_size = sizeof(ngx_http_upstream_chash_points_t) 142 | + sizeof(ngx_http_upstream_chash_point_t) * (new_npoints - 1); 143 | 144 | points = ngx_calloc(new_size, ngx_cycle->log); 145 | if (points == NULL ) { 146 | return NGX_ERROR; 147 | } 148 | ngx_free(hcf->points); /* free old points */ 149 | hcf->points = points; 150 | 151 | for (peer = peers->peer; peer; peer = peer->next) { 152 | server = &peer->server; 153 | 154 | /* 155 | * Hash expression is compatible with Cache::Memcached::Fast: 156 | * crc32(HOST \0 PORT PREV_HASH). 157 | */ 158 | 159 | if (server->len >= 5 160 | && ngx_strncasecmp(server->data, (u_char *) "unix:", 5) == 0) 161 | { 162 | host = server->data + 5; 163 | host_len = server->len - 5; 164 | port = NULL; 165 | port_len = 0; 166 | goto done; 167 | } 168 | 169 | for (j = 0; j < server->len; j++) { 170 | c = server->data[server->len - j - 1]; 171 | 172 | if (c == ':') { 173 | host = server->data; 174 | host_len = server->len - j - 1; 175 | port = server->data + server->len - j; 176 | port_len = j; 177 | goto done; 178 | } 179 | 180 | if (c < '0' || c > '9') { 181 | break; 182 | } 183 | } 184 | 185 | host = server->data; 186 | host_len = server->len; 187 | port = NULL; 188 | port_len = 0; 189 | 190 | done: 191 | 192 | ngx_crc32_init(base_hash); 193 | ngx_crc32_update(&base_hash, host, host_len); 194 | ngx_crc32_update(&base_hash, (u_char *) "", 1); 195 | ngx_crc32_update(&base_hash, port, port_len); 196 | 197 | prev_hash.value = 0; 198 | npoints = peer->weight * 160; 199 | 200 | for (j = 0; j < npoints; j++) { 201 | hash = base_hash; 202 | 203 | ngx_crc32_update(&hash, prev_hash.byte, 4); 204 | ngx_crc32_final(hash); 205 | 206 | points->point[points->number].hash = hash; 207 | points->point[points->number].server = server; 208 | points->number++; 209 | 210 | #if (NGX_HAVE_LITTLE_ENDIAN) 211 | prev_hash.value = hash; 212 | #else 213 | prev_hash.byte[0] = (u_char) (hash & 0xff); 214 | prev_hash.byte[1] = (u_char) ((hash >> 8) & 0xff); 215 | prev_hash.byte[2] = (u_char) ((hash >> 16) & 0xff); 216 | prev_hash.byte[3] = (u_char) ((hash >> 24) & 0xff); 217 | #endif 218 | } 219 | } 220 | 221 | } else { 222 | new_npoints = peers->total_weight * 160; 223 | 224 | new_size = sizeof(ngx_http_upstream_chash_points_t) 225 | + sizeof(ngx_http_upstream_chash_point_t) * (new_npoints - 1); 226 | 227 | points = ngx_calloc(new_size, ngx_cycle->log); 228 | if (points == NULL ) { 229 | return NGX_ERROR; 230 | } 231 | 232 | ngx_memcpy(points, hcf->points, new_size); 233 | ngx_pfree(ngx_cycle->pool, hcf->points); 234 | 235 | hcf->points = points; 236 | 237 | return NGX_OK; 238 | } 239 | 240 | ngx_qsort(points->point, 241 | points->number, 242 | sizeof(ngx_http_upstream_chash_point_t), 243 | ngx_http_upsync_chash_cmp_points); 244 | 245 | for (i = 0, j = 1; j < points->number; j++) { 246 | if (points->point[i].hash != points->point[j].hash) { 247 | points->point[++i] = points->point[j]; 248 | } 249 | } 250 | 251 | points->number = i + 1; 252 | 253 | return NGX_OK; 254 | } 255 | 256 | 257 | static ngx_int_t 258 | ngx_http_upsync_del_chash_peer(ngx_http_upstream_srv_conf_t *uscf) 259 | { 260 | size_t host_len, port_len; 261 | u_char *host, *port, c; 262 | uint32_t hash, base_hash; 263 | ngx_str_t *server; 264 | ngx_uint_t npoints, i, j; 265 | ngx_http_upstream_rr_peer_t *peer; 266 | ngx_http_upstream_rr_peers_t *peers; 267 | ngx_http_upstream_chash_points_t *points; 268 | ngx_http_upstream_hash_srv_conf_t *hcf; 269 | union { 270 | uint32_t value; 271 | u_char byte[4]; 272 | } prev_hash; 273 | 274 | hcf = ngx_http_conf_upstream_srv_conf(uscf, ngx_http_upstream_hash_module); 275 | if(hcf->points == NULL) { 276 | return 0; 277 | } 278 | 279 | peers = uscf->peer.data; 280 | 281 | points = hcf->points; 282 | points->number = 0; 283 | 284 | for (peer = peers->peer; peer; peer = peer->next) { 285 | server = &peer->server; 286 | 287 | /* 288 | * Hash expression is compatible with Cache::Memcached::Fast: 289 | * crc32(HOST \0 PORT PREV_HASH). 290 | */ 291 | 292 | if (server->len >= 5 293 | && ngx_strncasecmp(server->data, (u_char *) "unix:", 5) == 0) 294 | { 295 | host = server->data + 5; 296 | host_len = server->len - 5; 297 | port = NULL; 298 | port_len = 0; 299 | goto done; 300 | } 301 | 302 | for (j = 0; j < server->len; j++) { 303 | c = server->data[server->len - j - 1]; 304 | 305 | if (c == ':') { 306 | host = server->data; 307 | host_len = server->len - j - 1; 308 | port = server->data + server->len - j; 309 | port_len = j; 310 | goto done; 311 | } 312 | 313 | if (c < '0' || c > '9') { 314 | break; 315 | } 316 | } 317 | 318 | host = server->data; 319 | host_len = server->len; 320 | port = NULL; 321 | port_len = 0; 322 | 323 | done: 324 | 325 | ngx_crc32_init(base_hash); 326 | ngx_crc32_update(&base_hash, host, host_len); 327 | ngx_crc32_update(&base_hash, (u_char *) "", 1); 328 | ngx_crc32_update(&base_hash, port, port_len); 329 | 330 | prev_hash.value = 0; 331 | npoints = peer->weight * 160; 332 | 333 | for (j = 0; j < npoints; j++) { 334 | hash = base_hash; 335 | 336 | ngx_crc32_update(&hash, prev_hash.byte, 4); 337 | ngx_crc32_final(hash); 338 | 339 | points->point[points->number].hash = hash; 340 | points->point[points->number].server = server; 341 | points->number++; 342 | 343 | #if (NGX_HAVE_LITTLE_ENDIAN) 344 | prev_hash.value = hash; 345 | #else 346 | prev_hash.byte[0] = (u_char) (hash & 0xff); 347 | prev_hash.byte[1] = (u_char) ((hash >> 8) & 0xff); 348 | prev_hash.byte[2] = (u_char) ((hash >> 16) & 0xff); 349 | prev_hash.byte[3] = (u_char) ((hash >> 24) & 0xff); 350 | #endif 351 | } 352 | } 353 | 354 | ngx_qsort(points->point, 355 | points->number, 356 | sizeof(ngx_http_upstream_chash_point_t), 357 | ngx_http_upsync_chash_cmp_points); 358 | 359 | for (i = 0, j = 1; j < points->number; j++) { 360 | if (points->point[i].hash != points->point[j].hash) { 361 | points->point[++i] = points->point[j]; 362 | } 363 | } 364 | 365 | points->number = i + 1; 366 | 367 | return NGX_OK; 368 | } 369 | 370 | 371 | #endif //_NGX_HTTP_UPSYNC_MODELE_H_INCLUDED_ 372 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | NAME 2 | Test::Nginx - Testing modules for Nginx C module development 3 | 4 | DESCRIPTION 5 | This distribution provides two testing modules for Nginx C module 6 | development: 7 | 8 | * Test::Nginx::LWP 9 | 10 | * Test::Nginx::Socket 11 | 12 | All of them are based on Test::Base. 13 | 14 | Usually, Test::Nginx::Socket is preferred because it works on a much 15 | lower level and not that fault tolerant like Test::Nginx::LWP. 16 | 17 | Also, a lot of connection hang issues (like wrong "r->main->count" value 18 | in nginx 0.8.x) can only be captured by Test::Nginx::Socket because 19 | Perl's LWP::UserAgent client will close the connection itself which will 20 | conceal such issues from the testers. 21 | 22 | Test::Nginx automatically starts an nginx instance (from the "PATH" env) 23 | rooted at t/servroot/ and the default config template makes this nginx 24 | instance listen on the port 1984 by default. One can specify a different 25 | port number by setting his port number to the "TEST_NGINX_PORT" 26 | environment, as in 27 | 28 | export TEST_NGINX_PORT=1989 29 | 30 | etcproxy integration 31 | The default settings in etcproxy 32 | (https://github.com/chaoslawful/etcproxy) makes this small TCP proxy 33 | split the TCP packets into bytes and introduce 1 ms latency among them. 34 | 35 | There's usually various TCP chains that we can put etcproxy into, for 36 | example 37 | 38 | Test::Nginx <=> nginx 39 | $ ./etcproxy 1234 1984 40 | 41 | Here we tell etcproxy to listen on port 1234 and to delegate all the TCP 42 | traffic to the port 1984, the default port that Test::Nginx makes nginx 43 | listen to. 44 | 45 | And then we tell Test::Nginx to test against the port 1234, where 46 | etcproxy listens on, rather than the port 1984 that nginx directly 47 | listens on: 48 | 49 | $ TEST_NGINX_CLIENT_PORT=1234 prove -r t/ 50 | 51 | Then the TCP chain now looks like this: 52 | 53 | Test::Nginx <=> etcproxy (1234) <=> nginx (1984) 54 | 55 | So etcproxy can effectively emulate extreme network conditions and 56 | exercise "unusual" code paths in your nginx server by your tests. 57 | 58 | In practice, *tons* of weird bugs can be captured by this setting. Even 59 | ourselves didn't expect that this simple approach is so effective. 60 | 61 | nginx <=> memcached 62 | We first start the memcached server daemon on port 11211: 63 | 64 | memcached -p 11211 -vv 65 | 66 | and then we another etcproxy instance to listen on port 11984 like this 67 | 68 | $ ./etcproxy 11984 11211 69 | 70 | Then we tell our t/foo.t test script to connect to 11984 rather than 71 | 11211: 72 | 73 | # foo.t 74 | use Test::Nginx::Socket; 75 | repeat_each(1); 76 | plan tests => 2 * repeat_each() * blocks(); 77 | $ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211; # make this env take a default value 78 | run_tests(); 79 | 80 | __DATA__ 81 | 82 | === TEST 1: sanity 83 | --- config 84 | location /foo { 85 | set $memc_cmd set; 86 | set $memc_key foo; 87 | set $memc_value bar; 88 | memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT; 89 | } 90 | --- request 91 | GET /foo 92 | --- response_body_like: STORED 93 | 94 | The Test::Nginx library will automatically expand the special macro 95 | $TEST_NGINX_MEMCACHED_PORT to the environment with the same name. You 96 | can define your own $TEST_NGINX_BLAH_BLAH_PORT macros as long as its 97 | prefix is "TEST_NGINX_" and all in upper case letters. 98 | 99 | And now we can run your test script against the etcproxy port 11984: 100 | 101 | TEST_NGINX_MEMCACHED_PORT=11984 prove t/foo.t 102 | 103 | Then the TCP chains look like this: 104 | 105 | Test::Nginx <=> nginx (1984) <=> etcproxy (11984) <=> memcached (11211) 106 | 107 | If "TEST_NGINX_MEMCACHED_PORT" is not set, then it will take the default 108 | value 11211, which is what we want when there's no etcproxy configured: 109 | 110 | Test::Nginx <=> nginx (1984) <=> memcached (11211) 111 | 112 | This approach also works for proxied mysql and postgres traffic. Please 113 | see the live test suite of ngx_drizzle and ngx_postgres for more 114 | details. 115 | 116 | Usually we set both "TEST_NGINX_CLIENT_PORT" and 117 | "TEST_NGINX_MEMCACHED_PORT" (and etc) at the same time, effectively 118 | yielding the following chain: 119 | 120 | Test::Nginx <=> etcproxy (1234) <=> nginx (1984) <=> etcproxy (11984) <=> memcached (11211) 121 | 122 | as long as you run two separate etcproxy instances in two separate 123 | terminals. 124 | 125 | It's easy to verify if the traffic actually goes through your etcproxy 126 | server. Just check if the terminal running etcproxy emits outputs. By 127 | default, etcproxy always dump out the incoming and outgoing data to 128 | stdout/stderr. 129 | 130 | valgrind integration 131 | Test::Nginx has integrated support for valgrind () 132 | even though by default it does not bother running it with the tests 133 | because valgrind will significantly slow down the test sutie. 134 | 135 | First ensure that your valgrind executable visible in your PATH env. And 136 | then run your test suite with the "TEST_NGINX_USE_VALGRIND" env set to 137 | true: 138 | 139 | TEST_NGINX_USE_VALGRIND=1 prove -r t 140 | 141 | If you see false alarms, you do have a chance to skip them by defining a 142 | ./valgrind.suppress file at the root of your module source tree, as in 143 | 144 | 146 | 147 | This is the suppression file for ngx_drizzle. Test::Nginx will 148 | automatically use it to start nginx with valgrind memcheck if this file 149 | does exist at the expected location. 150 | 151 | If you do see a lot of "Connection refused" errors while running the 152 | tests this way, then you probably have a slow machine (or a very busy 153 | one) that the default waiting time is not sufficient for valgrind to 154 | start. You can define the sleep time to a larger value by setting the 155 | "TEST_NGINX_SLEEP" env: 156 | 157 | TEST_NGINX_SLEEP=1 prove -r t 158 | 159 | The time unit used here is "second". The default sleep setting just fits 160 | my ThinkPad ("Core2Duo T9600"). 161 | 162 | Applying the no-pool patch to your nginx core is recommended while 163 | running nginx with valgrind: 164 | 165 | 166 | 167 | The nginx memory pool can prevent valgrind from spotting lots of invalid 168 | memory reads/writes as well as certain double-free errors. We did find a 169 | lot more memory issues in many of our modules when we first introduced 170 | the no-pool patch in practice ;) 171 | 172 | There's also more advanced features in Test::Nginx that have never 173 | documented. I'd like to write more about them in the near future ;) 174 | 175 | Nginx C modules that use Test::Nginx to drive their test suites 176 | ngx_echo 177 | 178 | 179 | ngx_headers_more 180 | 181 | 182 | ngx_chunkin 183 | 184 | 185 | ngx_memc 186 | 187 | 188 | ngx_drizzle 189 | 190 | 191 | ngx_rds_json 192 | 193 | 194 | ngx_rds_csv 195 | 196 | 197 | ngx_xss 198 | 199 | 200 | ngx_srcache 201 | 202 | 203 | ngx_lua 204 | 205 | 206 | ngx_set_misc 207 | 208 | 209 | ngx_array_var 210 | 211 | 212 | ngx_form_input 213 | 214 | 215 | ngx_iconv 216 | 217 | 218 | ngx_set_cconv 219 | 220 | 221 | ngx_postgres 222 | 223 | 224 | ngx_coolkit 225 | 226 | 227 | Naxsi 228 | 229 | 230 | SOURCE REPOSITORY 231 | This module has a Git repository on Github, which has access for all. 232 | 233 | http://github.com/agentzh/test-nginx 234 | 235 | If you want a commit bit, feel free to drop me a line. 236 | 237 | AUTHORS 238 | agentzh (章亦春) "" 239 | 240 | Antoine BONAVITA "" 241 | 242 | COPYRIGHT & LICENSE 243 | Copyright (c) 2009-2012, agentzh "". 244 | 245 | Copyright (c) 2011-2012, Antoine Bonavita 246 | "". 247 | 248 | This module is licensed under the terms of the BSD license. 249 | 250 | Redistribution and use in source and binary forms, with or without 251 | modification, are permitted provided that the following conditions are 252 | met: 253 | 254 | * Redistributions of source code must retain the above copyright 255 | notice, this list of conditions and the following disclaimer. 256 | 257 | * Redistributions in binary form must reproduce the above copyright 258 | notice, this list of conditions and the following disclaimer in the 259 | documentation and/or other materials provided with the distribution. 260 | 261 | * Neither the name of the authors nor the names of its contributors 262 | may be used to endorse or promote products derived from this 263 | software without specific prior written permission. 264 | 265 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 266 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 267 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 268 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 269 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 270 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 271 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 272 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 273 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 274 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 275 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 276 | 277 | SEE ALSO 278 | Test::Nginx::LWP, Test::Nginx::Socket, Test::Base. 279 | 280 | -------------------------------------------------------------------------------- /test/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ ! -d "_nginx" ]; then 4 | echo "run: ./fetch NGINX_VERSION" >&2 5 | exit 1 6 | fi 7 | 8 | cd _nginx || exit 1 9 | 10 | exec make 11 | -------------------------------------------------------------------------------- /test/configure.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ ! -d "_nginx" ]; then 4 | echo "run: ./fetch NGINX_VERSION" >&2 5 | exit 1 6 | fi 7 | 8 | cd _nginx || exit 1 9 | 10 | exec ./configure --add-module=.. 11 | -------------------------------------------------------------------------------- /test/consul.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "$1" != "" ]; then 4 | wget -q https://releases.hashicorp.com/consul/${1}/consul_${1}_linux_amd64.zip || exit 1 5 | unzip consul_${1}_linux_amd64.zip || exit 1 6 | fi 7 | 8 | ./consul agent -server -bootstrap -data-dir=/tmp/consul -bind=127.0.0.1 9 | -------------------------------------------------------------------------------- /test/fetch.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ -d "_nginx" ]; then 4 | exit 5 | fi 6 | 7 | wget -q http://nginx.org/download/nginx-${1}.tar.gz || exit 1 8 | tar -xzf nginx-${1}.tar.gz || exit 1 9 | exec mv nginx-${1} _nginx 10 | -------------------------------------------------------------------------------- /test/t/lib/Test/Nginx.pm: -------------------------------------------------------------------------------- 1 | package Test::Nginx; 2 | 3 | # (C) Maxim Dounin 4 | 5 | # Generic module for nginx tests. 6 | 7 | ############################################################################### 8 | 9 | use warnings; 10 | use strict; 11 | 12 | use base qw/ Exporter /; 13 | 14 | our @EXPORT = qw/ log_in log_out http http_get http_head /; 15 | our @EXPORT_OK = qw/ http_gzip_request http_gzip_like http_start http_end /; 16 | our %EXPORT_TAGS = ( 17 | gzip => [ qw/ http_gzip_request http_gzip_like / ] 18 | ); 19 | 20 | ############################################################################### 21 | 22 | use File::Path qw/ rmtree /; 23 | use File::Temp qw/ tempdir /; 24 | use IO::Socket; 25 | use POSIX qw/ waitpid WNOHANG /; 26 | use Socket qw/ CRLF /; 27 | use Test::More qw//; 28 | 29 | ############################################################################### 30 | 31 | our $NGINX = defined $ENV{TEST_NGINX_BINARY} ? $ENV{TEST_NGINX_BINARY} 32 | : '../nginx/objs/nginx'; 33 | 34 | sub new { 35 | my $self = {}; 36 | bless $self; 37 | 38 | $self->{_pid} = $$; 39 | 40 | $self->{_testdir} = tempdir( 41 | 'nginx-test-XXXXXXXXXX', 42 | TMPDIR => 1, 43 | CLEANUP => not $ENV{TEST_NGINX_LEAVE} 44 | ) 45 | or die "Can't create temp directory: $!\n"; 46 | $self->{_testdir} =~ s!\\!/!g if $^O eq 'MSWin32'; 47 | $self->{_dso_module} = (); 48 | mkdir "$self->{_testdir}/logs" 49 | or die "Can't create logs directory: $!\n"; 50 | 51 | return $self; 52 | } 53 | 54 | sub DESTROY { 55 | my ($self) = @_; 56 | local $?; 57 | 58 | return if $self->{_pid} != $$; 59 | $self->stop(); 60 | $self->stop_daemons(); 61 | if ($ENV{TEST_NGINX_CATLOG}) { 62 | system("cat $self->{_testdir}/error.log"); 63 | } 64 | if (not $ENV{TEST_NGINX_LEAVE}) { 65 | eval { rmtree($self->{_testdir}); }; 66 | } 67 | } 68 | 69 | sub has($;) { 70 | my ($self, @features) = @_; 71 | 72 | foreach my $feature (@features) { 73 | Test::More::plan(skip_all => "$feature not compiled in") 74 | unless $self->has_module($feature); 75 | } 76 | 77 | return $self; 78 | } 79 | 80 | sub set_dso($;) { 81 | my ($self, $module_name, $module_path) = @_; 82 | 83 | $self->{_dso_module}{$module_name} = $module_path; 84 | } 85 | 86 | sub has_module($) { 87 | my ($self, $feature) = @_; 88 | 89 | my %regex = ( 90 | sni => 'TLS SNI support enabled', 91 | mail => '--with-mail(?!\S)', 92 | flv => '--with-http_flv_module', 93 | perl => '--with-http_perl_module', 94 | auth_request 95 | => '--with-http_auth_request_module', 96 | charset => '(?s)^(?!.*--without-http_charset_module)', 97 | gzip => '(?s)^(?!.*--without-http_gzip_module)', 98 | ssi => '(?s)^(?!.*--without-http_ssi_module)', 99 | userid => '(?s)^(?!.*--without-http_userid_module)', 100 | access => '(?s)^(?!.*--without-http_access_module)', 101 | auth_basic 102 | => '(?s)^(?!.*--without-http_auth_basic_module)', 103 | autoindex 104 | => '(?s)^(?!.*--without-http_autoindex_module)', 105 | geo => '(?s)^(?!.*--without-http_geo_module)', 106 | map => '(?s)^(?!.*--without-http_map_module)', 107 | referer => '(?s)^(?!.*--without-http_referer_module)', 108 | rewrite => '(?s)^(?!.*--without-http_rewrite_module)', 109 | proxy => '(?s)^(?!.*--without-http_proxy_module)', 110 | fastcgi => '(?s)^(?!.*--without-http_fastcgi_module)', 111 | uwsgi => '(?s)^(?!.*--without-http_uwsgi_module)', 112 | scgi => '(?s)^(?!.*--without-http_scgi_module)', 113 | memcached 114 | => '(?s)^(?!.*--without-http_memcached_module)', 115 | limit_conn 116 | => '(?s)^(?!.*--without-http_limit_conn_module)', 117 | limit_req 118 | => '(?s)^(?!.*--without-http_limit_req_module)', 119 | empty_gif 120 | => '(?s)^(?!.*--without-http_empty_gif_module)', 121 | browser => '(?s)^(?!.*--without-http_browser_module)', 122 | upstream_hash 123 | => '(?s)^(?!.*--without-http_upstream_hash_module)', 124 | upstream_ip_hash 125 | => '(?s)^(?!.*--without-http_upstream_ip_hash_module)', 126 | reqstat 127 | => '(?s)^(?!.*--without-http_reqstat_module)', 128 | upstream_least_conn 129 | => '(?s)^(?!.*--without-http_upstream_least_conn_mod)', 130 | upstream_keepalive 131 | => '(?s)^(?!.*--without-http_upstream_keepalive_modu)', 132 | http => '(?s)^(?!.*--without-http(?!\S))', 133 | cache => '(?s)^(?!.*--without-http-cache)', 134 | pop3 => '(?s)^(?!.*--without-mail_pop3_module)', 135 | imap => '(?s)^(?!.*--without-mail_imap_module)', 136 | smtp => '(?s)^(?!.*--without-mail_smtp_module)', 137 | pcre => '(?s)^(?!.*--without-pcre)', 138 | split_clients 139 | => '(?s)^(?!.*--without-http_split_clients_module)', 140 | ); 141 | 142 | my $re = $regex{$feature}; 143 | $re = $feature if !defined $re; 144 | 145 | $self->{_configure_args} = `$NGINX -V 2>&1` 146 | if !defined $self->{_configure_args}; 147 | 148 | return ($self->{_configure_args} =~ $re or $self->{_configure_args} =~ '--enable-mods-static=all') ? 1 : 0; 149 | } 150 | 151 | sub has_version($) { 152 | my ($self, $need) = @_; 153 | 154 | $self->{_configure_args} = `$NGINX -V 2>&1` 155 | if !defined $self->{_configure_args}; 156 | 157 | $self->{_configure_args} =~ m!nginx/([0-9.]+)!; 158 | 159 | my @v = split(/\./, $1); 160 | my ($n, $v); 161 | 162 | for $n (split(/\./, $need)) { 163 | $v = shift @v || 0; 164 | return 0 if $n > $v; 165 | return 1 if $v > $n; 166 | } 167 | 168 | return 1; 169 | } 170 | 171 | sub has_daemon($) { 172 | my ($self, $daemon) = @_; 173 | 174 | if ($^O eq 'MSWin32') { 175 | Test::More::plan(skip_all => "win32"); 176 | return $self; 177 | } 178 | 179 | if ($^O eq 'solaris') { 180 | Test::More::plan(skip_all => "$daemon not found") 181 | unless `command -v $daemon`; 182 | return $self; 183 | } 184 | 185 | Test::More::plan(skip_all => "$daemon not found") 186 | unless `which $daemon`; 187 | 188 | return $self; 189 | } 190 | 191 | sub try_run($$) { 192 | my ($self, $message) = @_; 193 | 194 | $self->run(); 195 | 196 | eval { 197 | open OLDERR, ">&", \*STDERR; close STDERR; 198 | $self->run(); 199 | open STDERR, ">&", \*OLDERR; 200 | }; 201 | 202 | Test::More::plan(skip_all => $message) if $@; 203 | return $self; 204 | } 205 | 206 | sub plan($) { 207 | my ($self, $plan) = @_; 208 | 209 | Test::More::plan(tests => $plan); 210 | 211 | return $self; 212 | } 213 | 214 | sub run(;$) { 215 | my ($self, $conf) = @_; 216 | 217 | my $testdir = $self->{_testdir}; 218 | 219 | if (defined $conf) { 220 | my $c = `cat $conf`; 221 | $self->write_file_expand('nginx.conf', $c); 222 | } 223 | 224 | my $pid = fork(); 225 | die "Unable to fork(): $!\n" unless defined $pid; 226 | 227 | if ($pid == 0) { 228 | my @globals = $self->{_test_globals} ? 229 | () : ('-g', "pid $testdir/nginx.pid; " 230 | . "error_log $testdir/error.log debug;"); 231 | exec($NGINX, '-p', $testdir, '-c', 'nginx.conf', @globals), 232 | or die "Unable to exec(): $!\n"; 233 | } 234 | 235 | # wait for nginx to start 236 | 237 | $self->waitforfile("$testdir/nginx.pid", $pid) 238 | or die "Can't start nginx"; 239 | 240 | $self->{_started} = 1; 241 | return $self; 242 | } 243 | 244 | sub waitforfile($;$) { 245 | my ($self, $file, $pid) = @_; 246 | my $exited; 247 | 248 | # wait for file to appear 249 | # or specified process to exit 250 | 251 | for (1 .. 30) { 252 | return 1 if -e $file; 253 | return 0 if $exited; 254 | $exited = waitpid($pid, WNOHANG) != 0 if $pid; 255 | select undef, undef, undef, 0.1; 256 | } 257 | 258 | return undef; 259 | } 260 | 261 | sub waitforsocket($) { 262 | my ($self, $peer) = @_; 263 | 264 | # wait for socket to accept connections 265 | 266 | for (1 .. 30) { 267 | my $s = IO::Socket::INET->new( 268 | Proto => 'tcp', 269 | PeerAddr => $peer 270 | ); 271 | 272 | return 1 if defined $s; 273 | 274 | select undef, undef, undef, 0.1; 275 | } 276 | 277 | return undef; 278 | } 279 | 280 | sub reload() { 281 | my ($self) = @_; 282 | 283 | return $self unless $self->{_started}; 284 | 285 | local $/; 286 | open F, '<' . $self->{_testdir} . '/nginx.pid' 287 | or die "Can't open nginx.pid: $!"; 288 | my $pid = ; 289 | close F; 290 | 291 | if ($^O eq 'MSWin32') { 292 | my $testdir = $self->{_testdir}; 293 | my @globals = $self->{_test_globals} ? 294 | () : ('-g', "pid $testdir/nginx.pid; " 295 | . "error_log $testdir/error.log debug;"); 296 | system($NGINX, '-c', "$testdir/nginx.conf", '-s', 'reload', 297 | @globals) == 0 298 | or die "system() failed: $?\n"; 299 | 300 | } else { 301 | kill 'HUP', $pid; 302 | } 303 | 304 | sleep(1); 305 | 306 | return $self; 307 | } 308 | 309 | sub stop() { 310 | my ($self) = @_; 311 | 312 | return $self unless $self->{_started}; 313 | 314 | local $/; 315 | open F, '<' . $self->{_testdir} . '/nginx.pid' 316 | or die "Can't open nginx.pid: $!"; 317 | my $pid = ; 318 | close F; 319 | 320 | if ($^O eq 'MSWin32') { 321 | my $testdir = $self->{_testdir}; 322 | my @globals = $self->{_test_globals} ? 323 | () : ('-g', "pid $testdir/nginx.pid; " 324 | . "error_log $testdir/error.log debug;"); 325 | system($NGINX, '-p', $testdir, '-c', "nginx.conf", 326 | '-s', 'stop', @globals) == 0 327 | or die "system() failed: $?\n"; 328 | 329 | } else { 330 | kill 'QUIT', $pid; 331 | } 332 | 333 | waitpid($pid, 0); 334 | 335 | $self->{_started} = 0; 336 | 337 | return $self; 338 | } 339 | 340 | sub stop_daemons() { 341 | my ($self) = @_; 342 | 343 | while ($self->{_daemons} && scalar @{$self->{_daemons}}) { 344 | my $p = shift @{$self->{_daemons}}; 345 | kill $^O eq 'MSWin32' ? 9 : 'TERM', $p; 346 | waitpid($p, 0); 347 | } 348 | 349 | return $self; 350 | } 351 | 352 | sub read_file($) { 353 | my ($self, $name) = @_; 354 | local $/; 355 | 356 | open F, '<', $self->{_testdir} . '/' . $name or die "Can't open $name: $!"; 357 | my $content = ; 358 | close F; 359 | 360 | return $content; 361 | } 362 | 363 | sub write_file($$) { 364 | my ($self, $name, $content) = @_; 365 | 366 | open F, '>' . $self->{_testdir} . '/' . $name 367 | or die "Can't create $name: $!"; 368 | print F $content; 369 | close F; 370 | 371 | return $self; 372 | } 373 | 374 | sub write_file_expand($$) { 375 | my ($self, $name, $content) = @_; 376 | 377 | $content =~ s/%%TEST_GLOBALS%%/$self->test_globals()/gmse; 378 | $content =~ s/%%TEST_GLOBALS_DSO%%/$self->test_globals_dso()/gmse; 379 | $content =~ s/%%TEST_GLOBALS_HTTP%%/$self->test_globals_http()/gmse; 380 | $content =~ s/%%TESTDIR%%/$self->{_testdir}/gms; 381 | 382 | return $self->write_file($name, $content); 383 | } 384 | 385 | sub run_daemon($;@) { 386 | my ($self, $code, @args) = @_; 387 | 388 | my $pid = fork(); 389 | die "Can't fork daemon: $!\n" unless defined $pid; 390 | 391 | if ($pid == 0) { 392 | if (ref($code) eq 'CODE') { 393 | $code->(@args); 394 | exit 0; 395 | } else { 396 | exec($code, @args); 397 | exit 0; 398 | } 399 | } 400 | 401 | $self->{_daemons} = [] unless defined $self->{_daemons}; 402 | push @{$self->{_daemons}}, $pid; 403 | 404 | return $self; 405 | } 406 | 407 | sub testdir() { 408 | my ($self) = @_; 409 | return $self->{_testdir}; 410 | } 411 | 412 | sub test_globals() { 413 | my ($self) = @_; 414 | 415 | return $self->{_test_globals} 416 | if defined $self->{_test_globals}; 417 | 418 | my $s = ''; 419 | 420 | $s .= "pid $self->{_testdir}/nginx.pid;\n"; 421 | $s .= "error_log $self->{_testdir}/error.log debug;\n"; 422 | 423 | $s .= $ENV{TEST_NGINX_GLOBALS} 424 | if $ENV{TEST_NGINX_GLOBALS}; 425 | $self->{_test_globals} = $s; 426 | } 427 | 428 | sub test_globals_dso() { 429 | my ($self) = @_; 430 | 431 | return "" unless defined $ENV{TEST_NGINX_DSO}; 432 | 433 | return $self->{_test_globals_dso} if defined $self->{_test_globals_dso}; 434 | 435 | my $s = ''; 436 | 437 | $s .= "dso {\n"; 438 | if (defined $ENV{NGINX_DSO_PATH}) { 439 | $s .= "path $ENV{NGINX_DSO_PATH};\n"; 440 | } 441 | 442 | while ( my ($key, $value) = each(%{$self->{_dso_module}}) ) { 443 | $s .= "load $key $value;\n"; 444 | } 445 | $s .= "}\n"; 446 | 447 | $self->{_test_globals_dso} = $s; 448 | } 449 | 450 | sub test_globals_http() { 451 | my ($self) = @_; 452 | 453 | return $self->{_test_globals_http} 454 | if defined $self->{_test_globals_http}; 455 | 456 | my $s = ''; 457 | 458 | $s .= "root $self->{_testdir};\n"; 459 | $s .= "access_log $self->{_testdir}/access.log;\n"; 460 | $s .= "client_body_temp_path $self->{_testdir}/client_body_temp;\n"; 461 | 462 | $s .= "fastcgi_temp_path $self->{_testdir}/fastcgi_temp;\n" 463 | if $self->has_module('fastcgi'); 464 | 465 | $s .= "proxy_temp_path $self->{_testdir}/proxy_temp;\n" 466 | if $self->has_module('proxy'); 467 | 468 | $s .= "uwsgi_temp_path $self->{_testdir}/uwsgi_temp;\n" 469 | if $self->has_module('uwsgi'); 470 | 471 | $s .= "scgi_temp_path $self->{_testdir}/scgi_temp;\n" 472 | if $self->has_module('scgi'); 473 | 474 | $s .= $ENV{TEST_NGINX_GLOBALS_HTTP} 475 | if $ENV{TEST_NGINX_GLOBALS_HTTP}; 476 | 477 | $self->{_test_globals_http} = $s; 478 | } 479 | 480 | ############################################################################### 481 | 482 | sub log_core { 483 | return unless $ENV{TEST_NGINX_VERBOSE}; 484 | my ($prefix, $msg) = @_; 485 | ($prefix, $msg) = ('', $prefix) unless defined $msg; 486 | $prefix .= ' ' if length($prefix) > 0; 487 | 488 | if (length($msg) > 2048) { 489 | $msg = substr($msg, 0, 2048) 490 | . "(...logged only 2048 of " . length($msg) 491 | . " bytes)"; 492 | } 493 | 494 | $msg =~ s/^/# $prefix/gm; 495 | $msg =~ s/([^\x20-\x7e])/sprintf('\\x%02x', ord($1)) . (($1 eq "\n") ? "\n" : '')/gmxe; 496 | $msg .= "\n" unless $msg =~ /\n\Z/; 497 | print $msg; 498 | } 499 | 500 | sub log_out { 501 | log_core('>>', @_); 502 | } 503 | 504 | sub log_in { 505 | log_core('<<', @_); 506 | } 507 | 508 | ############################################################################### 509 | 510 | sub http_get($;%) { 511 | my ($url, %extra) = @_; 512 | return http(<new( 547 | Proto => 'tcp', 548 | PeerAddr => '127.0.0.1:8080' 549 | ) 550 | or die "Can't connect to nginx: $!\n"; 551 | 552 | log_out($request); 553 | $s->print($request); 554 | 555 | select undef, undef, undef, $extra{sleep} if $extra{sleep}; 556 | return '' if $extra{aborted}; 557 | 558 | if ($extra{body}) { 559 | log_out($extra{body}); 560 | $s->print($extra{body}); 561 | } 562 | 563 | alarm(0); 564 | }; 565 | alarm(0); 566 | if ($@) { 567 | log_in("died: $@"); 568 | return undef; 569 | } 570 | 571 | return $s; 572 | } 573 | 574 | sub http_end($;%) { 575 | my ($s) = @_; 576 | my $reply; 577 | 578 | eval { 579 | local $SIG{ALRM} = sub { die "timeout\n" }; 580 | local $SIG{PIPE} = sub { die "sigpipe\n" }; 581 | alarm(5); 582 | 583 | local $/; 584 | $reply = $s->getline(); 585 | 586 | alarm(0); 587 | }; 588 | alarm(0); 589 | if ($@) { 590 | log_in("died: $@"); 591 | return undef; 592 | } 593 | 594 | log_in($reply); 595 | return $reply; 596 | } 597 | 598 | ############################################################################### 599 | 600 | sub http_gzip_request { 601 | my ($url) = @_; 602 | my $r = http(<builder->skip( 642 | "IO::Uncompress::Gunzip not installed", 1) if $@; 643 | 644 | my $in = http_content($text); 645 | my $out; 646 | 647 | IO::Uncompress::Gunzip::gunzip(\$in => \$out); 648 | 649 | Test::More->builder->like($out, $re, $name); 650 | } 651 | } 652 | 653 | ############################################################################### 654 | 655 | 1; 656 | 657 | ############################################################################### 658 | -------------------------------------------------------------------------------- /test/t/lib/Test/Nginx/IMAP.pm: -------------------------------------------------------------------------------- 1 | package Test::Nginx::IMAP; 2 | 3 | # (C) Maxim Dounin 4 | 5 | # Module for nginx imap tests. 6 | 7 | ############################################################################### 8 | 9 | use warnings; 10 | use strict; 11 | 12 | use Test::More qw//; 13 | use IO::Socket; 14 | use Socket qw/ CRLF /; 15 | 16 | use Test::Nginx; 17 | 18 | use base qw/ IO::Socket::INET /; 19 | 20 | sub new { 21 | my $class = shift; 22 | 23 | my $self = return $class->SUPER::new( 24 | Proto => "tcp", 25 | PeerAddr => "127.0.0.1:8143", 26 | @_ 27 | ) 28 | or die "Can't connect to nginx: $!\n"; 29 | 30 | $self->autoflush(1); 31 | 32 | return $self; 33 | } 34 | 35 | sub send { 36 | my ($self, $cmd) = @_; 37 | log_out($cmd); 38 | $self->print($cmd . CRLF); 39 | } 40 | 41 | sub read { 42 | my ($self) = @_; 43 | eval { 44 | local $SIG{ALRM} = sub { die "timeout\n" }; 45 | alarm(3); 46 | while (<$self>) { 47 | log_in($_); 48 | # XXX 49 | next if m/^\d\d\d-/; 50 | last; 51 | } 52 | alarm(0); 53 | }; 54 | alarm(0); 55 | if ($@) { 56 | log_in("died: $@"); 57 | return undef; 58 | } 59 | return $_; 60 | } 61 | 62 | sub check { 63 | my ($self, $regex, $name) = @_; 64 | Test::More->builder->like($self->read(), $regex, $name); 65 | } 66 | 67 | sub ok { 68 | my $self = shift; 69 | Test::More->builder->like($self->read(), qr/^\S+ OK/, @_); 70 | } 71 | 72 | ############################################################################### 73 | 74 | sub imap_test_daemon { 75 | my $server = IO::Socket::INET->new( 76 | Proto => 'tcp', 77 | LocalAddr => '127.0.0.1:8144', 78 | Listen => 5, 79 | Reuse => 1 80 | ) 81 | or die "Can't create listening socket: $!\n"; 82 | 83 | while (my $client = $server->accept()) { 84 | $client->autoflush(1); 85 | print $client "* OK fake imap server ready" . CRLF; 86 | 87 | while (<$client>) { 88 | my $tag = ''; 89 | 90 | $tag = $1 if m/^(\S+)/; 91 | s/^(\S+)\s+//; 92 | 93 | if (/^logout/i) { 94 | print $client $tag . ' OK logout ok' . CRLF; 95 | } elsif (/^login /i) { 96 | print $client $tag . ' OK login ok' . CRLF; 97 | } else { 98 | print $client $tag . ' ERR unknown command' . CRLF; 99 | } 100 | } 101 | 102 | close $client; 103 | } 104 | } 105 | 106 | ############################################################################### 107 | 108 | 1; 109 | 110 | ############################################################################### 111 | -------------------------------------------------------------------------------- /test/t/lib/Test/Nginx/POP3.pm: -------------------------------------------------------------------------------- 1 | package Test::Nginx::POP3; 2 | 3 | # (C) Maxim Dounin 4 | 5 | # Module for nginx pop3 tests. 6 | 7 | ############################################################################### 8 | 9 | use warnings; 10 | use strict; 11 | 12 | use Test::More qw//; 13 | use IO::Socket; 14 | use Socket qw/ CRLF /; 15 | 16 | use Test::Nginx; 17 | 18 | use base qw/ IO::Socket::INET /; 19 | 20 | sub new { 21 | my $class = shift; 22 | 23 | my $self = return $class->SUPER::new( 24 | Proto => "tcp", 25 | PeerAddr => "127.0.0.1:8110", 26 | @_ 27 | ) 28 | or die "Can't connect to nginx: $!\n"; 29 | 30 | $self->autoflush(1); 31 | 32 | return $self; 33 | } 34 | 35 | sub send { 36 | my ($self, $cmd) = @_; 37 | log_out($cmd); 38 | $self->print($cmd . CRLF); 39 | } 40 | 41 | sub read { 42 | my ($self) = @_; 43 | eval { 44 | local $SIG{ALRM} = sub { die "timeout\n" }; 45 | alarm(3); 46 | while (<$self>) { 47 | log_in($_); 48 | # XXX 49 | next if m/^\d\d\d-/; 50 | last; 51 | } 52 | alarm(0); 53 | }; 54 | alarm(0); 55 | if ($@) { 56 | log_in("died: $@"); 57 | return undef; 58 | } 59 | return $_; 60 | } 61 | 62 | sub check { 63 | my ($self, $regex, $name) = @_; 64 | Test::More->builder->like($self->read(), $regex, $name); 65 | } 66 | 67 | sub ok { 68 | my $self = shift; 69 | Test::More->builder->like($self->read(), qr/^\+OK/, @_); 70 | } 71 | 72 | ############################################################################### 73 | 74 | sub pop3_test_daemon { 75 | my $server = IO::Socket::INET->new( 76 | Proto => 'tcp', 77 | LocalAddr => '127.0.0.1:8111', 78 | Listen => 5, 79 | Reuse => 1 80 | ) 81 | or die "Can't create listening socket: $!\n"; 82 | 83 | while (my $client = $server->accept()) { 84 | $client->autoflush(1); 85 | print $client "+OK fake pop3 server ready" . CRLF; 86 | 87 | while (<$client>) { 88 | if (/^quit/i) { 89 | print $client '+OK quit ok' . CRLF; 90 | } elsif (/^user test\@example.com/i) { 91 | print $client '+OK user ok' . CRLF; 92 | } elsif (/^pass secret/i) { 93 | print $client '+OK pass ok' . CRLF; 94 | } else { 95 | print $client "-ERR unknown command" . CRLF; 96 | } 97 | } 98 | 99 | close $client; 100 | } 101 | } 102 | 103 | ############################################################################### 104 | 105 | 1; 106 | 107 | ############################################################################### 108 | -------------------------------------------------------------------------------- /test/t/lib/Test/Nginx/SMTP.pm: -------------------------------------------------------------------------------- 1 | package Test::Nginx::SMTP; 2 | 3 | # (C) Maxim Dounin 4 | 5 | # Module for nginx smtp tests. 6 | 7 | ############################################################################### 8 | 9 | use warnings; 10 | use strict; 11 | 12 | use Test::More qw//; 13 | use IO::Socket; 14 | use Socket qw/ CRLF /; 15 | 16 | use Test::Nginx; 17 | 18 | use base qw/ IO::Socket::INET /; 19 | 20 | sub new { 21 | my $class = shift; 22 | 23 | my $self = return $class->SUPER::new( 24 | Proto => "tcp", 25 | PeerAddr => "127.0.0.1:8025", 26 | @_ 27 | ) 28 | or die "Can't connect to nginx: $!\n"; 29 | 30 | $self->autoflush(1); 31 | 32 | return $self; 33 | } 34 | 35 | sub send { 36 | my ($self, $cmd) = @_; 37 | log_out($cmd); 38 | $self->print($cmd . CRLF); 39 | } 40 | 41 | sub read { 42 | my ($self) = @_; 43 | eval { 44 | local $SIG{ALRM} = sub { die "timeout\n" }; 45 | alarm(3); 46 | while (<$self>) { 47 | log_in($_); 48 | next if m/^\d\d\d-/; 49 | last; 50 | } 51 | alarm(0); 52 | }; 53 | alarm(0); 54 | if ($@) { 55 | log_in("died: $@"); 56 | return undef; 57 | } 58 | return $_; 59 | } 60 | 61 | sub check { 62 | my ($self, $regex, $name) = @_; 63 | Test::More->builder->like($self->read(), $regex, $name); 64 | } 65 | 66 | sub ok { 67 | my $self = shift; 68 | Test::More->builder->like($self->read(), qr/^2\d\d /, @_); 69 | } 70 | 71 | sub authok { 72 | my $self = shift; 73 | Test::More->builder->like($self->read(), qr/^235 /, @_); 74 | } 75 | 76 | ############################################################################### 77 | 78 | sub smtp_test_daemon { 79 | my $server = IO::Socket::INET->new( 80 | Proto => 'tcp', 81 | LocalAddr => '127.0.0.1:8026', 82 | Listen => 5, 83 | Reuse => 1 84 | ) 85 | or die "Can't create listening socket: $!\n"; 86 | 87 | while (my $client = $server->accept()) { 88 | $client->autoflush(1); 89 | print $client "220 fake esmtp server ready" . CRLF; 90 | 91 | while (<$client>) { 92 | Test::Nginx::log_core('||', $_); 93 | 94 | if (/^quit/i) { 95 | print $client '221 quit ok' . CRLF; 96 | } elsif (/^(ehlo|helo)/i) { 97 | print $client '250 hello ok' . CRLF; 98 | } elsif (/^rset/i) { 99 | print $client '250 rset ok' . CRLF; 100 | } elsif (/^mail from:[^@]+$/i) { 101 | print $client '500 mail from error' . CRLF; 102 | } elsif (/^mail from:/i) { 103 | print $client '250 mail from ok' . CRLF; 104 | } elsif (/^rcpt to:[^@]+$/i) { 105 | print $client '500 rcpt to error' . CRLF; 106 | } elsif (/^rcpt to:/i) { 107 | print $client '250 rcpt to ok' . CRLF; 108 | } elsif (/^xclient/i) { 109 | print $client '220 xclient ok' . CRLF; 110 | } else { 111 | print $client "500 unknown command" . CRLF; 112 | } 113 | } 114 | 115 | close $client; 116 | } 117 | } 118 | 119 | ############################################################################### 120 | 121 | 1; 122 | 123 | ############################################################################### 124 | -------------------------------------------------------------------------------- /test/t/lib/Time/Parse.pm: -------------------------------------------------------------------------------- 1 | # Date::Parse 2 | # 3 | # Copyright (c) 1995 Graham Barr. All rights reserved. This program is free 4 | # software; you can redistribute it and/or modify it under the same terms 5 | # as Perl itself. 6 | 7 | package Time::Parse; 8 | 9 | =head1 NAME 10 | 11 | Date::Parse - Parse date strings into time values 12 | 13 | =head1 SYNOPSIS 14 | 15 | use Date::Parse; 16 | 17 | $time = str2time($date); 18 | 19 | ($ss,$mm,$hh,$day,$month,$year,$zone) = strptime($date); 20 | 21 | =head1 DESCRIPTION 22 | 23 | C provides two routines for parsing date strings into time values. 24 | 25 | =over 4 26 | 27 | =item str2time(DATE [, ZONE]) 28 | 29 | C parses C and returns a unix time value, or undef upon failure. 30 | C, if given, specifies the timezone to assume when parsing if the 31 | date string does not specify a timezome. 32 | 33 | =item strptime(DATE [, ZONE]) 34 | 35 | C takes the same arguments as str2time but returns an array of 36 | values C<($ss,$mm,$hh,$day,$month,$year,$zone)>. Elements are only defined 37 | if they could be extracted from the date string. The C<$zone> element is 38 | the timezone offset in seconds from GMT. An empty array is returned upon 39 | failure. 40 | 41 | =head1 MULTI-LANGUAGE SUPPORT 42 | 43 | Date::Parse is capable of parsing dates in several languages, these are 44 | English, French, German and Italian. Changing the language is done via 45 | a static method call, for example 46 | 47 | Date::Parse->language('German'); 48 | 49 | will cause Date::Parse to attempt to parse any subsequent dates in German. 50 | 51 | This is only a first pass, I am considering changing this to be 52 | 53 | $lang = Date::Language->new('German'); 54 | $lang->str2time("25 Jun 1996 21:09:55 +0100"); 55 | 56 | I am open to suggestions on this. 57 | 58 | =head1 AUTHOR 59 | 60 | Graham Barr 61 | 62 | =head1 REVISION 63 | 64 | $Revision: 2.6 $ 65 | 66 | =head1 COPYRIGHT 67 | 68 | Copyright (c) 1995 Graham Barr. All rights reserved. This program is free 69 | software; you can redistribute it and/or modify it under the same terms 70 | as Perl itself. 71 | 72 | =cut 73 | 74 | require 5.000; 75 | use strict; 76 | use vars qw($VERSION @ISA @EXPORT); 77 | use Time::Local; 78 | use Carp; 79 | use Time::Zone; 80 | use Exporter; 81 | 82 | @ISA = qw(Exporter); 83 | @EXPORT = qw(&strtotime &str2time &strptime); 84 | 85 | $VERSION = sprintf("%d.%02d", q$Revision: 2.6 $ =~ m#(\d+)\.(\d+)#); 86 | 87 | my %month = ( 88 | january => 0, 89 | february => 1, 90 | march => 2, 91 | april => 3, 92 | may => 4, 93 | june => 5, 94 | july => 6, 95 | august => 7, 96 | september => 8, 97 | sept => 8, 98 | october => 9, 99 | november => 10, 100 | december => 11, 101 | ); 102 | 103 | my %day = ( 104 | sunday => 0, 105 | monday => 1, 106 | tuesday => 2, 107 | tues => 2, 108 | wednesday => 3, 109 | wednes => 3, 110 | thursday => 4, 111 | thur => 4, 112 | thurs => 4, 113 | friday => 5, 114 | saturday => 6, 115 | ); 116 | 117 | my @suf = (qw(th st nd rd th th th th th th)) x 3; 118 | @suf[11,12,13] = qw(th th th); 119 | 120 | #Abbreviations 121 | 122 | map { $month{substr($_,0,3)} = $month{$_} } keys %month; 123 | map { $day{substr($_,0,3)} = $day{$_} } keys %day; 124 | 125 | my $strptime = <<'ESQ'; 126 | my %month = map { lc $_ } %$mon_ref; 127 | my $daypat = join("|", map { lc $_ } keys %$day_ref); 128 | my $monpat = join("|", keys %month); 129 | my $sufpat = join("|", map { lc $_ } @$suf_ref); 130 | 131 | my %ampm = ( 132 | am => 0, 133 | pm => 12 134 | ); 135 | 136 | # allow map am +. a.m. 137 | map { my($z) = $_; $z =~ s#(\w)#$1\.#g; $ampm{$z} = $ampm{$_} } keys %ampm; 138 | 139 | my($AM, $PM) = (0,12); 140 | 141 | sub 142 | { 143 | 144 | my $dtstr = lc shift; 145 | my $merid = 24; 146 | 147 | my($year,$month,$day,$hh,$mm,$ss,$zone) = (undef) x 7; 148 | 149 | $zone = tz_offset(shift) 150 | if(@_); 151 | 152 | while(1) { last unless($dtstr =~ s#\([^\(\)]*\)# #o) } 153 | 154 | $dtstr =~ s#(\A|\n|\Z)# #sog; 155 | 156 | # ignore day names 157 | $dtstr =~ s#([\d\w\s])[\.\,]\s#$1 #sog; 158 | $dtstr =~ s#($daypat)\s*(den\s)?# #o; 159 | # Time: 12:00 or 12:00:00 with optional am/pm 160 | 161 | if($dtstr =~ s#[:\s](\d\d?):(\d\d)(:(\d\d)(?:\.\d+)?)?\s*([ap]\.?m\.?)?\s# #o) 162 | { 163 | ($hh,$mm,$ss) = ($1,$2,$4 || 0); 164 | $merid = $ampm{$5} if($5); 165 | } 166 | 167 | # Time: 12 am 168 | 169 | elsif($dtstr =~ s#\s(\d\d?)\s*([ap]\.?m\.?)\s# #o) 170 | { 171 | ($hh,$mm,$ss) = ($1,0,0); 172 | $merid = $ampm{$2}; 173 | } 174 | 175 | # Date: 12-June-96 (using - . or /) 176 | 177 | if($dtstr =~ s#\s(\d\d?)([\-\./])($monpat)(\2(\d\d+))?\s# #o) 178 | { 179 | ($month,$day) = ($month{$3},$1); 180 | $year = $5 181 | if($5); 182 | } 183 | 184 | # Date: 12-12-96 (using '-', '.' or '/' ) 185 | 186 | elsif($dtstr =~ s#\s(\d\d*)([\-\./])(\d\d?)(\2(\d\d+))?\s# #o) 187 | { 188 | ($month,$day) = ($1 - 1,$3); 189 | if($5) 190 | { 191 | $year = $5; 192 | # Possible match for 1995-01-24 (short mainframe date format); 193 | ($year,$month,$day) = ($1, $3 - 1, $5) 194 | if($month > 12); 195 | } 196 | } 197 | elsif($dtstr =~ s#\s(\d+)\s*($sufpat)?\s*($monpat)# #o) 198 | { 199 | ($month,$day) = ($month{$3},$1); 200 | } 201 | elsif($dtstr =~ s#($monpat)\s*(\d+)\s*($sufpat)?\s# #o) 202 | { 203 | ($month,$day) = ($month{$1},$2); 204 | } 205 | 206 | # Date: 961212 207 | 208 | elsif($dtstr =~ s#\s(\d\d)(\d\d)(\d\d)\s# #o) 209 | { 210 | ($year,$month,$day) = ($1,$2-1,$3); 211 | } 212 | 213 | $year = $1 214 | if(!defined($year) && $dtstr =~ s#\s(\d{2}(\d{2})?)[\s\.,]# #o); 215 | 216 | # Zone 217 | 218 | if($dtstr =~ s#\s"?(\w{3,})\s# #o) 219 | { 220 | $zone = tz_offset($1); 221 | return () 222 | unless(defined $zone); 223 | } 224 | elsif($dtstr =~ s#\s(([\-\+])\d\d?)(\d\d)\s# #o) 225 | { 226 | my $m = $2 . $3; 227 | $zone = 60 * ($m + (60 * $1)); 228 | } 229 | 230 | return () 231 | if($dtstr =~ /\S/o); 232 | 233 | $hh += 12 234 | if(defined $hh && $merid == $PM); 235 | 236 | $year -= 1900 237 | if(defined $year && $year > 1900); 238 | 239 | return ($ss,$mm,$hh,$day,$month,$year,$zone); 240 | } 241 | ESQ 242 | 243 | use vars qw($day_ref $mon_ref $suf_ref $obj); 244 | 245 | sub gen_parser 246 | { 247 | local($day_ref,$mon_ref,$suf_ref,$obj) = @_; 248 | 249 | if($obj) 250 | { 251 | my $obj_strptime = $strptime; 252 | substr($obj_strptime,index($strptime,"sub")+6,0) = <<'ESQ'; 253 | shift; # package 254 | ESQ 255 | return eval "$obj_strptime"; 256 | } 257 | 258 | eval "$strptime"; 259 | 260 | } 261 | 262 | *strptime = gen_parser(\%day,\%month,\@suf); 263 | 264 | sub str2time 265 | { 266 | my @t = strptime(@_); 267 | 268 | return undef 269 | unless @t; 270 | 271 | my($ss,$mm,$hh,$day,$month,$year,$zone) = @t; 272 | my @lt = localtime(time); 273 | 274 | $hh ||= 0; 275 | $mm ||= 0; 276 | $ss ||= 0; 277 | 278 | $month = $lt[4] 279 | unless(defined $month); 280 | 281 | $day = $lt[3] 282 | unless(defined $day); 283 | 284 | $year = ($month > $lt[4]) ? ($lt[5] - 1) : $lt[5] 285 | unless(defined $year); 286 | 287 | return defined $zone ? timegm($ss,$mm,$hh,$day,$month,$year) - $zone 288 | : timelocal($ss,$mm,$hh,$day,$month,$year); 289 | } 290 | 291 | 1; 292 | 293 | -------------------------------------------------------------------------------- /test/t/lib/Time/Zone.pm: -------------------------------------------------------------------------------- 1 | 2 | package Time::Zone; 3 | 4 | =head1 NAME 5 | 6 | Time::Zone -- miscellaneous timezone manipulations routines 7 | 8 | =head1 SYNOPSIS 9 | 10 | use Time::Zone; 11 | print tz2zone(); 12 | print tz2zone($ENV{'TZ'}); 13 | print tz2zone($ENV{'TZ'}, time()); 14 | print tz2zone($ENV{'TZ'}, undef, $isdst); 15 | $offset = tz_local_offset(); 16 | $offset = tz_offset($TZ); 17 | 18 | =head1 DESCRIPTION 19 | 20 | This is a collection of miscellaneous timezone manipulation routines. 21 | 22 | C parses the TZ environment variable and returns a timezone 23 | string suitable for inclusion in L-like output. It opionally takes 24 | a timezone string, a time, and a is-dst flag. 25 | 26 | C determines the offset from GMT time in seconds. It 27 | only does the calculation once. 28 | 29 | C determines the offset from GMT in seconds of a specified 30 | timezone. 31 | 32 | C determines the name of the timezone based on its offset 33 | 34 | =head1 AUTHORS 35 | 36 | Graham Barr 37 | David Muir Sharnoff 38 | Paul Foley 39 | 40 | =cut 41 | 42 | require 5.002; 43 | 44 | require Exporter; 45 | use Carp; 46 | use strict; 47 | use vars qw(@ISA @EXPORT $VERSION @tz_local); 48 | 49 | @ISA = qw(Exporter); 50 | @EXPORT = qw(tz2zone tz_local_offset tz_offset tz_name); 51 | $VERSION = "2.04"; 52 | 53 | # Parts stolen from code by Paul Foley 54 | 55 | sub tz2zone (;$$$) 56 | { 57 | my($TZ, $time, $isdst) = @_; 58 | 59 | use vars qw(%tzn_cache); 60 | 61 | $TZ = defined($ENV{'TZ'}) ? ( $ENV{'TZ'} ? $ENV{'TZ'} : 'GMT' ) : '' 62 | unless $TZ; 63 | 64 | # Hack to deal with 'PST8PDT' format of TZ 65 | # Note that this can't deal with all the esoteric forms, but it 66 | # does recognize the most common: [:]STDoff[DST[off][,rule]] 67 | 68 | if (! defined $isdst) { 69 | my $j; 70 | $time = time() unless $time; 71 | ($j, $j, $j, $j, $j, $j, $j, $j, $isdst) = localtime($time); 72 | } 73 | 74 | if (defined $tzn_cache{$TZ}->[$isdst]) { 75 | return $tzn_cache{$TZ}->[$isdst]; 76 | } 77 | 78 | if ($TZ =~ /^ 79 | ( [^:\d+\-,] {3,} ) 80 | ( [+-] ? 81 | \d {1,2} 82 | ( : \d {1,2} ) {0,2} 83 | ) 84 | ( [^\d+\-,] {3,} )? 85 | /x 86 | ) { 87 | $TZ = $isdst ? $4 : $1; 88 | $tzn_cache{$TZ} = [ $1, $4 ]; 89 | } else { 90 | $tzn_cache{$TZ} = [ $TZ, $TZ ]; 91 | } 92 | return $TZ; 93 | } 94 | 95 | sub tz_local_offset (;$) 96 | { 97 | my ($time) = @_; 98 | 99 | $time = time() unless $time; 100 | my (@l) = localtime($time); 101 | my $isdst = $l[8]; 102 | 103 | if (defined($tz_local[$isdst])) { 104 | return $tz_local[$isdst]; 105 | } 106 | 107 | $tz_local[$isdst] = &calc_off($time); 108 | 109 | return $tz_local[$isdst]; 110 | } 111 | 112 | sub calc_off 113 | { 114 | my ($time) = @_; 115 | 116 | my (@l) = localtime($time); 117 | my (@g) = gmtime($time); 118 | 119 | my $off; 120 | 121 | $off = $l[0] - $g[0] 122 | + ($l[1] - $g[1]) * 60 123 | + ($l[2] - $g[2]) * 3600; 124 | 125 | # subscript 7 is yday. 126 | 127 | if ($l[7] == $g[7]) { 128 | # done 129 | } elsif ($l[7] == $g[7] + 1) { 130 | $off += 86400; 131 | } elsif ($l[7] == $g[7] - 1) { 132 | $off -= 86400; 133 | } elsif ($l[7] < $g[7]) { 134 | # crossed over a year boundary! 135 | # localtime is beginning of year, gmt is end 136 | # therefore local is ahead 137 | $off += 86400; 138 | } else { 139 | $off -= 86400; 140 | } 141 | 142 | return $off; 143 | } 144 | 145 | # constants 146 | 147 | CONFIG: { 148 | use vars qw(%dstZone %zoneOff %dstZoneOff %Zone); 149 | 150 | %dstZone = ( 151 | # "ndt" => -2*3600-1800, # Newfoundland Daylight 152 | "adt" => -3*3600, # Atlantic Daylight 153 | "edt" => -4*3600, # Eastern Daylight 154 | "cdt" => -5*3600, # Central Daylight 155 | "mdt" => -6*3600, # Mountain Daylight 156 | "pdt" => -7*3600, # Pacific Daylight 157 | "ydt" => -8*3600, # Yukon Daylight 158 | "hdt" => -9*3600, # Hawaii Daylight 159 | "bst" => +1*3600, # British Summer 160 | "mest" => +2*3600, # Middle European Summer 161 | "sst" => +2*3600, # Swedish Summer 162 | "fst" => +2*3600, # French Summer 163 | "wadt" => +8*3600, # West Australian Daylight 164 | # "cadt" => +10*3600+1800, # Central Australian Daylight 165 | "eadt" => +11*3600, # Eastern Australian Daylight 166 | "nzdt" => +13*3600, # New Zealand Daylight 167 | ); 168 | 169 | %Zone = ( 170 | "gmt" => 0, # Greenwich Mean 171 | "ut" => 0, # Universal (Coordinated) 172 | "utc" => 0, 173 | "wet" => 0, # Western European 174 | "wat" => -1*3600, # West Africa 175 | "at" => -2*3600, # Azores 176 | # For completeness. BST is also British Summer, and GST is also Guam Standard. 177 | # "bst" => -3*3600, # Brazil Standard 178 | # "gst" => -3*3600, # Greenland Standard 179 | # "nft" => -3*3600-1800,# Newfoundland 180 | # "nst" => -3*3600-1800,# Newfoundland Standard 181 | "ast" => -4*3600, # Atlantic Standard 182 | "est" => -5*3600, # Eastern Standard 183 | "cst" => -6*3600, # Central Standard 184 | "mst" => -7*3600, # Mountain Standard 185 | "pst" => -8*3600, # Pacific Standard 186 | "yst" => -9*3600, # Yukon Standard 187 | "hst" => -10*3600, # Hawaii Standard 188 | "cat" => -10*3600, # Central Alaska 189 | "ahst" => -10*3600, # Alaska-Hawaii Standard 190 | "nt" => -11*3600, # Nome 191 | "idlw" => -12*3600, # International Date Line West 192 | "cet" => +1*3600, # Central European 193 | "met" => +1*3600, # Middle European 194 | "mewt" => +1*3600, # Middle European Winter 195 | "swt" => +1*3600, # Swedish Winter 196 | "fwt" => +1*3600, # French Winter 197 | "eet" => +2*3600, # Eastern Europe, USSR Zone 1 198 | "bt" => +3*3600, # Baghdad, USSR Zone 2 199 | # "it" => +3*3600+1800,# Iran 200 | "zp4" => +4*3600, # USSR Zone 3 201 | "zp5" => +5*3600, # USSR Zone 4 202 | # "ist" => +5*3600+1800,# Indian Standard 203 | "zp6" => +6*3600, # USSR Zone 5 204 | # For completeness. NST is also Newfoundland Stanard, and SST is also Swedish Summer. 205 | # "nst" => +6*3600+1800,# North Sumatra 206 | # "sst" => +7*3600, # South Sumatra, USSR Zone 6 207 | "wast" => +7*3600, # West Australian Standard 208 | # "jt" => +7*3600+1800,# Java (3pm in Cronusland!) 209 | "cct" => +8*3600, # China Coast, USSR Zone 7 210 | "jst" => +9*3600, # Japan Standard, USSR Zone 8 211 | # "cast" => +9*3600+1800,# Central Australian Standard 212 | "east" => +10*3600, # Eastern Australian Standard 213 | "gst" => +10*3600, # Guam Standard, USSR Zone 9 214 | "nzt" => +12*3600, # New Zealand 215 | "nzst" => +12*3600, # New Zealand Standard 216 | "idle" => +12*3600, # International Date Line East 217 | ); 218 | 219 | %zoneOff = reverse(%Zone); 220 | %dstZoneOff = reverse(%dstZone); 221 | 222 | # Preferences 223 | 224 | $zoneOff{0} = 'gmt'; 225 | $dstZoneOff{3600} = 'bst'; 226 | 227 | } 228 | 229 | sub tz_offset (;$$) 230 | { 231 | my ($zone, $time) = @_; 232 | 233 | return &tz_local_offset() unless($zone); 234 | 235 | $time = time() unless $time; 236 | my(@l) = localtime($time); 237 | my $dst = $l[8]; 238 | 239 | $zone = lc $zone; 240 | 241 | if($zone =~ /^(([\-\+])\d\d?)(\d\d)$/) { 242 | my $v = $2 . $3; 243 | return $1 * 3600 + $v * 60; 244 | } elsif (exists $dstZone{$zone} && ($dst || !exists $Zone{$zone})) { 245 | return $dstZone{$zone}; 246 | } elsif(exists $Zone{$zone}) { 247 | return $Zone{$zone}; 248 | } 249 | undef; 250 | } 251 | 252 | sub tz_name (;$$) 253 | { 254 | my ($off, $dst) = @_; 255 | 256 | $off = tz_offset() 257 | unless(defined $off); 258 | 259 | $dst = (localtime(time))[8] 260 | unless(defined $dst); 261 | 262 | if (exists $dstZoneOff{$off} && ($dst || !exists $zoneOff{$off})) { 263 | return $dstZoneOff{$off}; 264 | } elsif (exists $zoneOff{$off}) { 265 | return $zoneOff{$off}; 266 | } 267 | sprintf("%+05d", int($off / 60) * 100 + $off % 60); 268 | } 269 | 270 | 1; 271 | -------------------------------------------------------------------------------- /test/t/upsync.t: -------------------------------------------------------------------------------- 1 | use warnings; 2 | use strict; 3 | 4 | use Test::More; 5 | 6 | BEGIN { use FindBin; chdir($FindBin::Bin); } 7 | 8 | 9 | use lib 'lib'; 10 | use File::Path; 11 | use Test::Nginx; 12 | 13 | 14 | my $NGINX = defined $ENV{TEST_NGINX_BINARY} ? $ENV{TEST_NGINX_BINARY} 15 | : '../../nginx/objs/nginx'; 16 | my $t = Test::Nginx->new()->plan(58); 17 | 18 | sub mhttp_get($;$;$;%) { 19 | my ($url, $host, $port, %extra) = @_; 20 | return mhttp(< }; 54 | 55 | close $fd; 56 | 57 | } else { 58 | $content = 'The file could not be opened.'; 59 | } 60 | 61 | return $content; 62 | } 63 | 64 | sub mrun($;$) { 65 | my ($self, $conf) = @_; 66 | 67 | my $testdir = $self->{_testdir}; 68 | 69 | if (defined $conf) { 70 | my $c = `cat $conf`; 71 | $self->write_file_expand('nginx.conf', $c); 72 | } 73 | 74 | my $pid = fork(); 75 | die "Unable to fork(): $!\n" unless defined $pid; 76 | 77 | if ($pid == 0) { 78 | my @globals = $self->{_test_globals} ? 79 | () : ('-g', "pid $testdir/nginx.pid; " 80 | . "error_log $testdir/error.log debug;"); 81 | exec($NGINX, '-c', "$testdir/nginx.conf", '-p', "$testdir", 82 | @globals) or die "Unable to exec(): $!\n"; 83 | } 84 | 85 | # wait for nginx to start 86 | 87 | $self->waitforfile("$testdir/nginx.pid") 88 | or die "Can't start nginx"; 89 | 90 | $self->{_started} = 1; 91 | return $self; 92 | } 93 | 94 | ############################################################################### 95 | 96 | select STDERR; $| = 1; 97 | select STDOUT; $| = 1; 98 | 99 | warn "your test dir is ".$t->testdir(); 100 | 101 | $t->write_file_expand('nginx.conf', <<'EOF'); 102 | 103 | %%TEST_GLOBALS%% 104 | 105 | daemon off; 106 | 107 | worker_processes 1; 108 | 109 | events { 110 | accept_mutex off; 111 | } 112 | 113 | http { 114 | 115 | upstream test { 116 | upsync 127.0.0.1:8500/v1/kv/upstreams/test upsync_interval=50ms upsync_timeout=6m upsync_type=consul; 117 | upsync_dump_path /tmp/servers_test.conf; 118 | 119 | server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10; 120 | } 121 | 122 | upstream backend { 123 | server 127.0.0.1:8090 weight=10 max_fails=3 fail_timeout=10; 124 | } 125 | 126 | server { 127 | listen 8080; 128 | 129 | location / { 130 | proxy_pass http://$host; 131 | } 132 | 133 | location /upstream_list { 134 | upstream_show; 135 | } 136 | } 137 | } 138 | EOF 139 | 140 | mrun($t); 141 | 142 | ############################################################################### 143 | my $rep; 144 | my $dump; 145 | 146 | like(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8089', 8500), qr/true/m, '2015-12-27 18:21:37'); 147 | like(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8088', 8500), qr/true/m, '2015-12-27 18:20:37'); 148 | 149 | sleep(1); 150 | 151 | like(mhttp_delete('/v1/kv/upstreams/test?recurse', 8500), qr/true/m, '2015-12-27 18:22:37'); 152 | 153 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8089', '', 8500), qr/true/m, '2015-12-27 17:50:35'); 154 | 155 | $rep = qr/ 156 | Upstream name: test; Backend server count: 1 157 | server 127.0.0.1:8089 weight=1 max_fails=2 fail_timeout=10s; 158 | /m; 159 | 160 | sleep(1); 161 | 162 | like(mhttp_get('/upstream_list?test', 'localhost', 8080), $rep, '2015-12-27 17:43:30'); 163 | 164 | ############################################################################### 165 | 166 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8088', '{"weight":10,"max_fails":3,"fail_timeout":10}', 8500), qr/true/m, '2015-12-27 17:42:35'); 167 | 168 | $rep = qr/ 169 | Upstream name: test; Backend server count: 2 170 | server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10s; 171 | server 127.0.0.1:8089 weight=1 max_fails=2 fail_timeout=10s; 172 | /m; 173 | 174 | sleep(1); 175 | 176 | like(mhttp_get('/upstream_list?test', 'localhost', 8080), $rep, '2015-12-27 18:17:53'); 177 | 178 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 18:17:53'); 179 | 180 | ######################### 181 | 182 | like(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8089', 8500), qr/true/m, '2015-12-27 18:24:37'); 183 | 184 | $rep = qr/ 185 | Upstream name: test; Backend server count: 1 186 | server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10s; 187 | /m; 188 | 189 | sleep(1); 190 | 191 | like(mhttp_get('/upstream_list?test', 'localhost', 8080), $rep, '2015-12-27 18:30:40'); 192 | 193 | ######################### 194 | 195 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8089', '{"weight":20,"max_fails":0,"fail_timeout":30}', 8500), qr/true/m, '2015-12-27 18:31:39'); 196 | 197 | $rep = qr/ 198 | Upstream name: test; Backend server count: 2 199 | server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s; 200 | server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10s; 201 | /m; 202 | 203 | sleep(1); 204 | 205 | like(mhttp_get('/upstream_list?test', 'localhost', 8080), $rep, '2015-12-27 18:32:53'); 206 | 207 | ######################## 208 | 209 | like(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8088', 8500), qr/true/m, '2015-12-27 18:33:37'); 210 | 211 | $rep = qr/ 212 | Upstream name: test; Backend server count: 1 213 | server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s; 214 | /m; 215 | 216 | sleep(1); 217 | 218 | like(mhttp_get('/upstream_list?test', 'localhost', 8080), $rep, '2015-12-27 18:34:40'); 219 | 220 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 18:34:40'); 221 | 222 | ####################### 223 | 224 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8088', '{"weight":20,"max_fails":0,"fail_timeout":30}', 8500), qr/true/m, '2015-12-27 18:35:35'); 225 | 226 | $rep = qr/ 227 | Upstream name: test; Backend server count: 2 228 | server 127.0.0.1:8088 weight=20 max_fails=0 fail_timeout=30s; 229 | server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s; 230 | /m; 231 | 232 | sleep(1); 233 | 234 | like(mhttp_get('/upstream_list?test', 'localhost', 8080), $rep, '2015-12-27 18:41:40'); 235 | 236 | ####################### 237 | 238 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8088', '{"weight":40}', 8500), qr/true/m, '2015-12-27 18:42:35'); 239 | 240 | $rep = qr/ 241 | Upstream name: test; Backend server count: 2 242 | server 127.0.0.1:8088 weight=40 max_fails=2 fail_timeout=10s; 243 | server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s; 244 | /m; 245 | 246 | sleep(1); 247 | 248 | like(mhttp_get('/upstream_list?test', 'localhost', 8080), $rep, '2015-12-27 18:43:40'); 249 | 250 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 18:43:40'); 251 | 252 | ####################### 253 | 254 | like(mhttp_delete('/v1/kv/upstreams/test?recurse', 8500), qr/true/m, '2015-12-27 18:22:33'); 255 | 256 | sleep(1); 257 | 258 | like(mhttp_get('/upstream_list?test', 'localhost', 8080), $rep, '2015-12-27 18:44:51'); 259 | 260 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2016-04-14 18:44:51'); 261 | 262 | ####################### 263 | 264 | $dump = qr/server 127.0.0.1:8088 weight=40 max_fails=2 fail_timeout=10s; 265 | server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s; 266 | /m; 267 | 268 | like(get_dump_content('/tmp/servers_test.conf'), $dump, '2015-12-27 18:51:35'); 269 | 270 | ####################### 271 | 272 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8088', '{"weight":40,"max_fails":3,"fail_timeout":20, "down":1}', 8500), qr/true/m, '2016-03-15 18:35:35'); 273 | 274 | sleep(1); 275 | 276 | $dump = qr/server 127.0.0.1:8088 weight=40 max_fails=3 fail_timeout=20s down; 277 | /m; 278 | 279 | like(get_dump_content('/tmp/servers_test.conf'), $dump, '2016-03-15 18:51:35'); 280 | 281 | ####################### 282 | 283 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8088', '{"weight":40,"max_fails":0,"fail_timeout":30, "down":0}', 8500), qr/true/m, '2016-03-15 17:35:35'); 284 | 285 | sleep(1); 286 | 287 | $dump = qr/server 127.0.0.1:8088 weight=40 max_fails=0 fail_timeout=30s; 288 | /m; 289 | 290 | like(get_dump_content('/tmp/servers_test.conf'), $dump, '2016-03-15 17:51:35'); 291 | 292 | $t->stop(); 293 | 294 | ############################################################################## 295 | 296 | $t->write_file_expand('nginx.conf', <<'EOF'); 297 | 298 | %%TEST_GLOBALS%% 299 | 300 | daemon off; 301 | 302 | worker_processes auto; 303 | 304 | events { 305 | accept_mutex off; 306 | } 307 | 308 | http { 309 | 310 | upstream test { 311 | upsync 127.0.0.1:8500/v1/kv/upstreams/test upsync_interval=50ms upsync_timeout=6m upsync_type=consul; 312 | upsync_dump_path /tmp/servers_test.conf; 313 | 314 | server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10; 315 | } 316 | 317 | upstream backend { 318 | server 127.0.0.1:8090 weight=10 max_fails=3 fail_timeout=10; 319 | } 320 | 321 | server { 322 | listen 8080; 323 | 324 | location / { 325 | proxy_pass http://$host; 326 | } 327 | 328 | location /upstream_list { 329 | upstream_show; 330 | } 331 | } 332 | } 333 | EOF 334 | 335 | mrun($t); 336 | 337 | ############################################################################### 338 | 339 | like(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8088', 8500), qr/true/m, '2015-12-27 19:20:37'); 340 | like(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8089', 8500), qr/true/m, '2015-12-27 19:21:37'); 341 | 342 | sleep(1); 343 | 344 | like(mhttp_delete('/v1/kv/upstreams/test?recurse', 8500), qr/true/m, '2015-12-27 19:49:37'); 345 | 346 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8089', '', 8500), qr/true/m, '2015-12-27 19:25:35'); 347 | 348 | $rep = qr/ 349 | Upstream name: test; Backend server count: 1 350 | server 127.0.0.1:8089 weight=1 max_fails=2 fail_timeout=10s; 351 | /m; 352 | 353 | sleep(1); 354 | 355 | like(mhttp_get('/upstream_list?test', 'localhost', 8080), $rep, '2015-12-27 19:20:30'); 356 | 357 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 19:20:30'); 358 | 359 | ############################################################################### 360 | 361 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8088', '{"weight":10,"max_fails":3,"fail_timeout":10}', 8500), qr/true/m, '2015-12-27 17:50:35'); 362 | 363 | $rep = qr/ 364 | Upstream name: test; Backend server count: 2 365 | server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10s; 366 | server 127.0.0.1:8089 weight=1 max_fails=2 fail_timeout=10s; 367 | /m; 368 | 369 | sleep(1); 370 | 371 | like(mhttp_get('/upstream_list?test', 'localhost', 8080), $rep, '2015-12-27 19:26:53'); 372 | 373 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 19:26:53'); 374 | 375 | ########################### 376 | 377 | like(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8089', 8500), qr/true/m, '2015-12-27 19:28:37'); 378 | 379 | $rep = qr/ 380 | Upstream name: test; Backend server count: 1 381 | server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10s; 382 | /m; 383 | 384 | sleep(1); 385 | 386 | like(mhttp_get('/upstream_list?test', 'localhost', 8080), $rep, '2015-12-27 19:30:40'); 387 | 388 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 19:30:40'); 389 | 390 | ########################### 391 | 392 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8089', '{"weight":20,"max_fails":0,"fail_timeout":30}', 8500), qr/true/m, '2015-12-27 19:31:39'); 393 | 394 | $rep = qr/ 395 | Upstream name: test; Backend server count: 2 396 | server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s; 397 | server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10s; 398 | /m; 399 | 400 | sleep(1); 401 | 402 | like(mhttp_get('/upstream_list?test', 'localhost', 8080), $rep, '2015-12-27 19:32:53'); 403 | 404 | $rep = qr/ 405 | Upstream name: test; Backend server count: 2 406 | server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s; 407 | server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10s; 408 | 409 | Upstream name: backend; Backend server count: 1 410 | server 127.0.0.1:8090 weight=10 max_fails=3 fail_timeout=10s; 411 | /m; 412 | 413 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 19:32:53'); 414 | 415 | ########################## 416 | 417 | like(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8088', 8500), qr/true/m, '2015-12-27 19:35:37'); 418 | 419 | $rep = qr/ 420 | Upstream name: test; Backend server count: 1 421 | server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s; 422 | /m; 423 | 424 | sleep(1); 425 | 426 | like(mhttp_get('/upstream_list?test', 'localhost', 8080), $rep, '2015-12-27 19:37:40'); 427 | 428 | ########################### 429 | 430 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8088', '{"weight":20,"max_fails":0,"fail_timeout":30}', 8500), qr/true/m, '2015-12-27 19:39:35'); 431 | 432 | $rep = qr/ 433 | Upstream name: test; Backend server count: 2 434 | server 127.0.0.1:8088 weight=20 max_fails=0 fail_timeout=30s; 435 | server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s; 436 | /m; 437 | 438 | sleep(1); 439 | 440 | like(mhttp_get('/upstream_list?test', 'localhost', 8080), $rep, '2015-12-27 19:47:40'); 441 | 442 | ########################### 443 | 444 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8088', '{"weight":40}', 8500), qr/true/m, '2015-12-27 19:48:35'); 445 | 446 | $rep = qr/ 447 | Upstream name: test; Backend server count: 2 448 | server 127.0.0.1:8088 weight=40 max_fails=2 fail_timeout=10s; 449 | server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s; 450 | /m; 451 | 452 | sleep(1); 453 | 454 | like(mhttp_get('/upstream_list?test', 'localhost', 8080), $rep, '2015-12-27 19:49:40'); 455 | 456 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 19:49:40'); 457 | 458 | ########################### 459 | 460 | like(mhttp_delete('/v1/kv/upstreams/test?recurse', 8500), qr/true/m, '2015-12-27 19:50:37'); 461 | 462 | sleep(1); 463 | 464 | like(mhttp_get('/upstream_list?test', 'localhost', 8080), $rep, '2015-12-27 19:51:40'); 465 | 466 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 19:51:40'); 467 | 468 | ########################## 469 | 470 | $dump = qr/server 127.0.0.1:8088 weight=40 max_fails=2 fail_timeout=10s; 471 | server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s; 472 | /m; 473 | 474 | like(get_dump_content('/tmp/servers_test.conf'), $dump, '2015-12-27 19:53:35'); 475 | 476 | ########################## 477 | 478 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8088', '{"weight":40,"max_fails":3,"fail_timeout":20, "down":1}', 8500), qr/true/m, '2016-03-15 19:39:35'); 479 | 480 | sleep(1); 481 | 482 | $dump = qr/server 127.0.0.1:8088 weight=40 max_fails=3 fail_timeout=20s down; 483 | /m; 484 | 485 | like(get_dump_content('/tmp/servers_test.conf'), $dump, '2016-03-15 19:53:35'); 486 | 487 | ########################## 488 | 489 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8088', '{"weight":40,"max_fails":3,"fail_timeout":20, "down":0}', 8500), qr/true/m, '2016-03-15 20:39:35'); 490 | 491 | sleep(1); 492 | 493 | $dump = qr/server 127.0.0.1:8088 weight=40 max_fails=3 fail_timeout=20s; 494 | /m; 495 | 496 | like(get_dump_content('/tmp/servers_test.conf'), $dump, '2016-03-15 20:53:35'); 497 | 498 | $t->stop(); 499 | 500 | ############################################################################### 501 | 502 | sub mhttp($;$;%) { 503 | my ($request, $port, %extra) = @_; 504 | my $reply; 505 | eval { 506 | local $SIG{ALRM} = sub { die "timeout\n" }; 507 | local $SIG{PIPE} = sub { die "sigpipe\n" }; 508 | alarm(2); 509 | my $s = IO::Socket::INET->new( 510 | Proto => "tcp", 511 | PeerAddr => "127.0.0.1:$port" 512 | ); 513 | log_out($request); 514 | $s->print($request); 515 | local $/; 516 | select undef, undef, undef, $extra{sleep} if $extra{sleep}; 517 | return '' if $extra{aborted}; 518 | $reply = $s->getline(); 519 | alarm(0); 520 | }; 521 | alarm(0); 522 | if ($@) { 523 | log_in("died: $@"); 524 | return undef; 525 | } 526 | log_in($reply); 527 | return $reply; 528 | } 529 | -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | dir=$( pwd ) 4 | 5 | cd ${dir}/test || exit 1 6 | 7 | TEST_NGINX_USE_HUP=1 TEST_NGINX_BINARY=${dir}/_nginx/objs/nginx prove -r t 8 | --------------------------------------------------------------------------------