[
  {
    "path": "README.md",
    "content": "Name\n====\n\nnginx-stream-upsync-module - Nginx C module, sync upstreams from consul or others, dynamically modify backend-servers attribute(weight, max_fails,...), needn't reload nginx.\n\nIt 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.\n\nThe module can be more smoothly expansion and constriction, and will not influence the performance.\n\nAnother module, [nginx-upsync-module](https://github.com/weibocom/nginx-upsync-module) supports nginx http module(HTTP protocol), please be noticed.\n\nIf you want to use [nginx-upsync-module](https://github.com/weibocom/nginx-upsync-module) and [nginx-stream-upsync-module](https://github.com/xiaokai-wang/nginx-stream-upsync-module) both, please refer to [nginx-upsync](https://github.com/CallMeFoxie/nginx-upsync).\n\nTable of Contents\n=================\n\n* [Name](#name)\n* [Status](#status)\n* [Synopsis](#synopsis)\n* [Description](#description)\n* [Directives](#directives)\n    * [upsync](#upsync)\n        * [upsync_interval](#upsync_interval)\n        * [upsync_timeout](#upsync_timeout)\n        * [upsync_type](#upsync_type)\n        * [strong_dependency](#strong_dependency)\n    * [upsync_dump_path](#upsync_dump_path)\n    * [upsync_lb](#upsync_lb)\n    * [upstream_show](#upstream_show)\n* [Consul_interface](#consul_interface)\n* [Etcd_interface](#etcd_interface)\n* [TODO](#todo)\n* [Compatibility](#compatibility)\n* [Installation](#installation)\n* [Code style](#code-style)\n* [Author](#author)\n* [Copyright and License](#copyright-and-license)\n* [See Also](#see-also)\n* [Source Dependency](#source-dependency)\n\nStatus\n======\n\nThis module is still under active development and is considered production ready.\n\nSynopsis\n========\n\nnginx-consul:\n```nginx-consul\nstream {\n    upstream test {\n        upsync 127.0.0.1:8500/v1/kv/upstreams/test/ upsync_timeout=6m upsync_interval=500ms upsync_type=consul strong_dependency=off;\n        upsync_dump_path /usr/local/nginx/conf/servers/servers_test.conf;\n\n        include /usr/local/nginx/conf/servers/servers_test.conf;\n    }\n\n    upstream bar {\n        server 127.0.0.1:8090 weight=1 fail_timeout=10 max_fails=3;\n    }\n\n    server {\n        listen 12345;\n\n        proxy_connect_timeout 1s;\n        proxy_timeout 3s;\n        proxy_pass test;\n    }\n\n    server {\n        listen 2345;\n\n        upstream_show\n    }\n\n    server {\n        listen 127.0.0.1:9091;\n\n        proxy_responses 1;\n        proxy_timeout 20s;\n        proxy_pass bar;\n    }\n}\n```\nnginx-etcd:\n```nginx-etcd\nstream {\n    upstream test {\n        upsync 127.0.0.1:2379/v2/keys/upstreams/test upsync_timeout=6m upsync_interval=500ms upsync_type=etcd strong_dependency=off;\n        upsync_dump_path /usr/local/nginx/conf/servers/servers_test.conf;\n\n        include /usr/local/nginx/conf/servers/servers_test.conf;\n    }\n\n    upstream bar {\n        server 127.0.0.1:8090 weight=1 fail_timeout=10 max_fails=3;\n    }\n\n    server {\n        listen 12345;\n\n        proxy_connect_timeout 1s;\n        proxy_timeout 3s;\n        proxy_pass test;\n    }\n\n    server {\n        listen 2345;\n\n        upstream_show\n    }\n\n    server {\n        listen 127.0.0.1:9091;\n\n        proxy_responses 1;\n        proxy_timeout 20s;\n        proxy_pass bar;\n    }\n}\n```\nupsync_lb:\n```upsync_lb\nstream {\n    upstream test {\n        least_conn; //hash $uri consistent;\n\n        upsync 127.0.0.1:8500/v1/kv/upstreams/test/ upsync_timeout=6m upsync_interval=500ms upsync_type=consul strong_dependency=off;\n        upsync_dump_path /usr/local/nginx/conf/servers/servers_test.conf;\n        upsync_lb least_conn; //hash_ketama;\n\n        include /usr/local/nginx/conf/servers/servers_test.conf;\n    }\n\n    upstream bar {\n        server 127.0.0.1:8090 weight=1 fail_timeout=10 max_fails=3;\n    }\n\n    server {\n        listen 12345;\n\n        proxy_connect_timeout 1s;\n        proxy_timeout 3s;\n        proxy_pass test;\n    }\n\n    server {\n        listen 2345;\n\n        upstream_show\n    }\n\n    server {\n        listen 127.0.0.1:9091;\n\n        proxy_responses 1;\n        proxy_timeout 20s;\n        proxy_pass bar;\n    }\n}\n```\n\nNOTE: upstream: include command is neccesary, first time the dumped file should include all the servers.\n\n[Back to TOC](#table-of-contents)       \n\nDescription\n======\n\nThis module provides a method to discover backend servers. Supporting dynamicly adding or deleting backend server through consul/etcd and dynamicly adjusting backend servers weight, module will timely pull new backend server list from consul/etcd to upsync nginx ip router. Nginx needn't reload. Having some advantages than others:\n\n* timely\n\n      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.\n\n* performance\n\n      Pulling from consul/etcd equal a request to nginx, updating ip router nginx needn't reload, so affecting nginx performance is little.\n\n* stability\n\n      Even if one pulling failed, it will pull next upsync_interval, so guaranteing 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. \n\n[Back to TOC](#table-of-contents)       \n\nDirectives\n======\n\nupsync\n-----------\n```\nsyntax: 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]\n```\ndefault: none, if parameters omitted, default parameters are upsync_interval=5s upsync_timeout=6m strong_dependency=off\n\ncontext: upstream\n\ndescription: Pull upstream servers from consul/etcd... .\n\nThe parameters' meanings are:\n\n* upsync_interval\n\n    pulling servers from consul/etcd interval time.\n\n* upsync_timeout\n\n    pulling servers from consul/etcd request timeout.\n\n* upsync_type\n\n    pulling servers from conf server type.\n\n* strong_dependency\n\n    when nginx start up if strong_dependency is on that means servers will be depended on consul/etcd and will pull servers from consul/etcd.\n\n\nupsync_dump_path\n-----------\n`syntax: upsync_dump_path $path`\n\ndefault: /tmp/servers_$host.conf\n\ncontext: upstream\n\ndescription: dump the upstream backends to the $path.\n\n\nupsync_lb\n-----------\n`syntax: upsync_lb $load_balance`\n\ndefault: round_robin/ip_hash/hash modula\n\ncontext: upstream\n\ndescription: mainly for least_conn and hash consistent, when using one of them, you must point out using upsync_lb.\n\n\nupsync_show\n-----------\n`syntax: upsync_show`\n\ndefault: none\n\ncontext: server\n\ndescription: show all upstreams.\n\n```request\ncurl http://localhost:2345/upstream_show\n\nshow all upstreams\n```\n\n[Back to TOC](#table-of-contents)       \n\nConsul_interface\n======\n\nData can be taken from key/value store or service catalog. In the first case parameter upsync_type of directive must be *consul*. For example\n \n```nginx-consul\n    upsync 127.0.0.1:8500/v1/kv/upstreams/test upsync_timeout=6m upsync_interval=500ms upsync_type=consul strong_dependency=off;\n```\n\nIn the second case it must be *consul_services*.\n\n```nginx-consul\n    upsync 127.0.0.1:8500/v1/catalog/service/test upsync_timeout=6m upsync_interval=500ms upsync_type=consul_services strong_dependency=off;\n```\n\nyou can add or delete backend server through consul_ui or http_interface. Below are examples for key/value store.\n\nhttp_interface example:\n\n* add\n```\n    curl -X PUT http://$consul_ip:$port/v1/kv/upstreams/$upstream_name/$backend_ip:$backend_port\n```\n    default: weight=1 max_fails=2 fail_timeout=10 down=0 backup=0;\n\n```\n    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\nor\n    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\n```\n    value support json format.\n\n* delete\n```\n    curl -X DELETE http://$consul_ip:$port/v1/kv/upstreams/$upstream_name/$backend_ip:$backend_port\n```\n\n* adjust-weight\n```\n    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\nor\n    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\n```\n\n* mark server-down\n```\n    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\nor\n    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\n```\n\n* check\n```\n    curl http://$consul_ip:$port/v1/kv/upstreams/$upstream_name?recurse\n```\n\n[Back to TOC](#table-of-contents)       \n\nEtcd_interface\n======\n\nyou can add or delete backend server through http_interface.\n\nmainly like etcd, http_interface example:\n\n* add\n```\n    curl -X PUT http://$etcd_ip:$port/v2/keys/upstreams/$upstream_name/$backend_ip:$backend_port\n```\n    default: weight=1 max_fails=2 fail_timeout=10 down=0 backup=0;\n\n```\n    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\n```\n    value support json format.\n\n* delete\n```\n    curl -X DELETE http://$etcd_ip:$port/v2/keys/upstreams/$upstream_name/$backend_ip:$backend_port\n```\n\n* adjust-weight\n```\n    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\n```\n\n* mark server-down\n```\n    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\n```\n\n* check\n```\n    curl http://$etcd_ip:$port/v2/keys/upstreams/$upstream_name\n```\n\n[Back to TOC](#table-of-contents)       \n\nTODO\n====\n\n* support zookeeper and so on\n\n[Back to TOC](#table-of-contents)\n\nCompatibility\n=============\n\nThe module was developed base on nginx-1.9.10.\n\nMaster branch compatible with nginx-1.11.0+.\n\nNginx-1.10.3- branch compatible with nginx-1.9.10 ~ nginx-1.11.0+.\n\n[Back to TOC](#table-of-contents)\n\nInstallation\n============\n\nThis module can be used independently, can be download[Github](https://github.com/xiaokai-wang/nginx-stream-upsync-module.git).\n\nGrab 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:\n\n```bash\nwget 'http://nginx.org/download/nginx-1.8.0.tar.gz'\ntar -xzvf nginx-1.8.0.tar.gz\ncd nginx-1.8.0/\n```\n\n```bash\n./configure --add-module=/path/to/nginx-stream_upsync-module\nmake\nmake install\n```\n\nif you support nginx-upstream-check-module\n```bash\n./configure --add-module=/path/to/nginx-upstream-check-module --add-module=/path/to/nginx-stream_upsync-module\nmake\nmake install\n```\n\n[Back to TOC](#table-of-contents)\n\nCode style\n======\n\nCode style is mainly based on [style](http://tengine.taobao.org/book/appendix_a.html)\n\n[Back to TOC](#table-of-contents)\n\nAuthor\n======\n\nXiaokai Wang (王晓开) <xiaokai.wang@live.com>, Weibo Inc.\n\n[Back to TOC](#table-of-contents)\n\nCopyright and License\n=====================\n\nThis README template copy from agentzh.\n\nThis module is licensed under the BSD license.\n\nCopyright (C) 2014 by Xiaokai Wang <xiaokai.wang@live.com></xiaokai.wang@live.com>\n\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n\n* 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.\n\nTHIS 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.\n\n[Back to TOC](#table-of-contents)\n\nsee also\n========\n* the nginx_upstream_check_module: https://github.com/alibaba/tengine/blob/master/src/http/ngx_http_upstream_check_module.c\n* the nginx_upstream_check_module patch: https://github.com/yaoweibin/nginx_upstream_check_module\n* or based on https://github.com/xiaokai-wang/nginx_upstream_check_module\n\n[back to toc](#table-of-contents)\n\nsource dependency\n========\n* Cjson: https://github.com/kbranigan/cJSON\n* http-parser: https://github.com/nodejs/http-parser\n\n[back to toc](#table-of-contents)\n\n\n"
  },
  {
    "path": "config",
    "content": "ngx_addon_name=ngx_stream_upsync_module\n\nngx_feature_libs=\"-lm\"\n\nngx_module_incs=$ngx_addon_dir/src\n\n_STREAM_UPSYNC_SRCS=\"\\\n  $ngx_addon_dir/src/ngx_stream_upsync_module.c \\\n  $ngx_addon_dir/src/ngx_stream_json.c \\\n  $ngx_addon_dir/src/ngx_stream_http_parser.c \\\n\"\n\nhave=NGX_STREAM_UPSYNC . auto/have\n\nif test -n \"$ngx_module_link\"; then\n  ngx_module_type=STREAM\n  ngx_module_name=$ngx_addon_name\n  ngx_module_srcs=\"$_STREAM_UPSYNC_SRCS\"\n  ngx_module_libs=$ngx_feature_libs\n  . auto/module\nelse\n  NGX_ADDON_SRCS=\"$NGX_ADDON_SRCS $_STREAM_UPSYNC_SRCS\"\n  CORE_LIBS=\"$CORE_LIBS $ngx_feature_libs\"\n  CORE_INCS=\"$CORE_INCS $ngx_module_incs\"\n  STREAM_MODULES=\"$STREAM_MODULES $ngx_addon_name\"\nfi\n\n"
  },
  {
    "path": "src/ngx_stream_http_parser.c",
    "content": "/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev\n *\n * Additional changes are licensed under the same terms as NGINX and\n * copyright Joyent, Inc. and other Node contributors. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n#include <ngx_config.h>\n#ifndef NGX_HTTP_UPSYNC\n\n#include \"ngx_stream_http_parser.h\"\n#include <assert.h>\n#include <stddef.h>\n#include <ctype.h>\n#include <stdlib.h>\n#include <string.h>\n#include <limits.h>\n\n#ifndef ULLONG_MAX\n# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */\n#endif\n\n#ifndef MIN\n# define MIN(a,b) ((a) < (b) ? (a) : (b))\n#endif\n\n#ifndef ARRAY_SIZE\n# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))\n#endif\n\n#ifndef BIT_AT\n# define BIT_AT(a, i)                                                \\\n  (!!((unsigned int) (a)[(unsigned int) (i) >> 3] &                  \\\n   (1 << ((unsigned int) (i) & 7))))\n#endif\n\n#ifndef ELEM_AT\n# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v))\n#endif\n\n#define SET_ERRNO(e)                                                 \\\ndo {                                                                 \\\n  parser->http_errno = (e);                                          \\\n} while(0)\n\n#define CURRENT_STATE() p_state\n#define UPDATE_STATE(V) p_state = (enum state) (V);\n#define RETURN(V)                                                    \\\ndo {                                                                 \\\n  parser->state = CURRENT_STATE();                                   \\\n  return (V);                                                        \\\n} while (0);\n#define REEXECUTE()                                                  \\\n  goto reexecute;                                                    \\\n\n\n#ifdef __GNUC__\n# define LIKELY(X) __builtin_expect(!!(X), 1)\n# define UNLIKELY(X) __builtin_expect(!!(X), 0)\n#else\n# define LIKELY(X) (X)\n# define UNLIKELY(X) (X)\n#endif\n\n\n/* Run the notify callback FOR, returning ER if it fails */\n#define CALLBACK_NOTIFY_(FOR, ER)                                    \\\ndo {                                                                 \\\n  assert(HTTP_PARSER_ERRNO(parser) == HPE_OK);                       \\\n                                                                     \\\n  if (LIKELY(settings->on_##FOR)) {                                  \\\n    parser->state = CURRENT_STATE();                                 \\\n    if (UNLIKELY(0 != settings->on_##FOR(parser))) {                 \\\n      SET_ERRNO(HPE_CB_##FOR);                                       \\\n    }                                                                \\\n    UPDATE_STATE(parser->state);                                     \\\n                                                                     \\\n    /* We either errored above or got paused; get out */             \\\n    if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) {             \\\n      return (ER);                                                   \\\n    }                                                                \\\n  }                                                                  \\\n} while (0)\n\n/* Run the notify callback FOR and consume the current byte */\n#define CALLBACK_NOTIFY(FOR)            CALLBACK_NOTIFY_(FOR, p - data + 1)\n\n/* Run the notify callback FOR and don't consume the current byte */\n#define CALLBACK_NOTIFY_NOADVANCE(FOR)  CALLBACK_NOTIFY_(FOR, p - data)\n\n/* Run data callback FOR with LEN bytes, returning ER if it fails */\n#define CALLBACK_DATA_(FOR, LEN, ER)                                 \\\ndo {                                                                 \\\n  assert(HTTP_PARSER_ERRNO(parser) == HPE_OK);                       \\\n                                                                     \\\n  if (FOR##_mark) {                                                  \\\n    if (LIKELY(settings->on_##FOR)) {                                \\\n      parser->state = CURRENT_STATE();                               \\\n      if (UNLIKELY(0 !=                                              \\\n                   settings->on_##FOR(parser, FOR##_mark, (LEN)))) { \\\n        SET_ERRNO(HPE_CB_##FOR);                                     \\\n      }                                                              \\\n      UPDATE_STATE(parser->state);                                   \\\n                                                                     \\\n      /* We either errored above or got paused; get out */           \\\n      if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) {           \\\n        return (ER);                                                 \\\n      }                                                              \\\n    }                                                                \\\n    FOR##_mark = NULL;                                               \\\n  }                                                                  \\\n} while (0)\n  \n/* Run the data callback FOR and consume the current byte */\n#define CALLBACK_DATA(FOR)                                           \\\n    CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1)\n\n/* Run the data callback FOR and don't consume the current byte */\n#define CALLBACK_DATA_NOADVANCE(FOR)                                 \\\n    CALLBACK_DATA_(FOR, p - FOR##_mark, p - data)\n\n/* Set the mark FOR; non-destructive if mark is already set */\n#define MARK(FOR)                                                    \\\ndo {                                                                 \\\n  if (!FOR##_mark) {                                                 \\\n    FOR##_mark = p;                                                  \\\n  }                                                                  \\\n} while (0)\n\n/* Don't allow the total size of the HTTP headers (including the status\n * line) to exceed HTTP_MAX_HEADER_SIZE.  This check is here to protect\n * embedders against denial-of-service attacks where the attacker feeds\n * us a never-ending header that the embedder keeps buffering.\n *\n * This check is arguably the responsibility of embedders but we're doing\n * it on the embedder's behalf because most won't bother and this way we\n * make the web a little safer.  HTTP_MAX_HEADER_SIZE is still far bigger\n * than any reasonable request or response so this should never affect\n * day-to-day operation.\n */\n#define COUNT_HEADER_SIZE(V)                                         \\\ndo {                                                                 \\\n  parser->nread += (V);                                              \\\n  if (UNLIKELY(parser->nread > (HTTP_MAX_HEADER_SIZE))) {            \\\n    SET_ERRNO(HPE_HEADER_OVERFLOW);                                  \\\n    goto error;                                                      \\\n  }                                                                  \\\n} while (0)\n\n\n#define PROXY_CONNECTION \"proxy-connection\"\n#define CONNECTION \"connection\"\n#define CONTENT_LENGTH \"content-length\"\n#define TRANSFER_ENCODING \"transfer-encoding\"\n#define UPGRADE \"upgrade\"\n#define CHUNKED \"chunked\"\n#define KEEP_ALIVE \"keep-alive\"\n#define CLOSE \"close\"\n\n\nstatic const char *method_strings[] =\n  {\n#define XX(num, name, string) #string,\n  HTTP_METHOD_MAP(XX)\n#undef XX\n  };\n\n\n/* Tokens as defined by rfc 2616. Also lowercases them.\n *        token       = 1*<any CHAR except CTLs or separators>\n *     separators     = \"(\" | \")\" | \"<\" | \">\" | \"@\"\n *                    | \",\" | \";\" | \":\" | \"\\\" | <\">\n *                    | \"/\" | \"[\" | \"]\" | \"?\" | \"=\"\n *                    | \"{\" | \"}\" | SP | HT\n */\nstatic const char tokens[256] = {\n/*   0 nul    1 soh    2 stx    3 etx    4 eot    5 enq    6 ack    7 bel  */\n        0,       0,       0,       0,       0,       0,       0,       0,\n/*   8 bs     9 ht    10 nl    11 vt    12 np    13 cr    14 so    15 si   */\n        0,       0,       0,       0,       0,       0,       0,       0,\n/*  16 dle   17 dc1   18 dc2   19 dc3   20 dc4   21 nak   22 syn   23 etb */\n        0,       0,       0,       0,       0,       0,       0,       0,\n/*  24 can   25 em    26 sub   27 esc   28 fs    29 gs    30 rs    31 us  */\n        0,       0,       0,       0,       0,       0,       0,       0,\n/*  32 sp    33  !    34  \"    35  #    36  $    37  %    38  &    39  '  */\n        0,      '!',      0,      '#',     '$',     '%',     '&',    '\\'',\n/*  40  (    41  )    42  *    43  +    44  ,    45  -    46  .    47  /  */\n        0,       0,      '*',     '+',      0,      '-',     '.',      0,\n/*  48  0    49  1    50  2    51  3    52  4    53  5    54  6    55  7  */\n       '0',     '1',     '2',     '3',     '4',     '5',     '6',     '7',\n/*  56  8    57  9    58  :    59  ;    60  <    61  =    62  >    63  ?  */\n       '8',     '9',      0,       0,       0,       0,       0,       0,\n/*  64  @    65  A    66  B    67  C    68  D    69  E    70  F    71  G  */\n        0,      'a',     'b',     'c',     'd',     'e',     'f',     'g',\n/*  72  H    73  I    74  J    75  K    76  L    77  M    78  N    79  O  */\n       'h',     'i',     'j',     'k',     'l',     'm',     'n',     'o',\n/*  80  P    81  Q    82  R    83  S    84  T    85  U    86  V    87  W  */\n       'p',     'q',     'r',     's',     't',     'u',     'v',     'w',\n/*  88  X    89  Y    90  Z    91  [    92  \\    93  ]    94  ^    95  _  */\n       'x',     'y',     'z',      0,       0,       0,      '^',     '_',\n/*  96  `    97  a    98  b    99  c   100  d   101  e   102  f   103  g  */\n       '`',     'a',     'b',     'c',     'd',     'e',     'f',     'g',\n/* 104  h   105  i   106  j   107  k   108  l   109  m   110  n   111  o  */\n       'h',     'i',     'j',     'k',     'l',     'm',     'n',     'o',\n/* 112  p   113  q   114  r   115  s   116  t   117  u   118  v   119  w  */\n       'p',     'q',     'r',     's',     't',     'u',     'v',     'w',\n/* 120  x   121  y   122  z   123  {   124  |   125  }   126  ~   127 del */\n       'x',     'y',     'z',      0,      '|',      0,      '~',       0 };\n\n\nstatic const int8_t unhex[256] =\n  {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1\n  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1\n  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1\n  , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1\n  ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1\n  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1\n  ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1\n  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1\n  };\n\n\n#if HTTP_PARSER_STRICT\n# define T(v) 0\n#else\n# define T(v) v\n#endif\n\n\nstatic const uint8_t normal_url_char[32] = {\n/*   0 nul    1 soh    2 stx    3 etx    4 eot    5 enq    6 ack    7 bel  */\n        0    |   0    |   0    |   0    |   0    |   0    |   0    |   0,\n/*   8 bs     9 ht    10 nl    11 vt    12 np    13 cr    14 so    15 si   */\n        0    | T(2)   |   0    |   0    | T(16)  |   0    |   0    |   0,\n/*  16 dle   17 dc1   18 dc2   19 dc3   20 dc4   21 nak   22 syn   23 etb */\n        0    |   0    |   0    |   0    |   0    |   0    |   0    |   0,\n/*  24 can   25 em    26 sub   27 esc   28 fs    29 gs    30 rs    31 us  */\n        0    |   0    |   0    |   0    |   0    |   0    |   0    |   0,\n/*  32 sp    33  !    34  \"    35  #    36  $    37  %    38  &    39  '  */\n        0    |   2    |   4    |   0    |   16   |   32   |   64   |  128,\n/*  40  (    41  )    42  *    43  +    44  ,    45  -    46  .    47  /  */\n        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,\n/*  48  0    49  1    50  2    51  3    52  4    53  5    54  6    55  7  */\n        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,\n/*  56  8    57  9    58  :    59  ;    60  <    61  =    62  >    63  ?  */\n        1    |   2    |   4    |   8    |   16   |   32   |   64   |   0,\n/*  64  @    65  A    66  B    67  C    68  D    69  E    70  F    71  G  */\n        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,\n/*  72  H    73  I    74  J    75  K    76  L    77  M    78  N    79  O  */\n        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,\n/*  80  P    81  Q    82  R    83  S    84  T    85  U    86  V    87  W  */\n        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,\n/*  88  X    89  Y    90  Z    91  [    92  \\    93  ]    94  ^    95  _  */\n        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,\n/*  96  `    97  a    98  b    99  c   100  d   101  e   102  f   103  g  */\n        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,\n/* 104  h   105  i   106  j   107  k   108  l   109  m   110  n   111  o  */\n        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,\n/* 112  p   113  q   114  r   115  s   116  t   117  u   118  v   119  w  */\n        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,\n/* 120  x   121  y   122  z   123  {   124  |   125  }   126  ~   127 del */\n        1    |   2    |   4    |   8    |   16   |   32   |   64   |   0, };\n\n#undef T\n\nenum state\n  { s_dead = 1 /* important that this is > 0 */\n\n  , s_start_req_or_res\n  , s_res_or_resp_H\n  , s_start_res\n  , s_res_H\n  , s_res_HT\n  , s_res_HTT\n  , s_res_HTTP\n  , s_res_first_http_major\n  , s_res_http_major\n  , s_res_first_http_minor\n  , s_res_http_minor\n  , s_res_first_status_code\n  , s_res_status_code\n  , s_res_status_start\n  , s_res_status\n  , s_res_line_almost_done\n\n  , s_start_req\n\n  , s_req_method\n  , s_req_spaces_before_url\n  , s_req_schema\n  , s_req_schema_slash\n  , s_req_schema_slash_slash\n  , s_req_server_start\n  , s_req_server\n  , s_req_server_with_at\n  , s_req_path\n  , s_req_query_string_start\n  , s_req_query_string\n  , s_req_fragment_start\n  , s_req_fragment\n  , s_req_http_start\n  , s_req_http_H\n  , s_req_http_HT\n  , s_req_http_HTT\n  , s_req_http_HTTP\n  , s_req_first_http_major\n  , s_req_http_major\n  , s_req_first_http_minor\n  , s_req_http_minor\n  , s_req_line_almost_done\n\n  , s_header_field_start\n  , s_header_field\n  , s_header_value_discard_ws\n  , s_header_value_discard_ws_almost_done\n  , s_header_value_discard_lws\n  , s_header_value_start\n  , s_header_value\n  , s_header_value_lws\n\n  , s_header_almost_done\n\n  , s_chunk_size_start\n  , s_chunk_size\n  , s_chunk_parameters\n  , s_chunk_size_almost_done\n\n  , s_headers_almost_done\n  , s_headers_done\n\n  /* Important: 's_headers_done' must be the last 'header' state. All\n   * states beyond this must be 'body' states. It is used for overflow\n   * checking. See the PARSING_HEADER() macro.\n   */\n\n  , s_chunk_data\n  , s_chunk_data_almost_done\n  , s_chunk_data_done\n\n  , s_body_identity\n  , s_body_identity_eof\n\n  , s_message_done\n  };\n\n\n#define PARSING_HEADER(state) (state <= s_headers_done)\n\n\nenum header_states\n  { h_general = 0\n  , h_C\n  , h_CO\n  , h_CON\n\n  , h_matching_connection\n  , h_matching_proxy_connection\n  , h_matching_content_length\n  , h_matching_transfer_encoding\n  , h_matching_upgrade\n\n  , h_connection\n  , h_content_length\n  , h_transfer_encoding\n  , h_upgrade\n\n  , h_matching_transfer_encoding_chunked\n  , h_matching_connection_token_start\n  , h_matching_connection_keep_alive\n  , h_matching_connection_close\n  , h_matching_connection_upgrade\n  , h_matching_connection_token\n\n  , h_transfer_encoding_chunked\n  , h_connection_keep_alive\n  , h_connection_close\n  , h_connection_upgrade\n  };\n\nenum http_host_state\n  {\n    s_http_host_dead = 1\n  , s_http_userinfo_start\n  , s_http_userinfo\n  , s_http_host_start\n  , s_http_host_v6_start\n  , s_http_host\n  , s_http_host_v6\n  , s_http_host_v6_end\n  , s_http_host_port_start\n  , s_http_host_port\n};\n\n/* Macros for character classes; depends on strict-mode  */\n#define CR                  '\\r'\n#define LF                  '\\n'\n#define LOWER(c)            (unsigned char)(c | 0x20)\n#define IS_ALPHA(c)         (LOWER(c) >= 'a' && LOWER(c) <= 'z')\n#define IS_NUM(c)           ((c) >= '0' && (c) <= '9')\n#define IS_ALPHANUM(c)      (IS_ALPHA(c) || IS_NUM(c))\n#define IS_HEX(c)           (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f'))\n#define IS_MARK(c)          ((c) == '-' || (c) == '_' || (c) == '.' || \\\n  (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\\'' || (c) == '(' || \\\n  (c) == ')')\n#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \\\n  (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \\\n  (c) == '$' || (c) == ',')\n\n#define STRICT_TOKEN(c)     (tokens[(unsigned char)c])\n\n#if HTTP_PARSER_STRICT\n#define TOKEN(c)            (tokens[(unsigned char)c])\n#define IS_URL_CHAR(c)      (BIT_AT(normal_url_char, (unsigned char)c))\n#define IS_HOST_CHAR(c)     (IS_ALPHANUM(c) || (c) == '.' || (c) == '-')\n#else\n#define TOKEN(c)            ((c == ' ') ? ' ' : tokens[(unsigned char)c])\n#define IS_URL_CHAR(c)                                                         \\\n  (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80))\n#define IS_HOST_CHAR(c)                                                        \\\n  (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_')\n#endif\n\n\n#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res)\n\n\n#if HTTP_PARSER_STRICT\n# define STRICT_CHECK(cond)                                          \\\ndo {                                                                 \\\n  if (cond) {                                                        \\\n    SET_ERRNO(HPE_STRICT);                                           \\\n    goto error;                                                      \\\n  }                                                                  \\\n} while (0)\n# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead)\n#else\n# define STRICT_CHECK(cond)\n# define NEW_MESSAGE() start_state\n#endif\n\n\n/* Map errno values to strings for human-readable output */\n#define HTTP_STRERROR_GEN(n, s) { \"HPE_\" #n, s },\nstatic struct {\n  const char *name;\n  const char *description;\n} http_strerror_tab[] = {\n  HTTP_ERRNO_MAP(HTTP_STRERROR_GEN)\n};\n#undef HTTP_STRERROR_GEN\n\nint http_message_needs_eof(const http_parser *parser);\n\n/* Our URL parser.\n *\n * This is designed to be shared by http_parser_execute() for URL validation,\n * hence it has a state transition + byte-for-byte interface. In addition, it\n * is meant to be embedded in http_parser_parse_url(), which does the dirty\n * work of turning state transitions URL components for its API.\n *\n * This function should only be invoked with non-space characters. It is\n * assumed that the caller cares about (and can detect) the transition between\n * URL and non-URL states by looking for these.\n */\nstatic enum state\nparse_url_char(enum state s, const char ch)\n{\n  if (ch == ' ' || ch == '\\r' || ch == '\\n') {\n    return s_dead;\n  }\n\n#if HTTP_PARSER_STRICT\n  if (ch == '\\t' || ch == '\\f') {\n    return s_dead;\n  }\n#endif\n\n  switch (s) {\n    case s_req_spaces_before_url:\n      /* Proxied requests are followed by scheme of an absolute URI (alpha).\n       * All methods except CONNECT are followed by '/' or '*'.\n       */\n\n      if (ch == '/' || ch == '*') {\n        return s_req_path;\n      }\n\n      if (IS_ALPHA(ch)) {\n        return s_req_schema;\n      }\n\n      break;\n\n    case s_req_schema:\n      if (IS_ALPHA(ch)) {\n        return s;\n      }\n\n      if (ch == ':') {\n        return s_req_schema_slash;\n      }\n\n      break;\n\n    case s_req_schema_slash:\n      if (ch == '/') {\n        return s_req_schema_slash_slash;\n      }\n\n      break;\n\n    case s_req_schema_slash_slash:\n      if (ch == '/') {\n        return s_req_server_start;\n      }\n\n      break;\n\n    case s_req_server_with_at:\n      if (ch == '@') {\n        return s_dead;\n      }\n\n    /* FALLTHROUGH */\n    case s_req_server_start:\n    case s_req_server:\n      if (ch == '/') {\n        return s_req_path;\n      }\n\n      if (ch == '?') {\n        return s_req_query_string_start;\n      }\n\n      if (ch == '@') {\n        return s_req_server_with_at;\n      }\n\n      if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') {\n        return s_req_server;\n      }\n\n      break;\n\n    case s_req_path:\n      if (IS_URL_CHAR(ch)) {\n        return s;\n      }\n\n      switch (ch) {\n        case '?':\n          return s_req_query_string_start;\n\n        case '#':\n          return s_req_fragment_start;\n      }\n\n      break;\n\n    case s_req_query_string_start:\n    case s_req_query_string:\n      if (IS_URL_CHAR(ch)) {\n        return s_req_query_string;\n      }\n\n      switch (ch) {\n        case '?':\n          /* allow extra '?' in query string */\n          return s_req_query_string;\n\n        case '#':\n          return s_req_fragment_start;\n      }\n\n      break;\n\n    case s_req_fragment_start:\n      if (IS_URL_CHAR(ch)) {\n        return s_req_fragment;\n      }\n\n      switch (ch) {\n        case '?':\n          return s_req_fragment;\n\n        case '#':\n          return s;\n      }\n\n      break;\n\n    case s_req_fragment:\n      if (IS_URL_CHAR(ch)) {\n        return s;\n      }\n\n      switch (ch) {\n        case '?':\n        case '#':\n          return s;\n      }\n\n      break;\n\n    default:\n      break;\n  }\n\n  /* We should never fall out of the switch above unless there's an error */\n  return s_dead;\n}\n\nsize_t http_parser_execute (http_parser *parser,\n                            const http_parser_settings *settings,\n                            const char *data,\n                            size_t len)\n{\n  char c, ch;\n  int8_t unhex_val;\n  const char *p = data;\n  const char *header_field_mark = 0;\n  const char *header_value_mark = 0;\n  const char *url_mark = 0;\n  const char *body_mark = 0;\n  const char *status_mark = 0;\n  enum state p_state = (enum state) parser->state;\n\n  /* We're in an error state. Don't bother doing anything. */\n  if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {\n    return 0;\n  }\n\n  if (len == 0) {\n    switch (CURRENT_STATE()) {\n      case s_body_identity_eof:\n        /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if\n         * we got paused.\n         */\n        CALLBACK_NOTIFY_NOADVANCE(message_complete);\n        return 0;\n\n      case s_dead:\n      case s_start_req_or_res:\n      case s_start_res:\n      case s_start_req:\n        return 0;\n\n      default:\n        SET_ERRNO(HPE_INVALID_EOF_STATE);\n        return 1;\n    }\n  }\n\n\n  if (CURRENT_STATE() == s_header_field)\n    header_field_mark = data;\n  if (CURRENT_STATE() == s_header_value)\n    header_value_mark = data;\n  switch (CURRENT_STATE()) {\n  case s_req_path:\n  case s_req_schema:\n  case s_req_schema_slash:\n  case s_req_schema_slash_slash:\n  case s_req_server_start:\n  case s_req_server:\n  case s_req_server_with_at:\n  case s_req_query_string_start:\n  case s_req_query_string:\n  case s_req_fragment_start:\n  case s_req_fragment:\n    url_mark = data;\n    break;\n  case s_res_status:\n    status_mark = data;\n    break;\n  default:\n    break;\n  }\n\n  for (p=data; p != data + len; p++) {\n    ch = *p;\n\n    if (PARSING_HEADER(CURRENT_STATE()))\n      COUNT_HEADER_SIZE(1);\n\nreexecute:\n    switch (CURRENT_STATE()) {\n\n      case s_dead:\n        /* this state is used after a 'Connection: close' message\n         * the parser will error out if it reads another message\n         */\n        if (LIKELY(ch == CR || ch == LF))\n          break;\n\n        SET_ERRNO(HPE_CLOSED_CONNECTION);\n        goto error;\n\n      case s_start_req_or_res:\n      {\n        if (ch == CR || ch == LF)\n          break;\n        parser->flags = 0;\n        parser->content_length = ULLONG_MAX;\n\n        if (ch == 'H') {\n          UPDATE_STATE(s_res_or_resp_H);\n\n          CALLBACK_NOTIFY(message_begin);\n        } else {\n          parser->type = HTTP_REQUEST;\n          UPDATE_STATE(s_start_req);\n          REEXECUTE();\n        }\n\n        break;\n      }\n\n      case s_res_or_resp_H:\n        if (ch == 'T') {\n          parser->type = HTTP_RESPONSE;\n          UPDATE_STATE(s_res_HT);\n        } else {\n          if (UNLIKELY(ch != 'E')) {\n            SET_ERRNO(HPE_INVALID_CONSTANT);\n            goto error;\n          }\n\n          parser->type = HTTP_REQUEST;\n          parser->method = HTTP_HEAD;\n          parser->index = 2;\n          UPDATE_STATE(s_req_method);\n        }\n        break;\n\n      case s_start_res:\n      {\n        parser->flags = 0;\n        parser->content_length = ULLONG_MAX;\n\n        switch (ch) {\n          case 'H':\n            UPDATE_STATE(s_res_H);\n            break;\n\n          case CR:\n          case LF:\n            break;\n\n          default:\n            SET_ERRNO(HPE_INVALID_CONSTANT);\n            goto error;\n        }\n\n        CALLBACK_NOTIFY(message_begin);\n        break;\n      }\n\n      case s_res_H:\n        STRICT_CHECK(ch != 'T');\n        UPDATE_STATE(s_res_HT);\n        break;\n\n      case s_res_HT:\n        STRICT_CHECK(ch != 'T');\n        UPDATE_STATE(s_res_HTT);\n        break;\n\n      case s_res_HTT:\n        STRICT_CHECK(ch != 'P');\n        UPDATE_STATE(s_res_HTTP);\n        break;\n\n      case s_res_HTTP:\n        STRICT_CHECK(ch != '/');\n        UPDATE_STATE(s_res_first_http_major);\n        break;\n\n      case s_res_first_http_major:\n        if (UNLIKELY(ch < '0' || ch > '9')) {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        parser->http_major = ch - '0';\n        UPDATE_STATE(s_res_http_major);\n        break;\n\n      /* major HTTP version or dot */\n      case s_res_http_major:\n      {\n        if (ch == '.') {\n          UPDATE_STATE(s_res_first_http_minor);\n          break;\n        }\n\n        if (!IS_NUM(ch)) {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        parser->http_major *= 10;\n        parser->http_major += ch - '0';\n\n        if (UNLIKELY(parser->http_major > 999)) {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        break;\n      }\n\n      /* first digit of minor HTTP version */\n      case s_res_first_http_minor:\n        if (UNLIKELY(!IS_NUM(ch))) {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        parser->http_minor = ch - '0';\n        UPDATE_STATE(s_res_http_minor);\n        break;\n\n      /* minor HTTP version or end of request line */\n      case s_res_http_minor:\n      {\n        if (ch == ' ') {\n          UPDATE_STATE(s_res_first_status_code);\n          break;\n        }\n\n        if (UNLIKELY(!IS_NUM(ch))) {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        parser->http_minor *= 10;\n        parser->http_minor += ch - '0';\n\n        if (UNLIKELY(parser->http_minor > 999)) {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        break;\n      }\n\n      case s_res_first_status_code:\n      {\n        if (!IS_NUM(ch)) {\n          if (ch == ' ') {\n            break;\n          }\n\n          SET_ERRNO(HPE_INVALID_STATUS);\n          goto error;\n        }\n        parser->status_code = ch - '0';\n        UPDATE_STATE(s_res_status_code);\n        break;\n      }\n\n      case s_res_status_code:\n      {\n        if (!IS_NUM(ch)) {\n          switch (ch) {\n            case ' ':\n              UPDATE_STATE(s_res_status_start);\n              break;\n            case CR:\n              UPDATE_STATE(s_res_line_almost_done);\n              break;\n            case LF:\n              UPDATE_STATE(s_header_field_start);\n              break;\n            default:\n              SET_ERRNO(HPE_INVALID_STATUS);\n              goto error;\n          }\n          break;\n        }\n\n        parser->status_code *= 10;\n        parser->status_code += ch - '0';\n\n        if (UNLIKELY(parser->status_code > 999)) {\n          SET_ERRNO(HPE_INVALID_STATUS);\n          goto error;\n        }\n\n        break;\n      }\n\n      case s_res_status_start:\n      {\n        if (ch == CR) {\n          UPDATE_STATE(s_res_line_almost_done);\n          break;\n        }\n\n        if (ch == LF) {\n          UPDATE_STATE(s_header_field_start);\n          break;\n        }\n\n        MARK(status);\n        UPDATE_STATE(s_res_status);\n        parser->index = 0;\n        break;\n      }\n\n      case s_res_status:\n        if (ch == CR) {\n          UPDATE_STATE(s_res_line_almost_done);\n          CALLBACK_DATA(status);\n          break;\n        }\n\n        if (ch == LF) {\n          UPDATE_STATE(s_header_field_start);\n          CALLBACK_DATA(status);\n          break;\n        }\n\n        break;\n\n      case s_res_line_almost_done:\n        STRICT_CHECK(ch != LF);\n        UPDATE_STATE(s_header_field_start);\n        break;\n\n      case s_start_req:\n      {\n        if (ch == CR || ch == LF)\n          break;\n        parser->flags = 0;\n        parser->content_length = ULLONG_MAX;\n\n        if (UNLIKELY(!IS_ALPHA(ch))) {\n          SET_ERRNO(HPE_INVALID_METHOD);\n          goto error;\n        }\n\n        parser->method = (enum http_method) 0;\n        parser->index = 1;\n        switch (ch) {\n          case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break;\n          case 'D': parser->method = HTTP_DELETE; break;\n          case 'G': parser->method = HTTP_GET; break;\n          case 'H': parser->method = HTTP_HEAD; break;\n          case 'L': parser->method = HTTP_LOCK; break;\n          case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break;\n          case 'N': parser->method = HTTP_NOTIFY; break;\n          case 'O': parser->method = HTTP_OPTIONS; break;\n          case 'P': parser->method = HTTP_POST;\n            /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */\n            break;\n          case 'R': parser->method = HTTP_REPORT; break;\n          case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break;\n          case 'T': parser->method = HTTP_TRACE; break;\n          case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break;\n          default:\n            SET_ERRNO(HPE_INVALID_METHOD);\n            goto error;\n        }\n        UPDATE_STATE(s_req_method);\n\n        CALLBACK_NOTIFY(message_begin);\n\n        break;\n      }\n\n      case s_req_method:\n      {\n        const char *matcher;\n        if (UNLIKELY(ch == '\\0')) {\n          SET_ERRNO(HPE_INVALID_METHOD);\n          goto error;\n        }\n\n        matcher = method_strings[parser->method];\n        if (ch == ' ' && matcher[parser->index] == '\\0') {\n          UPDATE_STATE(s_req_spaces_before_url);\n        } else if (ch == matcher[parser->index]) {\n          ; /* nada */\n        } else if (parser->method == HTTP_CONNECT) {\n          if (parser->index == 1 && ch == 'H') {\n            parser->method = HTTP_CHECKOUT;\n          } else if (parser->index == 2  && ch == 'P') {\n            parser->method = HTTP_COPY;\n          } else {\n            SET_ERRNO(HPE_INVALID_METHOD);\n            goto error;\n          }\n        } else if (parser->method == HTTP_MKCOL) {\n          if (parser->index == 1 && ch == 'O') {\n            parser->method = HTTP_MOVE;\n          } else if (parser->index == 1 && ch == 'E') {\n            parser->method = HTTP_MERGE;\n          } else if (parser->index == 1 && ch == '-') {\n            parser->method = HTTP_MSEARCH;\n          } else if (parser->index == 2 && ch == 'A') {\n            parser->method = HTTP_MKACTIVITY;\n          } else if (parser->index == 3 && ch == 'A') {\n            parser->method = HTTP_MKCALENDAR;\n          } else {\n            SET_ERRNO(HPE_INVALID_METHOD);\n            goto error;\n          }\n        } else if (parser->method == HTTP_SUBSCRIBE) {\n          if (parser->index == 1 && ch == 'E') {\n            parser->method = HTTP_SEARCH;\n          } else {\n            SET_ERRNO(HPE_INVALID_METHOD);\n            goto error;\n          }\n        } else if (parser->index == 1 && parser->method == HTTP_POST) {\n          if (ch == 'R') {\n            parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */\n          } else if (ch == 'U') {\n            parser->method = HTTP_PUT; /* or HTTP_PURGE */\n          } else if (ch == 'A') {\n            parser->method = HTTP_PATCH;\n          } else {\n            SET_ERRNO(HPE_INVALID_METHOD);\n            goto error;\n          }\n        } else if (parser->index == 2) {\n          if (parser->method == HTTP_PUT) {\n            if (ch == 'R') {\n              parser->method = HTTP_PURGE;\n            } else {\n              SET_ERRNO(HPE_INVALID_METHOD);\n              goto error;\n            }\n          } else if (parser->method == HTTP_UNLOCK) {\n            if (ch == 'S') {\n              parser->method = HTTP_UNSUBSCRIBE;\n            } else {\n              SET_ERRNO(HPE_INVALID_METHOD);\n              goto error;\n            }\n          } else {\n            SET_ERRNO(HPE_INVALID_METHOD);\n            goto error;\n          }\n        } else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') {\n          parser->method = HTTP_PROPPATCH;\n        } else {\n          SET_ERRNO(HPE_INVALID_METHOD);\n          goto error;\n        }\n\n        ++parser->index;\n        break;\n      }\n\n      case s_req_spaces_before_url:\n      {\n        if (ch == ' ') break;\n\n        MARK(url);\n        if (parser->method == HTTP_CONNECT) {\n          UPDATE_STATE(s_req_server_start);\n        }\n\n        UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));\n        if (UNLIKELY(CURRENT_STATE() == s_dead)) {\n          SET_ERRNO(HPE_INVALID_URL);\n          goto error;\n        }\n\n        break;\n      }\n\n      case s_req_schema:\n      case s_req_schema_slash:\n      case s_req_schema_slash_slash:\n      case s_req_server_start:\n      {\n        switch (ch) {\n          /* No whitespace allowed here */\n          case ' ':\n          case CR:\n          case LF:\n            SET_ERRNO(HPE_INVALID_URL);\n            goto error;\n          default:\n            UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));\n            if (UNLIKELY(CURRENT_STATE() == s_dead)) {\n              SET_ERRNO(HPE_INVALID_URL);\n              goto error;\n            }\n        }\n\n        break;\n      }\n\n      case s_req_server:\n      case s_req_server_with_at:\n      case s_req_path:\n      case s_req_query_string_start:\n      case s_req_query_string:\n      case s_req_fragment_start:\n      case s_req_fragment:\n      {\n        switch (ch) {\n          case ' ':\n            UPDATE_STATE(s_req_http_start);\n            CALLBACK_DATA(url);\n            break;\n          case CR:\n          case LF:\n            parser->http_major = 0;\n            parser->http_minor = 9;\n            UPDATE_STATE((ch == CR) ?\n              s_req_line_almost_done :\n              s_header_field_start);\n            CALLBACK_DATA(url);\n            break;\n          default:\n            UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));\n            if (UNLIKELY(CURRENT_STATE() == s_dead)) {\n              SET_ERRNO(HPE_INVALID_URL);\n              goto error;\n            }\n        }\n        break;\n      }\n\n      case s_req_http_start:\n        switch (ch) {\n          case 'H':\n            UPDATE_STATE(s_req_http_H);\n            break;\n          case ' ':\n            break;\n          default:\n            SET_ERRNO(HPE_INVALID_CONSTANT);\n            goto error;\n        }\n        break;\n\n      case s_req_http_H:\n        STRICT_CHECK(ch != 'T');\n        UPDATE_STATE(s_req_http_HT);\n        break;\n\n      case s_req_http_HT:\n        STRICT_CHECK(ch != 'T');\n        UPDATE_STATE(s_req_http_HTT);\n        break;\n\n      case s_req_http_HTT:\n        STRICT_CHECK(ch != 'P');\n        UPDATE_STATE(s_req_http_HTTP);\n        break;\n\n      case s_req_http_HTTP:\n        STRICT_CHECK(ch != '/');\n        UPDATE_STATE(s_req_first_http_major);\n        break;\n\n      /* first digit of major HTTP version */\n      case s_req_first_http_major:\n        if (UNLIKELY(ch < '1' || ch > '9')) {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        parser->http_major = ch - '0';\n        UPDATE_STATE(s_req_http_major);\n        break;\n\n      /* major HTTP version or dot */\n      case s_req_http_major:\n      {\n        if (ch == '.') {\n          UPDATE_STATE(s_req_first_http_minor);\n          break;\n        }\n\n        if (UNLIKELY(!IS_NUM(ch))) {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        parser->http_major *= 10;\n        parser->http_major += ch - '0';\n\n        if (UNLIKELY(parser->http_major > 999)) {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        break;\n      }\n\n      /* first digit of minor HTTP version */\n      case s_req_first_http_minor:\n        if (UNLIKELY(!IS_NUM(ch))) {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        parser->http_minor = ch - '0';\n        UPDATE_STATE(s_req_http_minor);\n        break;\n\n      /* minor HTTP version or end of request line */\n      case s_req_http_minor:\n      {\n        if (ch == CR) {\n          UPDATE_STATE(s_req_line_almost_done);\n          break;\n        }\n\n        if (ch == LF) {\n          UPDATE_STATE(s_header_field_start);\n          break;\n        }\n\n        /* XXX allow spaces after digit? */\n\n        if (UNLIKELY(!IS_NUM(ch))) {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        parser->http_minor *= 10;\n        parser->http_minor += ch - '0';\n\n        if (UNLIKELY(parser->http_minor > 999)) {\n          SET_ERRNO(HPE_INVALID_VERSION);\n          goto error;\n        }\n\n        break;\n      }\n\n      /* end of request line */\n      case s_req_line_almost_done:\n      {\n        if (UNLIKELY(ch != LF)) {\n          SET_ERRNO(HPE_LF_EXPECTED);\n          goto error;\n        }\n\n        UPDATE_STATE(s_header_field_start);\n        break;\n      }\n\n      case s_header_field_start:\n      {\n        if (ch == CR) {\n          UPDATE_STATE(s_headers_almost_done);\n          break;\n        }\n\n        if (ch == LF) {\n          /* they might be just sending \\n instead of \\r\\n so this would be\n           * the second \\n to denote the end of headers*/\n          UPDATE_STATE(s_headers_almost_done);\n          REEXECUTE();\n        }\n\n        c = TOKEN(ch);\n\n        if (UNLIKELY(!c)) {\n          SET_ERRNO(HPE_INVALID_HEADER_TOKEN);\n          goto error;\n        }\n\n        MARK(header_field);\n\n        parser->index = 0;\n        UPDATE_STATE(s_header_field);\n\n        switch (c) {\n          case 'c':\n            parser->header_state = h_C;\n            break;\n\n          case 'p':\n            parser->header_state = h_matching_proxy_connection;\n            break;\n\n          case 't':\n            parser->header_state = h_matching_transfer_encoding;\n            break;\n\n          case 'u':\n            parser->header_state = h_matching_upgrade;\n            break;\n\n          default:\n            parser->header_state = h_general;\n            break;\n        }\n        break;\n      }\n\n      case s_header_field:\n      {\n        const char* start = p;\n        for (; p != data + len; p++) {\n          ch = *p;\n          c = TOKEN(ch);\n\n          if (!c)\n            break;\n\n          switch (parser->header_state) {\n            case h_general:\n              break;\n\n            case h_C:\n              parser->index++;\n              parser->header_state = (c == 'o' ? h_CO : h_general);\n              break;\n\n            case h_CO:\n              parser->index++;\n              parser->header_state = (c == 'n' ? h_CON : h_general);\n              break;\n\n            case h_CON:\n              parser->index++;\n              switch (c) {\n                case 'n':\n                  parser->header_state = h_matching_connection;\n                  break;\n                case 't':\n                  parser->header_state = h_matching_content_length;\n                  break;\n                default:\n                  parser->header_state = h_general;\n                  break;\n              }\n              break;\n\n            /* connection */\n\n            case h_matching_connection:\n              parser->index++;\n              if (parser->index > sizeof(CONNECTION)-1\n                  || c != CONNECTION[parser->index]) {\n                parser->header_state = h_general;\n              } else if (parser->index == sizeof(CONNECTION)-2) {\n                parser->header_state = h_connection;\n              }\n              break;\n\n            /* proxy-connection */\n\n            case h_matching_proxy_connection:\n              parser->index++;\n              if (parser->index > sizeof(PROXY_CONNECTION)-1\n                  || c != PROXY_CONNECTION[parser->index]) {\n                parser->header_state = h_general;\n              } else if (parser->index == sizeof(PROXY_CONNECTION)-2) {\n                parser->header_state = h_connection;\n              }\n              break;\n\n            /* content-length */\n\n            case h_matching_content_length:\n              parser->index++;\n              if (parser->index > sizeof(CONTENT_LENGTH)-1\n                  || c != CONTENT_LENGTH[parser->index]) {\n                parser->header_state = h_general;\n              } else if (parser->index == sizeof(CONTENT_LENGTH)-2) {\n                parser->header_state = h_content_length;\n              }\n              break;\n\n            /* transfer-encoding */\n\n            case h_matching_transfer_encoding:\n              parser->index++;\n              if (parser->index > sizeof(TRANSFER_ENCODING)-1\n                  || c != TRANSFER_ENCODING[parser->index]) {\n                parser->header_state = h_general;\n              } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) {\n                parser->header_state = h_transfer_encoding;\n              }\n              break;\n\n            /* upgrade */\n\n            case h_matching_upgrade:\n              parser->index++;\n              if (parser->index > sizeof(UPGRADE)-1\n                  || c != UPGRADE[parser->index]) {\n                parser->header_state = h_general;\n              } else if (parser->index == sizeof(UPGRADE)-2) {\n                parser->header_state = h_upgrade;\n              }\n              break;\n\n            case h_connection:\n            case h_content_length:\n            case h_transfer_encoding:\n            case h_upgrade:\n              if (ch != ' ') parser->header_state = h_general;\n              break;\n\n            default:\n              assert(0 && \"Unknown header_state\");\n              break;\n          }\n        }\n\n        COUNT_HEADER_SIZE(p - start);\n\n        if (p == data + len) {\n          --p;\n          break;\n        }\n\n        if (ch == ':') {\n          UPDATE_STATE(s_header_value_discard_ws);\n          CALLBACK_DATA(header_field);\n          break;\n        }\n\n        SET_ERRNO(HPE_INVALID_HEADER_TOKEN);\n        goto error;\n      }\n\n      case s_header_value_discard_ws:\n        if (ch == ' ' || ch == '\\t') break;\n\n        if (ch == CR) {\n          UPDATE_STATE(s_header_value_discard_ws_almost_done);\n          break;\n        }\n\n        if (ch == LF) {\n          UPDATE_STATE(s_header_value_discard_lws);\n          break;\n        }\n\n        /* FALLTHROUGH */\n\n      case s_header_value_start:\n      {\n        MARK(header_value);\n\n        UPDATE_STATE(s_header_value);\n        parser->index = 0;\n\n        c = LOWER(ch);\n\n        switch (parser->header_state) {\n          case h_upgrade:\n            parser->flags |= F_UPGRADE;\n            parser->header_state = h_general;\n            break;\n\n          case h_transfer_encoding:\n            /* looking for 'Transfer-Encoding: chunked' */\n            if ('c' == c) {\n              parser->header_state = h_matching_transfer_encoding_chunked;\n            } else {\n              parser->header_state = h_general;\n            }\n            break;\n\n          case h_content_length:\n            if (UNLIKELY(!IS_NUM(ch))) {\n              SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);\n              goto error;\n            }\n\n            parser->content_length = ch - '0';\n            break;\n\n          case h_connection:\n            /* looking for 'Connection: keep-alive' */\n            if (c == 'k') {\n              parser->header_state = h_matching_connection_keep_alive;\n            /* looking for 'Connection: close' */\n            } else if (c == 'c') {\n              parser->header_state = h_matching_connection_close;\n            } else if (c == 'u') {\n              parser->header_state = h_matching_connection_upgrade;\n            } else {\n              parser->header_state = h_matching_connection_token;\n            }\n            break;\n\n          /* Multi-value `Connection` header */\n          case h_matching_connection_token_start:\n            break;\n\n          default:\n            parser->header_state = h_general;\n            break;\n        }\n        break;\n      }\n\n      case s_header_value:\n      {\n        const char* start = p;\n        enum header_states h_state = (enum header_states) parser->header_state;\n        for (; p != data + len; p++) {\n          ch = *p;\n          if (ch == CR) {\n            UPDATE_STATE(s_header_almost_done);\n            parser->header_state = h_state;\n            CALLBACK_DATA(header_value);\n            break;\n          }\n\n          if (ch == LF) {\n            UPDATE_STATE(s_header_almost_done);\n            COUNT_HEADER_SIZE(p - start);\n            parser->header_state = h_state;\n            CALLBACK_DATA_NOADVANCE(header_value);\n            REEXECUTE();\n          }\n\n          c = LOWER(ch);\n\n          switch (h_state) {\n            case h_general:\n            {\n              const char* p_cr;\n              const char* p_lf;\n              size_t limit = data + len - p;\n\n              limit = MIN(limit, HTTP_MAX_HEADER_SIZE);\n\n              p_cr = (const char*) memchr(p, CR, limit);\n              p_lf = (const char*) memchr(p, LF, limit);\n              if (p_cr != NULL) {\n                if (p_lf != NULL && p_cr >= p_lf)\n                  p = p_lf;\n                else\n                  p = p_cr;\n              } else if (UNLIKELY(p_lf != NULL)) {\n                p = p_lf;\n              } else {\n                p = data + len;\n              }\n              --p;\n\n              break;\n            }\n\n            case h_connection:\n            case h_transfer_encoding:\n              assert(0 && \"Shouldn't get here.\");\n              break;\n\n            case h_content_length:\n            {\n              uint64_t t;\n\n              if (ch == ' ') break;\n\n              if (UNLIKELY(!IS_NUM(ch))) {\n                SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);\n                parser->header_state = h_state;\n                goto error;\n              }\n\n              t = parser->content_length;\n              t *= 10;\n              t += ch - '0';\n\n              /* Overflow? Test against a conservative limit for simplicity. */\n              if (UNLIKELY((ULLONG_MAX - 10) / 10 < parser->content_length)) {\n                SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);\n                parser->header_state = h_state;\n                goto error;\n              }\n\n              parser->content_length = t;\n              break;\n            }\n\n            /* Transfer-Encoding: chunked */\n            case h_matching_transfer_encoding_chunked:\n              parser->index++;\n              if (parser->index > sizeof(CHUNKED)-1\n                  || c != CHUNKED[parser->index]) {\n                h_state = h_general;\n              } else if (parser->index == sizeof(CHUNKED)-2) {\n                h_state = h_transfer_encoding_chunked;\n              }\n              break;\n\n            case h_matching_connection_token_start:\n              /* looking for 'Connection: keep-alive' */\n              if (c == 'k') {\n                h_state = h_matching_connection_keep_alive;\n              /* looking for 'Connection: close' */\n              } else if (c == 'c') {\n                h_state = h_matching_connection_close;\n              } else if (c == 'u') {\n                h_state = h_matching_connection_upgrade;\n              } else if (STRICT_TOKEN(c)) {\n                h_state = h_matching_connection_token;\n              } else if (c == ' ' || c == '\\t') {\n                /* Skip lws */\n              } else {\n                h_state = h_general;\n              }\n              break;\n\n            /* looking for 'Connection: keep-alive' */\n            case h_matching_connection_keep_alive:\n              parser->index++;\n              if (parser->index > sizeof(KEEP_ALIVE)-1\n                  || c != KEEP_ALIVE[parser->index]) {\n                h_state = h_matching_connection_token;\n              } else if (parser->index == sizeof(KEEP_ALIVE)-2) {\n                h_state = h_connection_keep_alive;\n              }\n              break;\n\n            /* looking for 'Connection: close' */\n            case h_matching_connection_close:\n              parser->index++;\n              if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) {\n                h_state = h_matching_connection_token;\n              } else if (parser->index == sizeof(CLOSE)-2) {\n                h_state = h_connection_close;\n              }\n              break;\n\n            /* looking for 'Connection: upgrade' */\n            case h_matching_connection_upgrade:\n              parser->index++;\n              if (parser->index > sizeof(UPGRADE) - 1 ||\n                  c != UPGRADE[parser->index]) {\n                h_state = h_matching_connection_token;\n              } else if (parser->index == sizeof(UPGRADE)-2) {\n                h_state = h_connection_upgrade;\n              }\n              break;\n\n            case h_matching_connection_token:\n              if (ch == ',') {\n                h_state = h_matching_connection_token_start;\n                parser->index = 0;\n              }\n              break;\n\n            case h_transfer_encoding_chunked:\n              if (ch != ' ') h_state = h_general;\n              break;\n\n            case h_connection_keep_alive:\n            case h_connection_close:\n            case h_connection_upgrade:\n              if (ch == ',') {\n                if (h_state == h_connection_keep_alive) {\n                  parser->flags |= F_CONNECTION_KEEP_ALIVE;\n                } else if (h_state == h_connection_close) {\n                  parser->flags |= F_CONNECTION_CLOSE;\n                } else if (h_state == h_connection_upgrade) {\n                  parser->flags |= F_CONNECTION_UPGRADE;\n                }\n                h_state = h_matching_connection_token_start;\n                parser->index = 0;\n              } else if (ch != ' ') {\n                h_state = h_matching_connection_token;\n              }\n              break;\n\n            default:\n              UPDATE_STATE(s_header_value);\n              h_state = h_general;\n              break;\n          }\n        }\n        parser->header_state = h_state;\n\n        COUNT_HEADER_SIZE(p - start);\n\n        if (p == data + len)\n          --p;\n        break;\n      }\n\n      case s_header_almost_done:\n      {\n        STRICT_CHECK(ch != LF);\n\n        UPDATE_STATE(s_header_value_lws);\n        break;\n      }\n\n      case s_header_value_lws:\n      {\n        if (ch == ' ' || ch == '\\t') {\n          UPDATE_STATE(s_header_value_start);\n          REEXECUTE();\n        }\n\n        /* finished the header */\n        switch (parser->header_state) {\n          case h_connection_keep_alive:\n            parser->flags |= F_CONNECTION_KEEP_ALIVE;\n            break;\n          case h_connection_close:\n            parser->flags |= F_CONNECTION_CLOSE;\n            break;\n          case h_transfer_encoding_chunked:\n            parser->flags |= F_CHUNKED;\n            break;\n          case h_connection_upgrade:\n            parser->flags |= F_CONNECTION_UPGRADE;\n            break;\n          default:\n            break;\n        }\n\n        UPDATE_STATE(s_header_field_start);\n        REEXECUTE();\n      }\n\n      case s_header_value_discard_ws_almost_done:\n      {\n        STRICT_CHECK(ch != LF);\n        UPDATE_STATE(s_header_value_discard_lws);\n        break;\n      }\n\n      case s_header_value_discard_lws:\n      {\n        if (ch == ' ' || ch == '\\t') {\n          UPDATE_STATE(s_header_value_discard_ws);\n          break;\n        } else {\n          switch (parser->header_state) {\n            case h_connection_keep_alive:\n              parser->flags |= F_CONNECTION_KEEP_ALIVE;\n              break;\n            case h_connection_close:\n              parser->flags |= F_CONNECTION_CLOSE;\n              break;\n            case h_connection_upgrade:\n              parser->flags |= F_CONNECTION_UPGRADE;\n              break;\n            case h_transfer_encoding_chunked:\n              parser->flags |= F_CHUNKED;\n              break;\n            default:\n              break;\n          }\n\n          /* header value was empty */\n          MARK(header_value);\n          UPDATE_STATE(s_header_field_start);\n          CALLBACK_DATA_NOADVANCE(header_value);\n          REEXECUTE();\n        }\n      }\n\n      case s_headers_almost_done:\n      {\n        STRICT_CHECK(ch != LF);\n\n        if (parser->flags & F_TRAILING) {\n          /* End of a chunked request */\n          UPDATE_STATE(NEW_MESSAGE());\n          CALLBACK_NOTIFY(message_complete);\n          break;\n        }\n\n        UPDATE_STATE(s_headers_done);\n\n        /* Set this here so that on_headers_complete() callbacks can see it */\n        parser->upgrade =\n          ((parser->flags & (F_UPGRADE | F_CONNECTION_UPGRADE)) ==\n           (F_UPGRADE | F_CONNECTION_UPGRADE) ||\n           parser->method == HTTP_CONNECT);\n\n        /* Here we call the headers_complete callback. This is somewhat\n         * different than other callbacks because if the user returns 1, we\n         * will interpret that as saying that this message has no body. This\n         * is needed for the annoying case of recieving a response to a HEAD\n         * request.\n         *\n         * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so\n         * we have to simulate it by handling a change in errno below.\n         */\n        if (settings->on_headers_complete) {\n          switch (settings->on_headers_complete(parser)) {\n            case 0:\n              break;\n\n            case 1:\n              parser->flags |= F_SKIPBODY;\n              break;\n\n            default:\n              SET_ERRNO(HPE_CB_headers_complete);\n              RETURN(p - data); /* Error */\n          }\n        }\n\n        if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {\n          RETURN(p - data);\n        }\n\n        REEXECUTE();\n      }\n\n      case s_headers_done:\n      {\n        STRICT_CHECK(ch != LF);\n\n        parser->nread = 0;\n\n        /* Exit, the rest of the connect is in a different protocol. */\n        if (parser->upgrade) {\n          UPDATE_STATE(NEW_MESSAGE());\n          CALLBACK_NOTIFY(message_complete);\n          RETURN((p - data) + 1);\n        }\n\n        if (parser->flags & F_SKIPBODY) {\n          UPDATE_STATE(NEW_MESSAGE());\n          CALLBACK_NOTIFY(message_complete);\n        } else if (parser->flags & F_CHUNKED) {\n          /* chunked encoding - ignore Content-Length header */\n          UPDATE_STATE(s_chunk_size_start);\n        } else {\n          if (parser->content_length == 0) {\n            /* Content-Length header given but zero: Content-Length: 0\\r\\n */\n            UPDATE_STATE(NEW_MESSAGE());\n            CALLBACK_NOTIFY(message_complete);\n          } else if (parser->content_length != ULLONG_MAX) {\n            /* Content-Length header given and non-zero */\n            UPDATE_STATE(s_body_identity);\n          } else {\n            if (parser->type == HTTP_REQUEST ||\n                !http_message_needs_eof(parser)) {\n              /* Assume content-length 0 - read the next */\n              UPDATE_STATE(NEW_MESSAGE());\n              CALLBACK_NOTIFY(message_complete);\n            } else {\n              /* Read body until EOF */\n              UPDATE_STATE(s_body_identity_eof);\n            }\n          }\n        }\n\n        break;\n      }\n\n      case s_body_identity:\n      {\n        uint64_t to_read = MIN(parser->content_length,\n                               (uint64_t) ((data + len) - p));\n\n        assert(parser->content_length != 0\n            && parser->content_length != ULLONG_MAX);\n\n        /* The difference between advancing content_length and p is because\n         * the latter will automaticaly advance on the next loop iteration.\n         * Further, if content_length ends up at 0, we want to see the last\n         * byte again for our message complete callback.\n         */\n        MARK(body);\n        parser->content_length -= to_read;\n        p += to_read - 1;\n\n        if (parser->content_length == 0) {\n          UPDATE_STATE(s_message_done);\n\n          /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte.\n           *\n           * The alternative to doing this is to wait for the next byte to\n           * trigger the data callback, just as in every other case. The\n           * problem with this is that this makes it difficult for the test\n           * harness to distinguish between complete-on-EOF and\n           * complete-on-length. It's not clear that this distinction is\n           * important for applications, but let's keep it for now.\n           */\n          CALLBACK_DATA_(body, p - body_mark + 1, p - data);\n          REEXECUTE();\n        }\n\n        break;\n      }\n\n      /* read until EOF */\n      case s_body_identity_eof:\n        MARK(body);\n        p = data + len - 1;\n\n        break;\n\n      case s_message_done:\n        UPDATE_STATE(NEW_MESSAGE());\n        CALLBACK_NOTIFY(message_complete);\n        break;\n\n      case s_chunk_size_start:\n      {\n        assert(parser->nread == 1);\n        assert(parser->flags & F_CHUNKED);\n\n        unhex_val = unhex[(unsigned char)ch];\n        if (UNLIKELY(unhex_val == -1)) {\n          SET_ERRNO(HPE_INVALID_CHUNK_SIZE);\n          goto error;\n        }\n\n        parser->content_length = unhex_val;\n        UPDATE_STATE(s_chunk_size);\n        break;\n      }\n\n      case s_chunk_size:\n      {\n        uint64_t t;\n\n        assert(parser->flags & F_CHUNKED);\n\n        if (ch == CR) {\n          UPDATE_STATE(s_chunk_size_almost_done);\n          break;\n        }\n\n        unhex_val = unhex[(unsigned char)ch];\n\n        if (unhex_val == -1) {\n          if (ch == ';' || ch == ' ') {\n            UPDATE_STATE(s_chunk_parameters);\n            break;\n          }\n\n          SET_ERRNO(HPE_INVALID_CHUNK_SIZE);\n          goto error;\n        }\n\n        t = parser->content_length;\n        t *= 16;\n        t += unhex_val;\n\n        /* Overflow? Test against a conservative limit for simplicity. */\n        if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) {\n          SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);\n          goto error;\n        }\n\n        parser->content_length = t;\n        break;\n      }\n\n      case s_chunk_parameters:\n      {\n        assert(parser->flags & F_CHUNKED);\n        /* just ignore this shit. TODO check for overflow */\n        if (ch == CR) {\n          UPDATE_STATE(s_chunk_size_almost_done);\n          break;\n        }\n        break;\n      }\n\n      case s_chunk_size_almost_done:\n      {\n        assert(parser->flags & F_CHUNKED);\n        STRICT_CHECK(ch != LF);\n\n        parser->nread = 0;\n\n        if (parser->content_length == 0) {\n          parser->flags |= F_TRAILING;\n          UPDATE_STATE(s_header_field_start);\n        } else {\n          UPDATE_STATE(s_chunk_data);\n        }\n        break;\n      }\n\n      case s_chunk_data:\n      {\n        uint64_t to_read = MIN(parser->content_length,\n                               (uint64_t) ((data + len) - p));\n\n        assert(parser->flags & F_CHUNKED);\n        assert(parser->content_length != 0\n            && parser->content_length != ULLONG_MAX);\n\n        /* See the explanation in s_body_identity for why the content\n         * length and data pointers are managed this way.\n         */\n        MARK(body);\n        parser->content_length -= to_read;\n        p += to_read - 1;\n\n        if (parser->content_length == 0) {\n          UPDATE_STATE(s_chunk_data_almost_done);\n        }\n\n        break;\n      }\n\n      case s_chunk_data_almost_done:\n        assert(parser->flags & F_CHUNKED);\n        assert(parser->content_length == 0);\n        STRICT_CHECK(ch != CR);\n        UPDATE_STATE(s_chunk_data_done);\n        CALLBACK_DATA(body);\n        break;\n\n      case s_chunk_data_done:\n        assert(parser->flags & F_CHUNKED);\n        STRICT_CHECK(ch != LF);\n        parser->nread = 0;\n        UPDATE_STATE(s_chunk_size_start);\n        break;\n\n      default:\n        assert(0 && \"unhandled state\");\n        SET_ERRNO(HPE_INVALID_INTERNAL_STATE);\n        goto error;\n    }\n  }\n\n  /* Run callbacks for any marks that we have leftover after we ran our of\n   * bytes. There should be at most one of these set, so it's OK to invoke\n   * them in series (unset marks will not result in callbacks).\n   *\n   * We use the NOADVANCE() variety of callbacks here because 'p' has already\n   * overflowed 'data' and this allows us to correct for the off-by-one that\n   * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p'\n   * value that's in-bounds).\n   */\n\n  assert(((header_field_mark ? 1 : 0) +\n          (header_value_mark ? 1 : 0) +\n          (url_mark ? 1 : 0)  +\n          (body_mark ? 1 : 0) +\n          (status_mark ? 1 : 0)) <= 1);\n\n  CALLBACK_DATA_NOADVANCE(header_field);\n  CALLBACK_DATA_NOADVANCE(header_value);\n  CALLBACK_DATA_NOADVANCE(url);\n  CALLBACK_DATA_NOADVANCE(body);\n  CALLBACK_DATA_NOADVANCE(status);\n\n  RETURN(len);\n\nerror:\n  if (HTTP_PARSER_ERRNO(parser) == HPE_OK) {\n    SET_ERRNO(HPE_UNKNOWN);\n  }\n\n  RETURN(p - data);\n}\n\n\n/* Does the parser need to see an EOF to find the end of the message? */\nint\nhttp_message_needs_eof (const http_parser *parser)\n{\n  if (parser->type == HTTP_REQUEST) {\n    return 0;\n  }\n\n  /* See RFC 2616 section 4.4 */\n  if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */\n      parser->status_code == 204 ||     /* No Content */\n      parser->status_code == 304 ||     /* Not Modified */\n      parser->flags & F_SKIPBODY) {     /* response to a HEAD request */\n    return 0;\n  }\n\n  if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) {\n    return 0;\n  }\n\n  return 1;\n}\n\n\nint\nhttp_should_keep_alive (const http_parser *parser)\n{\n  if (parser->http_major > 0 && parser->http_minor > 0) {\n    /* HTTP/1.1 */\n    if (parser->flags & F_CONNECTION_CLOSE) {\n      return 0;\n    }\n  } else {\n    /* HTTP/1.0 or earlier */\n    if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) {\n      return 0;\n    }\n  }\n\n  return !http_message_needs_eof(parser);\n}\n\n\nconst char *\nhttp_method_str (enum http_method m)\n{\n  return ELEM_AT(method_strings, m, \"<unknown>\");\n}\n\n\nvoid\nhttp_parser_init (http_parser *parser, enum http_parser_type t)\n{\n  void *data = parser->data; /* preserve application data */\n  memset(parser, 0, sizeof(*parser));\n  parser->data = data;\n  parser->type = t;\n  parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res));\n  parser->http_errno = HPE_OK;\n}\n\nvoid\nhttp_parser_settings_init(http_parser_settings *settings)\n{\n  memset(settings, 0, sizeof(*settings));\n}\n\nconst char *\nhttp_errno_name(enum http_errno err) {\n  assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0])));\n  return http_strerror_tab[err].name;\n}\n\nconst char *\nhttp_errno_description(enum http_errno err) {\n  assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0])));\n  return http_strerror_tab[err].description;\n}\n\nstatic enum http_host_state\nhttp_parse_host_char(enum http_host_state s, const char ch) {\n  switch(s) {\n    case s_http_userinfo:\n    case s_http_userinfo_start:\n      if (ch == '@') {\n        return s_http_host_start;\n      }\n\n      if (IS_USERINFO_CHAR(ch)) {\n        return s_http_userinfo;\n      }\n      break;\n\n    case s_http_host_start:\n      if (ch == '[') {\n        return s_http_host_v6_start;\n      }\n\n      if (IS_HOST_CHAR(ch)) {\n        return s_http_host;\n      }\n\n      break;\n\n    case s_http_host:\n      if (IS_HOST_CHAR(ch)) {\n        return s_http_host;\n      }\n\n    /* FALLTHROUGH */\n    case s_http_host_v6_end:\n      if (ch == ':') {\n        return s_http_host_port_start;\n      }\n\n      break;\n\n    case s_http_host_v6:\n      if (ch == ']') {\n        return s_http_host_v6_end;\n      }\n\n    /* FALLTHROUGH */\n    case s_http_host_v6_start:\n      if (IS_HEX(ch) || ch == ':' || ch == '.') {\n        return s_http_host_v6;\n      }\n\n      break;\n\n    case s_http_host_port:\n    case s_http_host_port_start:\n      if (IS_NUM(ch)) {\n        return s_http_host_port;\n      }\n\n      break;\n\n    default:\n      break;\n  }\n  return s_http_host_dead;\n}\n\nstatic int\nhttp_parse_host(const char * buf, struct http_parser_url *u, int found_at) {\n  enum http_host_state s;\n\n  const char *p;\n  size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len;\n\n  u->field_data[UF_HOST].len = 0;\n\n  s = found_at ? s_http_userinfo_start : s_http_host_start;\n\n  for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) {\n    enum http_host_state new_s = http_parse_host_char(s, *p);\n\n    if (new_s == s_http_host_dead) {\n      return 1;\n    }\n\n    switch(new_s) {\n      case s_http_host:\n        if (s != s_http_host) {\n          u->field_data[UF_HOST].off = p - buf;\n        }\n        u->field_data[UF_HOST].len++;\n        break;\n\n      case s_http_host_v6:\n        if (s != s_http_host_v6) {\n          u->field_data[UF_HOST].off = p - buf;\n        }\n        u->field_data[UF_HOST].len++;\n        break;\n\n      case s_http_host_port:\n        if (s != s_http_host_port) {\n          u->field_data[UF_PORT].off = p - buf;\n          u->field_data[UF_PORT].len = 0;\n          u->field_set |= (1 << UF_PORT);\n        }\n        u->field_data[UF_PORT].len++;\n        break;\n\n      case s_http_userinfo:\n        if (s != s_http_userinfo) {\n          u->field_data[UF_USERINFO].off = p - buf ;\n          u->field_data[UF_USERINFO].len = 0;\n          u->field_set |= (1 << UF_USERINFO);\n        }\n        u->field_data[UF_USERINFO].len++;\n        break;\n\n      default:\n        break;\n    }\n    s = new_s;\n  }\n\n  /* Make sure we don't end somewhere unexpected */\n  switch (s) {\n    case s_http_host_start:\n    case s_http_host_v6_start:\n    case s_http_host_v6:\n    case s_http_host_port_start:\n    case s_http_userinfo:\n    case s_http_userinfo_start:\n      return 1;\n    default:\n      break;\n  }\n\n  return 0;\n}\n\nint\nhttp_parser_parse_url(const char *buf, size_t buflen, int is_connect,\n                      struct http_parser_url *u)\n{\n  enum state s;\n  const char *p;\n  enum http_parser_url_fields uf, old_uf;\n  int found_at = 0;\n\n  u->port = u->field_set = 0;\n  s = is_connect ? s_req_server_start : s_req_spaces_before_url;\n  old_uf = UF_MAX;\n\n  for (p = buf; p < buf + buflen; p++) {\n    s = parse_url_char(s, *p);\n\n    /* Figure out the next field that we're operating on */\n    switch (s) {\n      case s_dead:\n        return 1;\n\n      /* Skip delimeters */\n      case s_req_schema_slash:\n      case s_req_schema_slash_slash:\n      case s_req_server_start:\n      case s_req_query_string_start:\n      case s_req_fragment_start:\n        continue;\n\n      case s_req_schema:\n        uf = UF_SCHEMA;\n        break;\n\n      case s_req_server_with_at:\n        found_at = 1;\n        /* FALLTHRU */\n\n      case s_req_server:\n        uf = UF_HOST;\n        break;\n\n      case s_req_path:\n        uf = UF_PATH;\n        break;\n\n      case s_req_query_string:\n        uf = UF_QUERY;\n        break;\n\n      case s_req_fragment:\n        uf = UF_FRAGMENT;\n        break;\n\n      default:\n        assert(!\"Unexpected state\");\n        return 1;\n    }\n\n    /* Nothing's changed; soldier on */\n    if (uf == old_uf) {\n      u->field_data[uf].len++;\n      continue;\n    }\n\n    u->field_data[uf].off = p - buf;\n    u->field_data[uf].len = 1;\n\n    u->field_set |= (1 << uf);\n    old_uf = uf;\n  }\n\n  /* host must be present if there is a schema */\n  /* parsing http:///toto will fail */\n  if ((u->field_set & ((1 << UF_SCHEMA) | (1 << UF_HOST))) != 0) {\n    if (http_parse_host(buf, u, found_at) != 0) {\n      return 1;\n    }\n  }\n\n  /* CONNECT requests can only contain \"hostname:port\" */\n  if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) {\n    return 1;\n  }\n\n  if (u->field_set & (1 << UF_PORT)) {\n    /* Don't bother with endp; we've already validated the string */\n    unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10);\n\n    /* Ports have a max value of 2^16 */\n    if (v > 0xffff) {\n      return 1;\n    }\n\n    u->port = (uint16_t) v;\n  }\n\n  return 0;\n}\n\nvoid\nhttp_parser_pause(http_parser *parser, int paused) {\n  /* Users should only be pausing/unpausing a parser that is not in an error\n   * state. In non-debug builds, there's not much that we can do about this\n   * other than ignore it.\n   */\n  if (HTTP_PARSER_ERRNO(parser) == HPE_OK ||\n      HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) {\n    SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK);\n  } else {\n    assert(0 && \"Attempting to pause parser in error state\");\n  }\n}\n\nint\nhttp_body_is_final(const struct http_parser *parser) {\n    return parser->state == s_message_done;\n}\n\nunsigned long\nhttp_parser_version(void) {\n  return HTTP_PARSER_VERSION_MAJOR * 0x10000 |\n         HTTP_PARSER_VERSION_MINOR * 0x00100 |\n         HTTP_PARSER_VERSION_PATCH * 0x00001;\n}\n\n#endif\n"
  },
  {
    "path": "src/ngx_stream_http_parser.h",
    "content": "/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n#ifndef http_parser_h\n#define http_parser_h\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* Also update SONAME in the Makefile whenever you change these. */\n#define HTTP_PARSER_VERSION_MAJOR 2\n#define HTTP_PARSER_VERSION_MINOR 4\n#define HTTP_PARSER_VERSION_PATCH 2\n\n#include <sys/types.h>\n#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600)\n#include <BaseTsd.h>\n#include <stddef.h>\ntypedef __int8 int8_t;\ntypedef unsigned __int8 uint8_t;\ntypedef __int16 int16_t;\ntypedef unsigned __int16 uint16_t;\ntypedef __int32 int32_t;\ntypedef unsigned __int32 uint32_t;\ntypedef __int64 int64_t;\ntypedef unsigned __int64 uint64_t;\n#else\n#include <stdint.h>\n#endif\n\n/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run\n * faster\n */\n#ifndef HTTP_PARSER_STRICT\n# define HTTP_PARSER_STRICT 1\n#endif\n\n/* Maximium header size allowed. If the macro is not defined\n * before including this header then the default is used. To\n * change the maximum header size, define the macro in the build\n * environment (e.g. -DHTTP_MAX_HEADER_SIZE=<value>). To remove\n * the effective limit on the size of the header, define the macro\n * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff)\n */\n#ifndef HTTP_MAX_HEADER_SIZE\n# define HTTP_MAX_HEADER_SIZE (80*1024)\n#endif\n\ntypedef struct http_parser http_parser;\ntypedef struct http_parser_settings http_parser_settings;\n\n\n/* Callbacks should return non-zero to indicate an error. The parser will\n * then halt execution.\n *\n * The one exception is on_headers_complete. In a HTTP_RESPONSE parser\n * returning '1' from on_headers_complete will tell the parser that it\n * should not expect a body. This is used when receiving a response to a\n * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:\n * chunked' headers that indicate the presence of a body.\n *\n * http_data_cb does not return data chunks. It will be called arbitrarily\n * many times for each string. E.G. you might get 10 callbacks for \"on_url\"\n * each providing just a few characters more data.\n */\ntypedef int (*http_data_cb) (http_parser*, const char *at, size_t length);\ntypedef int (*http_cb) (http_parser*);\n\n\n/* Request Methods */\n#define HTTP_METHOD_MAP(XX)         \\\n  XX(0,  DELETE,      DELETE)       \\\n  XX(1,  GET,         GET)          \\\n  XX(2,  HEAD,        HEAD)         \\\n  XX(3,  POST,        POST)         \\\n  XX(4,  PUT,         PUT)          \\\n  /* pathological */                \\\n  XX(5,  CONNECT,     CONNECT)      \\\n  XX(6,  OPTIONS,     OPTIONS)      \\\n  XX(7,  TRACE,       TRACE)        \\\n  /* webdav */                      \\\n  XX(8,  COPY,        COPY)         \\\n  XX(9,  LOCK,        LOCK)         \\\n  XX(10, MKCOL,       MKCOL)        \\\n  XX(11, MOVE,        MOVE)         \\\n  XX(12, PROPFIND,    PROPFIND)     \\\n  XX(13, PROPPATCH,   PROPPATCH)    \\\n  XX(14, SEARCH,      SEARCH)       \\\n  XX(15, UNLOCK,      UNLOCK)       \\\n  /* subversion */                  \\\n  XX(16, REPORT,      REPORT)       \\\n  XX(17, MKACTIVITY,  MKACTIVITY)   \\\n  XX(18, CHECKOUT,    CHECKOUT)     \\\n  XX(19, MERGE,       MERGE)        \\\n  /* upnp */                        \\\n  XX(20, MSEARCH,     M-SEARCH)     \\\n  XX(21, NOTIFY,      NOTIFY)       \\\n  XX(22, SUBSCRIBE,   SUBSCRIBE)    \\\n  XX(23, UNSUBSCRIBE, UNSUBSCRIBE)  \\\n  /* RFC-5789 */                    \\\n  XX(24, PATCH,       PATCH)        \\\n  XX(25, PURGE,       PURGE)        \\\n  /* CalDAV */                      \\\n  XX(26, MKCALENDAR,  MKCALENDAR)   \\\n\nenum http_method\n  {\n#define XX(num, name, string) HTTP_##name = num,\n  HTTP_METHOD_MAP(XX)\n#undef XX\n  };\n\n\nenum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };\n\n\n/* Flag values for http_parser.flags field */\nenum flags\n  { F_CHUNKED               = 1 << 0\n  , F_CONNECTION_KEEP_ALIVE = 1 << 1\n  , F_CONNECTION_CLOSE      = 1 << 2\n  , F_CONNECTION_UPGRADE    = 1 << 3\n  , F_TRAILING              = 1 << 4\n  , F_UPGRADE               = 1 << 5\n  , F_SKIPBODY              = 1 << 6\n  };\n\n\n/* Map for errno-related constants\n * \n * The provided argument should be a macro that takes 2 arguments.\n */\n#define HTTP_ERRNO_MAP(XX)                                           \\\n  /* No error */                                                     \\\n  XX(OK, \"success\")                                                  \\\n                                                                     \\\n  /* Callback-related errors */                                      \\\n  XX(CB_message_begin, \"the on_message_begin callback failed\")       \\\n  XX(CB_url, \"the on_url callback failed\")                           \\\n  XX(CB_header_field, \"the on_header_field callback failed\")         \\\n  XX(CB_header_value, \"the on_header_value callback failed\")         \\\n  XX(CB_headers_complete, \"the on_headers_complete callback failed\") \\\n  XX(CB_body, \"the on_body callback failed\")                         \\\n  XX(CB_message_complete, \"the on_message_complete callback failed\") \\\n  XX(CB_status, \"the on_status callback failed\")                     \\\n                                                                     \\\n  /* Parsing-related errors */                                       \\\n  XX(INVALID_EOF_STATE, \"stream ended at an unexpected time\")        \\\n  XX(HEADER_OVERFLOW,                                                \\\n     \"too many header bytes seen; overflow detected\")                \\\n  XX(CLOSED_CONNECTION,                                              \\\n     \"data received after completed connection: close message\")      \\\n  XX(INVALID_VERSION, \"invalid HTTP version\")                        \\\n  XX(INVALID_STATUS, \"invalid HTTP status code\")                     \\\n  XX(INVALID_METHOD, \"invalid HTTP method\")                          \\\n  XX(INVALID_URL, \"invalid URL\")                                     \\\n  XX(INVALID_HOST, \"invalid host\")                                   \\\n  XX(INVALID_PORT, \"invalid port\")                                   \\\n  XX(INVALID_PATH, \"invalid path\")                                   \\\n  XX(INVALID_QUERY_STRING, \"invalid query string\")                   \\\n  XX(INVALID_FRAGMENT, \"invalid fragment\")                           \\\n  XX(LF_EXPECTED, \"LF character expected\")                           \\\n  XX(INVALID_HEADER_TOKEN, \"invalid character in header\")            \\\n  XX(INVALID_CONTENT_LENGTH,                                         \\\n     \"invalid character in content-length header\")                   \\\n  XX(INVALID_CHUNK_SIZE,                                             \\\n     \"invalid character in chunk size header\")                       \\\n  XX(INVALID_CONSTANT, \"invalid constant string\")                    \\\n  XX(INVALID_INTERNAL_STATE, \"encountered unexpected internal state\")\\\n  XX(STRICT, \"strict mode assertion failed\")                         \\\n  XX(PAUSED, \"parser is paused\")                                     \\\n  XX(UNKNOWN, \"an unknown error occurred\")\n\n\n/* Define HPE_* values for each errno value above */\n#define HTTP_ERRNO_GEN(n, s) HPE_##n,\nenum http_errno {\n  HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)\n};\n#undef HTTP_ERRNO_GEN\n\n\n/* Get an http_errno value from an http_parser */\n#define HTTP_PARSER_ERRNO(p)            ((enum http_errno) (p)->http_errno)\n\n\nstruct http_parser {\n  /** PRIVATE **/\n  unsigned int type : 2;         /* enum http_parser_type */\n  unsigned int flags : 7;        /* F_* values from 'flags' enum; semi-public */\n  unsigned int state : 7;        /* enum state from http_parser.c */\n  unsigned int header_state : 8; /* enum header_state from http_parser.c */\n  unsigned int index : 8;        /* index into current matcher */\n\n  uint32_t nread;          /* # bytes read in various scenarios */\n  uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */\n\n  /** READ-ONLY **/\n  unsigned short http_major;\n  unsigned short http_minor;\n  unsigned int status_code : 16; /* responses only */\n  unsigned int method : 8;       /* requests only */\n  unsigned int http_errno : 7;\n\n  /* 1 = Upgrade header was present and the parser has exited because of that.\n   * 0 = No upgrade header present.\n   * Should be checked when http_parser_execute() returns in addition to\n   * error checking.\n   */\n  unsigned int upgrade : 1;\n\n  /** PUBLIC **/\n  void *data; /* A pointer to get hook to the \"connection\" or \"socket\" object */\n};\n\n\nstruct http_parser_settings {\n  http_cb      on_message_begin;\n  http_data_cb on_url;\n  http_data_cb on_status;\n  http_data_cb on_header_field;\n  http_data_cb on_header_value;\n  http_cb      on_headers_complete;\n  http_data_cb on_body;\n  http_cb      on_message_complete;\n};\n\n\nenum http_parser_url_fields\n  { UF_SCHEMA           = 0\n  , UF_HOST             = 1\n  , UF_PORT             = 2\n  , UF_PATH             = 3\n  , UF_QUERY            = 4\n  , UF_FRAGMENT         = 5\n  , UF_USERINFO         = 6\n  , UF_MAX              = 7\n  };\n\n\n/* Result structure for http_parser_parse_url().\n *\n * Callers should index into field_data[] with UF_* values iff field_set\n * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and\n * because we probably have padding left over), we convert any port to\n * a uint16_t.\n */\nstruct http_parser_url {\n  uint16_t field_set;           /* Bitmask of (1 << UF_*) values */\n  uint16_t port;                /* Converted UF_PORT string */\n\n  struct {\n    uint16_t off;               /* Offset into buffer in which field starts */\n    uint16_t len;               /* Length of run in buffer */\n  } field_data[UF_MAX];\n};\n\n\n/* Returns the library version. Bits 16-23 contain the major version number,\n * bits 8-15 the minor version number and bits 0-7 the patch level.\n * Usage example:\n *\n *   unsigned long version = http_parser_version();\n *   unsigned major = (version >> 16) & 255;\n *   unsigned minor = (version >> 8) & 255;\n *   unsigned patch = version & 255;\n *   printf(\"http_parser v%u.%u.%u\\n\", major, minor, patch);\n */\nunsigned long http_parser_version(void);\n\nvoid http_parser_init(http_parser *parser, enum http_parser_type type);\n\n\n/* Initialize http_parser_settings members to 0\n */\nvoid http_parser_settings_init(http_parser_settings *settings);\n\n\n/* Executes the parser. Returns number of parsed bytes. Sets\n * `parser->http_errno` on error. */\nsize_t http_parser_execute(http_parser *parser,\n                           const http_parser_settings *settings,\n                           const char *data,\n                           size_t len);\n\n\n/* If http_should_keep_alive() in the on_headers_complete or\n * on_message_complete callback returns 0, then this should be\n * the last message on the connection.\n * If you are the server, respond with the \"Connection: close\" header.\n * If you are the client, close the connection.\n */\nint http_should_keep_alive(const http_parser *parser);\n\n/* Returns a string version of the HTTP method. */\nconst char *http_method_str(enum http_method m);\n\n/* Return a string name of the given error */\nconst char *http_errno_name(enum http_errno err);\n\n/* Return a string description of the given error */\nconst char *http_errno_description(enum http_errno err);\n\n/* Parse a URL; return nonzero on failure */\nint http_parser_parse_url(const char *buf, size_t buflen,\n                          int is_connect,\n                          struct http_parser_url *u);\n\n/* Pause or un-pause the parser; a nonzero value pauses */\nvoid http_parser_pause(http_parser *parser, int paused);\n\n/* Checks if this is the final chunk of the body. */\nint http_body_is_final(const http_parser *parser);\n\n#ifdef __cplusplus\n}\n#endif\n#endif\n"
  },
  {
    "path": "src/ngx_stream_json.c",
    "content": "/*\n  Copyright (c) 2009 Dave Gamble\n\n  Permission is hereby granted, free of charge, to any person obtaining a copy\n  of this software and associated documentation files (the \"Software\"), to deal\n  in the Software without restriction, including without limitation the rights\n  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n  copies of the Software, and to permit persons to whom the Software is\n  furnished to do so, subject to the following conditions:\n\n  The above copyright notice and this permission notice shall be included in\n  all copies or substantial portions of the Software.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n  THE SOFTWARE.\n*/\n\n/* cJSON */\n/* JSON parser in C. */\n\n#include <ngx_config.h>\n#ifndef NGX_HTTP_UPSYNC\n\n#include <string.h>\n#include <stdio.h>\n#include <math.h>\n#include <stdlib.h>\n#include <float.h>\n#include <limits.h>\n#include <ctype.h>\n#include \"ngx_stream_json.h\"\n\nstatic const char *ep;\n\nconst char *cJSON_GetErrorPtr(void) {return ep;}\n\nstatic int cJSON_strcasecmp(const char *s1,const char *s2)\n{\n\tif (!s1) return (s1==s2)?0:1;\n\tif (!s2) return 1;\n\tfor(; tolower(*s1) == tolower(*s2); ++s1, ++s2)\tif(*s1 == 0)\treturn 0;\n\treturn tolower(*(const unsigned char *)s1) - tolower(*(const unsigned char *)s2);\n}\n\nstatic void *(*cJSON_malloc)(size_t sz) = malloc;\nstatic void (*cJSON_free)(void *ptr) = free;\n\nstatic char* cJSON_strdup(const char* str)\n{\n      size_t len;\n      char* copy;\n\n      len = strlen(str) + 1;\n      if (!(copy = (char*)cJSON_malloc(len))) return 0;\n      memcpy(copy,str,len);\n      return copy;\n}\n\nvoid cJSON_InitHooks(cJSON_Hooks* hooks)\n{\n    if (!hooks) { /* Reset hooks */\n        cJSON_malloc = malloc;\n        cJSON_free = free;\n        return;\n    }\n\n\tcJSON_malloc = (hooks->malloc_fn)?hooks->malloc_fn:malloc;\n\tcJSON_free\t = (hooks->free_fn)?hooks->free_fn:free;\n}\n\n/* Internal constructor. */\nstatic cJSON *cJSON_New_Item(void)\n{\n\tcJSON* node = (cJSON*)cJSON_malloc(sizeof(cJSON));\n\tif (node) memset(node,0,sizeof(cJSON));\n\treturn node;\n}\n\n/* Delete a cJSON structure. */\nvoid cJSON_Delete(cJSON *c)\n{\n\tcJSON *next;\n\twhile (c)\n\t{\n\t\tnext=c->next;\n\t\tif (!(c->type&cJSON_IsReference) && c->child) cJSON_Delete(c->child);\n\t\tif (!(c->type&cJSON_IsReference) && c->valuestring) cJSON_free(c->valuestring);\n\t\tif (c->string) cJSON_free(c->string);\n\t\tcJSON_free(c);\n\t\tc=next;\n\t}\n}\n\n/* Parse the input text to generate a number, and populate the result into item. */\nstatic const char *parse_number(cJSON *item,const char *num)\n{\n\tdouble n=0,sign=1,scale=0;int subscale=0,signsubscale=1;\n\n\t/* Could use sscanf for this? */\n\tif (*num=='-') sign=-1,num++;\t/* Has sign? */\n\tif (*num=='0') num++;\t\t\t/* is zero */\n\tif (*num>='1' && *num<='9')\tdo\tn=(n*10.0)+(*num++ -'0');\twhile (*num>='0' && *num<='9');\t/* Number? */\n\tif (*num=='.' && num[1]>='0' && num[1]<='9') {num++;\t\tdo\tn=(n*10.0)+(*num++ -'0'),scale--; while (*num>='0' && *num<='9');}\t/* Fractional part? */\n\tif (*num=='e' || *num=='E')\t\t/* Exponent? */\n\t{\tnum++;if (*num=='+') num++;\telse if (*num=='-') signsubscale=-1,num++;\t\t/* With sign? */\n\t\twhile (*num>='0' && *num<='9') subscale=(subscale*10)+(*num++ - '0');\t/* Number? */\n\t}\n\n\tn=sign*n*pow(10.0,(scale+subscale*signsubscale));\t/* number = +/- number.fraction * 10^+/- exponent */\n\t\n\titem->valuedouble=n;\n\titem->valueint=(int)n;\n\titem->type=cJSON_Number;\n\treturn num;\n}\n\n/* Render the number nicely from the given item into a string. */\nstatic char *print_number(cJSON *item)\n{\n\tchar *str;\n\tdouble d=item->valuedouble;\n\tif (fabs(((double)item->valueint)-d)<=DBL_EPSILON && d<=INT_MAX && d>=INT_MIN)\n\t{\n\t\tstr=(char*)cJSON_malloc(21);\t/* 2^64+1 can be represented in 21 chars. */\n\t\tif (str) sprintf(str,\"%d\",item->valueint);\n\t}\n\telse\n\t{\n\t\tstr=(char*)cJSON_malloc(64);\t/* This is a nice tradeoff. */\n\t\tif (str)\n\t\t{\n\t\t\tif (fabs(floor(d)-d)<=DBL_EPSILON && fabs(d)<1.0e60)sprintf(str,\"%.0f\",d);\n\t\t\telse if (fabs(d)<1.0e-6 || fabs(d)>1.0e9)\t\t\tsprintf(str,\"%e\",d);\n\t\t\telse\t\t\t\t\t\t\t\t\t\t\t\tsprintf(str,\"%f\",d);\n\t\t}\n\t}\n\treturn str;\n}\n\n/* Parse the input text into an unescaped cstring, and populate item. */\nstatic const unsigned char firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };\nstatic const char *parse_string(cJSON *item,const char *str)\n{\n\tconst char *ptr=str+1;char *ptr2;char *out;int len=0;unsigned uc,uc2;\n\tif (*str!='\\\"') {ep=str;return 0;}\t/* not a string! */\n\t\n\twhile (*ptr!='\\\"' && *ptr && ++len) if (*ptr++ == '\\\\') ptr++;\t/* Skip escaped quotes. */\n\t\n\tout=(char*)cJSON_malloc(len+1);\t/* This is how long we need for the string, roughly. */\n\tif (!out) return 0;\n\t\n\tptr=str+1;ptr2=out;\n\twhile (*ptr!='\\\"' && *ptr)\n\t{\n\t\tif (*ptr!='\\\\') *ptr2++=*ptr++;\n\t\telse\n\t\t{\n\t\t\tptr++;\n\t\t\tswitch (*ptr)\n\t\t\t{\n\t\t\t\tcase 'b': *ptr2++='\\b';\tbreak;\n\t\t\t\tcase 'f': *ptr2++='\\f';\tbreak;\n\t\t\t\tcase 'n': *ptr2++='\\n';\tbreak;\n\t\t\t\tcase 'r': *ptr2++='\\r';\tbreak;\n\t\t\t\tcase 't': *ptr2++='\\t';\tbreak;\n\t\t\t\tcase 'u':\t /* transcode utf16 to utf8. */\n\t\t\t\t\tsscanf(ptr+1,\"%4x\",&uc);ptr+=4;\t/* get the unicode char. */\n\n\t\t\t\t\tif ((uc>=0xDC00 && uc<=0xDFFF) || uc==0)\tbreak;\t/* check for invalid.\t*/\n\n\t\t\t\t\tif (uc>=0xD800 && uc<=0xDBFF)\t/* UTF16 surrogate pairs.\t*/\n\t\t\t\t\t{\n\t\t\t\t\t\tif (ptr[1]!='\\\\' || ptr[2]!='u')\tbreak;\t/* missing second-half of surrogate.\t*/\n\t\t\t\t\t\tsscanf(ptr+3,\"%4x\",&uc2);ptr+=6;\n\t\t\t\t\t\tif (uc2<0xDC00 || uc2>0xDFFF)\t\tbreak;\t/* invalid second-half of surrogate.\t*/\n\t\t\t\t\t\tuc=0x10000 + (((uc&0x3FF)<<10) | (uc2&0x3FF));\n\t\t\t\t\t}\n\n\t\t\t\t\tlen=4;if (uc<0x80) len=1;else if (uc<0x800) len=2;else if (uc<0x10000) len=3; ptr2+=len;\n\t\t\t\t\t\n\t\t\t\t\tswitch (len) {\n\t\t\t\t\t\tcase 4: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; /* FALLTHRU */\n\t\t\t\t\t\tcase 3: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; /* FALLTHRU */\n\t\t\t\t\t\tcase 2: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; /* FALLTHRU */\n\t\t\t\t\t\tcase 1: *--ptr2 =(uc | firstByteMark[len]);\n\t\t\t\t\t}\n\t\t\t\t\tptr2+=len;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:  *ptr2++=*ptr; break;\n\t\t\t}\n\t\t\tptr++;\n\t\t}\n\t}\n\t*ptr2=0;\n\tif (*ptr=='\\\"') ptr++;\n\titem->valuestring=out;\n\titem->type=cJSON_String;\n\treturn ptr;\n}\n\n/* Render the cstring provided to an escaped version that can be printed. */\nstatic char *print_string_ptr(const char *str)\n{\n\tconst char *ptr;char *ptr2,*out;int len=0;unsigned char token;\n\t\n\tif (!str) return cJSON_strdup(\"\");\n\tptr=str;while ((token=*ptr) && ++len) {if (strchr(\"\\\"\\\\\\b\\f\\n\\r\\t\",token)) len++; else if (token<32) len+=5;ptr++;}\n\t\n\tout=(char*)cJSON_malloc(len+3);\n\tif (!out) return 0;\n\n\tptr2=out;ptr=str;\n\t*ptr2++='\\\"';\n\twhile (*ptr)\n\t{\n\t\tif ((unsigned char)*ptr>31 && *ptr!='\\\"' && *ptr!='\\\\') *ptr2++=*ptr++;\n\t\telse\n\t\t{\n\t\t\t*ptr2++='\\\\';\n\t\t\tswitch (token=*ptr++)\n\t\t\t{\n\t\t\t\tcase '\\\\':\t*ptr2++='\\\\';\tbreak;\n\t\t\t\tcase '\\\"':\t*ptr2++='\\\"';\tbreak;\n\t\t\t\tcase '\\b':\t*ptr2++='b';\tbreak;\n\t\t\t\tcase '\\f':\t*ptr2++='f';\tbreak;\n\t\t\t\tcase '\\n':\t*ptr2++='n';\tbreak;\n\t\t\t\tcase '\\r':\t*ptr2++='r';\tbreak;\n\t\t\t\tcase '\\t':\t*ptr2++='t';\tbreak;\n\t\t\t\tdefault: sprintf(ptr2,\"u%04x\",token);ptr2+=5;\tbreak;\t/* escape and print */\n\t\t\t}\n\t\t}\n\t}\n\t*ptr2++='\\\"';*ptr2++=0;\n\treturn out;\n}\n/* Invote print_string_ptr (which is useful) on an item. */\nstatic char *print_string(cJSON *item)\t{return print_string_ptr(item->valuestring);}\n\n/* Predeclare these prototypes. */\nstatic const char *parse_value(cJSON *item,const char *value);\nstatic char *print_value(cJSON *item,int depth,int fmt);\nstatic const char *parse_array(cJSON *item,const char *value);\nstatic char *print_array(cJSON *item,int depth,int fmt);\nstatic const char *parse_object(cJSON *item,const char *value);\nstatic char *print_object(cJSON *item,int depth,int fmt);\n\n/* Utility to jump whitespace and cr/lf */\nstatic const char *skip(const char *in) {while (in && *in && (unsigned char)*in<=32) in++; return in;}\n\n/* Parse an object - create a new root, and populate. */\ncJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int require_null_terminated)\n{\n\tconst char *end=0;\n\tcJSON *c=cJSON_New_Item();\n\tep=0;\n\tif (!c) return 0;       /* memory fail */\n\n\tend=parse_value(c,skip(value));\n\tif (!end)\t{cJSON_Delete(c);return 0;}\t/* parse failure. ep is set. */\n\n\t/* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */\n\tif (require_null_terminated) {end=skip(end);if (*end) {cJSON_Delete(c);ep=end;return 0;}}\n\tif (return_parse_end) *return_parse_end=end;\n\treturn c;\n}\n/* Default options for cJSON_Parse */\ncJSON *cJSON_Parse(const char *value) {return cJSON_ParseWithOpts(value,0,0);}\n\n/* Render a cJSON item/entity/structure to text. */\nchar *cJSON_Print(cJSON *item)\t\t\t\t{return print_value(item,0,1);}\nchar *cJSON_PrintUnformatted(cJSON *item)\t{return print_value(item,0,0);}\n\n/* Parser core - when encountering text, process appropriately. */\nstatic const char *parse_value(cJSON *item,const char *value)\n{\n\tif (!value)\t\t\t\t\t\treturn 0;\t/* Fail on null. */\n\tif (!strncmp(value,\"null\",4))\t{ item->type=cJSON_NULL;  return value+4; }\n\tif (!strncmp(value,\"false\",5))\t{ item->type=cJSON_False; return value+5; }\n\tif (!strncmp(value,\"true\",4))\t{ item->type=cJSON_True; item->valueint=1;\treturn value+4; }\n\tif (*value=='\\\"')\t\t\t\t{ return parse_string(item,value); }\n\tif (*value=='-' || (*value>='0' && *value<='9'))\t{ return parse_number(item,value); }\n\tif (*value=='[')\t\t\t\t{ return parse_array(item,value); }\n\tif (*value=='{')\t\t\t\t{ return parse_object(item,value); }\n\n\tep=value;return 0;\t/* failure. */\n}\n\n/* Render a value to text. */\nstatic char *print_value(cJSON *item,int depth,int fmt)\n{\n\tchar *out=0;\n\tif (!item) return 0;\n\tswitch ((item->type)&255)\n\t{\n\t\tcase cJSON_NULL:\tout=cJSON_strdup(\"null\");\tbreak;\n\t\tcase cJSON_False:\tout=cJSON_strdup(\"false\");break;\n\t\tcase cJSON_True:\tout=cJSON_strdup(\"true\"); break;\n\t\tcase cJSON_Number:\tout=print_number(item);break;\n\t\tcase cJSON_String:\tout=print_string(item);break;\n\t\tcase cJSON_Array:\tout=print_array(item,depth,fmt);break;\n\t\tcase cJSON_Object:\tout=print_object(item,depth,fmt);break;\n\t}\n\treturn out;\n}\n\n/* Build an array from input text. */\nstatic const char *parse_array(cJSON *item,const char *value)\n{\n\tcJSON *child;\n\tif (*value!='[')\t{ep=value;return 0;}\t/* not an array! */\n\n\titem->type=cJSON_Array;\n\tvalue=skip(value+1);\n\tif (*value==']') return value+1;\t/* empty array. */\n\n\titem->child=child=cJSON_New_Item();\n\tif (!item->child) return 0;\t\t /* memory fail */\n\tvalue=skip(parse_value(child,skip(value)));\t/* skip any spacing, get the value. */\n\tif (!value) return 0;\n\n\twhile (*value==',')\n\t{\n\t\tcJSON *new_item;\n\t\tif (!(new_item=cJSON_New_Item())) return 0; \t/* memory fail */\n\t\tchild->next=new_item;new_item->prev=child;child=new_item;\n\t\tvalue=skip(parse_value(child,skip(value+1)));\n\t\tif (!value) return 0;\t/* memory fail */\n\t}\n\n\tif (*value==']') return value+1;\t/* end of array */\n\tep=value;return 0;\t/* malformed. */\n}\n\n/* Render an array to text */\nstatic char *print_array(cJSON *item,int depth,int fmt)\n{\n\tchar **entries;\n\tchar *out=0,*ptr,*ret;int len=5;\n\tcJSON *child=item->child;\n\tint numentries=0,i=0,fail=0;\n\t\n\t/* How many entries in the array? */\n\twhile (child) numentries++,child=child->next;\n\t/* Explicitly handle numentries==0 */\n\tif (!numentries)\n\t{\n\t\tout=(char*)cJSON_malloc(3);\n\t\tif (out) strcpy(out,\"[]\");\n\t\treturn out;\n\t}\n\t/* Allocate an array to hold the values for each */\n\tentries=(char**)cJSON_malloc(numentries*sizeof(char*));\n\tif (!entries) return 0;\n\tmemset(entries,0,numentries*sizeof(char*));\n\t/* Retrieve all the results: */\n\tchild=item->child;\n\twhile (child && !fail)\n\t{\n\t\tret=print_value(child,depth+1,fmt);\n\t\tentries[i++]=ret;\n\t\tif (ret) len+=strlen(ret)+2+(fmt?1:0); else fail=1;\n\t\tchild=child->next;\n\t}\n\t\n\t/* If we didn't fail, try to malloc the output string */\n\tif (!fail) out=(char*)cJSON_malloc(len);\n\t/* If that fails, we fail. */\n\tif (!out) fail=1;\n\n\t/* Handle failure. */\n\tif (fail)\n\t{\n\t\tfor (i=0;i<numentries;i++) if (entries[i]) cJSON_free(entries[i]);\n\t\tcJSON_free(entries);\n\t\treturn 0;\n\t}\n\t\n\t/* Compose the output array. */\n\t*out='[';\n\tptr=out+1;*ptr=0;\n\tfor (i=0;i<numentries;i++)\n\t{\n\t\tstrcpy(ptr,entries[i]);ptr+=strlen(entries[i]);\n\t\tif (i!=numentries-1) {*ptr++=',';if(fmt)*ptr++=' ';*ptr=0;}\n\t\tcJSON_free(entries[i]);\n\t}\n\tcJSON_free(entries);\n\t*ptr++=']';*ptr++=0;\n\treturn out;\t\n}\n\n/* Build an object from the text. */\nstatic const char *parse_object(cJSON *item,const char *value)\n{\n\tcJSON *child;\n\tif (*value!='{')\t{ep=value;return 0;}\t/* not an object! */\n\t\n\titem->type=cJSON_Object;\n\tvalue=skip(value+1);\n\tif (*value=='}') return value+1;\t/* empty array. */\n\t\n\titem->child=child=cJSON_New_Item();\n\tif (!item->child) return 0;\n\tvalue=skip(parse_string(child,skip(value)));\n\tif (!value) return 0;\n\tchild->string=child->valuestring;child->valuestring=0;\n\tif (*value!=':') {ep=value;return 0;}\t/* fail! */\n\tvalue=skip(parse_value(child,skip(value+1)));\t/* skip any spacing, get the value. */\n\tif (!value) return 0;\n\t\n\twhile (*value==',')\n\t{\n\t\tcJSON *new_item;\n\t\tif (!(new_item=cJSON_New_Item()))\treturn 0; /* memory fail */\n\t\tchild->next=new_item;new_item->prev=child;child=new_item;\n\t\tvalue=skip(parse_string(child,skip(value+1)));\n\t\tif (!value) return 0;\n\t\tchild->string=child->valuestring;child->valuestring=0;\n\t\tif (*value!=':') {ep=value;return 0;}\t/* fail! */\n\t\tvalue=skip(parse_value(child,skip(value+1)));\t/* skip any spacing, get the value. */\n\t\tif (!value) return 0;\n\t}\n\t\n\tif (*value=='}') return value+1;\t/* end of array */\n\tep=value;return 0;\t/* malformed. */\n}\n\n/* Render an object to text. */\nstatic char *print_object(cJSON *item,int depth,int fmt)\n{\n\tchar **entries=0,**names=0;\n\tchar *out=0,*ptr,*ret,*str;int len=7,i=0,j;\n\tcJSON *child=item->child;\n\tint numentries=0,fail=0;\n\t/* Count the number of entries. */\n\twhile (child) numentries++,child=child->next;\n\t/* Explicitly handle empty object case */\n\tif (!numentries)\n\t{\n\t\tout=(char*)cJSON_malloc(fmt?depth+3:3);\n\t\tif (!out)\treturn 0;\n\t\tptr=out;*ptr++='{';\n\t\tif (fmt) {*ptr++='\\n';for (i=0;i<depth-1;i++) *ptr++='\\t';}\n\t\t*ptr++='}';*ptr++=0;\n\t\treturn out;\n\t}\n\t/* Allocate space for the names and the objects */\n\tentries=(char**)cJSON_malloc(numentries*sizeof(char*));\n\tif (!entries) return 0;\n\tnames=(char**)cJSON_malloc(numentries*sizeof(char*));\n\tif (!names) {cJSON_free(entries);return 0;}\n\tmemset(entries,0,sizeof(char*)*numentries);\n\tmemset(names,0,sizeof(char*)*numentries);\n\n\t/* Collect all the results into our arrays: */\n\tchild=item->child;depth++;if (fmt) len+=depth;\n\twhile (child)\n\t{\n\t\tnames[i]=str=print_string_ptr(child->string);\n\t\tentries[i++]=ret=print_value(child,depth,fmt);\n\t\tif (str && ret) len+=strlen(ret)+strlen(str)+2+(fmt?2+depth:0); else fail=1;\n\t\tchild=child->next;\n\t}\n\t\n\t/* Try to allocate the output string */\n\tif (!fail) out=(char*)cJSON_malloc(len);\n\tif (!out) fail=1;\n\n\t/* Handle failure */\n\tif (fail)\n\t{\n\t\tfor (i=0;i<numentries;i++) {if (names[i]) cJSON_free(names[i]);if (entries[i]) cJSON_free(entries[i]);}\n\t\tcJSON_free(names);cJSON_free(entries);\n\t\treturn 0;\n\t}\n\t\n\t/* Compose the output: */\n\t*out='{';ptr=out+1;if (fmt)*ptr++='\\n';*ptr=0;\n\tfor (i=0;i<numentries;i++)\n\t{\n\t\tif (fmt) for (j=0;j<depth;j++) *ptr++='\\t';\n\t\tstrcpy(ptr,names[i]);ptr+=strlen(names[i]);\n\t\t*ptr++=':';if (fmt) *ptr++='\\t';\n\t\tstrcpy(ptr,entries[i]);ptr+=strlen(entries[i]);\n\t\tif (i!=numentries-1) *ptr++=',';\n\t\tif (fmt) *ptr++='\\n';\n\t\t*ptr=0;\n\t\tcJSON_free(names[i]);cJSON_free(entries[i]);\n\t}\n\t\n\tcJSON_free(names);cJSON_free(entries);\n\tif (fmt) for (i=0;i<depth-1;i++) *ptr++='\\t';\n\t*ptr++='}';*ptr++=0;\n\treturn out;\t\n}\n\n/* Get Array size/item / object item. */\nint    cJSON_GetArraySize(cJSON *array)\t\t\t\t\t\t\t{cJSON *c=array->child;int i=0;while(c)i++,c=c->next;return i;}\ncJSON *cJSON_GetArrayItem(cJSON *array,int item)\t\t\t\t{cJSON *c=array->child;  while (c && item>0) item--,c=c->next; return c;}\ncJSON *cJSON_GetObjectItem(cJSON *object,const char *string)\t{cJSON *c=object->child; while (c && cJSON_strcasecmp(c->string,string)) c=c->next; return c;}\n\n/* Utility for array list handling. */\nstatic void suffix_object(cJSON *prev,cJSON *item) {prev->next=item;item->prev=prev;}\n/* Utility for handling references. */\nstatic 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;}\n\n/* Add item to array/object. */\nvoid   cJSON_AddItemToArray(cJSON *array, cJSON *item)\t\t\t\t\t\t{cJSON *c=array->child;if (!item) return; if (!c) {array->child=item;} else {while (c && c->next) c=c->next; suffix_object(c,item);}}\nvoid   cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item)\t{if (!item) return; if (item->string) cJSON_free(item->string);item->string=cJSON_strdup(string);cJSON_AddItemToArray(object,item);}\nvoid\tcJSON_AddItemReferenceToArray(cJSON *array, cJSON *item)\t\t\t\t\t\t{cJSON_AddItemToArray(array,create_reference(item));}\nvoid\tcJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item)\t{cJSON_AddItemToObject(object,string,create_reference(item));}\n\ncJSON *cJSON_DetachItemFromArray(cJSON *array,int which)\t\t\t{cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) return 0;\n\tif (c->prev) c->prev->next=c->next;\n\tif (c->next) c->next->prev=c->prev;\n\tif (c==array->child) array->child=c->next;\n\tc->prev=c->next=0;return c;}\nvoid   cJSON_DeleteItemFromArray(cJSON *array,int which)\t\t\t{cJSON_Delete(cJSON_DetachItemFromArray(array,which));}\ncJSON *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;}\nvoid   cJSON_DeleteItemFromObject(cJSON *object,const char *string) {cJSON_Delete(cJSON_DetachItemFromObject(object,string));}\n\n/* Replace array/object items with new ones. */\nvoid   cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem)\t\t{cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) return;\n\tnewitem->next=c->next;newitem->prev=c->prev;if (newitem->next) newitem->next->prev=newitem;\n\tif (c==array->child) array->child=newitem; else newitem->prev->next=newitem;c->next=c->prev=0;cJSON_Delete(c);}\nvoid   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);}}\n\n/* Create basic types: */\ncJSON *cJSON_CreateNull(void)\t\t\t\t\t{cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_NULL;return item;}\ncJSON *cJSON_CreateTrue(void)\t\t\t\t\t{cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_True;return item;}\ncJSON *cJSON_CreateFalse(void)\t\t\t\t\t{cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_False;return item;}\ncJSON *cJSON_CreateBool(int b)\t\t\t\t\t{cJSON *item=cJSON_New_Item();if(item)item->type=b?cJSON_True:cJSON_False;return item;}\ncJSON *cJSON_CreateNumber(double num)\t\t\t{cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_Number;item->valuedouble=num;item->valueint=(int)num;}return item;}\ncJSON *cJSON_CreateString(const char *string)\t{cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_String;item->valuestring=cJSON_strdup(string);}return item;}\ncJSON *cJSON_CreateArray(void)\t\t\t\t\t{cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_Array;return item;}\ncJSON *cJSON_CreateObject(void)\t\t\t\t\t{cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_Object;return item;}\n\n/* Create Arrays: */\ncJSON *cJSON_CreateIntArray(int *numbers,int count)\t\t\t\t{int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && i<count;i++){n=cJSON_CreateNumber(numbers[i]);if(!i)a->child=n;else suffix_object(p,n);p=n;}return a;}\ncJSON *cJSON_CreateFloatArray(float *numbers,int count)\t\t\t{int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && i<count;i++){n=cJSON_CreateNumber(numbers[i]);if(!i)a->child=n;else suffix_object(p,n);p=n;}return a;}\ncJSON *cJSON_CreateDoubleArray(double *numbers,int count)\t\t{int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && i<count;i++){n=cJSON_CreateNumber(numbers[i]);if(!i)a->child=n;else suffix_object(p,n);p=n;}return a;}\ncJSON *cJSON_CreateStringArray(const char **strings,int count)\t{int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && i<count;i++){n=cJSON_CreateString(strings[i]);if(!i)a->child=n;else suffix_object(p,n);p=n;}return a;}\n\n/* Duplication */\ncJSON *cJSON_Duplicate(cJSON *item,int recurse)\n{\n\tcJSON *newitem,*cptr,*nptr=0,*newchild;\n\t/* Bail on bad ptr */\n\tif (!item) return 0;\n\t/* Create new item */\n\tnewitem=cJSON_New_Item();\n\tif (!newitem) return 0;\n\t/* Copy over all vars */\n\tnewitem->type=item->type&(~cJSON_IsReference),newitem->valueint=item->valueint,newitem->valuedouble=item->valuedouble;\n\tif (item->valuestring)\t{newitem->valuestring=cJSON_strdup(item->valuestring);\tif (!newitem->valuestring)\t{cJSON_Delete(newitem);return 0;}}\n\tif (item->string)\t\t{newitem->string=cJSON_strdup(item->string);\t\t\tif (!newitem->string)\t\t{cJSON_Delete(newitem);return 0;}}\n\t/* If non-recursive, then we're done! */\n\tif (!recurse) return newitem;\n\t/* Walk the ->next chain for the child. */\n\tcptr=item->child;\n\twhile (cptr)\n\t{\n\t\tnewchild=cJSON_Duplicate(cptr,1);\t\t/* Duplicate (with recurse) each item in the ->next chain */\n\t\tif (!newchild) {cJSON_Delete(newitem);return 0;}\n\t\tif (nptr)\t{nptr->next=newchild,newchild->prev=nptr;nptr=newchild;}\t/* If newitem->child already set, then crosswire ->prev and ->next and move on */\n\t\telse\t\t{newitem->child=newchild;nptr=newchild;}\t\t\t\t\t/* Set newitem->child and move to it */\n\t\tcptr=cptr->next;\n\t}\n\treturn newitem;\n}\n\n#endif\n"
  },
  {
    "path": "src/ngx_stream_json.h",
    "content": "/*\n  Copyright (c) 2009 Dave Gamble\n \n  Permission is hereby granted, free of charge, to any person obtaining a copy\n  of this software and associated documentation files (the \"Software\"), to deal\n  in the Software without restriction, including without limitation the rights\n  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n  copies of the Software, and to permit persons to whom the Software is\n  furnished to do so, subject to the following conditions:\n \n  The above copyright notice and this permission notice shall be included in\n  all copies or substantial portions of the Software.\n \n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n  THE SOFTWARE.\n*/\n\n#ifndef cJSON__h\n#define cJSON__h\n\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif\n\n/* cJSON Types: */\n#define cJSON_False 0\n#define cJSON_True 1\n#define cJSON_NULL 2\n#define cJSON_Number 3\n#define cJSON_String 4\n#define cJSON_Array 5\n#define cJSON_Object 6\n\t\n#define cJSON_IsReference 256\n\n/* The cJSON structure: */\ntypedef struct cJSON {\n\tstruct cJSON *next,*prev;\t/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */\n\tstruct cJSON *child;\t\t/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */\n\n\tint type;\t\t\t\t\t/* The type of the item, as above. */\n\n\tchar *valuestring;\t\t\t/* The item's string, if type==cJSON_String */\n\tint valueint;\t\t\t\t/* The item's number, if type==cJSON_Number */\n\tdouble valuedouble;\t\t\t/* The item's number, if type==cJSON_Number */\n\n\tchar *string;\t\t\t\t/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */\n} cJSON;\n\ntypedef struct cJSON_Hooks {\n      void *(*malloc_fn)(size_t sz);\n      void (*free_fn)(void *ptr);\n} cJSON_Hooks;\n\n/* Supply malloc, realloc and free functions to cJSON */\nextern void cJSON_InitHooks(cJSON_Hooks* hooks);\n\n\n/* Supply a block of JSON, and this returns a cJSON object you can interrogate. Call cJSON_Delete when finished. */\nextern cJSON *cJSON_Parse(const char *value);\n/* Render a cJSON entity to text for transfer/storage. Free the char* when finished. */\nextern char  *cJSON_Print(cJSON *item);\n/* Render a cJSON entity to text for transfer/storage without any formatting. Free the char* when finished. */\nextern char  *cJSON_PrintUnformatted(cJSON *item);\n/* Delete a cJSON entity and all subentities. */\nextern void   cJSON_Delete(cJSON *c);\n\n/* Returns the number of items in an array (or object). */\nextern int\t  cJSON_GetArraySize(cJSON *array);\n/* Retrieve item number \"item\" from array \"array\". Returns NULL if unsuccessful. */\nextern cJSON *cJSON_GetArrayItem(cJSON *array,int item);\n/* Get item \"string\" from object. Case insensitive. */\nextern cJSON *cJSON_GetObjectItem(cJSON *object,const char *string);\n\n/* 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. */\nextern const char *cJSON_GetErrorPtr(void);\n\t\n/* These calls create a cJSON item of the appropriate type. */\nextern cJSON *cJSON_CreateNull(void);\nextern cJSON *cJSON_CreateTrue(void);\nextern cJSON *cJSON_CreateFalse(void);\nextern cJSON *cJSON_CreateBool(int b);\nextern cJSON *cJSON_CreateNumber(double num);\nextern cJSON *cJSON_CreateString(const char *string);\nextern cJSON *cJSON_CreateArray(void);\nextern cJSON *cJSON_CreateObject(void);\n\n/* These utilities create an Array of count items. */\nextern cJSON *cJSON_CreateIntArray(int *numbers,int count);\nextern cJSON *cJSON_CreateFloatArray(float *numbers,int count);\nextern cJSON *cJSON_CreateDoubleArray(double *numbers,int count);\nextern cJSON *cJSON_CreateStringArray(const char **strings,int count);\n\n/* Append item to the specified array/object. */\nextern void cJSON_AddItemToArray(cJSON *array, cJSON *item);\nextern void\tcJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item);\n/* 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. */\nextern void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);\nextern void\tcJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item);\n\n/* Remove/Detatch items from Arrays/Objects. */\nextern cJSON *cJSON_DetachItemFromArray(cJSON *array,int which);\nextern void   cJSON_DeleteItemFromArray(cJSON *array,int which);\nextern cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string);\nextern void   cJSON_DeleteItemFromObject(cJSON *object,const char *string);\n\t\n/* Update array items. */\nextern void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem);\nextern void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);\n\n/* Duplicate a cJSON item */\nextern cJSON *cJSON_Duplicate(cJSON *item,int recurse);\n/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will\nneed to be released. With recurse!=0, it will duplicate any children connected to the item.\nThe item->next and ->prev pointers are always zero on return from Duplicate. */\n\n/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */\nextern cJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int require_null_terminated);\n\n/* Macros for creating things quickly. */\n#define cJSON_AddNullToObject(object,name)\t\tcJSON_AddItemToObject(object, name, cJSON_CreateNull())\n#define cJSON_AddTrueToObject(object,name)\t\tcJSON_AddItemToObject(object, name, cJSON_CreateTrue())\n#define cJSON_AddFalseToObject(object,name)\t\tcJSON_AddItemToObject(object, name, cJSON_CreateFalse())\n#define cJSON_AddBoolToObject(object,name,b)\tcJSON_AddItemToObject(object, name, cJSON_CreateBool(b))\n#define cJSON_AddNumberToObject(object,name,n)\tcJSON_AddItemToObject(object, name, cJSON_CreateNumber(n))\n#define cJSON_AddStringToObject(object,name,s)\tcJSON_AddItemToObject(object, name, cJSON_CreateString(s))\n\n/* When assigning an integer value, it needs to be propagated to valuedouble too. */\n#define cJSON_SetIntValue(object,val)\t\t\t((object)?(object)->valueint=(object)->valuedouble=(val):(val))\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "src/ngx_stream_upsync_module.c",
    "content": "/*\n * Copyright (C) 2015 Weibo Group Holding Limited\n * Copyright (C) 2015 Xiaokai Wang (xiaokai.wang@live.com)\n */\n\n\n#include <ngx_core.h>\n#include <ngx_stream.h>\n#include <ngx_config.h>\n\n#include \"ngx_stream_upsync_module.h\"\n\n\n/* client for conf service */\ntypedef struct {\n    ngx_int_t                 sd;\n    ngx_int_t                 port;\n    ngx_int_t                 connected;\n\n    char                      ip[16];\n    struct sockaddr_in        addr;\n} ngx_stream_conf_client;\n\n\ntypedef struct {\n    u_char                           sockaddr[NGX_SOCKADDRLEN];\n\n    ngx_int_t                        weight;\n    ngx_uint_t                       max_fails;\n    time_t                           fail_timeout;\n\n    unsigned                         down:1;\n    unsigned                         backup:1;\n} ngx_stream_upsync_conf_t;\n\n\n#define NGX_STREAM_UPSYNC_CONSUL               0x0001\n#define NGX_STREAM_UPSYNC_CONSUL_SERVICES      0x0002\n#define NGX_STREAM_UPSYNC_ETCD                 0x0003\n\n\ntypedef ngx_int_t (*ngx_stream_upsync_packet_init_pt)\n    (void *upsync_server);\ntypedef ngx_int_t (*ngx_stream_upsync_packet_parse_pt)\n    (void *upsync_server);\ntypedef void (*ngx_stream_upsync_packet_clean_pt)\n    (void *upsync_server);\n\n\ntypedef struct {\n    ngx_str_t                                name;\n\n    ngx_uint_t                               upsync_type;\n\n    ngx_event_handler_pt                     send_handler;\n    ngx_event_handler_pt                     recv_handler;\n\n    ngx_stream_upsync_packet_init_pt         init;\n    ngx_stream_upsync_packet_parse_pt        parse;\n    ngx_stream_upsync_packet_clean_pt        clean;\n} ngx_upsync_conf_t;\n\n\ntypedef struct {\n    ngx_pool_t                      *pool;\n\n    ngx_buf_t                        send;\n    ngx_buf_t                        recv;\n\n    ngx_buf_t                        body;\n\n    ngx_array_t                      del_upstream;  /* ngx_stream_upsync_conf_t */\n    ngx_array_t                      add_upstream;\n\n    ngx_array_t                      upstream_conf;\n} ngx_stream_upsync_ctx_t;\n\n\ntypedef struct {\n    ngx_str_t                        upsync_host;\n    ngx_int_t                        upsync_port;\n\n    ngx_msec_t                       upsync_timeout;\n    ngx_msec_t                       upsync_interval;\n\n    ngx_int_t                        upsync_lb;\n    ngx_uint_t                       strong_dependency;\n\n    ngx_str_t                        upsync_send;\n    ngx_str_t                        upsync_dump_path;\n\n    ngx_open_file_t                 *conf_file;\n\n    ngx_upsync_conf_t               *upsync_type_conf;\n\n    ngx_stream_upstream_server_t     conf_server;         /* conf server */\n} ngx_stream_upsync_srv_conf_t;\n\n\n/* based on upstream conf, every unit upsync from consul */\ntypedef struct {\n    ngx_str_t                                host;\n\n    uint64_t                                 index;\n    uint64_t                                 update_generation;\n\n    ngx_event_t                              upsync_ev;\n    ngx_event_t                              upsync_timeout_ev;\n\n    ngx_queue_t                              delete_ev;\n\n    ngx_shmtx_t                              upsync_accept_mutex;\n\n    ngx_peer_connection_t                    pc;\n\n    ngx_stream_upsync_ctx_t                  ctx;\n\n    ngx_stream_upsync_srv_conf_t            *upscf;\n\n    ngx_stream_upstream_srv_conf_t          *uscf;\n} ngx_stream_upsync_server_t;\n\n\ntypedef struct {\n    ngx_event_t                              delay_delete_ev;\n\n    ngx_queue_t                              queue;\n\n    time_t                                   start_sec;\n    ngx_msec_t                               start_msec;\n\n    void                                    *data;\n} ngx_delay_event_t;\n\n\ntypedef struct {\n    ngx_uint_t                               upstream_num;\n\n    ngx_stream_upsync_server_t              *upsync_server;\n} ngx_stream_upsync_main_conf_t;\n\n\n/* http parser state */\ntypedef struct {\n    u_char     status[3];\n\n    char       headers[NGX_MAX_HEADERS][2][NGX_MAX_ELEMENT_SIZE];\n\n    ngx_uint_t num_headers;\n\n    enum { NONE=0, FIELD, VALUE } last_header;\n\n    u_char     http_body[NGX_PAGE_SIZE * NGX_PAGE_NUMBER];\n} ngx_stream_http_state;\n\n\nstatic ngx_upsync_conf_t *ngx_stream_upsync_get_type_conf(ngx_str_t *str);\nstatic char *ngx_stream_upsync_set_lb(ngx_conf_t *cf, ngx_command_t *cmd, \n    void *conf);\nstatic char *ngx_stream_upsync_server(ngx_conf_t *cf, \n    ngx_command_t *cmd, void *conf);\nstatic char *ngx_stream_upsync_set_conf_dump(ngx_conf_t *cf, \n    ngx_command_t *cmd, void *conf);\n\nstatic void *ngx_stream_upsync_create_main_conf(ngx_conf_t *cf);\nstatic void *ngx_stream_upsync_create_srv_conf(ngx_conf_t *cf);\nstatic char *ngx_stream_upsync_init_main_conf(ngx_conf_t *cf, void *conf);\nstatic char *ngx_stream_upsync_init_srv_conf(ngx_conf_t *cf, void *conf, \n    ngx_uint_t num);\n\nstatic void ngx_stream_upsync_process(ngx_stream_upsync_server_t *upsync_server);\n\nstatic ngx_int_t ngx_stream_upsync_init_process(ngx_cycle_t *cycle);\nstatic ngx_int_t ngx_stream_upsync_init_module(ngx_cycle_t *cycle);\nstatic ngx_int_t ngx_stream_upsync_init_shm_mutex(ngx_cycle_t *cycle);\nstatic ngx_int_t ngx_stream_upsync_add_timers(ngx_cycle_t *cycle);\n\nstatic void ngx_stream_upsync_begin_handler(ngx_event_t *event);\nstatic void ngx_stream_upsync_connect_handler(ngx_event_t *event);\nstatic void ngx_stream_upsync_recv_handler(ngx_event_t *event);\nstatic void ngx_stream_upsync_send_handler(ngx_event_t *event);\nstatic void ngx_stream_upsync_recv_empty_handler(ngx_event_t *event);\nstatic void ngx_stream_upsync_send_empty_handler(ngx_event_t *event);\nstatic void ngx_stream_upsync_timeout_handler(ngx_event_t *event);\nstatic void ngx_stream_upsync_clean_event(void *upsync_server);\nstatic ngx_int_t ngx_stream_upsync_etcd_parse_init(void *upsync_server);\nstatic ngx_int_t ngx_stream_upsync_consul_parse_init(void *upsync_server);\nstatic ngx_int_t ngx_stream_upsync_dump_server(\n    ngx_stream_upsync_server_t *upsync_server);\nstatic ngx_int_t ngx_stream_upsync_init_server(ngx_event_t *event);\n\nstatic ngx_int_t ngx_stream_upsync_add_peers(ngx_cycle_t *cycle, \n    ngx_stream_upsync_server_t *upsync_server);\nstatic ngx_int_t ngx_stream_upsync_del_peers(ngx_cycle_t *cycle,\n    ngx_stream_upsync_server_t *upsync_server);\nstatic ngx_int_t ngx_stream_upsync_replace_peers(ngx_cycle_t *cycle,\n    ngx_stream_upsync_server_t *upsync_server);\nstatic void ngx_stream_upsync_update_peer(ngx_stream_upstream_rr_peers_t *peers,\n    ngx_stream_upstream_rr_peer_t *peer,\n    ngx_stream_upsync_conf_t *upstream_conf,\n    ngx_uint_t *updated);\nstatic void ngx_stream_upsync_diff_filter(ngx_cycle_t *cycle, \n    ngx_stream_upsync_server_t *upsync_server,\n    ngx_uint_t *diff);\n\nstatic void ngx_stream_upsync_event_init(ngx_stream_upstream_rr_peer_t *peer, \n    ngx_stream_upsync_server_t *upsync_server);\n\nstatic ngx_int_t ngx_stream_http_parser_init();\n\nstatic int ngx_stream_http_status(http_parser *p, const char *buf, size_t len);\nstatic int ngx_stream_http_header_field_cb(http_parser *p, const char *buf, \n    size_t len);\nstatic int ngx_stream_http_header_value_cb(http_parser *p, const char *buf, \n    size_t len);\nstatic int ngx_stream_http_body(http_parser *p, const char *buf, size_t len);\n\nstatic ngx_int_t ngx_stream_upsync_check_index(\n    ngx_stream_upsync_server_t *upsync_server);\nstatic ngx_int_t ngx_stream_upsync_consul_parse_json(void *upsync_server);\nstatic ngx_int_t ngx_stream_upsync_consul_services_parse_json(\n    void *upsync_server);\nstatic ngx_int_t ngx_stream_upsync_etcd_parse_json(void *upsync_server);\nstatic ngx_int_t ngx_stream_upsync_check_key(u_char *key, ngx_str_t host);\nstatic void *ngx_stream_upsync_servers(ngx_cycle_t *cycle, \n    ngx_stream_upsync_server_t *upsync_server, ngx_flag_t flag);\nstatic void *ngx_stream_upsync_addrs(ngx_pool_t *pool, u_char *sockaddr);\n\nstatic void ngx_stream_upsync_del_delay_delete(ngx_event_t *event);\n\nstatic ngx_int_t ngx_stream_upsync_need_exit();\nstatic void ngx_stream_upsync_clear_all_events(ngx_cycle_t *cycle);\n\nstatic ngx_int_t ngx_stream_upsync_get_upstream(ngx_cycle_t *cycle, \n    ngx_stream_upsync_server_t *upsync_server, char **conf_value);\nstatic ngx_stream_conf_client *ngx_stream_create_client(ngx_cycle_t *cycle, \n    ngx_stream_upsync_server_t *upsync_server);\nstatic ngx_int_t ngx_stream_client_conn(ngx_stream_conf_client *client);\nstatic void ngx_stream_client_destroy(ngx_stream_conf_client *client);\nstatic ngx_int_t ngx_stream_client_send(ngx_stream_conf_client *client, \n    ngx_stream_upsync_server_t *upsync_server);\nstatic ngx_int_t ngx_stream_client_recv(ngx_stream_conf_client *client, \n    char **data, int size);\n\nstatic char *ngx_stream_upsync_set(ngx_conf_t *cf, ngx_command_t *cmd, \n    void *conf);\nstatic void ngx_stream_upsync_show(ngx_stream_session_t *r);\n\n\nstatic http_parser_settings settings = {\n    .on_message_begin = 0,\n    .on_header_field = ngx_stream_http_header_field_cb,\n    .on_header_value = ngx_stream_http_header_value_cb,\n    .on_url = 0,\n    .on_status = ngx_stream_http_status,\n    .on_body = ngx_stream_http_body,\n    .on_headers_complete = 0,\n    .on_message_complete = 0\n};\n\nngx_atomic_t   stream_upsync_shared_created0;\nngx_atomic_t  *stream_upsync_shared_created = &stream_upsync_shared_created0;\n\nstatic http_parser *parser = NULL;\nstatic ngx_stream_http_state state;\n\nstatic ngx_stream_upsync_main_conf_t  *upsync_ctx = NULL;\n\nstatic ngx_command_t  ngx_stream_upsync_commands[] = {\n\n    {  ngx_string(\"upsync\"),\n        NGX_STREAM_UPS_CONF|NGX_CONF_1MORE,\n        ngx_stream_upsync_server,\n        NGX_STREAM_SRV_CONF_OFFSET,\n        0,\n        NULL },\n\n    {  ngx_string(\"upsync_lb\"),\n        NGX_STREAM_UPS_CONF|NGX_CONF_TAKE1,\n        ngx_stream_upsync_set_lb,\n        0,\n        0,\n        NULL },\n\n    {  ngx_string(\"upsync_dump_path\"),\n        NGX_STREAM_UPS_CONF|NGX_CONF_TAKE1,\n        ngx_stream_upsync_set_conf_dump,\n        NGX_STREAM_SRV_CONF_OFFSET,\n        0,\n        NULL },\n\n    {  ngx_string(\"upstream_show\"),\n        NGX_STREAM_SRV_CONF|NGX_CONF_NOARGS,\n        ngx_stream_upsync_set,\n        0,\n        0,\n        NULL },\n\n    ngx_null_command\n};\n\n\nstatic ngx_stream_module_t  ngx_stream_upsync_module_ctx = {\n    NULL,                                       /* preconfiguration */\n    NULL,                                       /* postconfiguration */\n\n    ngx_stream_upsync_create_main_conf,         /* create main configuration */\n    ngx_stream_upsync_init_main_conf,           /* init main configuration */\n\n    ngx_stream_upsync_create_srv_conf,          /* create server configuration */\n    NULL,                                       /* merge server configuration */\n};\n\n\nngx_module_t  ngx_stream_upsync_module = {\n    NGX_MODULE_V1,\n    &ngx_stream_upsync_module_ctx,               /* module context */\n    ngx_stream_upsync_commands,                  /* module directives */\n    NGX_STREAM_MODULE,                           /* module type */\n    NULL,                                        /* init master */\n    ngx_stream_upsync_init_module,               /* init module */\n    ngx_stream_upsync_init_process,              /* init process */\n    NULL,                                        /* init thread */\n    NULL,                                        /* exit thread */\n    ngx_stream_upsync_clear_all_events,          /* exit process */\n    NULL,                                        /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\nstatic ngx_upsync_conf_t  ngx_upsync_types[] = {\n\n    { ngx_string(\"consul\"),\n      NGX_STREAM_UPSYNC_CONSUL,\n      ngx_stream_upsync_send_handler,\n      ngx_stream_upsync_recv_handler,\n      ngx_stream_upsync_consul_parse_init,\n      ngx_stream_upsync_consul_parse_json,\n      ngx_stream_upsync_clean_event },\n\n    { ngx_string(\"consul_services\"),\n      NGX_STREAM_UPSYNC_CONSUL_SERVICES,\n      ngx_stream_upsync_send_handler,\n      ngx_stream_upsync_recv_handler,\n      ngx_stream_upsync_consul_parse_init,\n      ngx_stream_upsync_consul_services_parse_json,\n      ngx_stream_upsync_clean_event },\n\n    { ngx_string(\"etcd\"),\n      NGX_STREAM_UPSYNC_ETCD,\n      ngx_stream_upsync_send_handler,\n      ngx_stream_upsync_recv_handler,\n      ngx_stream_upsync_etcd_parse_init,\n      ngx_stream_upsync_etcd_parse_json,\n      ngx_stream_upsync_clean_event },\n\n    { ngx_null_string,\n      0,\n      NULL,\n      NULL,\n      NULL,\n      NULL,\n      NULL }\n};\n\n\nstatic char *\nngx_stream_upsync_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    u_char                             *p = NULL;\n    time_t                              upsync_timeout = 0, upsync_interval = 0;\n    ngx_str_t                          *value, s;\n    ngx_url_t                           u;\n    ngx_uint_t                          i, strong_dependency = 0;\n    ngx_stream_upstream_server_t       *conf_server;\n    ngx_stream_upsync_srv_conf_t       *upscf;\n\n    value = cf->args->elts;\n\n    upscf = ngx_stream_conf_get_module_srv_conf(cf,\n                                                ngx_stream_upsync_module);\n    conf_server = &upscf->conf_server;\n\n    for (i = 2; i < cf->args->nelts; i++) {\n\n        if (ngx_strncmp(value[i].data, \"upsync_timeout=\", 15) == 0) {\n\n            s.len = value[i].len - 15;\n            s.data = &value[i].data[15];\n\n            upsync_timeout = ngx_parse_time(&s, 0);\n            if (upsync_timeout == (time_t) NGX_ERROR) {\n                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,\n                                   \"upsync_server: invalid parameter:\\\"%V\\\"\", \n                                   &value[i]);\n                goto invalid;\n            }\n\n            continue;\n        }\n\n        if (ngx_strncmp(value[i].data, \"upsync_interval=\", 16) == 0) {\n\n            s.len = value[i].len - 16;\n            s.data = &value[i].data[16];\n\n            upsync_interval = ngx_parse_time(&s, 0);\n            if (upsync_interval == (time_t) NGX_ERROR) {\n                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,\n                                   \"upsync_server: invalid parameter: \\\"%V\\\"\", \n                                   &value[i]);\n                goto invalid;\n            }\n\n            continue;\n        }\n\n        if (ngx_strncmp(value[i].data, \"strong_dependency=\", 18) == 0) {\n            s.len = value[i].len - 18;\n            s.data = value[i].data + 18;\n\n            if (ngx_strcasecmp(s.data, (u_char *) \"on\") == 0) {\n                strong_dependency = 1;\n            } else if (ngx_strcasecmp(s.data, (u_char *) \"off\") == 0) {\n                strong_dependency = 0;\n            } else {\n                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,\n                                   \"invalid value \\\"%s\\\", \"\n                                   \"it must be \\\"on\\\" or \\\"off\\\"\",\n                                   value[i].data);\n                return NGX_CONF_ERROR;\n            }\n\n            continue;\n        }\n\n        if (ngx_strncmp(value[i].data, \"upsync_type=\", 12) == 0) {\n            s.len = value[i].len - 12;\n            s.data = value[i].data + 12;\n\n            upscf->upsync_type_conf = ngx_stream_upsync_get_type_conf(&s);\n            if (upscf->upsync_type_conf == NULL) {\n                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,\n                                   \"upsync_server: upsync_type invalid para\");\n                goto invalid;\n            }\n\n            continue;\n        }\n\n        goto invalid;\n    }\n\n    if (upsync_interval != 0) {\n        upscf->upsync_interval = upsync_interval;\n    }\n    if (upsync_timeout != 0) {\n        upscf->upsync_timeout = upsync_timeout;\n    }\n    if (strong_dependency != 0) {\n        upscf->strong_dependency = strong_dependency;\n    }\n    if (upscf->upsync_type_conf == NGX_CONF_UNSET_PTR) {\n         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,\n                            \"upsync_server: upsync_type cannt be null\");\n          goto invalid;\n    }\n\n    ngx_memzero(&u, sizeof(ngx_url_t));\n\n    p = (u_char *)ngx_strchr(value[1].data, '/');\n    if (p == NULL) {\n        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, \"upsync_server: \"\n                           \"please input conf_server upstream key in upstream\");\n        return NGX_CONF_ERROR;\n    }\n    upscf->upsync_send.data = p;\n    upscf->upsync_send.len = value[1].len - (p - value[1].data);\n\n    u.url.data = value[1].data;\n    u.url.len = p - value[1].data;\n\n    p = (u_char *)ngx_strchr(value[1].data, ':');\n    if (p != NULL) {\n        upscf->upsync_host.data = value[1].data;\n        upscf->upsync_host.len = p - value[1].data;\n\n        upscf->upsync_port = ngx_atoi(p + 1, upscf->upsync_send.data - p - 1);\n        if (upscf->upsync_port < 1 || upscf->upsync_port > 65535) {\n            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, \"upsync_server: \"\n                               \"conf server port is invalid\");\n            return NGX_CONF_ERROR;\n        }\n\n    } else {\n        upscf->upsync_host.data = value[1].data;\n        upscf->upsync_host.len = u.url.len;\n\n        upscf->upsync_port = 80;\n    }\n\n    u.default_port = 80;\n    if (ngx_parse_url(cf->pool, &u) != NGX_OK) {\n        if (u.err) {\n            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, \"upsync_server: \"\n                               \"%s in upstream \\\"%V\\\"\", u.err, &u.url);\n        }\n        return NGX_CONF_ERROR;\n    }\n\n    conf_server->name = u.url;\n    conf_server->addrs = u.addrs;\n    conf_server->naddrs = u.naddrs;\n    conf_server->weight = 1;\n    conf_server->max_fails = 1;\n    conf_server->fail_timeout = 10;\n\n    return NGX_CONF_OK;\n\ninvalid:\n\n    return NGX_CONF_ERROR;\n}\n\n\nstatic ngx_upsync_conf_t *\nngx_stream_upsync_get_type_conf(ngx_str_t *str)\n{\n    ngx_uint_t  i;\n\n    for (i = 0; /* void */ ; i++) {\n\n        if (ngx_upsync_types[i].upsync_type == 0) {\n            break;\n        }\n\n        if (str->len != ngx_upsync_types[i].name.len) {\n            continue;\n        }\n\n        if (ngx_strncmp(str->data, ngx_upsync_types[i].name.data,\n                        str->len) == 0)\n        {\n            return &ngx_upsync_types[i];\n        }\n    }\n\n    return NULL;\n}\n\n\nstatic char *\nngx_stream_upsync_set_lb(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    ngx_str_t                         *value, *str;\n    ngx_stream_upsync_srv_conf_t      *upscf;\n\n    upscf = ngx_stream_conf_get_module_srv_conf(cf,\n                                                ngx_stream_upsync_module);\n    value = cf->args->elts;\n\n    str = &value[1];\n    if (str->len == NGX_CONF_UNSET_SIZE) {\n        upscf->upsync_lb = NGX_STREAM_LB_DEFAULT;\n\n        return NGX_CONF_OK; \n    }\n\n    switch(str->len) {\n        case 7:\n            if (ngx_memcmp((char *)str->data, \"ip_hash\", 7) == 0) {\n                upscf->upsync_lb = NGX_STREAM_LB_IP_HASH;\n\n                return NGX_CONF_OK;\n            }\n\n            break;\n\n        case 10:\n            if (ngx_memcmp((char *)str->data, \"roundrobin\", 10) == 0) {\n                upscf->upsync_lb = NGX_STREAM_LB_ROUNDROBIN;\n\n                return NGX_CONF_OK;\n            }\n\n            if (ngx_memcmp((char *)str->data, \"least_conn\", 10) == 0) {\n                upscf->upsync_lb = NGX_STREAM_LB_LEAST_CONN;\n\n                return NGX_CONF_OK;\n            }\n\n            break;\n\n        case 11:\n            if (ngx_memcmp((char *)str->data, \"hash_modula\", 11) == 0) {\n                upscf->upsync_lb = NGX_STREAM_LB_HASH_MODULA;\n\n                return NGX_CONF_OK;\n            }\n\n            if (ngx_memcmp((char *)str->data, \"hash_ketama\", 11) == 0) {\n                upscf->upsync_lb = NGX_STREAM_LB_HASH_KETAMA;\n\n                return NGX_CONF_OK;\n            }\n\n            break;\n    }\n\n    return NGX_CONF_OK;\n}\n\n\nstatic char *\nngx_stream_upsync_set_conf_dump(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    ngx_str_t                         *value;\n    ngx_stream_upsync_srv_conf_t      *upscf;\n\n    upscf = ngx_stream_conf_get_module_srv_conf(cf,\n                                                ngx_stream_upsync_module);\n    value = cf->args->elts;\n\n    upscf->upsync_dump_path = value[1]; \n    if (upscf->upsync_dump_path.len == NGX_CONF_UNSET_SIZE) {\n        return NGX_CONF_ERROR; \n    }\n\n    upscf->conf_file = ngx_conf_open_file(cf->cycle, &value[1]); \n    if (upscf->conf_file == NULL) {\n        return NGX_CONF_ERROR; \n    }\n\n    return NGX_CONF_OK;\n}\n\n\nstatic void\nngx_stream_upsync_process(ngx_stream_upsync_server_t *upsync_server)\n{\n    ngx_uint_t                   diff = 0;\n    ngx_upsync_conf_t           *upsync_type_conf;\n    ngx_stream_upsync_ctx_t     *ctx;\n\n    if (ngx_stream_upsync_need_exit()) {\n        return;\n    }\n\n    ctx = &upsync_server->ctx;\n    upsync_type_conf = upsync_server->upscf->upsync_type_conf;\n\n    if (ngx_stream_upsync_check_index(upsync_server) == NGX_ERROR) {\n        return;\n    }\n\n    if (upsync_type_conf->parse(upsync_server) == NGX_ERROR) {\n        if (upsync_server->index != 0) {\n            ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                          \"upsync_process: parse json error\");\n        }\n        return;\n    }\n\n    ngx_log_debug0(NGX_LOG_DEBUG, ngx_cycle->log, 0,\n                   \"upsync_process: parse json success\");\n\n    ngx_stream_upsync_diff_filter((ngx_cycle_t *)ngx_cycle, upsync_server, &diff);\n\n    if (ctx->add_upstream.nelts > 0) {\n\n        if (upsync_server->update_generation != 0) {\n            if (ngx_stream_upsync_add_peers((ngx_cycle_t *)ngx_cycle, \n                        upsync_server) != NGX_OK) {\n                ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                        \"upsync_process: upstream add peers failed\");\n                return;\n            }\n\n        } else {\n            if (ngx_stream_upsync_replace_peers((ngx_cycle_t *)ngx_cycle, \n                        upsync_server) != NGX_OK) {\n                ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                        \"upsync_process: upstream add/replace peers failed\");\n                return;\n            }\n        }\n\n        upsync_server->update_generation++;\n    }\n\n    if (ctx->del_upstream.nelts > 0) {\n\n        if (upsync_server->update_generation != 0) {\n            if (ngx_stream_upsync_del_peers((ngx_cycle_t *)ngx_cycle, \n                        upsync_server) != NGX_OK) {\n                ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                        \"upsync_process: upstream del peers failed\");\n                return;\n            }\n\n        } else {\n            if (ngx_stream_upsync_replace_peers((ngx_cycle_t *)ngx_cycle, \n                        upsync_server) != NGX_OK) {\n                ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                        \"upsync_process: upstream del/replace peers failed\");\n                return;\n            }\n        }\n\n        upsync_server->update_generation++;\n    }\n\n    if (diff) {\n        if (ngx_shmtx_trylock(&upsync_server->upsync_accept_mutex)) {\n\n            ngx_stream_upsync_dump_server(upsync_server);\n            ngx_shmtx_unlock(&upsync_server->upsync_accept_mutex);\n        }\n    }\n\n    return;\n}\n\n\nstatic ngx_int_t\nngx_stream_upsync_check_index(ngx_stream_upsync_server_t *upsync_server)\n{\n    char                        *p;\n    ngx_uint_t                   i;\n    uint64_t                     index = 0;\n    ngx_upsync_conf_t           *upsync_type_conf;\n\n    upsync_type_conf = upsync_server->upscf->upsync_type_conf;\n\n    if (upsync_type_conf->upsync_type == NGX_STREAM_UPSYNC_CONSUL\n        || upsync_type_conf->upsync_type == NGX_STREAM_UPSYNC_CONSUL_SERVICES) {\n        for (i = 0; i < state.num_headers; i++) {\n\n            if (ngx_memcmp(state.headers[i][0], NGX_INDEX_HEADER, \n                           NGX_INDEX_HEADER_LEN) == 0) {\n                p = ngx_strchr(state.headers[i][1], '\\r');\n                *p = '\\0';\n                index = ngx_strtoull((u_char *)state.headers[i][1], \n                                     (char **)NULL, 10);\n                break;\n            }\n        }\n\n        if (index == upsync_server->index) {\n            ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,\n                          \"upsync_check_index: upstream index has not changed: %V\",\n                          &upsync_server->upscf->upsync_dump_path);\n            return NGX_ERROR;\n\n        } else {\n            upsync_server->index = index;\n        }\n    }\n\n    if (upsync_type_conf->upsync_type == NGX_STREAM_UPSYNC_ETCD) {\n        for (i = 0; i < state.num_headers; i++) {\n\n            if (ngx_memcmp(state.headers[i][0], NGX_INDEX_ETCD_HEADER, \n                           NGX_INDEX_ETCD_HEADER_LEN) == 0) {\n                p = ngx_strchr(state.headers[i][1], '\\r');\n                *p = '\\0';\n                index = ngx_strtoull((u_char *)state.headers[i][1], \n                                     (char **)NULL, 10);\n                break;\n            }\n        }\n\n        upsync_server->index = index + 1;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_stream_upsync_add_peers(ngx_cycle_t *cycle,\n    ngx_stream_upsync_server_t *upsync_server)\n{\n    ngx_uint_t                       i=0, n=0, w=0, len=0;\n    ngx_array_t                     *servers;\n    ngx_stream_upstream_server_t    *server = NULL;\n    ngx_stream_upstream_rr_peer_t   *peer = NULL;\n    ngx_stream_upstream_rr_peers_t  *peers = NULL;\n    ngx_stream_upstream_srv_conf_t  *uscf;\n\n    if (ngx_stream_upsync_need_exit()) {\n        return NGX_OK;\n    }\n\n    u_char *namep = NULL;\n    struct sockaddr *saddr = NULL;\n    len = sizeof(struct sockaddr);\n\n    uscf = upsync_server->uscf;\n\n    servers = ngx_stream_upsync_servers(cycle, upsync_server, NGX_ADD);\n    if (servers == NULL) {\n        return NGX_ERROR;\n    }\n\n    if (servers->nelts < 1) {\n        ngx_log_error(NGX_LOG_ERR, cycle->log, 0,\n                      \"upsync_add_peers: no servers to add \\\"%V\\\"\", &uscf->host);\n        return NGX_ERROR;\n    }\n\n    if (uscf->peer.data == NULL) {\n        return NGX_ERROR;\n    }\n\n    peers = (ngx_stream_upstream_rr_peers_t *)uscf->peer.data;\n\n    if (peers && servers->nelts >= 1) {\n        n = peers->number + servers->nelts;\n\n        for (i = 0; i < servers->nelts; i++) {\n\n            server = (ngx_stream_upstream_server_t *)servers->elts + i;\n            //if (server->backup) {\n            //    continue;\n            //}\n            //\n            // FIXME: until backup is fully implemented this causes crashes\n            //        on startup with nodes set backup=1. Let them in for now\n\n            peer = ngx_calloc(sizeof(ngx_stream_upstream_rr_peer_t), \n                              cycle->log);\n            if (peer == NULL) {\n                goto invalid;\n            }\n\n            if ((saddr = ngx_calloc(len, cycle->log)) == NULL) {\n                goto invalid;\n            }\n            ngx_memcpy(saddr, server->addrs->sockaddr, len);\n            peer->sockaddr = saddr;\n\n            if ((namep = ngx_calloc(server->addrs->name.len,\n                                    cycle->log)) == NULL) {\n                goto invalid;\n            }\n            ngx_memcpy(namep, server->addrs->name.data,\n                       server->addrs->name.len);\n            peer->name.data = namep;\n\n            peer->socklen = server->addrs->socklen;\n            peer->name.len = server->addrs->name.len;\n            peer->max_fails = server->max_fails;\n            peer->fail_timeout = server->fail_timeout;\n            peer->down = server->down;\n            peer->weight = server->weight;\n            peer->effective_weight = server->weight;\n            peer->current_weight = 0;\n\n            peer->conns = 0;\n\n            peer->next = peers->peer;\n            peers->peer = peer;\n\n            w += server->weight;\n        }\n        w += peers->total_weight;\n\n        peers->single = (n == 1);\n        peers->number = n;\n        peers->weighted = (w != n);\n        peers->total_weight = w;\n\n        if (upsync_server->upscf->upsync_lb == NGX_STREAM_LB_HASH_KETAMA) {\n            ngx_stream_upsync_chash_init(uscf, peers);\n        }\n    }\n\n    return NGX_OK;\n\ninvalid:\n    ngx_log_error(NGX_LOG_ERR, cycle->log, 0,\n                  \"upsync_add_peers: calloc peer failed \\\"%V\\\"\", \n                  &uscf->host);\n\n    if (peer != NULL) {\n        if (peer->sockaddr != NULL) {\n            ngx_free(peer->sockaddr);\n        }\n \n        ngx_free(peer);\n        peer = NULL;\n    }\n\n    return NGX_ERROR;\n}\n\n\nstatic void\nngx_stream_upsync_update_peer(ngx_stream_upstream_rr_peers_t *peers,\n    ngx_stream_upstream_rr_peer_t *peer,\n    ngx_stream_upsync_conf_t *upstream_conf,\n    ngx_uint_t *updated)\n{\n    ngx_uint_t  w = peers->total_weight, pw = 0;\n\n    *updated = 0;\n\n    if (peer->max_fails == upstream_conf->max_fails &&\n        peer->fail_timeout == upstream_conf->fail_timeout &&\n        peer->down == upstream_conf->down &&\n        peer->weight == upstream_conf->weight) {\n        return;\n    }\n\n    pw = peer->weight;\n    peer->max_fails = upstream_conf->max_fails;\n    peer->fail_timeout = upstream_conf->fail_timeout;\n    peer->down = upstream_conf->down;\n    peer->weight = upstream_conf->weight;\n    peer->effective_weight = upstream_conf->weight;\n    peer->current_weight = 0;\n\n    w = w + upstream_conf->weight - pw;\n\n    peers->weighted = (w != peers->number);\n    peers->total_weight = w;\n\n    *updated = 1;\n\n    return;\n}\n\n\nstatic void\nngx_stream_upsync_diff_filter(ngx_cycle_t *cycle, \n    ngx_stream_upsync_server_t *upsync_server,\n    ngx_uint_t *diff)\n{\n    ngx_uint_t                            i, j, len, updated;\n    ngx_uint_t                           *flags = NULL;\n    ngx_array_t                           flag_array;\n    ngx_stream_upsync_ctx_t              *ctx;\n    ngx_stream_upsync_conf_t             *upstream_conf;\n    ngx_stream_upsync_conf_t             *add_upstream, *del_upstream;\n    ngx_stream_upstream_rr_peer_t        *peer = NULL;\n    ngx_stream_upstream_rr_peers_t       *peers = NULL;\n    ngx_stream_upstream_srv_conf_t       *uscf;\n\n    *diff = 0;\n    ctx = &upsync_server->ctx;\n\n    if (ngx_stream_upsync_need_exit()) {\n        return;\n    }\n\n    if (ngx_array_init(&ctx->add_upstream, ctx->pool, 16,\n                       sizeof(*add_upstream)) != NGX_OK)\n    {\n        ngx_log_error(NGX_LOG_ERR, cycle->log, 0,\n                      \"upsync_diff_filter_add: alloc error\");\n        return;\n    }\n\n    if (ngx_array_init(&ctx->del_upstream, ctx->pool, 16,\n                       sizeof(*del_upstream)) != NGX_OK) {\n        ngx_log_error(NGX_LOG_ERR, cycle->log, 0,\n                      \"upsync_diff_filter_del: alloc error\");\n        return;\n    }\n\n    uscf = upsync_server->uscf;\n    if (uscf->peer.data == NULL) {\n        return;\n    }\n    \n    peers = (ngx_stream_upstream_rr_peers_t *)uscf->peer.data;\n    if (peers->number != 0) {\n        if (ngx_array_init(&flag_array, ctx->pool, peers->number,\n                       sizeof(*flags)) != NGX_OK) {\n            ngx_log_error(NGX_LOG_ERR, cycle->log, 0,\n                          \"upsync_diff_filter: alloc error\");\n            return;\n        }\n\n        ngx_memzero(flag_array.elts, sizeof(ngx_uint_t) * flag_array.nalloc);\n        flags = (ngx_uint_t*)flag_array.elts;\n    }\n\n    len = ctx->upstream_conf.nelts;\n    for (i = 0; i < len; i++) {\n        upstream_conf = (ngx_stream_upsync_conf_t *)ctx->upstream_conf.elts + i;\n\n        for (peer = peers->peer, j = 0; peer; peer = peer->next, j++) {\n            if (*(flags + j) == 1) {\n                continue;\n            }\n\n            if (ngx_memn2cmp(peer->name.data, upstream_conf->sockaddr,\n                             peer->name.len,\n                             ngx_strlen(upstream_conf->sockaddr)) == 0) {\n                // update peer\n                ngx_stream_upsync_update_peer(peers, peer, upstream_conf, &updated);\n                *diff |= updated;\n\n                // set flag, not to be deleted\n                *(flags + j) = 1;\n\n                break;\n            }\n        }\n\n        // add_upstream\n        if (j == peers->number) {\n            add_upstream = ngx_array_push(&ctx->add_upstream);\n            ngx_memcpy(add_upstream, upstream_conf, sizeof(*upstream_conf));\n        }\n    }\n\n    // del_upstream\n    for (peer = peers->peer, j = 0; peer; peer = peer->next, j++) {\n        if (*(flags + j) == 1) {\n            continue;\n        }\n\n        del_upstream = ngx_array_push(&ctx->del_upstream);\n        ngx_memzero(del_upstream, sizeof(*del_upstream));\n        ngx_memcpy(&del_upstream->sockaddr, peer->name.data, peer->name.len);\n    }\n\n    *diff |= (ctx->add_upstream.nelts > 0);\n    *diff |= (ctx->del_upstream.nelts > 0);\n\n    return;\n}\n\n\nstatic ngx_int_t\nngx_stream_upsync_del_peers(ngx_cycle_t *cycle,\n    ngx_stream_upsync_server_t *upsync_server)\n{\n    ngx_uint_t                       i, n=0, w=0, len=0;\n    ngx_array_t                     *servers;\n    ngx_stream_upstream_server_t    *server = NULL;\n    ngx_stream_upstream_rr_peer_t   *peer = NULL, *pre_peer = NULL;\n    ngx_stream_upstream_rr_peer_t   *del_peer = NULL, *tmp_del_peer = NULL;\n    ngx_stream_upstream_rr_peers_t  *peers = NULL;\n    ngx_stream_upstream_srv_conf_t  *uscf;\n\n    len = sizeof(struct sockaddr);\n    uscf = upsync_server->uscf;\n\n    if (ngx_stream_upsync_need_exit()) {\n        return NGX_OK;\n    }\n\n    servers = ngx_stream_upsync_servers(cycle, upsync_server, NGX_DEL);\n    if (servers == NULL) {\n        return NGX_ERROR;\n    }\n\n    if (servers->nelts < 1) {\n        ngx_log_error(NGX_LOG_ERR, cycle->log, 0,\n                      \"upsync_del_peers: no servers to delete \\\"%V\\\"\", &uscf->host);\n        return NGX_ERROR;\n    }\n\n    if (uscf->peer.data == NULL) {\n        return NGX_ERROR;\n    }\n    peers = (ngx_stream_upstream_rr_peers_t *)uscf->peer.data;\n\n    if (peers->number <= servers->nelts) {\n        ngx_log_error(NGX_LOG_ERR, cycle->log, 0,\n                      \"upsync_del_peer: upstream \\\"%V\\\" cannot delete all peers\", \n                      &uscf->host);\n        return NGX_ERROR;\n    }\n\n    n = peers->number - servers->nelts;\n    w = peers->total_weight;\n\n    pre_peer = peers->peer;\n    for (peer = peers->peer; peer; peer = peer->next) {\n        for (i = 0; i < servers->nelts; i++) {\n\n            server = (ngx_stream_upstream_server_t *)servers->elts + i;\n            if (ngx_memn2cmp((u_char *) peer->sockaddr, \n                             (u_char *) server->addrs->sockaddr, len, len) == 0) \n            {\n                if (del_peer == NULL) {\n                    del_peer = peer;\n                    tmp_del_peer = peer;\n\n                } else {\n                    tmp_del_peer->next = peer;\n                    tmp_del_peer = peer;\n                }\n\n                if (pre_peer == peer) {\n\n                    peers->peer = peer->next;\n                    pre_peer = peer->next;\n\n                } else {\n                    pre_peer->next = peer->next;\n                }\n\n                w -= peer->weight;\n                break;\n            }\n        }\n\n        if (i == servers->nelts) {\n            pre_peer = peer;\n        }\n    }\n\n    if (tmp_del_peer) {\n        tmp_del_peer->next = NULL;\n    }\n\n    peers->single = (n == 1);\n    peers->number = n;\n    peers->weighted = (w != n);\n    peers->total_weight = w;\n\n    if (upsync_server->upscf->upsync_lb == NGX_STREAM_LB_HASH_KETAMA) {\n        ngx_stream_upsync_del_chash_peer(uscf);\n    }\n\n    ngx_stream_upsync_event_init(del_peer, upsync_server);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_stream_upsync_replace_peers(ngx_cycle_t *cycle,\n    ngx_stream_upsync_server_t *upsync_server)\n{\n    ngx_uint_t                        i, len, n=0, w=0;\n    ngx_array_t                      *servers;\n    ngx_stream_upstream_server_t     *server = NULL;\n    ngx_stream_upstream_rr_peer_t    *peer = NULL;\n    ngx_stream_upstream_rr_peers_t   *peers = NULL;\n    ngx_stream_upstream_srv_conf_t   *uscf;\n\n    uscf = upsync_server->uscf;\n\n    u_char *namep = NULL;\n    struct sockaddr *saddr = NULL;\n    len = sizeof(struct sockaddr);\n\n    if (uscf->peer.data == NULL) {\n        return NGX_ERROR;\n    }\n    peers = (ngx_stream_upstream_rr_peers_t *)uscf->peer.data;\n\n    servers = ngx_stream_upsync_servers(cycle, upsync_server, NGX_ALL);\n    if (servers == NULL) {\n        return NGX_ERROR;\n    }\n    if (servers->nelts < 1) {\n        ngx_log_error(NGX_LOG_ERR, cycle->log, 0,\n                      \"upsync_replace_peers: no servers to replace \\\"%V\\\"\", &uscf->host);\n        return NGX_ERROR;\n    }\n\n    //tmp_peer = peers->peer;\n    if (peers && servers->nelts >= 1) {\n        n = servers->nelts;\n\n        for (i = 0; i < servers->nelts; i++) {\n            server = (ngx_stream_upstream_server_t *)servers->elts + i;\n\n            peer = ngx_calloc(sizeof(ngx_stream_upstream_rr_peer_t), \n                              cycle->log);\n            if (peer == NULL) {\n                goto invalid;\n            }\n\n            if ((saddr = ngx_calloc(len, cycle->log)) == NULL) {\n                goto invalid;\n            }\n            ngx_memcpy(saddr, server->addrs->sockaddr, len);\n            peer->sockaddr = saddr;\n\n            if ((namep = ngx_calloc(server->addrs->name.len,\n                                    cycle->log)) == NULL) {\n                goto invalid;\n            }\n            ngx_memcpy(namep, server->addrs->name.data,\n                       server->addrs->name.len);\n            peer->name.data = namep;\n\n            peer->socklen = server->addrs->socklen;\n            peer->name.len = server->addrs->name.len;\n            peer->max_fails = server->max_fails;\n            peer->fail_timeout = server->fail_timeout;\n            peer->down = server->down;\n            peer->weight = server->weight;\n            peer->effective_weight = server->weight;\n            peer->current_weight = 0;\n\n            peer->conns = 0;\n\n            peer->next = peers->peer;\n            peers->peer = peer;\n\n            w += server->weight;\n\n            if(i == 0) {\n                peer->next = NULL;\n            }\n        }\n\n        peers->single = (n == 1);\n        peers->number = n;\n        peers->weighted = (w != n);\n        peers->total_weight = w;\n\n        if (upsync_server->upscf->upsync_lb == NGX_STREAM_LB_HASH_KETAMA) {\n            ngx_stream_upsync_chash_init(uscf, NULL);\n        }\n\n        //ngx_pfree(cycle->pool, tmp_peer);  not free for caused address invalid.\n    }\n\n    return NGX_OK;\n\ninvalid:\n    ngx_log_error(NGX_LOG_ERR, cycle->log, 0,\n                  \"upsync_init_peers: copy failed \\\"%V\\\"\", &uscf->host);\n\n    return NGX_ERROR;\n}\n\n\nstatic ngx_int_t\nngx_stream_upsync_consul_parse_json(void *data)\n{\n    u_char                         *p;\n    ngx_buf_t                      *buf;\n    ngx_int_t                       max_fails=2, backup=0, down=0;\n    ngx_str_t                       src, dst;\n    ngx_stream_upsync_ctx_t        *ctx;\n    ngx_stream_upsync_conf_t       *upstream_conf = NULL;\n    ngx_stream_upsync_server_t     *upsync_server = data;\n\n    ctx = &upsync_server->ctx;\n    buf = &ctx->body;\n\n    src.len = 0, src.data = NULL;\n    dst.len = 0, dst.data = NULL;\n\n    cJSON *root = cJSON_Parse((char *)buf->pos);\n    if (root == NULL) {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                      \"consul_upsync_parse_json: root error\");\n        return NGX_ERROR;\n    }\n\n    if (ngx_array_init(&ctx->upstream_conf, ctx->pool, 16,\n                       sizeof(*upstream_conf)) != NGX_OK)\n    {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                      \"consul_upsync_parse_json: array init error\");\n        cJSON_Delete(root);\n        return NGX_ERROR;\n    }\n\n    cJSON *server_next;\n    for (server_next = root->child; server_next != NULL; \n         server_next = server_next->next) \n    {\n        cJSON *temp1 = cJSON_GetObjectItem(server_next, \"Key\");\n        if (temp1 != NULL && temp1->valuestring != NULL) {\n            if (ngx_stream_upsync_check_key((u_char *)temp1->valuestring,\n                                            upsync_server->host) != NGX_OK) {\n                continue;\n            }\n\n            p = (u_char *)ngx_strrchr(temp1->valuestring, '/');\n            upstream_conf = ngx_array_push(&ctx->upstream_conf);\n            ngx_memzero(upstream_conf, sizeof(*upstream_conf));\n            ngx_sprintf(upstream_conf->sockaddr, \"%*s\", ngx_strlen(p + 1), p + 1);\n        }\n        temp1 = NULL;\n\n        if (upstream_conf == NULL) {\n            continue;\n        }\n\n        temp1 = cJSON_GetObjectItem(server_next, \"Value\");\n        if (temp1 != NULL && temp1->valuestring != NULL) {\n\n            src.data = (u_char *)temp1->valuestring;\n            src.len = ngx_strlen(temp1->valuestring);\n\n            if (dst.data == NULL) {\n                dst.data = ngx_pcalloc(ctx->pool, 1024);\n\n            } else {\n                ngx_memzero(dst.data, 1024);\n            }\n            dst.len = 0;\n\n            ngx_decode_base64(&dst, &src);\n        }\n        temp1 = NULL;\n\n        /* default value, server attribute */\n        upstream_conf->weight = 1;\n        upstream_conf->max_fails = 2;\n        upstream_conf->fail_timeout = 10;\n\n        upstream_conf->down = 0;\n        upstream_conf->backup = 0;\n\n        p = NULL;\n\n        if (dst.data != NULL && dst.len != 0) {\n\n            p = dst.data;\n            cJSON *sub_root = cJSON_Parse((char *)p);\n            if (sub_root == NULL) {\n                ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                              \"consul_upsync_parse_json: parse \\'%s\\' failed\", p);\n                continue;\n            }\n\n            cJSON *sub_attribute = sub_root;\n            cJSON *temp1 = cJSON_GetObjectItem(sub_attribute, \"weight\");\n            if (temp1 != NULL) {\n\n                if (temp1->valuestring != NULL) {\n                    upstream_conf->weight = ngx_atoi((u_char *)temp1->valuestring, \n                                            (size_t)ngx_strlen(temp1->valuestring));\n\n                } else if (temp1->valueint >= 0) {\n                    upstream_conf->weight = temp1->valueint;\n                }\n            }\n            temp1 = NULL;\n\n            temp1 = cJSON_GetObjectItem(sub_attribute, \"max_fails\");\n            if (temp1 != NULL) {\n\n                if (temp1->valuestring != NULL) {\n                    max_fails = ngx_atoi((u_char *)temp1->valuestring, \n                                         (size_t)ngx_strlen(temp1->valuestring));\n\n                } else if (temp1->valueint >= 0) {\n                    max_fails = temp1->valueint;\n                }\n            }\n            temp1 = NULL;\n\n            temp1 = cJSON_GetObjectItem(sub_attribute, \"fail_timeout\");\n            if (temp1 != NULL){\n\n                if (temp1->valuestring != NULL) {\n\n                    upstream_conf->fail_timeout = ngx_atoi((u_char *)temp1->valuestring, \n                                                (size_t)ngx_strlen(temp1->valuestring));\n\n                } else if (temp1->valueint >= 0) {\n                    upstream_conf->fail_timeout = temp1->valueint;\n                }\n            }\n            temp1 = NULL;\n\n            temp1 = cJSON_GetObjectItem(sub_attribute, \"down\");\n            if (temp1 != NULL) {\n                    \n                if (temp1->valueint != 0) {\n                    down = temp1->valueint;\n\n                } else if (temp1->valuestring != NULL) {\n                    down = ngx_atoi((u_char *)temp1->valuestring, \n                                    (size_t)ngx_strlen(temp1->valuestring));\n                }\n            }\n            temp1 = NULL;\n\n            temp1 = cJSON_GetObjectItem(sub_attribute, \"backup\");\n            if (temp1 != NULL) {\n                    \n                if (temp1->valueint != 0) {\n                    backup = temp1->valueint;\n\n                } else if (temp1->valuestring != NULL) {\n                    backup = ngx_atoi((u_char *)temp1->valuestring, \n                                      (size_t)ngx_strlen(temp1->valuestring));\n                }\n            }\n            temp1 = NULL;\n\n            dst.len = 0;\n            cJSON_Delete(sub_root);\n        }\n\n        if (upstream_conf->weight <= 0) {\n            ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                          \"consul_upsync_parse_json: \\\"weight\\\" value is \"\n                          \"invalid, setting default value 1\");\n            upstream_conf->weight = 1;\n        }\n\n        if (max_fails < 0) {\n            ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                          \"consul_upsync_parse_json: \\\"max_fails\\\" value is \"\n                          \"invalid, setting default value 2\");\n        } else {\n            upstream_conf->max_fails = (ngx_uint_t)max_fails;\n        }\n\n        if (upstream_conf->fail_timeout < 0) {\n            ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                          \"consul_upsync_parse_json: \\\"fail_timeout\\\" value is \"\n                          \"invalid, setting default value 10\");\n            upstream_conf->fail_timeout = 10;\n        }\n\n        if (down != 1 && down != 0) {\n            ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                          \"consul_upsync_parse_json: \\\"down\\\" value is invalid,\"\n                          \"setting default value 0\");\n        } else {\n            upstream_conf->down = (ngx_uint_t)down;\n        }\n\n        if (backup != 1 && backup != 0) {\n            ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                          \"upsync_parse_json: \\\"backup\\\" value is invalid,\"\n                          \"setting default value 0\");\n        } else {\n            upstream_conf->backup = (ngx_uint_t)backup;\n        }\n\n        max_fails=2, backup=0, down=0;\n    }\n    cJSON_Delete(root);\n\n    return NGX_OK;\n}\n\nstatic ngx_int_t\nngx_stream_upsync_consul_services_parse_json(void *data)\n{\n    ngx_buf_t                      *buf;\n    ngx_int_t                       attr_value;\n    ngx_stream_upsync_ctx_t        *ctx;\n    ngx_stream_upsync_conf_t       *upstream_conf = NULL;\n    ngx_stream_upsync_server_t     *upsync_server = data;\n\n    ctx = &upsync_server->ctx;\n    buf = &ctx->body;\n\n    cJSON *root = cJSON_Parse((char *)buf->pos);\n    if (root == NULL) {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                      \"upsync_parse_json: root error\");\n        return NGX_ERROR;\n    }\n\n    if (ngx_array_init(&ctx->upstream_conf, ctx->pool, 16,\n                       sizeof(*upstream_conf)) != NGX_OK)\n    {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                      \"upsync_parse_json: array init error\");\n        cJSON_Delete(root);\n        return NGX_ERROR;\n    }\n\n    cJSON *server_next;\n    for (server_next = root->child; server_next != NULL; \n         server_next = server_next->next) \n    {\n        cJSON *addr, *port, *tags, *tag_next;\n        size_t addr_len, port_len;\n        u_char port_buf[8];\n\n        addr = cJSON_GetObjectItem(server_next, \"ServiceAddress\");\n        if (addr == NULL || addr->valuestring == NULL\n            || addr->valuestring[0] == '\\0')\n        {\n            addr = cJSON_GetObjectItem(server_next, \"Address\");\n            if (addr == NULL || addr->valuestring == NULL) {\n                continue;\n            }\n        }\n\n        port = cJSON_GetObjectItem(server_next, \"ServicePort\");\n        if (port == NULL || port->valueint < 1 || port->valueint > 65535) {\n            continue;\n        }\n        ngx_memzero(port_buf, 8);\n        ngx_sprintf(port_buf, \"%d\", port->valueint);\n\n        addr_len = ngx_strlen(addr->valuestring);\n        port_len = ngx_strlen(port_buf);\n\n        if (addr_len + port_len + 2 > NGX_SOCKADDRLEN) {\n            continue;\n        }\n\n        upstream_conf = ngx_array_push(&ctx->upstream_conf);\n        if (upstream_conf == NULL) {\n            cJSON_Delete(root);\n            return NGX_ERROR;\n        }\n        ngx_memzero(upstream_conf, sizeof(*upstream_conf));\n\n        ngx_memcpy(upstream_conf->sockaddr, addr->valuestring, addr_len);\n        ngx_memcpy(upstream_conf->sockaddr + addr_len, \":\", 1);\n        ngx_memcpy(upstream_conf->sockaddr + addr_len + 1, port_buf, port_len);\n\n        /* default value, server attribute */\n        upstream_conf->weight = 1;\n        upstream_conf->max_fails = 2;\n        upstream_conf->fail_timeout = 10;\n\n        upstream_conf->down = 0;\n        upstream_conf->backup = 0;\n\n        tags = cJSON_GetObjectItem(server_next, \"ServiceTags\");\n        if (tags == NULL) {\n            continue;\n        }\n\n        for (tag_next = tags->child; tag_next != NULL; \n             tag_next = tag_next->next) \n        {\n            u_char *tag = (u_char *) tag_next->valuestring;\n            if (tag == NULL) {\n                continue;\n            }\n            if (ngx_strncmp(tag, \"weight=\", 7) == 0) {\n                attr_value = ngx_atoi(tag + 7, (size_t)ngx_strlen(tag) - 7);\n\n                if (attr_value == NGX_ERROR || attr_value <= 0) {\n                    ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                                  \"upsync_parse_json: \\\"weight\\\" value is \"\n                                  \"invalid, setting default value 1\");\n                    continue; \n                } else {\n                    upstream_conf->weight = attr_value;\n                }\n            }\n            if (ngx_strncmp(tag, \"max_fails=\", 10) == 0) {\n                attr_value = ngx_atoi(tag + 10, (size_t)ngx_strlen(tag) - 10);\n\n                if (attr_value == NGX_ERROR || attr_value < 0) {\n                    ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                                  \"upsync_parse_json: \\\"max_fails\\\" value is \"\n                                  \"invalid, setting default value 2\");\n                    continue; \n                } else {\n                    upstream_conf->max_fails = attr_value;\n                }\n            }\n            if (ngx_strncmp(tag, \"fail_timeout=\", 13) == 0) {\n                ngx_str_t  value = {ngx_strlen(tag) - 13, tag + 13};\n                attr_value = ngx_parse_time(&value, 1);\n\n                if (attr_value == NGX_ERROR || attr_value < 0) {\n                    ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                                  \"upsync_parse_json: \\\"fail_timeout\\\" value is \"\n                                  \"invalid, setting default value 10\");\n                    continue; \n                } else {\n                    upstream_conf->fail_timeout = attr_value;\n                }\n            }\n            if (ngx_strncmp(tag, \"down\", 4) == 0 && tag[4] == '\\0') {\n                upstream_conf->down = 1;\n            }\n            if (ngx_strncmp(tag, \"backup\", 6) == 0 && tag[6] == '\\0') {\n                upstream_conf->backup = 1;\n            }\n        }\n    }\n\n    cJSON_Delete(root);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_stream_upsync_etcd_parse_json(void *data)\n{\n    u_char                         *p;\n    ngx_buf_t                      *buf;\n    ngx_int_t                       max_fails=2, backup=0, down=0;\n    ngx_stream_upsync_ctx_t        *ctx;\n    ngx_stream_upsync_conf_t       *upstream_conf = NULL;\n    ngx_stream_upsync_server_t     *upsync_server = data;\n\n    ctx = &upsync_server->ctx;\n    buf = &ctx->body;\n\n    cJSON *root = cJSON_Parse((char *)buf->pos);\n    if (root == NULL) {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                      \"etcd_upsync_parse_json: root error\");\n        return NGX_ERROR;\n    }\n    \n    cJSON *errorCode = cJSON_GetObjectItem(root, \"errorCode\");\n    \n    if (errorCode != NULL) {\n        if (errorCode->valueint == 401) { // trigger reload, we've gone too far with index\n            upsync_server->index = 0;\n\n            ngx_del_timer(&upsync_server->upsync_timeout_ev);\n            ngx_add_timer(&upsync_server->upsync_ev, 0);\n        }\n        cJSON_Delete(root);\n        return NGX_ERROR;\n    }\n\n    cJSON *action = cJSON_GetObjectItem(root, \"action\");\n    if (action != NULL) {\n\n        if (action->valuestring != NULL) {\n\n            if (ngx_memcmp(action->valuestring, \"get\", 3) != 0) {\n                upsync_server->index = 0;\n\n                ngx_del_timer(&upsync_server->upsync_timeout_ev);\n                ngx_add_timer(&upsync_server->upsync_ev, 0);\n\n                cJSON_Delete(root);\n                return NGX_ERROR;\n            }\n        }\n    }\n    action = NULL;\n\n    if (ngx_array_init(&ctx->upstream_conf, ctx->pool, 16,\n                       sizeof(*upstream_conf)) != NGX_OK)\n    {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                      \"etcd_upsync_parse_json: array init error\");\n        cJSON_Delete(root);\n        return NGX_ERROR;\n    }\n\n    cJSON *node = cJSON_GetObjectItem(root, \"node\");\n    if (node == NULL) {\n        cJSON_Delete(root);\n        return NGX_ERROR;\n    }\n\n    cJSON *nodes = cJSON_GetObjectItem(node, \"nodes\");\n    if (nodes == NULL) {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                      \"etcd_upsync_parse_json: nodes is null, no servers\");\n        cJSON_Delete(root);\n        return NGX_ERROR;\n    }\n\n    cJSON *server_next;\n    for (server_next = nodes->child; server_next != NULL; \n         server_next = server_next->next) \n    {\n        cJSON *temp0 = cJSON_GetObjectItem(server_next, \"key\");\n        if (temp0 != NULL && temp0->valuestring != NULL) {\n            if (ngx_stream_upsync_check_key((u_char *)temp0->valuestring,\n                                            upsync_server->host) != NGX_OK) {\n                continue;\n            }\n\n            p = (u_char *)ngx_strrchr(temp0->valuestring, '/');\n            upstream_conf = ngx_array_push(&ctx->upstream_conf);\n            ngx_memzero(upstream_conf, sizeof(*upstream_conf));\n            ngx_sprintf(upstream_conf->sockaddr, \"%*s\", ngx_strlen(p + 1), p + 1);\n        }\n        temp0 = NULL;\n\n        /* default value, server attribute */\n        upstream_conf->weight = 1;\n        upstream_conf->max_fails = 2;\n        upstream_conf->fail_timeout = 10;\n\n        upstream_conf->down = 0;\n        upstream_conf->backup = 0;\n\n        temp0 = cJSON_GetObjectItem(server_next, \"value\");\n        if (temp0 != NULL && ngx_strlen(temp0->valuestring) != 0) {\n\n            cJSON *sub_attribute = cJSON_Parse((char *)temp0->valuestring);\n            if (sub_attribute == NULL) {\n                ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                              \"etcd_upsync_parse_json: \\'%s\\' is invalid\",\n                              temp0->valuestring);\n                continue;\n            }\n\n            cJSON *temp1 = cJSON_GetObjectItem(sub_attribute, \"weight\");\n            if (temp1 != NULL) {\n\n                if (temp1->valuestring != NULL) {\n                    upstream_conf->weight = ngx_atoi((u_char *)temp1->valuestring, \n                                            (size_t)ngx_strlen(temp1->valuestring));\n\n                } else if (temp1->valueint >= 0) {\n                    upstream_conf->weight = temp1->valueint;\n                }\n            }\n            temp1 = NULL;\n\n            temp1 = cJSON_GetObjectItem(sub_attribute, \"max_fails\");\n            if (temp1 != NULL) {\n\n                if (temp1->valuestring != NULL) {\n                    max_fails = ngx_atoi((u_char *)temp1->valuestring, \n                                         (size_t)ngx_strlen(temp1->valuestring));\n\n                } else if (temp1->valueint >= 0) {\n                    max_fails = temp1->valueint;\n                }\n            }\n            temp1 = NULL;\n\n            temp1 = cJSON_GetObjectItem(sub_attribute, \"fail_timeout\");\n            if (temp1 != NULL){\n\n                if (temp1->valuestring != NULL) {\n\n                    upstream_conf->fail_timeout = ngx_atoi((u_char *)temp1->valuestring, \n                                                (size_t)ngx_strlen(temp1->valuestring));\n\n                } else if (temp1->valueint >= 0) {\n                    upstream_conf->fail_timeout = temp1->valueint;\n                }\n            }\n            temp1 = NULL;\n\n            temp1 = cJSON_GetObjectItem(sub_attribute, \"down\");\n            if (temp1 != NULL) {\n                    \n                if (temp1->valueint != 0) {\n                    down = temp1->valueint;\n\n                } else if (temp1->valuestring != NULL) {\n                    down = ngx_atoi((u_char *)temp1->valuestring, \n                                    (size_t)ngx_strlen(temp1->valuestring));\n                }\n            }\n            temp1 = NULL;\n\n            temp1 = cJSON_GetObjectItem(sub_attribute, \"backup\");\n            if (temp1 != NULL) {\n                    \n                if (temp1->valueint != 0) {\n                    backup = temp1->valueint;\n\n                } else if (temp1->valuestring != NULL) {\n                    backup = ngx_atoi((u_char *)temp1->valuestring, \n                                      (size_t)ngx_strlen(temp1->valuestring));\n                }\n            }\n            temp1 = NULL;\n\n        } else {\n            continue;\n        }\n\n        if (upstream_conf->weight <= 0) {\n            ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                          \"etcd_upsync_parse_json: \\\"weight\\\" value is invalid,\"\n                          \" setting default value 1\");\n            upstream_conf->weight = 1;\n        }\n\n        if (max_fails < 0) {\n            ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                          \"etcd_upsync_parse_json: \\\"max_fails\\\" value is invalid,\"\n                          \" setting default value 2\");\n        } else {\n            upstream_conf->max_fails = (ngx_uint_t)max_fails;\n        }\n\n        if (upstream_conf->fail_timeout < 0) {\n            ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                          \"etcd_upsync_parse_json: \\\"fail_timeout\\\" value is \"\n                          \"invalid, setting default value 10\");\n            upstream_conf->fail_timeout = 10;\n        }\n\n        if (down != 1 && down != 0) {\n            ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                          \"etcd_upsync_parse_json: \\\"down\\\" value is invalid\"\n                          \", setting default value 0\");\n        } else {\n            upstream_conf->down = (ngx_uint_t)down;\n        }\n\n        if (backup != 1 && backup != 0) {\n            ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                          \"etcd_upsync_parse_json: \\\"backup\\\" value is invalid\"\n                          \", setting default value 0\");\n        } else {\n            upstream_conf->backup = (ngx_uint_t)backup;\n        }\n\n        max_fails=2, backup=0, down=0;\n    }\n    cJSON_Delete(root);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_stream_upsync_check_key(u_char *key, ngx_str_t host)\n{\n    u_char          *last, *ip_p, *port_p, *s_p; // *u_p;\n    ngx_int_t        port;\n/*\n    u_p = (u_char *)ngx_strstr(key, host.data);\n    if (u_p == NULL) {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                      \"upsync_parse_json: %s is illegal, \"\n                      \"dont contains subkey %V\", key, &host);\n        return NGX_ERROR;\n    }\n    if (*(u_p + host.len) != '/' || *(u_p - 1) != '/') {\n        return NGX_ERROR;\n    }\n*/\n    s_p = (u_char *)ngx_strrchr(key, '/');\n    if (s_p == NULL) {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                      \"upsync_parse_json: %s key format is illegal, \"\n                      \"contains no slash ('/')\", key);\n        return NGX_ERROR;\n    }\n\n    port_p = (u_char *)ngx_strchr(s_p, ':');\n    if (port_p == NULL) {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, \n                      \"upsync_check_key: has no port in %s\", s_p);\n        return NGX_ERROR;\n    }\n\n    ip_p = s_p + 1;\n    if (ngx_inet_addr(ip_p, port_p - ip_p) == INADDR_NONE) {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, \n                      \"upsync_check_key: invalid ip in %s\", s_p);\n        return NGX_ERROR;\n    }\n\n    last = ip_p + ngx_strlen(ip_p);\n    port = ngx_atoi(port_p + 1, last - port_p - 1);\n    if (port < 1 || port > 65535) {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, \n                      \"upsync_check_key: invalid port in %s\", s_p);\n        return NGX_ERROR;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic void *\nngx_stream_upsync_servers(ngx_cycle_t *cycle, \n    ngx_stream_upsync_server_t *upsync_server, ngx_flag_t flag)\n{\n    ngx_uint_t                       i;\n    ngx_addr_t                      *addrs;\n    ngx_array_t                     *servers;  /* ngx_stream_upstream_server_t */\n    ngx_stream_upsync_ctx_t         *ctx;\n    ngx_stream_upsync_conf_t        *conf;\n    ngx_stream_upstream_server_t    *server;\n\n    ctx = &upsync_server->ctx;\n\n    servers = ngx_pcalloc(ctx->pool, sizeof(ngx_array_t));\n    if (servers == NULL) {\n        ngx_log_error(NGX_LOG_ERR, cycle->log, 0,\n                      \"upsync_servers: alloc error\");\n        return NULL;\n    }\n\n    if (ngx_array_init(servers, ctx->pool, 16, sizeof(*server)) != NGX_OK) {\n        ngx_log_error(NGX_LOG_ERR, cycle->log, 0,\n                      \"upsync_servers: alloc error\");\n        return NULL;\n    }\n\n    if (flag == NGX_ADD) {\n        for (i = 0; i < ctx->add_upstream.nelts; i++) {\n            conf = (ngx_stream_upsync_conf_t *)ctx->add_upstream.elts + i;\n\n            addrs = ngx_stream_upsync_addrs(ctx->pool, conf->sockaddr);\n            if (addrs == NULL) {\n                continue;\n            }\n\n            server = ngx_array_push(servers);\n            ngx_memzero(server, sizeof(ngx_stream_upstream_server_t));\n\n            server->addrs = addrs;\n            server->naddrs = 1;\n            server->down = conf->down;\n            server->backup = conf->backup;\n            server->weight = conf->weight;\n            server->max_fails = conf->max_fails;\n            server->fail_timeout = conf->fail_timeout;\n        }\n\n    } else if (flag == NGX_DEL) {\n        for (i = 0; i < ctx->del_upstream.nelts; i++) {\n            conf = (ngx_stream_upsync_conf_t *)ctx->del_upstream.elts + i;\n\n            addrs = ngx_stream_upsync_addrs(ctx->pool, conf->sockaddr);\n            if (addrs == NULL) {\n                continue;\n            }\n\n            server = ngx_array_push(servers);\n            ngx_memzero(server, sizeof(ngx_stream_upstream_server_t));\n\n            server->addrs = addrs;\n            server->naddrs = 1;\n            server->down = conf->down;\n            server->backup = conf->backup;\n            server->weight = conf->weight;\n            server->max_fails = conf->max_fails;\n            server->fail_timeout = conf->fail_timeout;\n        }\n\n    } else {\n        for (i = 0; i < ctx->upstream_conf.nelts; i++) {\n            conf = (ngx_stream_upsync_conf_t *)ctx->upstream_conf.elts + i;\n\n            addrs = ngx_stream_upsync_addrs(ctx->pool, conf->sockaddr);\n            if (addrs == NULL) {\n                continue;\n            }\n\n            server = ngx_array_push(servers);\n            ngx_memzero(server, sizeof(ngx_stream_upstream_server_t));\n\n            server->addrs = addrs;\n            server->naddrs = 1;\n            server->down = conf->down;\n            server->backup = conf->backup;\n            server->weight = conf->weight;\n            server->max_fails = conf->max_fails;\n            server->fail_timeout = conf->fail_timeout;\n        }\n    }\n\n    return servers;\n}\n\n\nstatic void *\nngx_stream_upsync_addrs(ngx_pool_t *pool, u_char *sockaddr)\n{\n    u_char                 *port_p, *p, *last, *pp;\n    ngx_int_t               port;\n    ngx_addr_t             *addrs;\n\n    struct sockaddr_in  *sin;\n\n    p = sockaddr;\n    last = p + ngx_strlen(p);\n\n    port_p = ngx_strlchr(p, last, ':');\n    if (port_p == NULL) {\n        ngx_log_error(NGX_LOG_ERR, pool->log, 0, \n                      \"upsync_addrs: has no port in %s\", p);\n        return NULL;\n    }\n\n    port = ngx_atoi(port_p + 1, last - port_p - 1);\n    if (port < 1 || port > 65535) {\n        ngx_log_error(NGX_LOG_ERR, pool->log, 0, \n                      \"upsync_addrs: invalid port in %s\", p);\n        return NULL;\n    }\n\n    sin = ngx_pcalloc(pool, sizeof(struct sockaddr_in));\n    if (sin == NULL) {\n        return NULL;\n    }\n\n    sin->sin_family = AF_INET;\n    sin->sin_port = htons((in_port_t) port);\n    sin->sin_addr.s_addr = ngx_inet_addr(p, port_p - p);\n\n    if (sin->sin_addr.s_addr == INADDR_NONE) {\n        ngx_log_error(NGX_LOG_ERR, pool->log, 0, \n                      \"upsync_addrs: invalid ip in %s\", p);\n        return NULL;\n    }\n\n    addrs = ngx_pcalloc(pool, sizeof(ngx_addr_t));\n    if (addrs == NULL) {\n        return NULL;\n    }\n\n    addrs->sockaddr = (struct sockaddr *) sin;\n    addrs->socklen = sizeof(struct sockaddr_in);\n\n    pp = ngx_pcalloc(pool, last - p);\n    if (pp == NULL) {\n        return NULL;\n    }\n    addrs->name.len = ngx_sprintf(pp, \"%s\", p) - pp;\n    addrs->name.data = pp;\n\n    return addrs;\n}\n\n\nstatic void *\nngx_stream_upsync_create_main_conf(ngx_conf_t *cf)\n{\n    ngx_stream_upsync_main_conf_t       *upmcf;\n\n    upmcf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_upsync_main_conf_t));\n    if (upmcf == NULL) {\n        return NULL;\n    }\n\n    upmcf->upstream_num = NGX_CONF_UNSET_UINT;\n    upmcf->upsync_server = NGX_CONF_UNSET_PTR;\n\n    return upmcf;\n}\n\n\nstatic char *\nngx_stream_upsync_init_main_conf(ngx_conf_t *cf, void *conf)\n{\n    ngx_uint_t                                     i;\n    ngx_stream_upsync_main_conf_t                 *upmcf = conf;\n    ngx_stream_upstream_srv_conf_t               **uscfp;\n    ngx_stream_upstream_main_conf_t               *umcf;\n\n    umcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_upstream_module);\n\n    upmcf->upsync_server = ngx_pcalloc(cf->pool, \n                      umcf->upstreams.nelts * sizeof(ngx_stream_upsync_server_t));\n\n    if (upmcf->upsync_server == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    upmcf->upstream_num = 0;\n    upsync_ctx = upmcf;\n    uscfp = umcf->upstreams.elts;\n\n    for (i = 0; i < umcf->upstreams.nelts; i++) {\n\n        if (ngx_stream_upsync_init_srv_conf(cf, uscfp[i], i) != NGX_OK) {\n            return NGX_CONF_ERROR;\n        }\n    }\n\n    return NGX_CONF_OK;\n}\n\n\nstatic void *\nngx_stream_upsync_create_srv_conf(ngx_conf_t *cf)\n{\n    ngx_stream_upsync_srv_conf_t  *upscf;\n\n    upscf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_upsync_srv_conf_t));\n    if (upscf == NULL) {\n        return NULL;\n    }\n\n    upscf->upsync_host.len = NGX_CONF_UNSET_SIZE;\n    upscf->upsync_host.data = NGX_CONF_UNSET_PTR;\n\n    upscf->upsync_port = NGX_CONF_UNSET;\n\n    upscf->upsync_dump_path.len = NGX_CONF_UNSET_SIZE;\n    upscf->upsync_dump_path.data = NGX_CONF_UNSET_PTR;\n\n    upscf->upsync_timeout = NGX_CONF_UNSET_MSEC;\n    upscf->upsync_interval = NGX_CONF_UNSET_MSEC;\n\n    upscf->upsync_lb = NGX_CONF_UNSET;\n\n    upscf->strong_dependency = NGX_CONF_UNSET_UINT;\n\n    upscf->conf_file = NGX_CONF_UNSET_PTR;\n\n    upscf->upsync_type_conf = NGX_CONF_UNSET_PTR;\n\n    ngx_memzero(&upscf->conf_server, sizeof(upscf->conf_server));\n\n    return upscf;\n}\n\n\nstatic char *\nngx_stream_upsync_init_srv_conf(ngx_conf_t *cf, void *conf, ngx_uint_t num)\n{\n    u_char                                      *buf;\n    ngx_stream_upsync_server_t                  *upsync_server;\n    ngx_stream_upsync_srv_conf_t                *upscf;\n    ngx_stream_upstream_srv_conf_t              *uscf = conf;\n\n    if (uscf->srv_conf == NULL) {\n        return NGX_CONF_OK;\n    }\n\n    upscf = ngx_stream_conf_upstream_srv_conf(uscf, ngx_stream_upsync_module);\n    if (upscf->upsync_host.data == NGX_CONF_UNSET_PTR \n        && upscf->upsync_host.len == NGX_CONF_UNSET_SIZE) {\n        return NGX_CONF_OK;\n    }\n\n    upsync_ctx->upstream_num++;\n\n    upsync_server = &upsync_ctx->upsync_server[upsync_ctx->upstream_num - 1];\n    if (upsync_server == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    if (upscf->upsync_timeout == NGX_CONF_UNSET_MSEC) {\n        upscf->upsync_timeout = 1000 * 60 * 6;\n    }\n\n    if (upscf->upsync_interval == NGX_CONF_UNSET_MSEC) {\n        upscf->upsync_interval = 1000 * 5;\n    }\n\n    if (upscf->upsync_lb == NGX_CONF_UNSET) {\n        upscf->upsync_lb = NGX_STREAM_LB_DEFAULT;\n    }\n\n    if (upscf->strong_dependency == NGX_CONF_UNSET_UINT) {\n        upscf->strong_dependency = 0;\n    }\n\n    if (upscf->upsync_dump_path.len == NGX_CONF_UNSET_SIZE) {\n        buf = ngx_pcalloc(cf->pool, \n                          ngx_strlen(\"/tmp/servers_.conf\") + uscf->host.len + 1);\n        ngx_sprintf(buf, \"/tmp/servers_%V.conf\", &uscf->host);\n\n        upscf->upsync_dump_path.data = buf;\n        upscf->upsync_dump_path.len = ngx_strlen(\"/tmp/servers_.conf\")\n                                      + uscf->host.len;\n    }\n\n    upscf->conf_file = ngx_pcalloc(cf->pool, sizeof(ngx_open_file_t));\n    if (upscf->conf_file == NULL) {\n        return NGX_CONF_ERROR; \n    }\n    upscf->conf_file->fd = NGX_INVALID_FILE;\n    upscf->conf_file->name = upscf->upsync_dump_path;\n    upscf->conf_file->flush = NULL;\n    upscf->conf_file->data = NULL;\n\n    upsync_server->index = 0;\n    upsync_server->update_generation = 0;\n\n    upsync_server->upscf = upscf;\n    upsync_server->uscf = uscf;\n\n    upsync_server->host.len = uscf->host.len;\n    upsync_server->host.data = uscf->host.data;\n\n    return NGX_CONF_OK;\n}\n\n\nstatic ngx_int_t \nngx_stream_upsync_init_module(ngx_cycle_t *cycle)\n{\n    ngx_uint_t                         i;\n    ngx_stream_upsync_server_t        *upsync_server;\n    ngx_stream_upsync_srv_conf_t      *upscf;\n    \n    // no stream {} block found\n    if (upsync_ctx == NULL) {\n        return NGX_OK;\n    }\n    \n    upsync_server = upsync_ctx->upsync_server;\n\n    if (ngx_stream_upsync_init_shm_mutex(cycle) != NGX_OK) {\n        ngx_log_error(NGX_LOG_ERR, cycle->log, 0, \"upsync_init_module:\"\n                      \" init shm mutex failed\");\n        return NGX_ERROR;\n    }\n\n    for (i = 0; i < upsync_ctx->upstream_num; i++) {\n\n        upscf = upsync_server[i].upscf;\n        if (upscf->conf_file->fd != NGX_INVALID_FILE) {\n            ngx_close_file(upscf->conf_file->fd);\n            upscf->conf_file->fd = NGX_INVALID_FILE;\n        }\n        ngx_change_file_access(upscf->upsync_dump_path.data, \n                               S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH|S_IWOTH);\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_stream_upsync_init_shm_mutex(ngx_cycle_t *cycle)\n{\n    u_char                                *shared, *file;\n    size_t                                 size, cl;\n    ngx_shm_t                              shm;\n    ngx_uint_t                             i;\n    ngx_stream_upsync_server_t            *upsync_server;\n\n    upsync_server = upsync_ctx->upsync_server;\n\n    if (*stream_upsync_shared_created) {\n        shm.size = 128 * (*stream_upsync_shared_created);\n        shm.log = cycle->log;\n        shm.addr = (u_char *)(stream_upsync_shared_created);\n        shm.name.len = sizeof(\"ngx_upsync_shared_zone\");\n        shm.name.data = (u_char *)\"ngx_upsync_shared_zone\";\n\n        ngx_shm_free(&shm);\n    }\n\n    /* cl should be equal to or greater than cache line size \n       shared created flag\n       upsync_accept_mutex for every upstream \n    */\n\n    cl = 128;\n    size = cl                                       \n         + cl * upsync_ctx->upstream_num;\n\n    shm.size = size;\n    shm.log = cycle->log;\n    shm.name.len = sizeof(\"ngx_upsync_shared_zone\");\n    shm.name.data = (u_char *)\"ngx_upsync_shared_zone\";\n\n    if (ngx_shm_alloc(&shm) != NGX_OK) {\n        return NGX_ERROR;\n    }\n    shared = shm.addr;\n\n    stream_upsync_shared_created = (ngx_atomic_t *)shared;\n\n    for (i = 0; i < upsync_ctx->upstream_num; i++) {\n\n#if (NGX_HAVE_ATOMIC_OPS)\n\n        file = NULL;\n\n#else\n\n        file = ngx_pcalloc(cycle->pool, \n                           cycle->lock_file.len + ngx_strlen(\"upsync\") + 3);\n        if (file == NULL) {\n            return NGX_ERROR;\n        }\n\n        (void) ngx_sprintf(file, \"%V%s%d%Z\", &ngx_cycle->lock_file, \"upsync\", i);\n\n#endif\n\n        if (ngx_shmtx_create(&upsync_server[i].upsync_accept_mutex, \n                             (ngx_shmtx_sh_t *)(shared + (i + 1) * cl), file) \n                != NGX_OK) \n        {\n            return NGX_ERROR;\n        }\n    }\n\n    ngx_atomic_cmp_set(stream_upsync_shared_created, *stream_upsync_shared_created, \n                       upsync_ctx->upstream_num);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_stream_upsync_init_process(ngx_cycle_t *cycle)\n{\n    char                                *conf_value = NULL;\n    ngx_int_t                            status = 0;\n    ngx_uint_t                           i, j;\n    ngx_pool_t                          *pool;\n    ngx_upsync_conf_t                   *upsync_type_conf;\n    ngx_stream_upsync_ctx_t             *ctx;\n    ngx_stream_upsync_server_t          *upsync_server;\n\n    // no stream {} block found\n    if (upsync_ctx == NULL) {\n        return NGX_OK;\n    }\n    upsync_server = upsync_ctx->upsync_server;\n\n    for (i = 0; i < upsync_ctx->upstream_num; i++) {\n\n        ngx_queue_init(&upsync_server[i].delete_ev);\n        if (upsync_server[i].upscf->strong_dependency == 0) {\n            continue;\n        }\n\n        ctx = &upsync_server[i].ctx;\n        ngx_memzero(ctx, sizeof(*ctx));\n        upsync_type_conf = upsync_server[i].upscf->upsync_type_conf;\n\n        pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, ngx_cycle->log);\n        if (pool == NULL) {\n            ngx_log_error(NGX_LOG_ERR, cycle->log, 0, \n                          \"upsync_init_process: recv error, \"\n                          \"server no enough memory\");\n            return NGX_ERROR;\n        }\n        ctx->pool = pool;\n\n        for (j = 0; j < NGX_STREAM_RETRY_TIMES; j++) {\n            status = ngx_stream_upsync_get_upstream(cycle, \n                                                  &upsync_server[i], &conf_value);\n            if (status == NGX_OK) {\n                break;\n            }\n        }\n\n        if (status != NGX_OK) {\n            ngx_log_error(NGX_LOG_ERR, cycle->log, 0, \n                          \"upsync_init_process: pull upstream \\\"%V\\\" conf failed\",\n                          &upsync_server->host);\n            return NGX_ERROR;\n        }\n\n        ctx->recv.pos = (u_char *)conf_value;\n        ctx->recv.last = (u_char *)(conf_value + ngx_strlen(conf_value));\n        ctx->recv.end = ctx->recv.last;\n\n        if (upsync_type_conf->init(&upsync_server[i]) == NGX_ERROR) {\n            ngx_free(conf_value);\n            conf_value = NULL;\n\n            ngx_destroy_pool(pool);\n            ctx->pool = NULL;\n\n            continue;\n        }\n\n        ngx_stream_upsync_process(&upsync_server[i]);\n\n        ngx_free(conf_value);\n        conf_value = NULL;\n\n        ngx_destroy_pool(pool);\n        ctx->pool = NULL;\n    }\n\n    ngx_stream_upsync_add_timers(cycle);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_stream_upsync_add_timers(ngx_cycle_t *cycle)\n{\n    ngx_msec_t                                   t, tmp;\n    ngx_uint_t                                   i;\n    ngx_stream_upsync_server_t                  *upsync_server;\n    ngx_stream_upsync_srv_conf_t                *upscf;\n\n    upsync_server = upsync_ctx->upsync_server;\n    if (upsync_server == NULL) {\n        return NGX_OK;\n    }\n\n    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cycle->log, 0, \"upsync_add_timers\");\n\n    srandom(ngx_pid);\n    for (i = 0; i < upsync_ctx->upstream_num; i++) {\n        upsync_server[i].upsync_ev.handler = ngx_stream_upsync_begin_handler;\n        upsync_server[i].upsync_ev.log = cycle->log;\n        upsync_server[i].upsync_ev.data = &upsync_server[i];\n        upsync_server[i].upsync_ev.timer_set = 0;\n\n        upsync_server[i].upsync_timeout_ev.handler =\n            ngx_stream_upsync_timeout_handler;\n        upsync_server[i].upsync_timeout_ev.log = cycle->log;\n        upsync_server[i].upsync_timeout_ev.data = &upsync_server[i];\n        upsync_server[i].upsync_timeout_ev.timer_set = 0;\n\n        /*\n         * We add a random start time here, since we don't want to trigger\n         * the check events too close to each other at the beginning.\n         */\n        upscf = upsync_server[i].upscf;\n        tmp = upscf->upsync_interval;\n        t = ngx_random() % 1000 + tmp;\n\n        ngx_add_timer(&upsync_server[i].upsync_ev, t);\n    }\n\n    return NGX_OK;\n}\n\n\nstatic void\nngx_stream_upsync_begin_handler(ngx_event_t *event)\n{\n    ngx_stream_upsync_ctx_t          *ctx;\n    ngx_stream_upsync_server_t       *upsync_server;\n\n    if (ngx_stream_upsync_need_exit()) {\n        return;\n    }\n\n    upsync_server = event->data;\n    if (upsync_server == NULL) {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                      \"ngx_stream_upsync_begin_handler: upsync_server is null\");\n        return;\n    }\n\n    ctx = &upsync_server->ctx;\n    if (ctx->pool != NULL) {\n        ngx_destroy_pool(ctx->pool);\n    }\n    ctx->pool = NULL;\n\n    ngx_memzero(ctx, sizeof(*ctx));\n\n    if (parser != NULL) {\n        ngx_free(parser);\n        parser = NULL;\n    }\n\n    if (upsync_server->upsync_ev.timer_set) {\n        ngx_del_timer(&upsync_server->upsync_ev);\n    }\n\n    ngx_stream_upsync_connect_handler(event);\n}\n\n\nstatic void\nngx_stream_upsync_connect_handler(ngx_event_t *event)\n{\n    ngx_int_t                                 rc;\n    ngx_connection_t                         *c;\n    ngx_upsync_conf_t                        *upsync_type_conf;\n    ngx_stream_upsync_server_t               *upsync_server;\n    ngx_stream_upsync_srv_conf_t             *upscf;\n\n    if (ngx_stream_upsync_need_exit()) {\n        return;\n    }\n\n    if (ngx_stream_upsync_init_server(event) != NGX_OK) {\n        return;\n    }\n\n    upsync_server = event->data;\n    upscf = upsync_server->upscf;\n    upsync_type_conf = upscf->upsync_type_conf;\n\n    ngx_add_timer(&upsync_server->upsync_timeout_ev, upscf->upsync_timeout);\n\n    rc = ngx_event_connect_peer(&upsync_server->pc);\n    if (rc == NGX_ERROR || rc == NGX_DECLINED) {\n        ngx_log_error(NGX_LOG_ERR, event->log, 0,\n                      \"upsync_connect_handler: cannot connect to upsync_server: %V \",\n                      upsync_server->pc.name);\n\n        ngx_del_timer(&upsync_server->upsync_timeout_ev);\n        ngx_add_timer(&upsync_server->upsync_ev, 0);\n\n        return;\n    }\n\n    /* NGX_OK or NGX_AGAIN */\n    c = upsync_server->pc.connection;\n    c->data = upsync_server;\n    c->log = upsync_server->pc.log;\n    c->sendfile = 0;\n    c->read->log = c->log;\n    c->write->log = c->log;\n\n    c->idle = 1; //for quick exit.\n\n    c->write->handler = upsync_type_conf->send_handler;\n    c->read->handler = upsync_type_conf->recv_handler;\n\n    /* The kqueue's loop interface needs it. */\n    if (rc == NGX_OK) {\n        c->write->handler(c->write);\n    }\n}\n\n\nstatic void\nngx_stream_upsync_send_handler(ngx_event_t *event)\n{\n    ssize_t                                   size;\n    ngx_connection_t                         *c;\n    ngx_upsync_conf_t                        *upsync_type_conf;\n    ngx_stream_upsync_ctx_t                  *ctx;\n    ngx_stream_upsync_server_t               *upsync_server;\n    ngx_stream_upsync_srv_conf_t             *upscf;\n\n    if (ngx_stream_upsync_need_exit()) {\n        return;\n    }\n\n    c = event->data;\n    upsync_server = c->data;\n    upscf = upsync_server->upscf;\n    upsync_type_conf = upscf->upsync_type_conf;\n\n    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, \"upsync_send\");\n\n    ctx = &upsync_server->ctx;\n\n    u_char request[ngx_pagesize];\n    ngx_memzero(request, ngx_pagesize);\n\n    if (upsync_type_conf->upsync_type == NGX_STREAM_UPSYNC_CONSUL\n        || upsync_type_conf->upsync_type == NGX_STREAM_UPSYNC_CONSUL_SERVICES) {\n        ngx_sprintf(request, \"GET %V?recurse&index=%uL HTTP/1.0\\r\\nHost: %V\\r\\n\"\n                    \"Accept: */*\\r\\n\\r\\n\", \n                    &upscf->upsync_send, upsync_server->index, \n                    &upscf->upsync_host);\n    }\n\n    if (upsync_type_conf->upsync_type == NGX_STREAM_UPSYNC_ETCD) {\n        if (upsync_server->index != 0) {\n            ngx_sprintf(request, \"GET %V?wait=true&recursive=true&waitIndex=%uL\"\n                        \" HTTP/1.0\\r\\nHost: %V\\r\\nAccept: */*\\r\\n\\r\\n\", \n                        &upscf->upsync_send, upsync_server->index, \n                        &upscf->upsync_host);\n\n        } else {\n            ngx_sprintf(request, \"GET %V?\" \n                        \" HTTP/1.0\\r\\nHost: %V\\r\\nAccept: */*\\r\\n\\r\\n\", \n                        &upscf->upsync_send, &upscf->upsync_host);\n\n        }\n    }\n\n    ctx->send.pos = request;\n    ctx->send.last = ctx->send.pos + ngx_strlen(request);\n    while (ctx->send.pos < ctx->send.last) {\n        size = c->send(c, ctx->send.pos, ctx->send.last - ctx->send.pos);\n\n#if (NGX_DEBUG)\n        {\n            ngx_err_t  err;\n\n            err = (size >=0) ? 0 : ngx_socket_errno;\n            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, err,\n                           \"upsync_send: send size: %z, total: %z\",\n                           size, ctx->send.last - ctx->send.pos);\n        }\n#endif\n\n        if (size > 0) {\n            ctx->send.pos += size;\n\n        } else if (size == 0 || size == NGX_AGAIN) {\n            return;\n\n        } else {\n            c->error = 1;\n            goto upsync_send_fail;\n        }\n    }\n\n    if (ctx->send.pos == ctx->send.last) {\n        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, \n                       \"upsync_send: send done.\");\n    }\n\n    c->write->handler = ngx_stream_upsync_send_empty_handler;\n\n    return;\n\nupsync_send_fail:\n    ngx_log_error(NGX_LOG_ERR, event->log, 0,\n                  \"upsync_send: send error with upsync_server: %V\", \n                  upsync_server->pc.name);\n\n    ngx_stream_upsync_clean_event(upsync_server);\n}\n\n\nstatic void\nngx_stream_upsync_recv_handler(ngx_event_t *event)\n{\n    u_char                                *new_buf;\n    ssize_t                                size, n;\n    ngx_pool_t                            *pool;\n    ngx_connection_t                      *c;\n    ngx_upsync_conf_t                     *upsync_type_conf;\n    ngx_stream_upsync_ctx_t               *ctx;\n    ngx_stream_upsync_server_t            *upsync_server;\n\n    if (ngx_stream_upsync_need_exit()) {\n        return;\n    }\n\n    c = event->data;\n    upsync_server = c->data;\n    upsync_type_conf = upsync_server->upscf->upsync_type_conf;\n    ctx = &upsync_server->ctx;\n\n    if (ctx->pool == NULL) {\n        pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, ngx_cycle->log);\n        if (pool == NULL) {\n            ngx_log_error(NGX_LOG_ERR, event->log, 0, \n                          \"upsync_recv: recv not enough memory\");\n            return;\n        }\n        ctx->pool = pool;\n\n    } else {\n        pool = ctx->pool;\n    }\n\n    if (ctx->recv.start == NULL) {\n        /* 1 of the page_size, is it enough? */\n        ctx->recv.start = ngx_pcalloc(pool, ngx_pagesize);\n        if (ctx->recv.start == NULL) {\n            goto upsync_recv_fail;\n        }\n\n        ctx->recv.last = ctx->recv.pos = ctx->recv.start;\n        ctx->recv.end = ctx->recv.start + ngx_pagesize;\n    }\n\n    while (1) {\n        n = ctx->recv.end - ctx->recv.last;\n\n        /* buffer not big enough? enlarge it by twice */\n        if (n == 0) {\n            size = ctx->recv.end - ctx->recv.start;\n            new_buf = ngx_pcalloc(pool, size * 2);\n            if (new_buf == NULL) {\n                goto upsync_recv_fail;\n            }\n            ngx_memcpy(new_buf, ctx->recv.start, size);\n\n            ctx->recv.pos = ctx->recv.start = new_buf;\n            ctx->recv.last = new_buf + size;\n            ctx->recv.end = new_buf + size * 2;\n\n            n = ctx->recv.end - ctx->recv.last;\n        }\n\n        size = c->recv(c, ctx->recv.last, n);\n\n#if (NGX_DEBUG)\n        {\n            ngx_err_t  err;\n\n            err = (size >= 0) ? 0 : ngx_socket_errno;\n            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, err,\n                           \"upsync_recv: recv size: %z, upsync_server: %V \",\n                           size, upsync_server->pc.name);\n        }\n#endif\n\n        if (size > 0) {\n            ctx->recv.last += size;\n            continue;\n        } else if (size == 0) {\n            break;\n        } else if (size == NGX_AGAIN) {\n            return;\n        } else {\n            c->error = 1;\n            goto upsync_recv_fail;\n        }\n    }\n\n    if (upsync_type_conf->init(upsync_server) == NGX_OK) {\n        ngx_stream_upsync_process(upsync_server);\n\n        c->read->handler = ngx_stream_upsync_recv_empty_handler;\n    }\n\n    upsync_type_conf->clean(upsync_server);\n\n    return;\n\nupsync_recv_fail:\n    ngx_log_error(NGX_LOG_ERR, event->log, 0,\n                  \"upsync_recv: recv error with upstream: \\\"%V\\\"\", \n                  &upsync_server->host);\n\n    ngx_stream_upsync_clean_event(upsync_server);\n}\n\n\nstatic void\nngx_stream_upsync_send_empty_handler(ngx_event_t *event)\n{\n    /* void */\n}\n\n\nstatic void\nngx_stream_upsync_recv_empty_handler(ngx_event_t *event)\n{\n    /* void */\n}\n\n\nstatic ngx_int_t\nngx_stream_upsync_consul_parse_init(void *data)\n{\n    char                                  *buf;\n    size_t                                 parsed;\n    ngx_stream_upsync_ctx_t               *ctx;\n    ngx_stream_upsync_server_t            *upsync_server = data;\n\n    ctx = &upsync_server->ctx;\n\n    if (ngx_stream_http_parser_init() == NGX_ERROR) {\n        return NGX_ERROR;\n    }\n\n    buf = (char *)ctx->recv.pos;\n\n    ctx->body.pos = ctx->body.last = NULL;\n\n    parsed = http_parser_execute(parser, &settings, buf, ngx_strlen(buf));\n    if (parsed != ngx_strlen(buf)) {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                      \"upsync_consul_parse_init: parsed upstream \\\"%V\\\" wrong\",\n                      &upsync_server->host);\n\n        if (parser != NULL) {\n            ngx_free(parser);\n            parser = NULL;\n        }\n\n        return NGX_ERROR;\n    }\n\n    if (ngx_strncmp(state.status, \"OK\", 2) == 0) {\n\n        if (ngx_strlen(state.http_body) != 0) {\n            ctx->body.pos = state.http_body;\n            ctx->body.last = state.http_body + ngx_strlen(state.http_body);\n\n            *(ctx->body.last + 1) = '\\0';\n        }\n\n    } else {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                      \"upsync_consul_parse_init: recv upstream \\\"%V\\\" error; \"\n                      \"http_status: %d\", &upsync_server->host, parser->status_code);\n\n        if (parser != NULL) {\n            ngx_free(parser);\n            parser = NULL;\n        }\n\n        return NGX_ERROR;\n    }\n\n    if (parser != NULL) {\n        ngx_free(parser);\n        parser = NULL;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_stream_upsync_etcd_parse_init(void *data)\n{\n    char                                  *buf;\n    size_t                                 parsed;\n    ngx_stream_upsync_ctx_t               *ctx;\n    ngx_stream_upsync_server_t            *upsync_server = data;\n\n    ctx = &upsync_server->ctx;\n\n    if (ngx_stream_http_parser_init() == NGX_ERROR) {\n        return NGX_ERROR;\n    }\n\n    buf = (char *)ctx->recv.pos;\n\n    ctx->body.pos = ctx->body.last = NULL;\n\n    parsed = http_parser_execute(parser, &settings, buf, ngx_strlen(buf));\n    if (parsed != ngx_strlen(buf)) {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                      \"upsync_etcd_parse_init: parsed upstream \\\"%V\\\" wrong\",\n                      &upsync_server->host);\n\n        if (parser != NULL) {\n            ngx_free(parser);\n            parser = NULL;\n        }\n\n        return NGX_ERROR;\n    }\n\n    if (ngx_strncmp(state.status, \"OK\", 2) == 0\n        || ngx_strncmp(state.status, \"Bad\", 3) == 0) {\n\n        if (ngx_strlen(state.http_body) != 0) {\n            ctx->body.pos = state.http_body;\n            ctx->body.last = state.http_body + ngx_strlen(state.http_body);\n\n            *(ctx->body.last + 1) = '\\0';\n        }\n\n    } else {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                      \"upsync_consul_parse_init: recv upstream \\\"%V\\\" error; \"\n                      \"http_status: %d\", &upsync_server->host, parser->status_code);\n\n        if (parser != NULL) {\n            ngx_free(parser);\n            parser = NULL;\n        }\n\n        return NGX_ERROR;\n    }\n\n    if (parser != NULL) {\n        ngx_free(parser);\n        parser = NULL;\n    }\n    \n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_stream_upsync_dump_server(ngx_stream_upsync_server_t *upsync_server)\n{\n    ngx_buf_t                               *b=NULL;\n    ngx_stream_upsync_srv_conf_t            *upscf = NULL;\n    ngx_stream_upstream_rr_peer_t           *peer = NULL;\n    ngx_stream_upstream_rr_peers_t          *peers = NULL;\n    ngx_stream_upstream_srv_conf_t          *uscf = NULL;\n\n    uscf = upsync_server->uscf;\n    if (uscf->peer.data != NULL) {\n        peers = (ngx_stream_upstream_rr_peers_t *)uscf->peer.data;\n\n    } else {\n        ngx_log_error(NGX_LOG_ERR, upsync_server->ctx.pool->log, 0,\n                      \"upsync_dump_server: no peers\");\n        return NGX_ERROR;\n    }\n\n    if (peers->number == 0) {\n        ngx_log_error(NGX_LOG_ERR, upsync_server->ctx.pool->log, 0,\n                      \"upsync_dump_server: there are no peers to dump\");\n        return NGX_ERROR;\n    }\n\n    b = ngx_create_temp_buf(upsync_server->ctx.pool, \n                            NGX_PAGE_SIZE * NGX_PAGE_NUMBER);\n    if (b == NULL) {\n        ngx_log_error(NGX_LOG_ERR, upsync_server->ctx.pool->log, 0,\n                      \"upsync_dump_server: dump failed %V\", &uscf->host);\n        return NGX_ERROR;\n    }\n\n    for (peer = peers->peer; peer; peer = peer->next) {\n        b->last = ngx_snprintf(b->last, b->end - b->last, \n                               \"server %V\", &peer->name);\n        b->last = ngx_snprintf(b->last, b->end - b->last, \n                               \" weight=%d\", peer->weight);\n        b->last = ngx_snprintf(b->last, b->end - b->last, \n                               \" max_fails=%d\", peer->max_fails);\n        b->last = ngx_snprintf(b->last, b->end - b->last, \n                               \" fail_timeout=%ds\", peer->fail_timeout);\n\n        if (peer->down) {\n            b->last = ngx_snprintf(b->last, b->end - b->last, \" down\");\n        }\n \n        b->last = ngx_snprintf(b->last, b->end - b->last, \";\\n\");\n    }\n\n    upscf = upsync_server->upscf;\n    upscf->conf_file->fd = ngx_open_file(upscf->upsync_dump_path.data,\n                                         NGX_FILE_TRUNCATE,\n                                         NGX_FILE_WRONLY,\n                                         NGX_FILE_DEFAULT_ACCESS);\n    if (upscf->conf_file->fd == NGX_INVALID_FILE) {\n        ngx_log_error(NGX_LOG_ERR, upsync_server->ctx.pool->log, 0,\n                      \"upsync_dump_server: open dump file \\\"%V\\\" failed\", \n                      &upscf->upsync_dump_path);\n        return NGX_ERROR;\n    }\n\n    ngx_lseek(upscf->conf_file->fd, 0, SEEK_SET);\n    if (ngx_write_fd(upscf->conf_file->fd, b->start, b->last - b->start) == NGX_ERROR) {\n        ngx_log_error(NGX_LOG_ERR, upsync_server->ctx.pool->log, 0,\n                      \"upsync_dump_server: write file failed %V\", \n                      &upscf->upsync_dump_path);\n        ngx_close_file(upscf->conf_file->fd);\n        return NGX_ERROR;\n    }\n\n    if (ngx_ftruncate(upscf->conf_file->fd, b->last - b->start) != NGX_OK) {\n        ngx_log_error(NGX_LOG_ERR, upsync_server->ctx.pool->log, 0,\n                      \"upsync_dump_server: truncate file failed %V\", \n                      &upscf->upsync_dump_path);\n        ngx_close_file(upscf->conf_file->fd);\n        return NGX_ERROR;\n    }\n\n    ngx_close_file(upscf->conf_file->fd);\n    upscf->conf_file->fd = NGX_INVALID_FILE;\n\n    ngx_log_error(NGX_LOG_NOTICE, upsync_server->ctx.pool->log, 0,\n                  \"upsync_dump_server: dump conf file %V succeeded, number of servers is %d\", \n                  &upscf->upsync_dump_path, peers->number);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_stream_upsync_init_server(ngx_event_t *event)\n{\n    ngx_uint_t                               n = 0, r = 0, cur = 0;\n    ngx_pool_t                              *pool;\n    ngx_stream_upsync_ctx_t                 *ctx;\n    ngx_stream_upsync_server_t              *upsync_server;\n    ngx_stream_upsync_srv_conf_t            *upscf;\n    ngx_stream_upstream_server_t            *conf_server;\n\n    u_char               *p, *host = NULL;\n    size_t                len;\n    ngx_str_t            *name;\n    struct addrinfo       hints, *res = NULL, *rp = NULL;\n    struct sockaddr_in   *sin;\n\n    upsync_server = event->data;\n    upscf = upsync_server->upscf;\n    conf_server = &upscf->conf_server;\n\n    ctx = &upsync_server->ctx;\n    if (ctx->pool == NULL) {\n\n        pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, ngx_cycle->log);\n        if (pool == NULL) {\n            ngx_log_error(NGX_LOG_ERR, event->log, 0, \n                          \"upsync_init_consul: cannot create pool, not enough memory\");\n            return NGX_ERROR;\n        }\n        ctx->pool = pool;\n    }\n\n    ngx_memzero(&upsync_server->pc, sizeof(ngx_peer_connection_t));\n\n    upsync_server->pc.get = ngx_event_get_peer;\n    upsync_server->pc.log = event->log;\n    upsync_server->pc.log_error = NGX_ERROR_ERR;\n\n    upsync_server->pc.cached = 0;\n    upsync_server->pc.connection = NULL;\n\n    if (ngx_inet_addr(upscf->upsync_host.data, upscf->upsync_host.len)\n            == INADDR_NONE) \n    {\n\n        host = ngx_pcalloc(ctx->pool, upscf->upsync_host.len + 1);\n        if (host == NULL) {\n            return NGX_ERROR;\n        }\n\n        (void) ngx_cpystrn(host, upscf->upsync_host.data, upscf->upsync_host.len + 1);\n\n        ngx_memzero(&hints, sizeof(struct addrinfo));\n        hints.ai_family = AF_UNSPEC;\n        hints.ai_socktype = SOCK_STREAM;\n#ifdef AI_ADDRCONFIG\n        hints.ai_flags = AI_ADDRCONFIG;\n#endif\n\n        if (getaddrinfo((char *) host, NULL, &hints, &res) != 0) {\n            res = NULL;\n            goto valid;\n        }\n\n        /* bad method: for get random server*/\n        for (rp = res; rp != NULL; rp = rp->ai_next) {\n            if (rp->ai_family != AF_INET) {\n                continue;\n            }\n\n            n++;\n        }\n\n        r = ngx_random() % n;\n        for (rp = res; rp != NULL; rp = rp->ai_next) {\n\n            if (rp->ai_family != AF_INET) {\n                continue;\n            }\n\n            if (cur != r) {\n                cur++;\n                continue;\n            }\n\n            sin = ngx_pcalloc(ctx->pool, rp->ai_addrlen);\n            if (sin == NULL) {\n                goto valid;\n            }\n\n            ngx_memcpy(sin, rp->ai_addr, rp->ai_addrlen);\n            sin->sin_port = htons((in_port_t) upscf->upsync_port);\n\n            upsync_server->pc.sockaddr = (struct sockaddr *) sin;\n            upsync_server->pc.socklen = rp->ai_addrlen;\n\n            len = NGX_INET_ADDRSTRLEN + sizeof(\":65535\") - 1;\n            p = ngx_pcalloc(ctx->pool, len);\n            if (p == NULL) {\n                goto valid;\n            }\n            len = ngx_sock_ntop((struct sockaddr *) sin, rp->ai_addrlen, p, len, 1);\n\n            name = ngx_pcalloc(ctx->pool, sizeof(*name));\n            if (name == NULL) {\n                goto valid;\n            }\n            name->len = len;\n            name->data = p;\n\n            upsync_server->pc.name = name;\n\n            freeaddrinfo(res);\n            return NGX_OK;\n        }\n    }\n\nvalid:\n\n    upsync_server->pc.sockaddr = conf_server->addrs[0].sockaddr;\n    upsync_server->pc.socklen = conf_server->addrs[0].socklen;\n    upsync_server->pc.name = &conf_server->addrs[0].name;\n\n    if (res != NULL) {\n        freeaddrinfo(res);\n    }\n\n    return NGX_OK;\n}\n\n\nstatic void\nngx_stream_upsync_event_init(ngx_stream_upstream_rr_peer_t *peer, \n    ngx_stream_upsync_server_t *upsync_server)\n{\n    ngx_time_t                  *tp;\n    ngx_delay_event_t           *delay_event;\n\n    delay_event = ngx_calloc(sizeof(*delay_event), ngx_cycle->log);\n    if (delay_event == NULL) {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                      \"upsync_event_init: calloc failed\");\n        return;\n    }\n\n    tp = ngx_timeofday();\n    delay_event->start_sec = tp->sec;\n    delay_event->start_msec = tp->msec;\n\n    delay_event->delay_delete_ev.handler = ngx_stream_upsync_del_delay_delete;\n    delay_event->delay_delete_ev.log = ngx_cycle->log;\n    delay_event->delay_delete_ev.data = delay_event;\n    delay_event->delay_delete_ev.timer_set = 0;\n\n    ngx_queue_insert_head(&upsync_server->delete_ev, &delay_event->queue);\n\n    delay_event->data = peer;\n    ngx_add_timer(&delay_event->delay_delete_ev, NGX_DELAY_DELETE);\n\n    return;\n}\n\n\nstatic void\nngx_stream_upsync_del_delay_delete(ngx_event_t *event)\n{\n    ngx_msec_t                       t;\n    ngx_uint_t                       i, conn_interval;\n    ngx_connection_t                *c;\n    ngx_delay_event_t               *delay_event;\n    ngx_stream_session_t            *s = NULL;\n    ngx_stream_upstream_rr_peer_t   *peer = NULL, *pre_peer = NULL;\n\n    u_char *namep = NULL;\n    struct sockaddr *saddr = NULL;\n\n    delay_event = event->data;\n    if (delay_event == NULL) {\n        return;\n    }\n    peer = delay_event->data;\n\n    c = ngx_cycle->connections;\n    conn_interval = ngx_cycle->connection_n / 30;\n    for (i = 0; i < ngx_cycle->connection_n; i += conn_interval) {\n\n        if (c[i].fd == (ngx_socket_t) -1) {\n            continue;\n        } else {\n\n            if (c[i].log->data != NULL) {\n                s = c[i].log->data;\n            }\n        }\n\n        if (s) {\n            if (s->upstream->start_sec < delay_event->start_sec) {\n                t = ngx_random() % NGX_DELAY_DELETE + NGX_DELAY_DELETE;\n                ngx_add_timer(&delay_event->delay_delete_ev, t);\n                return;\n            }\n        }\n    }\n\n    while (peer != NULL) {\n        saddr = peer->sockaddr;\n        if (saddr != NULL) {\n            ngx_free(saddr);\n            saddr = NULL;\n        }\n\n        namep = peer->name.data;\n        if (namep != NULL) {\n            ngx_free(namep);\n            namep = NULL;\n        }\n\n        pre_peer = peer;\n        peer = peer->next;\n\n        ngx_free(pre_peer);\n    }\n\n    ngx_queue_remove(&delay_event->queue);\n    ngx_free(delay_event);\n    delay_event = NULL;\n\n    return;\n}\n\n\nstatic size_t\nngx_stream_upsync_strnlen(const char *s, size_t maxlen)\n{\n    const char *p;\n\n    p = ngx_strchr(s, '\\0');\n    if (p == NULL) {\n        return maxlen;\n    }\n\n    return p - s;\n}\n\n\nstatic size_t\nngx_strlncat(char *dst, size_t len, const char *src, size_t n)\n{\n    size_t slen;\n    size_t dlen;\n    size_t rlen;\n    size_t ncpy;\n\n    slen = ngx_stream_upsync_strnlen(src, n);\n    dlen = ngx_stream_upsync_strnlen(dst, len);\n\n    if (dlen < len) {\n        rlen = len - dlen;\n        ncpy = slen < rlen ? slen : (rlen - 1);\n        ngx_memcpy(dst + dlen, src, ncpy);\n        dst[dlen + ncpy] = '\\0';\n    }\n\n    return slen + dlen;\n}\n\n\nstatic ngx_int_t\nngx_stream_http_parser_init()\n{\n    ngx_memzero(state.status, 3);\n    ngx_memzero(state.http_body, NGX_PAGE_SIZE * NGX_PAGE_NUMBER);\n    ngx_memzero(state.headers, NGX_MAX_HEADERS * 2 * NGX_MAX_ELEMENT_SIZE);\n\n    state.num_headers = 0;\n    state.last_header = NONE;\n\n    parser = ngx_calloc(sizeof(http_parser), ngx_cycle->log);\n    if (parser == NULL) {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                      \"ngx_stream_http_parser_init: ngx_calloc failed\");\n        return NGX_ERROR;\n    }\n\n    http_parser_init(parser, HTTP_RESPONSE);\n\n    return NGX_OK;\n}\n\n\nstatic int\nngx_stream_http_status(http_parser *p, const char *buf, size_t len)\n{\n    if (p != parser) {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                      \"ngx_stream_http_status: parser argument is wrong\");\n        return NGX_ERROR;\n    }\n\n    ngx_memcpy(state.status, buf, len);\n\n    return 0;\n}\n\n\nstatic int\nngx_stream_http_header_field_cb(http_parser *p, const char *buf, size_t len)\n{\n    if (p != parser) {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                      \"ngx_stream_http_header_field_cb: parser argument is wrong\");\n        return NGX_ERROR;\n    }\n\n    if (state.last_header != FIELD) {\n        state.num_headers++;\n    }\n\n    ngx_strlncat(state.headers[state.num_headers-1][0],\n                 sizeof(state.headers[state.num_headers-1][0]),\n                 buf,\n                 len);\n\n    state.last_header = FIELD;\n\n    return NGX_OK;\n}\n\n\nstatic int\nngx_stream_http_header_value_cb(http_parser *p, const char *buf, size_t len)\n{\n    if (p != parser) {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                      \"ngx_stream_http_header_field_cb: parser argument is wrong\");\n        return NGX_ERROR;\n    }\n\n    ngx_strlncat(state.headers[state.num_headers-1][1],\n                 sizeof(state.headers[state.num_headers-1][1]),\n                 buf,\n                 len);\n\n    state.last_header = VALUE;\n\n    return NGX_OK;\n}\n\n\nstatic int\nngx_stream_http_body(http_parser *p, const char *buf, size_t len)\n{\n    char *tmp_buf;\n\n    tmp_buf = (char *)state.http_body;\n\n    if (p != parser) {\n        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                      \"ngx_stream_http_body: parser argument is wrong\");\n        return NGX_ERROR;\n    }\n\n    ngx_memcpy(tmp_buf, buf, len);\n\n    tmp_buf += len;\n\n    return NGX_OK;\n}\n\n\nstatic void\nngx_stream_upsync_timeout_handler(ngx_event_t *event)\n{\n    ngx_stream_upsync_server_t    *upsync_server;\n\n    if (ngx_stream_upsync_need_exit()) {\n        return;\n    }\n\n    upsync_server = event->data;\n\n    ngx_log_error(NGX_LOG_ERR, event->log, 0,\n                  \"[WARN] upsync_timeout: timed out reading upsync_server: %V \",\n                  upsync_server->pc.name);\n\n    ngx_stream_upsync_clean_event(upsync_server);\n}\n\n\nstatic void\nngx_stream_upsync_clean_event(void *data)\n{\n    ngx_msec_t                          t, tmp;\n    ngx_pool_t                         *pool;\n    ngx_connection_t                   *c;\n    ngx_upsync_conf_t                  *upsync_type_conf;\n    ngx_stream_upsync_ctx_t            *ctx;\n    ngx_stream_upsync_server_t         *upsync_server = data;\n    ngx_stream_upsync_srv_conf_t       *upscf;\n\n    upscf = upsync_server->upscf;\n    upsync_type_conf = upscf->upsync_type_conf;\n\n    ctx = &upsync_server->ctx;\n    pool = ctx->pool;\n\n    c = upsync_server->pc.connection;\n\n    if (c) {\n        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,\n                       \"upsync_clean_event: clean event: fd: %d\", c->fd);\n\n        ngx_close_connection(c);\n        upsync_server->pc.connection = NULL;\n    }\n\n    if (upsync_type_conf->upsync_type == NGX_STREAM_UPSYNC_CONSUL\n        || upsync_type_conf->upsync_type == NGX_STREAM_UPSYNC_CONSUL_SERVICES\n        || upsync_type_conf->upsync_type == NGX_STREAM_UPSYNC_ETCD)\n    {\n        if (parser != NULL) {\n            ngx_free(parser);\n            parser = NULL;\n        }\n    }\n\n    if (pool != NULL) {\n        ngx_destroy_pool(pool);\n    }\n    ctx->pool = NULL;\n\n    if (!upsync_server->upsync_ev.timer_set) {\n        tmp = upscf->upsync_interval;\n        t = ngx_random() % 1000 + tmp;\n        ngx_add_timer(&upsync_server->upsync_ev, t);\n    }\n\n    if (upsync_server->upsync_timeout_ev.timer_set) {\n        ngx_del_timer(&upsync_server->upsync_timeout_ev);\n    }\n\n    return;\n}\n\n\nstatic ngx_int_t\nngx_stream_upsync_need_exit()\n{\n    if (ngx_terminate || ngx_exiting || ngx_quit) {\n        ngx_stream_upsync_clear_all_events((ngx_cycle_t *)ngx_cycle);\n\n        return 1;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic void\nngx_stream_upsync_clear_all_events(ngx_cycle_t *cycle)\n{\n    ngx_uint_t                          i;\n    ngx_queue_t                        *head, *next;\n    ngx_connection_t                   *c;\n    ngx_delay_event_t                  *queue_event;\n    ngx_upsync_conf_t                  *upsync_type_conf;\n    ngx_stream_upsync_server_t         *upsync_server;\n\n    static ngx_flag_t                   has_cleared = 0;\n\n    if (has_cleared || upsync_ctx == NULL || upsync_ctx->upstream_num == 0) {\n        return;\n    }\n\n    ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, \"[WARN]:\"\n                  \"upsync_clear_all_events: on %P \", ngx_pid);\n\n    has_cleared = 1;\n\n    upsync_server = upsync_ctx->upsync_server;\n    upsync_type_conf = upsync_server->upscf->upsync_type_conf;\n\n    for (i = 0; i < upsync_ctx->upstream_num; i++) {\n\n        if (upsync_server[i].upsync_ev.timer_set) {\n            ngx_del_timer(&upsync_server[i].upsync_ev);\n        }\n\n        if (upsync_server[i].upsync_timeout_ev.timer_set) {\n            c = upsync_server[i].pc.connection;\n            if (c) {\n                ngx_close_connection(c);\n                upsync_server->pc.connection = NULL;\n            }\n            ngx_del_timer(&upsync_server[i].upsync_timeout_ev);\n        }\n\n        head = &upsync_server[i].delete_ev;\n        for (next = ngx_queue_head(head);\n                next != ngx_queue_sentinel(head);\n                next = ngx_queue_next(next)) {\n\n            queue_event = ngx_queue_data(next, ngx_delay_event_t, queue);\n            if (queue_event->delay_delete_ev.timer_set) {\n                ngx_del_timer(&queue_event->delay_delete_ev);\n            }\n        }\n    }\n\n    if (upsync_type_conf->upsync_type == NGX_STREAM_UPSYNC_CONSUL\n        || upsync_type_conf->upsync_type == NGX_STREAM_UPSYNC_CONSUL_SERVICES\n        || upsync_type_conf->upsync_type == NGX_STREAM_UPSYNC_ETCD) {\n\n        if (parser != NULL) {\n            ngx_free(parser);\n            parser = NULL;\n        }\n    }\n\n    return;\n}\n\n\nstatic ngx_int_t\nngx_stream_upsync_get_upstream(ngx_cycle_t *cycle, \n    ngx_stream_upsync_server_t *upsync_server, char **conf_value)\n{\n    ngx_stream_conf_client *client = ngx_stream_create_client(cycle, upsync_server);\n\n    if (client == NULL) {\n        ngx_log_error(NGX_LOG_ERR, cycle->log, 0,\n                      \"upsync_get_upstream: http client create error\");\n        return NGX_ERROR;\n    }\n\n    ngx_int_t status = ngx_stream_client_conn(client);\n    if (status != NGX_OK) {\n        ngx_log_error(NGX_LOG_ERR, cycle->log, 0,\n                      \"upsync_get_upstream: http client conn error\");\n\n        ngx_stream_client_destroy(client);\n        return NGX_ERROR;\n    }\n\n    char *response = NULL;\n\n    ngx_stream_client_send(client, upsync_server);\n    if (ngx_stream_client_recv(client, &response, 0) <= 0) {\n        ngx_log_error(NGX_LOG_ERR, cycle->log, 0,\n                      \"upsync_get_upstream: http client recv fail\");\n\n        if (response != NULL) {\n            ngx_free(response);\n            response = NULL;\n        }\n        ngx_stream_client_destroy(client);\n\n        return NGX_ERROR;\n    }\n    ngx_stream_client_destroy(client);\n\n    if (ngx_stream_http_parser_init() == NGX_ERROR) {\n        return NGX_ERROR;\n    }\n    http_parser_execute(parser, &settings, response, ngx_strlen(response));\n    if (parser != NULL) {\n        ngx_free(parser);\n        parser = NULL;\n    }\n    if (ngx_strncmp(state.status, \"OK\", 2) != 0) {\n        return NGX_ERROR;\n    }\n\n    *conf_value = response;\n\n    return NGX_OK;\n}\n\n\nstatic ngx_stream_conf_client *\nngx_stream_create_client(ngx_cycle_t *cycle, ngx_stream_upsync_server_t *upsync_server)\n{\n    ngx_stream_conf_client                         *client = NULL;\n    ngx_stream_upstream_server_t                   *conf_server;\n    ngx_stream_upsync_srv_conf_t                   *upscf;\n\n    upscf = upsync_server->upscf;\n    conf_server = &upscf->conf_server;\n\n    client = ngx_calloc(sizeof(ngx_stream_conf_client), cycle->log);\n    if (client == NULL) {\n        return NULL;\n    }\n\n    client->sd = -1;\n    client->connected = 0;\n    client->addr = *(struct sockaddr_in *)conf_server->addrs[0].sockaddr;\n\n    if((client->sd = socket(AF_INET,SOCK_STREAM, 0)) == NGX_ERROR) {\n        ngx_free(client);\n        client = NULL;\n\n        return NULL;\n    }\n\n    struct timeval tv_timeout;\n    tv_timeout.tv_sec = NGX_STREAM_SOCKET_TIMEOUT;\n    tv_timeout.tv_usec = 0;\n\n    if (setsockopt(client->sd, SOL_SOCKET, SO_SNDTIMEO, (void *) &tv_timeout, \n                   sizeof(struct timeval)) < 0) \n    {\n        ngx_log_error(NGX_LOG_ERR, cycle->log, 0,\n                      \"ngx_stream_create_client: setsockopt SO_SNDTIMEO error\");\n        ngx_stream_client_destroy(client);\n        return NULL;\n    }\n\n    if (setsockopt(client->sd, SOL_SOCKET, SO_RCVTIMEO, (void *) &tv_timeout, \n                   sizeof(struct timeval)) < 0) \n    {\n        ngx_log_error(NGX_LOG_ERR, cycle->log, 0,\n                      \"ngx_stream_create_client: setsockopt SO_RCVTIMEO error\");\n        ngx_stream_client_destroy(client);\n        return NULL;\n    }\n\n    return client;\n}\n\n\nstatic ngx_int_t\nngx_stream_client_conn(ngx_stream_conf_client *client) \n{\n    if (connect(client->sd, (struct sockaddr *)&(client->addr), \n                sizeof(struct sockaddr)) == NGX_ERROR) {\n        return NGX_ERROR;\n    }\n\n    client->connected = 1;\n    return NGX_OK;\n}\n\n\nstatic void \nngx_stream_client_destroy(ngx_stream_conf_client *client) \n{\n    close(client->sd);\n\n    ngx_free(client);\n    client = NULL;\n}\n\n\nstatic ngx_int_t \nngx_stream_client_send(ngx_stream_conf_client *client, \n    ngx_stream_upsync_server_t *upsync_server)\n{\n    size_t       size = 0;\n    ngx_int_t    tmp_send = 0;\n    ngx_uint_t   send_num = 0;\n\n    ngx_upsync_conf_t             *upsync_type_conf;\n    ngx_stream_upsync_srv_conf_t  *upscf;\n\n    upscf = upsync_server->upscf;\n    upsync_type_conf = upscf->upsync_type_conf;\n\n    u_char request[ngx_pagesize];\n    ngx_memzero(request, ngx_pagesize);\n\n    if (upsync_type_conf->upsync_type == NGX_STREAM_UPSYNC_CONSUL\n        || upsync_type_conf->upsync_type == NGX_STREAM_UPSYNC_CONSUL_SERVICES) {\n        ngx_sprintf(request, \"GET %V?recurse&index=%uL HTTP/1.0\\r\\nHost: %V\\r\\n\"\n                    \"Accept: */*\\r\\n\\r\\n\", \n                    &upscf->upsync_send, upsync_server->index, \n                    &upscf->conf_server.name);\n    }\n\n    if (upsync_type_conf->upsync_type == NGX_STREAM_UPSYNC_ETCD) {\n        ngx_sprintf(request, \"GET %V? HTTP/1.0\\r\\nHost: %V\\r\\n\"\n                    \"Accept: */*\\r\\n\\r\\n\", \n                    &upscf->upsync_send, &upscf->conf_server.name);\n    }\n\n    size = ngx_strlen(request);\n    while(send_num < size) {\n        tmp_send = send(client->sd, request + send_num, size - send_num, 0);\n        /* TODO if tmp send is 0? */\n        if (tmp_send < 0) {\n            ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,\n                          \"ngx_stream_client_send: send byte %d\", tmp_send);\n            return NGX_ERROR;\n        }\n\n        send_num += tmp_send;\n    }\n\n    return send_num;\n}\n\n\nstatic ngx_int_t\nngx_stream_client_recv(ngx_stream_conf_client *client, char **data, int size) \n{\n    ssize_t      recv_num = 0, tmp_recv = 0;\n    char         buff[ngx_pagesize];\n    char        *tmp_data;\n    ngx_int_t    page_count = 0;\n\n    *data = NULL;\n\n    while(recv_num < size || size == 0) {  \n        tmp_recv = recv(client->sd, buff, ngx_pagesize, 0);\n        if (tmp_recv <= 0) {\n            break;\n        }\n\n        recv_num += tmp_recv;\n        if (*data == NULL) {\n            *data = (char *) ngx_calloc(ngx_pagesize, ngx_cycle->log);\n            if (*data == NULL) {\n                return NGX_ERROR;\n            }\n            page_count++;\n        }\n\n        if (recv_num >= (ssize_t)(page_count * ngx_pagesize)) {\n            tmp_data = *data;\n            page_count++;\n\n            *data = (char *) ngx_calloc(page_count * ngx_pagesize, ngx_cycle->log);\n            if (*data == NULL) {\n                ngx_free(tmp_data);\n                return NGX_ERROR;\n            }\n            ngx_memcpy(*data, tmp_data, recv_num - tmp_recv);\n\n            ngx_free(tmp_data);\n        }\n\n        ngx_memcpy(*data + recv_num - tmp_recv, buff, tmp_recv);\n    }\n\n    if (*data != NULL) {\n        *(*data + recv_num) = '\\0';\n    }\n\n    return recv_num;\n}\n\n\nstatic char *\nngx_stream_upsync_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    ngx_stream_core_srv_conf_t *cscf;\n\n    cscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_core_module);\n    cscf->handler = ngx_stream_upsync_show;\n\n    return NGX_CONF_OK;\n}\n\n\nstatic void\nngx_stream_upsync_show_send(ngx_stream_session_t *s, ngx_buf_t *b)\n{\n    u_char                  *data;\n    ssize_t                  temp_send = 0, send_num = 0, len;\n    ngx_connection_t        *c;\n\n    c = s->connection;\n    data = b->pos;\n    len = b->last - b->pos;\n\n    ngx_log_debug0(NGX_LOG_DEBUG, c->log, 0, \"upstream_show_send\");\n\n    while (send_num < len) {\n\n        temp_send = c->send(c, data + temp_send, len - send_num);\n\n#if (NGX_DEBUG)\n        {\n        ngx_err_t  err;\n\n        err = (temp_send >=0) ? 0 : ngx_socket_errno;\n        ngx_log_debug2(NGX_LOG_DEBUG, c->log, err,\n                       \"upsync show send size: %z, total: %z\",\n                       temp_send, len);\n\n        if (temp_send > 0) {\n            ngx_log_debug2(NGX_LOG_DEBUG, c->log, err,\n                           \"upsync show send content: %*s \", temp_send, data);\n        }\n        }\n#endif\n\n        if (temp_send > 0) {\n            send_num += temp_send;\n\n        } else if (temp_send == 0 || temp_send == NGX_AGAIN) {\n            continue;\n\n        } else {\n            c->error = 1;\n            break;\n        }\n    }\n\n    if (send_num == len) {\n        ngx_log_debug0(NGX_LOG_DEBUG, c->log, 0, \"upsync_show_send done.\");\n    }\n\n    return;\n}\n\n\nstatic void \nngx_stream_upsync_show_upstream(ngx_stream_upstream_srv_conf_t *uscf, ngx_buf_t *b)\n{\n    ngx_str_t                         *host;\n    ngx_stream_upstream_rr_peer_t     *peer = NULL;\n    ngx_stream_upstream_rr_peers_t    *peers = NULL;\n\n    host = &(uscf->host);\n\n    //HTTP Body\n    b->last = ngx_snprintf(b->last, b->end - b->last,\n                           \"Upstream name: %V; \", host);\n\n    if (uscf->peer.data == NULL) {\n        b->last = ngx_snprintf(b->last, b->end - b->last,\n                               \"Backend server count: %d\\n\", 0);\n        return;\n    } else {\n        peers = (ngx_stream_upstream_rr_peers_t *) uscf->peer.data;\n        b->last = ngx_snprintf(b->last, b->end - b->last,\n                               \"Backend server count: %d\\n\", peers->number);        \n    }\n        \n    \n    for (peer = peers->peer; peer; peer = peer->next) {\n        b->last = ngx_snprintf(b->last, b->end - b->last, \n                               \"        server %V\", &peer->name);\n        b->last = ngx_snprintf(b->last, b->end - b->last, \n                               \" weight=%d\", peer->weight);\n        b->last = ngx_snprintf(b->last, b->end - b->last, \n                               \" max_fails=%d\", peer->max_fails);\n        b->last = ngx_snprintf(b->last, b->end - b->last, \n                               \" fail_timeout=%ds\", peer->fail_timeout);\n\n        if (peer->down) {\n            b->last = ngx_snprintf(b->last, b->end - b->last, \" down\");\n        }\n\n        b->last = ngx_snprintf(b->last, b->end - b->last, \";\\n\");\n    }\n}\n\n\nstatic void\nngx_stream_upsync_show(ngx_stream_session_t *s)\n{\n    ngx_buf_t                           *b_header, *b_body;\n    ngx_uint_t                           i;\n    ngx_stream_upstream_srv_conf_t     **uscfp = NULL;\n    ngx_stream_upstream_main_conf_t     *umcf;\n\n    umcf = ngx_stream_cycle_get_module_main_conf(ngx_cycle, \n                                                 ngx_stream_upstream_module);\n\n    uscfp = umcf->upstreams.elts;\n\n    b_header = ngx_create_temp_buf(s->connection->pool, NGX_PAGE_SIZE);\n    b_body = ngx_create_temp_buf(s->connection->pool, \n                                 NGX_PAGE_SIZE * NGX_PAGE_NUMBER);\n    if (b_header == NULL || b_body == NULL) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, \n                      \"alloc upsync_show buf failed\");\n\n        ngx_stream_finalize_session(s, NGX_OK);\n        return;\n    }\n\n    if (umcf->upstreams.nelts == 0) {\n        b_body->last = ngx_snprintf(b_body->last, b_body->end - b_body->last,\n                                    \"No upstreams defined\\n\");\n    }\n    \t\n    for (i = 0; i < umcf->upstreams.nelts; i++) {\n        ngx_stream_upsync_show_upstream(uscfp[i], b_body);\n        b_body->last = ngx_snprintf(b_body->last, b_body->end - b_body->last, \"\\n\");\n    }\n \n    //HTTP Header\n    b_header->last = ngx_snprintf(b_header->last, b_header->end - b_header->last,\n                                  \"HTTP/1.0 200 OK\\r\\n\");\n    b_header->last = ngx_snprintf(b_header->last, b_header->end - b_header->last,\n                                  \"Server: nginx\\r\\n\");\n    b_header->last = ngx_snprintf(b_header->last, b_header->end - b_header->last,\n                                  \"Content-Type: text/plain\\r\\n\");\n    b_header->last = ngx_snprintf(b_header->last, b_header->end - b_header->last,\n                                  \"Content-Length: %d\\r\\n\", b_body->last - b_body->pos);\n    b_header->last = ngx_snprintf(b_header->last, b_header->end - b_header->last,\n                                  \"Connection: close\\r\\n\\r\\n\");\n\n    ngx_stream_upsync_show_send(s, b_header); //send header\n    ngx_stream_upsync_show_send(s, b_body); //send body\n\n    ngx_stream_finalize_session(s, NGX_OK);\n\n    return;\n}\n"
  },
  {
    "path": "src/ngx_stream_upsync_module.h",
    "content": "#ifndef _NGX_HTTP_UPSYNC_MODELE_H_INCLUDED_\n#define _NGX_HTTP_UPSYNC_MODELE_H_INCLUDED_\n\n\n#include <ngx_core.h>\n#include <ngx_stream.h>\n#include <ngx_config.h>\n\n#include \"ngx_stream_json.h\"\n#include \"ngx_stream_http_parser.h\"\n\n\n#define ngx_strrchr(s1, c)              strrchr((const char *) s1, (int) c)\n#define ngx_ftruncate(fd, offset)       ftruncate(fd, offset)\n#define ngx_lseek(fd, offset, whence)   lseek(fd, offset, whence)\n#define ngx_fgets(fp, offset, whence)   fgets(fp, offset, whence)\n#define ngx_fopen(path, mode)           fopen(path, mode)\n#define ngx_fclose(fp)                  fclose(fp)\n\n#define ngx_strtoull(nptr, endptr, base) strtoull((const char *) nptr, \\\n                                                  (char **) endptr, (int) base)\n\n#define NGX_INDEX_HEADER \"X-Consul-Index\"\n#define NGX_INDEX_HEADER_LEN 14\n\n#define NGX_INDEX_ETCD_HEADER \"X-Etcd-Index\"\n#define NGX_INDEX_ETCD_HEADER_LEN 12\n\n#define NGX_MAX_HEADERS 20\n#define NGX_MAX_ELEMENT_SIZE 512\n\n#define NGX_DELAY_DELETE 30 * 60 * 1000     //75 * 1000\n\n#define NGX_ADD 0\n#define NGX_DEL 1\n#define NGX_ALL 2\n\n#define NGX_PAGE_SIZE 4 * 1024\n#define NGX_PAGE_NUMBER 1024\n\n#define NGX_STREAM_RETRY_TIMES 3\n#define NGX_STREAM_SOCKET_TIMEOUT 1\n\n#define NGX_STREAM_LB_DEFAULT        0\n#define NGX_STREAM_LB_ROUNDROBIN     1\n#define NGX_STREAM_LB_IP_HASH        2\n#define NGX_STREAM_LB_LEAST_CONN     4\n#define NGX_STREAM_LB_HASH_MODULA    8\n#define NGX_STREAM_LB_HASH_KETAMA    16\n\n\n/******************************hash*********************************/\n\nextern  ngx_module_t ngx_stream_upstream_hash_module;\n\n\ntypedef struct {\n    uint32_t                            hash;\n    ngx_str_t                          *server;\n} ngx_stream_upstream_chash_point_t;\n\n\ntypedef struct {\n    ngx_uint_t                            number;\n    ngx_stream_upstream_chash_point_t     point[1];\n} ngx_stream_upstream_chash_points_t;\n\n\ntypedef struct {\n    ngx_stream_complex_value_t            key;\n    ngx_stream_upstream_chash_points_t   *points;\n} ngx_stream_upstream_hash_srv_conf_t;\n\n/****************************hash_end*******************************/\n\n\nstatic int ngx_libc_cdecl ngx_stream_upsync_chash_cmp_points(const void *one, \n    const void *two);\nstatic ngx_int_t ngx_stream_upsync_chash_init(ngx_stream_upstream_srv_conf_t *uscf,\n    ngx_stream_upstream_rr_peers_t *tmp_peers);\nstatic ngx_int_t ngx_stream_upsync_del_chash_peer(\n    ngx_stream_upstream_srv_conf_t *uscf);\n\n\nstatic int ngx_libc_cdecl\nngx_stream_upsync_chash_cmp_points(const void *one, const void *two)\n{\n    ngx_stream_upstream_chash_point_t *first =\n                                       (ngx_stream_upstream_chash_point_t *) one;\n    ngx_stream_upstream_chash_point_t *second =\n                                       (ngx_stream_upstream_chash_point_t *) two;\n\n    if (first->hash < second->hash) {\n        return -1;\n\n    } else if (first->hash > second->hash) {\n        return 1;\n\n    } else {\n        return 0;\n    }\n}\n\n\nstatic ngx_int_t\nngx_stream_upsync_chash_init(ngx_stream_upstream_srv_conf_t *uscf,\n    ngx_stream_upstream_rr_peers_t *tmp_peers)\n{\n    size_t                                    new_size;\n    size_t                                    host_len, port_len;\n    u_char                                   *host, *port, c;\n    uint32_t                                  hash, base_hash;\n    ngx_str_t                                *server;\n    ngx_uint_t                                npoints, new_npoints;\n    ngx_uint_t                                i, j;\n    ngx_stream_upstream_rr_peer_t            *peer;\n    ngx_stream_upstream_rr_peers_t           *peers;\n    ngx_stream_upstream_chash_points_t       *points;\n    ngx_stream_upstream_hash_srv_conf_t      *hcf;\n    union {\n        uint32_t                              value;\n        u_char                                byte[4];\n    } prev_hash;\n\n    hcf = ngx_stream_conf_upstream_srv_conf(uscf, ngx_stream_upstream_hash_module);\n    if(hcf->points == NULL) {\n        return 0;\n    }\n\n    peers = uscf->peer.data;\n    if (tmp_peers != NULL) {\n        new_npoints = peers->total_weight * 160;\n\n        new_size = sizeof(ngx_stream_upstream_chash_points_t)\n                   + sizeof(ngx_stream_upstream_chash_point_t) * (new_npoints - 1);\n\n        points = ngx_calloc(new_size, ngx_cycle->log);\n        if (points == NULL ) {\n            return NGX_ERROR;\n        }\n        ngx_free(hcf->points); /* free old points */\n        hcf->points = points;\n\n        for (peer = peers->peer; peer; peer = peer->next) {\n            server = &peer->server;\n\n            /*\n            * Hash expression is compatible with Cache::Memcached::Fast:\n            * crc32(HOST \\0 PORT PREV_HASH).\n            */\n\n            if (server->len >= 5\n                && ngx_strncasecmp(server->data, (u_char *) \"unix:\", 5) == 0)\n            {\n                host = server->data + 5;\n                host_len = server->len - 5;\n                port = NULL;\n                port_len = 0;\n                goto done;\n            }\n\n            for (j = 0; j < server->len; j++) {\n                c = server->data[server->len - j - 1];\n\n                if (c == ':') {\n                    host = server->data;\n                    host_len = server->len - j - 1;\n                    port = server->data + server->len - j;\n                    port_len = j;\n                    goto done;\n                }\n\n                if (c < '0' || c > '9') {\n                    break;\n                }\n            }\n\n            host = server->data;\n            host_len = server->len;\n            port = NULL;\n            port_len = 0;\n\n        done:\n\n            ngx_crc32_init(base_hash);\n            ngx_crc32_update(&base_hash, host, host_len);\n            ngx_crc32_update(&base_hash, (u_char *) \"\", 1);\n            ngx_crc32_update(&base_hash, port, port_len);\n\n            prev_hash.value = 0;\n            npoints = peer->weight * 160;\n\n            for (j = 0; j < npoints; j++) {\n                hash = base_hash;\n\n                ngx_crc32_update(&hash, prev_hash.byte, 4);\n                ngx_crc32_final(hash);\n\n                points->point[points->number].hash = hash;\n                points->point[points->number].server = server;\n                points->number++;\n\n#if (NGX_HAVE_LITTLE_ENDIAN)\n                prev_hash.value = hash;\n#else\n                prev_hash.byte[0] = (u_char) (hash & 0xff);\n                prev_hash.byte[1] = (u_char) ((hash >> 8) & 0xff);\n                prev_hash.byte[2] = (u_char) ((hash >> 16) & 0xff);\n                prev_hash.byte[3] = (u_char) ((hash >> 24) & 0xff);\n#endif\n            }\n        }\n\n    } else {\n        new_npoints = peers->total_weight * 160;\n\n        new_size = sizeof(ngx_stream_upstream_chash_points_t)\n                   + sizeof(ngx_stream_upstream_chash_point_t) * (new_npoints - 1);\n\n        points = ngx_calloc(new_size, ngx_cycle->log);\n        if (points == NULL ) {\n            return NGX_ERROR;\n        }\n\n        ngx_memcpy(points, hcf->points, new_size);\n        ngx_pfree(ngx_cycle->pool, hcf->points);\n\n        hcf->points = points;\n\n        return NGX_OK;\n    }\n\n    ngx_qsort(points->point,\n              points->number,\n              sizeof(ngx_stream_upstream_chash_point_t),\n              ngx_stream_upsync_chash_cmp_points);\n\n    for (i = 0, j = 1; j < points->number; j++) {\n        if (points->point[i].hash != points->point[j].hash) {\n            points->point[++i] = points->point[j];\n        }\n    }\n\n    points->number = i + 1;\n    \n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_stream_upsync_del_chash_peer(ngx_stream_upstream_srv_conf_t *uscf)\n{\n    size_t                                    host_len, port_len;\n    u_char                                   *host, *port, c;\n    uint32_t                                  hash, base_hash;\n    ngx_str_t                                *server;\n    ngx_uint_t                                npoints, i, j;\n    ngx_stream_upstream_rr_peer_t            *peer;\n    ngx_stream_upstream_rr_peers_t           *peers;\n    ngx_stream_upstream_chash_points_t       *points;\n    ngx_stream_upstream_hash_srv_conf_t      *hcf;    \n    union {\n        uint32_t                              value;\n        u_char                                byte[4];\n    } prev_hash;\n\n    hcf = ngx_stream_conf_upstream_srv_conf(uscf, ngx_stream_upstream_hash_module);\n    if(hcf->points == NULL) {\n        return 0;    \n    }\n\n    peers = uscf->peer.data;\n\n    points = hcf->points;\n    points->number = 0;\n\n    for (peer = peers->peer; peer; peer = peer->next) {\n        server = &peer->server;\n\n        /*\n         * Hash expression is compatible with Cache::Memcached::Fast:\n         * crc32(HOST \\0 PORT PREV_HASH).\n         */\n\n        if (server->len >= 5\n            && ngx_strncasecmp(server->data, (u_char *) \"unix:\", 5) == 0)\n        {\n            host = server->data + 5;\n            host_len = server->len - 5;\n            port = NULL;\n            port_len = 0;\n            goto done;\n        }\n\n        for (j = 0; j < server->len; j++) {\n            c = server->data[server->len - j - 1];\n\n            if (c == ':') {\n                host = server->data;\n                host_len = server->len - j - 1;\n                port = server->data + server->len - j;\n                port_len = j;\n                goto done;\n            }\n\n            if (c < '0' || c > '9') {\n                break;\n            }\n        }\n\n        host = server->data;\n        host_len = server->len;\n        port = NULL;\n        port_len = 0;\n\n    done:\n\n        ngx_crc32_init(base_hash);\n        ngx_crc32_update(&base_hash, host, host_len);\n        ngx_crc32_update(&base_hash, (u_char *) \"\", 1);\n        ngx_crc32_update(&base_hash, port, port_len);\n\n        prev_hash.value = 0;\n        npoints = peer->weight * 160;\n\n        for (j = 0; j < npoints; j++) {\n            hash = base_hash;\n\n            ngx_crc32_update(&hash, prev_hash.byte, 4);\n            ngx_crc32_final(hash);\n\n            points->point[points->number].hash = hash;\n            points->point[points->number].server = server;\n            points->number++;\n\n#if (NGX_HAVE_LITTLE_ENDIAN)\n            prev_hash.value = hash;\n#else\n            prev_hash.byte[0] = (u_char) (hash & 0xff);\n            prev_hash.byte[1] = (u_char) ((hash >> 8) & 0xff);\n            prev_hash.byte[2] = (u_char) ((hash >> 16) & 0xff);\n            prev_hash.byte[3] = (u_char) ((hash >> 24) & 0xff);\n#endif\n        }\n    }\n\n    ngx_qsort(points->point,\n              points->number,\n              sizeof(ngx_stream_upstream_chash_point_t),\n              ngx_stream_upsync_chash_cmp_points);\n\n    for (i = 0, j = 1; j < points->number; j++) {\n        if (points->point[i].hash != points->point[j].hash) {\n            points->point[++i] = points->point[j];\n        }\n    }\n\n    points->number = i + 1;\n\n    return NGX_OK;\n}\n\n\n#endif //_NGX_HTTP_UPSYNC_MODELE_H_INCLUDED_\n"
  },
  {
    "path": "test/README",
    "content": "NAME\n    Test::Nginx - Testing modules for Nginx C module development\n\nDESCRIPTION\n    This distribution provides two testing modules for Nginx C module\n    development:\n\n    *   Test::Nginx::LWP\n\n    *   Test::Nginx::Socket\n\n    All of them are based on Test::Base.\n\n    Usually, Test::Nginx::Socket is preferred because it works on a much\n    lower level and not that fault tolerant like Test::Nginx::LWP.\n\n    Also, a lot of connection hang issues (like wrong \"r->main->count\" value\n    in nginx 0.8.x) can only be captured by Test::Nginx::Socket because\n    Perl's LWP::UserAgent client will close the connection itself which will\n    conceal such issues from the testers.\n\n    Test::Nginx automatically starts an nginx instance (from the \"PATH\" env)\n    rooted at t/servroot/ and the default config template makes this nginx\n    instance listen on the port 1984 by default. One can specify a different\n    port number by setting his port number to the \"TEST_NGINX_PORT\"\n    environment, as in\n\n        export TEST_NGINX_PORT=1989\n\n  etcproxy integration\n    The default settings in etcproxy\n    (https://github.com/chaoslawful/etcproxy) makes this small TCP proxy\n    split the TCP packets into bytes and introduce 1 ms latency among them.\n\n    There's usually various TCP chains that we can put etcproxy into, for\n    example\n\n   Test::Nginx <=> nginx\n      $ ./etcproxy 1234 1984\n\n    Here we tell etcproxy to listen on port 1234 and to delegate all the TCP\n    traffic to the port 1984, the default port that Test::Nginx makes nginx\n    listen to.\n\n    And then we tell Test::Nginx to test against the port 1234, where\n    etcproxy listens on, rather than the port 1984 that nginx directly\n    listens on:\n\n      $ TEST_NGINX_CLIENT_PORT=1234 prove -r t/\n\n    Then the TCP chain now looks like this:\n\n      Test::Nginx <=> etcproxy (1234) <=> nginx (1984)\n\n    So etcproxy can effectively emulate extreme network conditions and\n    exercise \"unusual\" code paths in your nginx server by your tests.\n\n    In practice, *tons* of weird bugs can be captured by this setting. Even\n    ourselves didn't expect that this simple approach is so effective.\n\n   nginx <=> memcached\n    We first start the memcached server daemon on port 11211:\n\n       memcached -p 11211 -vv\n\n    and then we another etcproxy instance to listen on port 11984 like this\n\n       $ ./etcproxy 11984 11211\n\n    Then we tell our t/foo.t test script to connect to 11984 rather than\n    11211:\n\n      # foo.t\n      use Test::Nginx::Socket;\n      repeat_each(1);\n      plan tests => 2 * repeat_each() * blocks();\n      $ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211;  # make this env take a default value\n      run_tests();\n\n      __DATA__\n\n      === TEST 1: sanity\n      --- config\n      location /foo {\n           set $memc_cmd set;\n           set $memc_key foo;\n           set $memc_value bar;\n           memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;\n      }\n      --- request\n          GET /foo\n      --- response_body_like: STORED\n\n    The Test::Nginx library will automatically expand the special macro\n    $TEST_NGINX_MEMCACHED_PORT to the environment with the same name. You\n    can define your own $TEST_NGINX_BLAH_BLAH_PORT macros as long as its\n    prefix is \"TEST_NGINX_\" and all in upper case letters.\n\n    And now we can run your test script against the etcproxy port 11984:\n\n       TEST_NGINX_MEMCACHED_PORT=11984 prove t/foo.t\n\n    Then the TCP chains look like this:\n\n       Test::Nginx <=> nginx (1984) <=> etcproxy (11984) <=> memcached (11211)\n\n    If \"TEST_NGINX_MEMCACHED_PORT\" is not set, then it will take the default\n    value 11211, which is what we want when there's no etcproxy configured:\n\n       Test::Nginx <=> nginx (1984) <=> memcached (11211)\n\n    This approach also works for proxied mysql and postgres traffic. Please\n    see the live test suite of ngx_drizzle and ngx_postgres for more\n    details.\n\n    Usually we set both \"TEST_NGINX_CLIENT_PORT\" and\n    \"TEST_NGINX_MEMCACHED_PORT\" (and etc) at the same time, effectively\n    yielding the following chain:\n\n       Test::Nginx <=> etcproxy (1234) <=> nginx (1984) <=> etcproxy (11984) <=> memcached (11211)\n\n    as long as you run two separate etcproxy instances in two separate\n    terminals.\n\n    It's easy to verify if the traffic actually goes through your etcproxy\n    server. Just check if the terminal running etcproxy emits outputs. By\n    default, etcproxy always dump out the incoming and outgoing data to\n    stdout/stderr.\n\n  valgrind integration\n    Test::Nginx has integrated support for valgrind (<http://valgrind.org>)\n    even though by default it does not bother running it with the tests\n    because valgrind will significantly slow down the test sutie.\n\n    First ensure that your valgrind executable visible in your PATH env. And\n    then run your test suite with the \"TEST_NGINX_USE_VALGRIND\" env set to\n    true:\n\n       TEST_NGINX_USE_VALGRIND=1 prove -r t\n\n    If you see false alarms, you do have a chance to skip them by defining a\n    ./valgrind.suppress file at the root of your module source tree, as in\n\n    <https://github.com/chaoslawful/drizzle-nginx-module/blob/master/valgrin\n    d.suppress>\n\n    This is the suppression file for ngx_drizzle. Test::Nginx will\n    automatically use it to start nginx with valgrind memcheck if this file\n    does exist at the expected location.\n\n    If you do see a lot of \"Connection refused\" errors while running the\n    tests this way, then you probably have a slow machine (or a very busy\n    one) that the default waiting time is not sufficient for valgrind to\n    start. You can define the sleep time to a larger value by setting the\n    \"TEST_NGINX_SLEEP\" env:\n\n       TEST_NGINX_SLEEP=1 prove -r t\n\n    The time unit used here is \"second\". The default sleep setting just fits\n    my ThinkPad (\"Core2Duo T9600\").\n\n    Applying the no-pool patch to your nginx core is recommended while\n    running nginx with valgrind:\n\n    <https://github.com/shrimp/no-pool-nginx>\n\n    The nginx memory pool can prevent valgrind from spotting lots of invalid\n    memory reads/writes as well as certain double-free errors. We did find a\n    lot more memory issues in many of our modules when we first introduced\n    the no-pool patch in practice ;)\n\n    There's also more advanced features in Test::Nginx that have never\n    documented. I'd like to write more about them in the near future ;)\n\nNginx C modules that use Test::Nginx to drive their test suites\n    ngx_echo\n        <http://github.com/agentzh/echo-nginx-module>\n\n    ngx_headers_more\n        <http://github.com/agentzh/headers-more-nginx-module>\n\n    ngx_chunkin\n        <http://wiki.nginx.org/NginxHttpChunkinModule>\n\n    ngx_memc\n        <http://wiki.nginx.org/NginxHttpMemcModule>\n\n    ngx_drizzle\n        <http://github.com/chaoslawful/drizzle-nginx-module>\n\n    ngx_rds_json\n        <http://github.com/agentzh/rds-json-nginx-module>\n\n    ngx_rds_csv\n        <http://github.com/agentzh/rds-csv-nginx-module>\n\n    ngx_xss\n        <http://github.com/agentzh/xss-nginx-module>\n\n    ngx_srcache\n        <http://github.com/agentzh/srcache-nginx-module>\n\n    ngx_lua\n        <http://github.com/chaoslawful/lua-nginx-module>\n\n    ngx_set_misc\n        <http://github.com/agentzh/set-misc-nginx-module>\n\n    ngx_array_var\n        <http://github.com/agentzh/array-var-nginx-module>\n\n    ngx_form_input\n        <http://github.com/calio/form-input-nginx-module>\n\n    ngx_iconv\n        <http://github.com/calio/iconv-nginx-module>\n\n    ngx_set_cconv\n        <http://github.com/liseen/set-cconv-nginx-module>\n\n    ngx_postgres\n        <http://github.com/FRiCKLE/ngx_postgres>\n\n    ngx_coolkit\n        <http://github.com/FRiCKLE/ngx_coolkit>\n\n    Naxsi\n        <http://code.google.com/p/naxsi/>\n\nSOURCE REPOSITORY\n    This module has a Git repository on Github, which has access for all.\n\n        http://github.com/agentzh/test-nginx\n\n    If you want a commit bit, feel free to drop me a line.\n\nAUTHORS\n    agentzh (章亦春) \"<agentzh@gmail.com>\"\n\n    Antoine BONAVITA \"<antoine.bonavita@gmail.com>\"\n\nCOPYRIGHT & LICENSE\n    Copyright (c) 2009-2012, agentzh \"<agentzh@gmail.com>\".\n\n    Copyright (c) 2011-2012, Antoine Bonavita\n    \"<antoine.bonavita@gmail.com>\".\n\n    This module is licensed under the terms of the BSD license.\n\n    Redistribution and use in source and binary forms, with or without\n    modification, are permitted provided that the following conditions are\n    met:\n\n    *   Redistributions of source code must retain the above copyright\n        notice, this list of conditions and the following disclaimer.\n\n    *   Redistributions in binary form must reproduce the above copyright\n        notice, this list of conditions and the following disclaimer in the\n        documentation and/or other materials provided with the distribution.\n\n    *   Neither the name of the authors nor the names of its contributors\n        may be used to endorse or promote products derived from this\n        software without specific prior written permission.\n\n    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n    IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n    TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\n    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n    HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n    TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nSEE ALSO\n    Test::Nginx::LWP, Test::Nginx::Socket, Test::Base.\n\n"
  },
  {
    "path": "test/conf-server.sh",
    "content": "#!/bin/sh\n\n/usr/local/bin/consul agent -server -bootstrap -data-dir=/tmp/consul\n\n#/usr/local/bin/etcd --peers 127.0.0.1:8500\n"
  },
  {
    "path": "test/t/lib/Test/Nginx/IMAP.pm",
    "content": "package Test::Nginx::IMAP;\n\n# (C) Maxim Dounin\n\n# Module for nginx imap tests.\n\n###############################################################################\n\nuse warnings;\nuse strict;\n\nuse Test::More qw//;\nuse IO::Socket;\nuse Socket qw/ CRLF /;\n\nuse Test::Nginx;\n\nuse base qw/ IO::Socket::INET /;\n\nsub new {\n\tmy $class = shift;\n\n\tmy $self = return $class->SUPER::new(\n\t\tProto => \"tcp\",\n\t\tPeerAddr => \"127.0.0.1:8143\",\n\t\t@_\n\t)\n\t\tor die \"Can't connect to nginx: $!\\n\";\n\n\t$self->autoflush(1);\n\n\treturn $self;\n}\n\nsub send {\n\tmy ($self, $cmd) = @_;\n\tlog_out($cmd);\n\t$self->print($cmd . CRLF);\n}\n\nsub read {\n\tmy ($self) = @_;\n\teval {\n\t\tlocal $SIG{ALRM} = sub { die \"timeout\\n\" };\n\t\talarm(3);\n\t\twhile (<$self>) {\n\t\t\tlog_in($_);\n\t\t\t# XXX\n\t\t\tnext if m/^\\d\\d\\d-/;\n\t\t\tlast;\n\t\t}\n\t\talarm(0);\n\t};\n\talarm(0);\n\tif ($@) {\n\t\tlog_in(\"died: $@\");\n\t\treturn undef;\n\t}\n\treturn $_;\n}\n\nsub check {\n\tmy ($self, $regex, $name) = @_;\n\tTest::More->builder->like($self->read(), $regex, $name);\n}\n\nsub ok {\n\tmy $self = shift;\n\tTest::More->builder->like($self->read(), qr/^\\S+ OK/, @_);\n}\n\n###############################################################################\n\nsub imap_test_daemon {\n\tmy $server = IO::Socket::INET->new(\n\t\tProto => 'tcp',\n\t\tLocalAddr => '127.0.0.1:8144',\n\t\tListen => 5,\n\t\tReuse => 1\n\t)\n\t\tor die \"Can't create listening socket: $!\\n\";\n\n\twhile (my $client = $server->accept()) {\n\t\t$client->autoflush(1);\n\t\tprint $client \"* OK fake imap server ready\" . CRLF;\n\n\t\twhile (<$client>) {\n\t\t\tmy $tag = '';\n\n\t\t\t$tag = $1 if m/^(\\S+)/;\n\t\t\ts/^(\\S+)\\s+//;\n\n\t\t\tif (/^logout/i) {\n\t\t\t\tprint $client $tag . ' OK logout ok' . CRLF;\n\t\t\t} elsif (/^login /i) {\n\t\t\t\tprint $client $tag . ' OK login ok' . CRLF;\n\t\t\t} else {\n\t\t\t\tprint $client $tag . ' ERR unknown command' . CRLF;\n\t\t\t}\n                }\n\n\t\tclose $client;\n\t}\n}\n\n###############################################################################\n\n1;\n\n###############################################################################\n"
  },
  {
    "path": "test/t/lib/Test/Nginx/POP3.pm",
    "content": "package Test::Nginx::POP3;\n\n# (C) Maxim Dounin\n\n# Module for nginx pop3 tests.\n\n###############################################################################\n\nuse warnings;\nuse strict;\n\nuse Test::More qw//;\nuse IO::Socket;\nuse Socket qw/ CRLF /;\n\nuse Test::Nginx;\n\nuse base qw/ IO::Socket::INET /;\n\nsub new {\n\tmy $class = shift;\n\n\tmy $self = return $class->SUPER::new(\n\t\tProto => \"tcp\",\n\t\tPeerAddr => \"127.0.0.1:8110\",\n\t\t@_\n\t)\n\t\tor die \"Can't connect to nginx: $!\\n\";\n\n\t$self->autoflush(1);\n\n\treturn $self;\n}\n\nsub send {\n\tmy ($self, $cmd) = @_;\n\tlog_out($cmd);\n\t$self->print($cmd . CRLF);\n}\n\nsub read {\n\tmy ($self) = @_;\n\teval {\n\t\tlocal $SIG{ALRM} = sub { die \"timeout\\n\" };\n\t\talarm(3);\n\t\twhile (<$self>) {\n\t\t\tlog_in($_);\n\t\t\t# XXX\n\t\t\tnext if m/^\\d\\d\\d-/;\n\t\t\tlast;\n\t\t}\n\t\talarm(0);\n\t};\n\talarm(0);\n\tif ($@) {\n\t\tlog_in(\"died: $@\");\n\t\treturn undef;\n\t}\n\treturn $_;\n}\n\nsub check {\n\tmy ($self, $regex, $name) = @_;\n\tTest::More->builder->like($self->read(), $regex, $name);\n}\n\nsub ok {\n\tmy $self = shift;\n\tTest::More->builder->like($self->read(), qr/^\\+OK/, @_);\n}\n\n###############################################################################\n\nsub pop3_test_daemon {\n\tmy $server = IO::Socket::INET->new(\n\t\tProto => 'tcp',\n\t\tLocalAddr => '127.0.0.1:8111',\n\t\tListen => 5,\n\t\tReuse => 1\n\t)\n\t\tor die \"Can't create listening socket: $!\\n\";\n\n\twhile (my $client = $server->accept()) {\n\t\t$client->autoflush(1);\n\t\tprint $client \"+OK fake pop3 server ready\" . CRLF;\n\n\t\twhile (<$client>) {\n\t\t\tif (/^quit/i) {\n\t\t\t\tprint $client '+OK quit ok' . CRLF;\n\t\t\t} elsif (/^user test\\@example.com/i) {\n\t\t\t\tprint $client '+OK user ok' . CRLF;\n\t\t\t} elsif (/^pass secret/i) {\n\t\t\t\tprint $client '+OK pass ok' . CRLF;\n\t\t\t} else {\n\t\t\t\tprint $client \"-ERR unknown command\" . CRLF;\n\t\t\t}\n                }\n\n\t\tclose $client;\n\t}\n}\n\n###############################################################################\n\n1;\n\n###############################################################################\n"
  },
  {
    "path": "test/t/lib/Test/Nginx/SMTP.pm",
    "content": "package Test::Nginx::SMTP;\n\n# (C) Maxim Dounin\n\n# Module for nginx smtp tests.\n\n###############################################################################\n\nuse warnings;\nuse strict;\n\nuse Test::More qw//;\nuse IO::Socket;\nuse Socket qw/ CRLF /;\n\nuse Test::Nginx;\n\nuse base qw/ IO::Socket::INET /;\n\nsub new {\n\tmy $class = shift;\n\n\tmy $self = return $class->SUPER::new(\n\t\tProto => \"tcp\",\n\t\tPeerAddr => \"127.0.0.1:8025\",\n\t\t@_\n\t)\n\t\tor die \"Can't connect to nginx: $!\\n\";\n\n\t$self->autoflush(1);\n\n\treturn $self;\n}\n\nsub send {\n\tmy ($self, $cmd) = @_;\n\tlog_out($cmd);\n\t$self->print($cmd . CRLF);\n}\n\nsub read {\n\tmy ($self) = @_;\n\teval {\n\t\tlocal $SIG{ALRM} = sub { die \"timeout\\n\" };\n\t\talarm(3);\n\t\twhile (<$self>) {\n\t\t\tlog_in($_);\n\t\t\tnext if m/^\\d\\d\\d-/;\n\t\t\tlast;\n\t\t}\n\t\talarm(0);\n\t};\n\talarm(0);\n\tif ($@) {\n\t\tlog_in(\"died: $@\");\n\t\treturn undef;\n\t}\n\treturn $_;\n}\n\nsub check {\n\tmy ($self, $regex, $name) = @_;\n\tTest::More->builder->like($self->read(), $regex, $name);\n}\n\nsub ok {\n\tmy $self = shift;\n\tTest::More->builder->like($self->read(), qr/^2\\d\\d /, @_);\n}\n\nsub authok {\n\tmy $self = shift;\n\tTest::More->builder->like($self->read(), qr/^235 /, @_);\n}\n\n###############################################################################\n\nsub smtp_test_daemon {\n\tmy $server = IO::Socket::INET->new(\n\t\tProto => 'tcp',\n\t\tLocalAddr => '127.0.0.1:8026',\n\t\tListen => 5,\n\t\tReuse => 1\n\t)\n\t\tor die \"Can't create listening socket: $!\\n\";\n\n\twhile (my $client = $server->accept()) {\n\t\t$client->autoflush(1);\n\t\tprint $client \"220 fake esmtp server ready\" . CRLF;\n\n\t\twhile (<$client>) {\n\t\t\tTest::Nginx::log_core('||', $_);\n\n\t\t\tif (/^quit/i) {\n\t\t\t\tprint $client '221 quit ok' . CRLF;\n\t\t\t} elsif (/^(ehlo|helo)/i) {\n\t\t\t\tprint $client '250 hello ok' . CRLF;\n\t\t\t} elsif (/^rset/i) {\n\t\t\t\tprint $client '250 rset ok' . CRLF;\n\t\t\t} elsif (/^mail from:[^@]+$/i) {\n\t\t\t\tprint $client '500 mail from error' . CRLF;\n\t\t\t} elsif (/^mail from:/i) {\n\t\t\t\tprint $client '250 mail from ok' . CRLF;\n\t\t\t} elsif (/^rcpt to:[^@]+$/i) {\n\t\t\t\tprint $client '500 rcpt to error' . CRLF;\n\t\t\t} elsif (/^rcpt to:/i) {\n\t\t\t\tprint $client '250 rcpt to ok' . CRLF;\n\t\t\t} elsif (/^xclient/i) {\n\t\t\t\tprint $client '220 xclient ok' . CRLF;\n\t\t\t} else {\n\t\t\t\tprint $client \"500 unknown command\" . CRLF;\n\t\t\t}\n                }\n\n\t\tclose $client;\n\t}\n}\n\n###############################################################################\n\n1;\n\n###############################################################################\n"
  },
  {
    "path": "test/t/lib/Test/Nginx.pm",
    "content": "package Test::Nginx;\n\n# (C) Maxim Dounin\n\n# Generic module for nginx tests.\n\n###############################################################################\n\nuse warnings;\nuse strict;\n\nuse base qw/ Exporter /;\n\nour @EXPORT = qw/ log_in log_out http http_get http_head /;\nour @EXPORT_OK = qw/ http_gzip_request http_gzip_like http_start http_end /;\nour %EXPORT_TAGS = (\n\tgzip => [ qw/ http_gzip_request http_gzip_like / ]\n);\n\n###############################################################################\n\nuse File::Path qw/ rmtree /;\nuse File::Temp qw/ tempdir /;\nuse IO::Socket;\nuse POSIX qw/ waitpid WNOHANG /;\nuse Socket qw/ CRLF /;\nuse Test::More qw//;\n\n###############################################################################\n\nour $NGINX = defined $ENV{TEST_NGINX_BINARY} ? $ENV{TEST_NGINX_BINARY}\n\t: '../nginx/objs/nginx';\n\nsub new {\n\tmy $self = {};\n\tbless $self;\n\n\t$self->{_pid} = $$;\n\n\t$self->{_testdir} = tempdir(\n\t\t'nginx-test-XXXXXXXXXX',\n\t\tTMPDIR => 1,\n\t\tCLEANUP => not $ENV{TEST_NGINX_LEAVE}\n\t)\n\t\tor die \"Can't create temp directory: $!\\n\";\n\t$self->{_testdir} =~ s!\\\\!/!g if $^O eq 'MSWin32';\n\t$self->{_dso_module} = ();\n\tmkdir \"$self->{_testdir}/logs\"\n\t\tor die \"Can't create logs directory: $!\\n\";\n\n\treturn $self;\n}\n\nsub DESTROY {\n\tmy ($self) = @_;\n\tlocal $?;\n\n\treturn if $self->{_pid} != $$;\n\t$self->stop();\n\t$self->stop_daemons();\n\tif ($ENV{TEST_NGINX_CATLOG}) {\n\t\tsystem(\"cat $self->{_testdir}/error.log\");\n\t}\n\tif (not $ENV{TEST_NGINX_LEAVE}) {\n\t\teval { rmtree($self->{_testdir}); };\n\t}\n}\n\nsub has($;) {\n\tmy ($self, @features) = @_;\n\n\tforeach my $feature (@features) {\n\t\tTest::More::plan(skip_all => \"$feature not compiled in\")\n\t\t\tunless $self->has_module($feature);\n\t}\n\n\treturn $self;\n}\n\nsub set_dso($;) {\n\tmy ($self, $module_name, $module_path) = @_;\n\n\t$self->{_dso_module}{$module_name} = $module_path;\n}\n\nsub has_module($) {\n\tmy ($self, $feature) = @_;\n\n\tmy %regex = (\n\t\tsni\t=> 'TLS SNI support enabled',\n\t\tmail\t=> '--with-mail(?!\\S)',\n\t\tflv\t=> '--with-http_flv_module',\n\t\tperl\t=> '--with-http_perl_module',\n\t\tauth_request\n\t\t\t=> '--with-http_auth_request_module',\n\t\tcharset\t=> '(?s)^(?!.*--without-http_charset_module)',\n\t\tgzip\t=> '(?s)^(?!.*--without-http_gzip_module)',\n\t\tssi\t=> '(?s)^(?!.*--without-http_ssi_module)',\n\t\tuserid\t=> '(?s)^(?!.*--without-http_userid_module)',\n\t\taccess\t=> '(?s)^(?!.*--without-http_access_module)',\n\t\tauth_basic\n\t\t\t=> '(?s)^(?!.*--without-http_auth_basic_module)',\n\t\tautoindex\n\t\t\t=> '(?s)^(?!.*--without-http_autoindex_module)',\n\t\tgeo\t=> '(?s)^(?!.*--without-http_geo_module)',\n\t\tmap\t=> '(?s)^(?!.*--without-http_map_module)',\n\t\treferer\t=> '(?s)^(?!.*--without-http_referer_module)',\n\t\trewrite\t=> '(?s)^(?!.*--without-http_rewrite_module)',\n\t\tproxy\t=> '(?s)^(?!.*--without-http_proxy_module)',\n\t\tfastcgi\t=> '(?s)^(?!.*--without-http_fastcgi_module)',\n\t\tuwsgi\t=> '(?s)^(?!.*--without-http_uwsgi_module)',\n\t\tscgi\t=> '(?s)^(?!.*--without-http_scgi_module)',\n\t\tmemcached\n\t\t\t=> '(?s)^(?!.*--without-http_memcached_module)',\n\t\tlimit_conn\n\t\t\t=> '(?s)^(?!.*--without-http_limit_conn_module)',\n\t\tlimit_req\n\t\t\t=> '(?s)^(?!.*--without-http_limit_req_module)',\n\t\tempty_gif\n\t\t\t=> '(?s)^(?!.*--without-http_empty_gif_module)',\n\t\tbrowser\t=> '(?s)^(?!.*--without-http_browser_module)',\n\t\tupstream_hash\n\t\t\t=> '(?s)^(?!.*--without-http_upstream_hash_module)',\n\t\tupstream_ip_hash\n\t\t\t=> '(?s)^(?!.*--without-http_upstream_ip_hash_module)',\n\t\treqstat\n\t\t\t=> '(?s)^(?!.*--without-http_reqstat_module)',\n\t\tupstream_least_conn\n\t\t\t=> '(?s)^(?!.*--without-http_upstream_least_conn_mod)',\n\t\tupstream_keepalive\n\t\t\t=> '(?s)^(?!.*--without-http_upstream_keepalive_modu)',\n\t\thttp\t=> '(?s)^(?!.*--without-http(?!\\S))',\n\t\tcache\t=> '(?s)^(?!.*--without-http-cache)',\n\t\tpop3\t=> '(?s)^(?!.*--without-mail_pop3_module)',\n\t\timap\t=> '(?s)^(?!.*--without-mail_imap_module)',\n\t\tsmtp\t=> '(?s)^(?!.*--without-mail_smtp_module)',\n\t\tpcre\t=> '(?s)^(?!.*--without-pcre)',\n\t\tsplit_clients\n\t\t\t=> '(?s)^(?!.*--without-http_split_clients_module)',\n\t);\n\n\tmy $re = $regex{$feature};\n\t$re = $feature if !defined $re;\n\n\t$self->{_configure_args} = `$NGINX -V 2>&1`\n\t\tif !defined $self->{_configure_args};\n\n\treturn ($self->{_configure_args} =~ $re or $self->{_configure_args} =~ '--enable-mods-static=all') ? 1 : 0;\n}\n\nsub has_version($) {\n\tmy ($self, $need) = @_;\n\n\t$self->{_configure_args} = `$NGINX -V 2>&1`\n\t\tif !defined $self->{_configure_args};\n\n\t$self->{_configure_args} =~ m!nginx/([0-9.]+)!;\n\n\tmy @v = split(/\\./, $1);\n\tmy ($n, $v);\n\n\tfor $n (split(/\\./, $need)) {\n\t\t$v = shift @v || 0;\n\t\treturn 0 if $n > $v;\n\t\treturn 1 if $v > $n;\n\t}\n\n\treturn 1;\n}\n\nsub has_daemon($) {\n\tmy ($self, $daemon) = @_;\n\n\tif ($^O eq 'MSWin32') {\n\t\tTest::More::plan(skip_all => \"win32\");\n\t\treturn $self;\n\t}\n\n\tif ($^O eq 'solaris') {\n\t\tTest::More::plan(skip_all => \"$daemon not found\")\n\t\t\tunless `command -v $daemon`;\n\t\treturn $self;\n\t}\n\n\tTest::More::plan(skip_all => \"$daemon not found\")\n\t\tunless `which $daemon`;\n\n\treturn $self;\n}\n\nsub try_run($$) {\n\tmy ($self, $message) = @_;\n\n        $self->run();\n\n\teval {\n\t\topen OLDERR, \">&\", \\*STDERR; close STDERR;\n\t\t$self->run();\n\t\topen STDERR, \">&\", \\*OLDERR;\n\t};\n\n\tTest::More::plan(skip_all => $message) if $@;\n\treturn $self;\n}\n\nsub plan($) {\n\tmy ($self, $plan) = @_;\n\n\tTest::More::plan(tests => $plan);\n\n\treturn $self;\n}\n\nsub run(;$) {\n\tmy ($self, $conf) = @_;\n\n\tmy $testdir = $self->{_testdir};\n\n\tif (defined $conf) {\n\t\tmy $c = `cat $conf`;\n\t\t$self->write_file_expand('nginx.conf', $c);\n\t}\n\n\tmy $pid = fork();\n\tdie \"Unable to fork(): $!\\n\" unless defined $pid;\n\n\tif ($pid == 0) {\n\t\tmy @globals = $self->{_test_globals} ?\n\t\t\t() : ('-g', \"pid $testdir/nginx.pid; \"\n\t\t\t. \"error_log $testdir/error.log debug;\");\n\t\texec($NGINX, '-p', $testdir, '-c', 'nginx.conf', @globals),\n\t\t\tor die \"Unable to exec(): $!\\n\";\n\t}\n\n\t# wait for nginx to start\n\n\t$self->waitforfile(\"$testdir/nginx.pid\", $pid)\n\t\tor die \"Can't start nginx\";\n\n\t$self->{_started} = 1;\n\treturn $self;\n}\n\nsub waitforfile($;$) {\n\tmy ($self, $file, $pid) = @_;\n\tmy $exited;\n\n\t# wait for file to appear\n\t# or specified process to exit\n\n\tfor (1 .. 30) {\n\t\treturn 1 if -e $file;\n\t\treturn 0 if $exited;\n\t\t$exited = waitpid($pid, WNOHANG) != 0 if $pid;\n\t\tselect undef, undef, undef, 0.1;\n\t}\n\n\treturn undef;\n}\n\nsub waitforsocket($) {\n\tmy ($self, $peer) = @_;\n\n\t# wait for socket to accept connections\n\n\tfor (1 .. 30) {\n\t\tmy $s = IO::Socket::INET->new(\n\t\t\tProto => 'tcp',\n\t\t\tPeerAddr => $peer\n\t\t);\n\n\t\treturn 1 if defined $s;\n\n\t\tselect undef, undef, undef, 0.1;\n\t}\n\n\treturn undef;\n}\n\nsub reload() {\n\tmy ($self) = @_; \n\n\treturn $self unless $self->{_started};\n\n\tlocal $/;\n\topen F, '<' . $self->{_testdir} . '/nginx.pid'\n\t\tor die \"Can't open nginx.pid: $!\";\n\tmy $pid = <F>;\n\tclose F;\n\n\tif ($^O eq 'MSWin32') {\n\t\tmy $testdir = $self->{_testdir};\n\t\tmy @globals = $self->{_test_globals} ?\n\t\t\t() : ('-g', \"pid $testdir/nginx.pid; \"\n\t\t\t. \"error_log $testdir/error.log debug;\");\n\t\tsystem($NGINX, '-c', \"$testdir/nginx.conf\", '-s', 'reload',\n\t\t\t@globals) == 0\n\t\t\tor die \"system() failed: $?\\n\";\n\n\t} else {\n\t\tkill 'HUP', $pid;\n\t}\n\n\tsleep(1);\n\n\treturn $self;\n}\n\nsub stop() {\n\tmy ($self) = @_;\n\n\treturn $self unless $self->{_started};\n\n\tlocal $/;\n\topen F, '<' . $self->{_testdir} . '/nginx.pid'\n\t\tor die \"Can't open nginx.pid: $!\";\n\tmy $pid = <F>;\n\tclose F;\n\n\tif ($^O eq 'MSWin32') {\n\t\tmy $testdir = $self->{_testdir};\n\t\tmy @globals = $self->{_test_globals} ?\n\t\t\t() : ('-g', \"pid $testdir/nginx.pid; \"\n\t\t\t. \"error_log $testdir/error.log debug;\");\n\t\tsystem($NGINX, '-p', $testdir, '-c', \"nginx.conf\",\n\t\t\t'-s', 'stop', @globals) == 0\n\t\t\tor die \"system() failed: $?\\n\";\n\n\t} else {\n\t\tkill 'QUIT', $pid;\n\t}\n\n\twaitpid($pid, 0);\n\n\t$self->{_started} = 0;\n\n\treturn $self;\n}\n\nsub stop_daemons() {\n\tmy ($self) = @_;\n\n\twhile ($self->{_daemons} && scalar @{$self->{_daemons}}) {\n\t\tmy $p = shift @{$self->{_daemons}};\n\t\tkill $^O eq 'MSWin32' ? 9 : 'TERM', $p;\n\t\twaitpid($p, 0);\n\t}\n\n\treturn $self;\n}\n\nsub read_file($) {\n    my ($self, $name) = @_;\n    local $/;\n\n    open F, '<', $self->{_testdir} . '/' . $name or die \"Can't open $name: $!\";\n    my $content = <F>;\n    close F;\n    \n    return $content;\n}\n\nsub write_file($$) {\n\tmy ($self, $name, $content) = @_;\n\n\topen F, '>' . $self->{_testdir} . '/' . $name\n\t\tor die \"Can't create $name: $!\";\n\tprint F $content;\n\tclose F;\n\n\treturn $self;\n}\n\nsub write_file_expand($$) {\n\tmy ($self, $name, $content) = @_;\n\n\t$content =~ s/%%TEST_GLOBALS%%/$self->test_globals()/gmse;\n\t$content =~ s/%%TEST_GLOBALS_DSO%%/$self->test_globals_dso()/gmse;\n\t$content =~ s/%%TEST_GLOBALS_HTTP%%/$self->test_globals_http()/gmse;\n\t$content =~ s/%%TESTDIR%%/$self->{_testdir}/gms;\n\n\treturn $self->write_file($name, $content);\n}\n\nsub run_daemon($;@) {\n\tmy ($self, $code, @args) = @_;\n\n\tmy $pid = fork();\n\tdie \"Can't fork daemon: $!\\n\" unless defined $pid;\n\n\tif ($pid == 0) {\n\t\tif (ref($code) eq 'CODE') {\n\t\t\t$code->(@args);\n\t\t\texit 0;\n\t\t} else {\n\t\t\texec($code, @args);\n\t\t\texit 0;\n\t\t}\n\t}\n\n\t$self->{_daemons} = [] unless defined $self->{_daemons};\n\tpush @{$self->{_daemons}}, $pid;\n\n\treturn $self;\n}\n\nsub testdir() {\n\tmy ($self) = @_;\n\treturn $self->{_testdir};\n}\n\nsub test_globals() {\n\tmy ($self) = @_;\n\n\treturn $self->{_test_globals}\n\t\tif defined $self->{_test_globals};\n\n\tmy $s = '';\n\n\t$s .= \"pid $self->{_testdir}/nginx.pid;\\n\";\n\t$s .= \"error_log $self->{_testdir}/error.log debug;\\n\";\n\n\t$s .= $ENV{TEST_NGINX_GLOBALS}\n\t\tif $ENV{TEST_NGINX_GLOBALS};\n\t$self->{_test_globals} = $s;\n}\n\nsub test_globals_dso() {\n\tmy ($self) = @_;\n\n\treturn \"\" unless defined $ENV{TEST_NGINX_DSO};\n\n\treturn $self->{_test_globals_dso} if defined $self->{_test_globals_dso};\n\n\tmy $s = '';\n\n\t$s .= \"dso {\\n\";\n\tif (defined $ENV{NGINX_DSO_PATH}) {\n\t\t$s .= \"path $ENV{NGINX_DSO_PATH};\\n\";\n\t}\n\n\twhile ( my ($key, $value) = each(%{$self->{_dso_module}}) ) {\n\t\t$s .= \"load $key $value;\\n\";\n\t}\n\t$s .= \"}\\n\";\n\n\t$self->{_test_globals_dso} = $s;\n}\n\nsub test_globals_http() {\n\tmy ($self) = @_;\n\n\treturn $self->{_test_globals_http}\n\t\tif defined $self->{_test_globals_http};\n\n\tmy $s = '';\n\n\t$s .= \"root $self->{_testdir};\\n\";\n\t$s .= \"access_log $self->{_testdir}/access.log;\\n\";\n\t$s .= \"client_body_temp_path $self->{_testdir}/client_body_temp;\\n\";\n\n\t$s .= \"fastcgi_temp_path $self->{_testdir}/fastcgi_temp;\\n\"\n\t\tif $self->has_module('fastcgi');\n\n\t$s .= \"proxy_temp_path $self->{_testdir}/proxy_temp;\\n\"\n\t\tif $self->has_module('proxy');\n\n\t$s .= \"uwsgi_temp_path $self->{_testdir}/uwsgi_temp;\\n\"\n\t\tif $self->has_module('uwsgi');\n\n\t$s .= \"scgi_temp_path $self->{_testdir}/scgi_temp;\\n\"\n\t\tif $self->has_module('scgi');\n\n\t$s .= $ENV{TEST_NGINX_GLOBALS_HTTP}\n\t\tif $ENV{TEST_NGINX_GLOBALS_HTTP};\n\n\t$self->{_test_globals_http} = $s;\n}\n\n###############################################################################\n\nsub log_core {\n\treturn unless $ENV{TEST_NGINX_VERBOSE};\n\tmy ($prefix, $msg) = @_;\n\t($prefix, $msg) = ('', $prefix) unless defined $msg;\n\t$prefix .= ' ' if length($prefix) > 0;\n\n\tif (length($msg) > 2048) {\n\t\t$msg = substr($msg, 0, 2048)\n\t\t\t. \"(...logged only 2048 of \" . length($msg)\n\t\t\t. \" bytes)\";\n\t}\n\n\t$msg =~ s/^/# $prefix/gm;\n\t$msg =~ s/([^\\x20-\\x7e])/sprintf('\\\\x%02x', ord($1)) . (($1 eq \"\\n\") ? \"\\n\" : '')/gmxe;\n\t$msg .= \"\\n\" unless $msg =~ /\\n\\Z/;\n\tprint $msg;\n}\n\nsub log_out {\n\tlog_core('>>', @_);\n}\n\nsub log_in {\n\tlog_core('<<', @_);\n}\n\n###############################################################################\n\nsub http_get($;%) {\n\tmy ($url, %extra) = @_;\n\treturn http(<<EOF, %extra);\nGET $url HTTP/1.0\nHost: localhost\n\nEOF\n}\n\nsub http_head($;%) {\n\tmy ($url, %extra) = @_;\n\treturn http(<<EOF, %extra);\nHEAD $url HTTP/1.0\nHost: localhost\n\nEOF\n}\n\nsub http($;%) {\n\tmy ($request, %extra) = @_;\n\n\tmy $s = http_start($request, %extra);\n\n\treturn $s if $extra{start} or !defined $s;\n\treturn http_end($s);\n}\n\nsub http_start($;%) {\n\tmy ($request, %extra) = @_;\n\tmy $s;\n\n\teval {\n\t\tlocal $SIG{ALRM} = sub { die \"timeout\\n\" };\n\t\tlocal $SIG{PIPE} = sub { die \"sigpipe\\n\" };\n\t\talarm(5);\n\n\t\t$s = $extra{socket} || IO::Socket::INET->new(\n\t\t\tProto => 'tcp',\n\t\t\tPeerAddr => '127.0.0.1:8080'\n\t\t)\n\t\t\tor die \"Can't connect to nginx: $!\\n\";\n\n\t\tlog_out($request);\n\t\t$s->print($request);\n\n\t\tselect undef, undef, undef, $extra{sleep} if $extra{sleep};\n\t\treturn '' if $extra{aborted};\n\n\t\tif ($extra{body}) {\n\t\t\tlog_out($extra{body});\n\t\t\t$s->print($extra{body});\n\t\t}\n\n\t\talarm(0);\n\t};\n\talarm(0);\n\tif ($@) {\n\t\tlog_in(\"died: $@\");\n\t\treturn undef;\n\t}\n\n\treturn $s;\n}\n\nsub http_end($;%) {\n\tmy ($s) = @_;\n\tmy $reply;\n\n\teval {\n\t\tlocal $SIG{ALRM} = sub { die \"timeout\\n\" };\n\t\tlocal $SIG{PIPE} = sub { die \"sigpipe\\n\" };\n\t\talarm(5);\n\n\t\tlocal $/;\n\t\t$reply = $s->getline();\n\n\t\talarm(0);\n\t};\n\talarm(0);\n\tif ($@) {\n\t\tlog_in(\"died: $@\");\n\t\treturn undef;\n\t}\n\n\tlog_in($reply);\n\treturn $reply;\n}\n\n###############################################################################\n\nsub http_gzip_request {\n\tmy ($url) = @_;\n\tmy $r = http(<<EOF);\nGET $url HTTP/1.1\nHost: localhost\nConnection: close\nAccept-Encoding: gzip\n\nEOF\n}\n\nsub http_content {\n\tmy ($text) = @_;\n\n\treturn undef if !defined $text;\n\n\tif ($text !~ /(.*?)\\x0d\\x0a?\\x0d\\x0a?(.*)/ms) {\n\t\treturn undef;\n\t}\n\n\tmy ($headers, $body) = ($1, $2);\n\n\tif ($headers !~ /Transfer-Encoding: chunked/i) {\n\t\treturn $body;\n\t}\n\n\tmy $content = '';\n\twhile ($body =~ /\\G\\x0d?\\x0a?([0-9a-f]+)\\x0d\\x0a?/gcmsi) {\n\t\tmy $len = hex($1);\n\t\t$content .= substr($body, pos($body), $len);\n\t\tpos($body) += $len;\n\t}\n\n\treturn $content;\n}\n\nsub http_gzip_like {\n\tmy ($text, $re, $name) = @_;\n\n\tSKIP: {\n\t\teval { require IO::Uncompress::Gunzip; };\n\t\tTest::More->builder->skip(\n\t\t\t\"IO::Uncompress::Gunzip not installed\", 1) if $@;\n\n\t\tmy $in = http_content($text);\n\t\tmy $out;\n\n\t\tIO::Uncompress::Gunzip::gunzip(\\$in => \\$out);\n\n\t\tTest::More->builder->like($out, $re, $name);\n\t}\n}\n\n###############################################################################\n\n1;\n\n###############################################################################\n"
  },
  {
    "path": "test/t/lib/Time/Parse.pm",
    "content": "# Date::Parse\n#\n# Copyright (c) 1995 Graham Barr. All rights reserved. This program is free\n# software; you can redistribute it and/or modify it under the same terms\n# as Perl itself.\n\npackage Time::Parse;\n\n=head1 NAME\n\nDate::Parse - Parse date strings into time values\n\n=head1 SYNOPSIS\n\n\tuse Date::Parse;\n\t\n\t$time = str2time($date);\n\t\n\t($ss,$mm,$hh,$day,$month,$year,$zone) = strptime($date);\n\n=head1 DESCRIPTION\n\nC<Date::Parse> provides two routines for parsing date strings into time values.\n\n=over 4\n\n=item str2time(DATE [, ZONE])\n\nC<str2time> parses C<DATE> and returns a unix time value, or undef upon failure.\nC<ZONE>, if given, specifies the timezone to assume when parsing if the\ndate string does not specify a timezome.\n\n=item strptime(DATE [, ZONE])\n\nC<strptime> takes the same arguments as str2time but returns an array of\nvalues C<($ss,$mm,$hh,$day,$month,$year,$zone)>. Elements are only defined\nif they could be extracted from the date string. The C<$zone> element is\nthe timezone offset in seconds from GMT. An empty array is returned upon\nfailure.\n\n=head1 MULTI-LANGUAGE SUPPORT\n\nDate::Parse is capable of parsing dates in several languages, these are\nEnglish, French, German and Italian. Changing the language is done via\na static method call, for example\n\n\tDate::Parse->language('German');\n\nwill cause Date::Parse to attempt to parse any subsequent dates in German.\n\nThis is only a first pass, I am considering changing this to be\n\n\t$lang = Date::Language->new('German');\n\t$lang->str2time(\"25 Jun 1996 21:09:55 +0100\");\n\nI am open to suggestions on this.\n\n=head1 AUTHOR\n\nGraham Barr <Graham.Barr@tiuk.ti.com>\n\n=head1 REVISION\n\n$Revision: 2.6 $\n\n=head1 COPYRIGHT\n\nCopyright (c) 1995 Graham Barr. All rights reserved. This program is free\nsoftware; you can redistribute it and/or modify it under the same terms\nas Perl itself.\n\n=cut\n\nrequire 5.000;\nuse strict;\nuse vars qw($VERSION @ISA @EXPORT);\nuse Time::Local;\nuse Carp;\nuse Time::Zone;\nuse Exporter;\n\n@ISA = qw(Exporter);\n@EXPORT = qw(&strtotime &str2time &strptime);\n\n$VERSION = sprintf(\"%d.%02d\", q$Revision: 2.6 $ =~ m#(\\d+)\\.(\\d+)#);\n\nmy %month = (\n\tjanuary\t\t=> 0,\n\tfebruary\t=> 1,\n\tmarch\t\t=> 2,\n\tapril\t\t=> 3,\n\tmay\t\t=> 4,\n\tjune\t\t=> 5,\n\tjuly\t\t=> 6,\n\taugust\t\t=> 7,\n\tseptember\t=> 8,\n\tsept\t\t=> 8,\n\toctober\t\t=> 9,\n\tnovember\t=> 10,\n\tdecember\t=> 11,\n\t);\n\nmy %day = (\n\tsunday\t\t=> 0,\n\tmonday\t\t=> 1,\n\ttuesday\t\t=> 2,\n\ttues\t\t=> 2,\n\twednesday\t=> 3,\n\twednes\t\t=> 3,\n\tthursday\t=> 4,\n\tthur\t\t=> 4,\n\tthurs\t\t=> 4,\n\tfriday\t\t=> 5,\n\tsaturday\t=> 6,\n\t);\n\nmy @suf = (qw(th st nd rd th th th th th th)) x 3;\n@suf[11,12,13] = qw(th th th);\n\n#Abbreviations\n\nmap { $month{substr($_,0,3)} = $month{$_} } keys %month;\nmap { $day{substr($_,0,3)}   = $day{$_} }   keys %day;\n\nmy $strptime = <<'ESQ';\n my %month = map { lc $_ } %$mon_ref;\n my $daypat = join(\"|\", map { lc $_ } keys %$day_ref);\n my $monpat = join(\"|\", keys %month);\n my $sufpat = join(\"|\", map { lc $_ } @$suf_ref);\n\n my %ampm = (\n\tam => 0,\n\tpm => 12\n\t);\n\n # allow map am +. a.m.\n map { my($z) = $_; $z =~ s#(\\w)#$1\\.#g; $ampm{$z} = $ampm{$_} } keys %ampm;\n\n my($AM, $PM) = (0,12);\n\nsub\n{\n\n my $dtstr = lc shift;\n my $merid = 24;\n\n my($year,$month,$day,$hh,$mm,$ss,$zone) = (undef) x 7;\n\n $zone = tz_offset(shift)\n    if(@_);\n\n while(1) { last unless($dtstr =~ s#\\([^\\(\\)]*\\)# #o) }\n\n $dtstr =~ s#(\\A|\\n|\\Z)# #sog;\n\n # ignore day names\n $dtstr =~ s#([\\d\\w\\s])[\\.\\,]\\s#$1 #sog;\n $dtstr =~ s#($daypat)\\s*(den\\s)?# #o;\n # Time: 12:00 or 12:00:00 with optional am/pm\n  \n if($dtstr =~ s#[:\\s](\\d\\d?):(\\d\\d)(:(\\d\\d)(?:\\.\\d+)?)?\\s*([ap]\\.?m\\.?)?\\s# #o)\n  {\n   ($hh,$mm,$ss) = ($1,$2,$4 || 0);\n   $merid = $ampm{$5} if($5);\n  }\n\n # Time: 12 am\n  \n elsif($dtstr =~ s#\\s(\\d\\d?)\\s*([ap]\\.?m\\.?)\\s# #o)\n  {\n   ($hh,$mm,$ss) = ($1,0,0);\n   $merid = $ampm{$2};\n  }\n  \n # Date: 12-June-96 (using - . or /)\n  \n if($dtstr =~ s#\\s(\\d\\d?)([\\-\\./])($monpat)(\\2(\\d\\d+))?\\s# #o)\n  {\n   ($month,$day) = ($month{$3},$1);\n   $year = $5\n        if($5);\n  }\n  \n # Date: 12-12-96 (using '-', '.' or '/' )\n  \n elsif($dtstr =~ s#\\s(\\d\\d*)([\\-\\./])(\\d\\d?)(\\2(\\d\\d+))?\\s# #o)\n  {\n   ($month,$day) = ($1 - 1,$3);\n   if($5)\n    {\n     $year = $5;\n     # Possible match for 1995-01-24 (short mainframe date format);\n     ($year,$month,$day) = ($1, $3 - 1, $5)\n    \t    if($month > 12);\n    }\n  }\n elsif($dtstr =~ s#\\s(\\d+)\\s*($sufpat)?\\s*($monpat)# #o)\n  {\n   ($month,$day) = ($month{$3},$1);\n  }\n elsif($dtstr =~ s#($monpat)\\s*(\\d+)\\s*($sufpat)?\\s# #o)\n  {\n   ($month,$day) = ($month{$1},$2);\n  }\n\n # Date: 961212\n\n elsif($dtstr =~ s#\\s(\\d\\d)(\\d\\d)(\\d\\d)\\s# #o)\n  {\n   ($year,$month,$day) = ($1,$2-1,$3);\n  }\n\n $year = $1\n    if(!defined($year) && $dtstr =~ s#\\s(\\d{2}(\\d{2})?)[\\s\\.,]# #o);\n\n # Zone\n\n if($dtstr =~ s#\\s\"?(\\w{3,})\\s# #o) \n  {\n   $zone = tz_offset($1);\n   return ()\n    unless(defined $zone);\n  }\n elsif($dtstr =~ s#\\s(([\\-\\+])\\d\\d?)(\\d\\d)\\s# #o)\n  {\n   my $m = $2 . $3;\n   $zone = 60 * ($m + (60 * $1));\n  }\n\n return ()\n    if($dtstr =~ /\\S/o);\n\n $hh += 12\n\tif(defined $hh && $merid == $PM);\n\n $year -= 1900\n\tif(defined $year && $year > 1900);\n\n return ($ss,$mm,$hh,$day,$month,$year,$zone);\n}\nESQ\n\nuse vars qw($day_ref $mon_ref $suf_ref $obj);\n\nsub gen_parser\n{\n local($day_ref,$mon_ref,$suf_ref,$obj) = @_;\n\n if($obj)\n  {\n   my $obj_strptime = $strptime;\n   substr($obj_strptime,index($strptime,\"sub\")+6,0) = <<'ESQ';\n shift; # package\nESQ\n   return eval \"$obj_strptime\";\n  }\n\n eval \"$strptime\";\n\n}\n\n*strptime = gen_parser(\\%day,\\%month,\\@suf);\n\nsub str2time\n{\n my @t = strptime(@_);\n\n return undef\n\tunless @t;\n\n my($ss,$mm,$hh,$day,$month,$year,$zone) = @t;\n my @lt  = localtime(time);\n\n $hh    ||= 0;\n $mm    ||= 0;\n $ss    ||= 0;\n\n $month = $lt[4]\n\tunless(defined $month);\n\n $day  = $lt[3]\n\tunless(defined $day);\n\n $year = ($month > $lt[4]) ? ($lt[5] - 1) : $lt[5]\n\tunless(defined $year);\n\n return defined $zone ? timegm($ss,$mm,$hh,$day,$month,$year) - $zone\n    \t    \t      : timelocal($ss,$mm,$hh,$day,$month,$year);\n}\n\n1;\n\n"
  },
  {
    "path": "test/t/lib/Time/Zone.pm",
    "content": "\npackage Time::Zone;\n\n=head1 NAME\n\nTime::Zone -- miscellaneous timezone manipulations routines\n\n=head1 SYNOPSIS\n\n\tuse Time::Zone;\n\tprint tz2zone();\n\tprint tz2zone($ENV{'TZ'});\n\tprint tz2zone($ENV{'TZ'}, time());\n\tprint tz2zone($ENV{'TZ'}, undef, $isdst);\n\t$offset = tz_local_offset();\n\t$offset = tz_offset($TZ);\n\n=head1 DESCRIPTION\n\nThis is a collection of miscellaneous timezone manipulation routines.\n\nC<tz2zone()> parses the TZ environment variable and returns a timezone\nstring suitable for inclusion in L<date>-like output.  It opionally takes\na timezone string, a time, and a is-dst flag.\n\nC<tz_local_offset()> determins the offset from GMT time in seconds.  It\nonly does the calculation once.\n\nC<tz_offset()> determines the offset from GMT in seconds of a specified\ntimezone.  \n\nC<tz_name()> determines the name of the timezone based on its offset\n\n=head1 AUTHORS\n\nGraham Barr <bodg@tiuk.ti.com>\nDavid Muir Sharnoff <muir@idiom.com>\nPaul Foley <paul@ascent.com>\n\n=cut\n\nrequire 5.002;\n\nrequire Exporter;\nuse Carp;\nuse strict;\nuse vars qw(@ISA @EXPORT $VERSION @tz_local);\n\n@ISA = qw(Exporter);\n@EXPORT = qw(tz2zone tz_local_offset tz_offset tz_name);\n$VERSION = \"2.04\";\n\n# Parts stolen from code by Paul Foley <paul@ascent.com>\n\nsub tz2zone (;$$$)\n{\n\tmy($TZ, $time, $isdst) = @_;\n\n\tuse vars qw(%tzn_cache);\n\n\t$TZ = defined($ENV{'TZ'}) ? ( $ENV{'TZ'} ? $ENV{'TZ'} : 'GMT' ) : ''\n\t    unless $TZ;\n\n\t# Hack to deal with 'PST8PDT' format of TZ\n\t# Note that this can't deal with all the esoteric forms, but it\n\t# does recognize the most common: [:]STDoff[DST[off][,rule]]\n\n\tif (! defined $isdst) {\n\t\tmy $j;\n\t\t$time = time() unless $time;\n\t\t($j, $j, $j, $j, $j, $j, $j, $j, $isdst) = localtime($time);\n\t}\n\n\tif (defined $tzn_cache{$TZ}->[$isdst]) {\n\t\treturn $tzn_cache{$TZ}->[$isdst];\n\t}\n      \n\tif ($TZ =~ /^\n\t\t    ( [^:\\d+\\-,] {3,} )\n\t\t    ( [+-] ?\n\t\t      \\d {1,2}\n\t\t      ( : \\d {1,2} ) {0,2} \n\t\t    )\n\t\t    ( [^\\d+\\-,] {3,} )?\n\t\t    /x\n\t    ) {\n\t\t$TZ = $isdst ? $4 : $1;\n\t\t$tzn_cache{$TZ} = [ $1, $4 ];\n\t} else {\n\t\t$tzn_cache{$TZ} = [ $TZ, $TZ ];\n\t}\n\treturn $TZ;\n}\n\nsub tz_local_offset (;$)\n{\n\tmy ($time) = @_;\n\n\t$time = time() unless $time;\n\tmy (@l) = localtime($time);\n\tmy $isdst = $l[8];\n\n\tif (defined($tz_local[$isdst])) {\n\t\treturn $tz_local[$isdst];\n\t}\n\n\t$tz_local[$isdst] = &calc_off($time);\n\n\treturn $tz_local[$isdst];\n}\n\nsub calc_off\n{\n\tmy ($time) = @_;\n\n\tmy (@l) = localtime($time);\n\tmy (@g) = gmtime($time);\n\n\tmy $off;\n\n\t$off =     $l[0] - $g[0]\n\t\t+ ($l[1] - $g[1]) * 60\n\t\t+ ($l[2] - $g[2]) * 3600;\n\n\t# subscript 7 is yday.\n\n\tif ($l[7] == $g[7]) {\n\t\t# done\n\t} elsif ($l[7] == $g[7] + 1) {\n\t\t$off += 86400;\n\t} elsif ($l[7] == $g[7] - 1) {\n\t\t$off -= 86400;\n\t} elsif ($l[7] < $g[7]) {\n\t\t# crossed over a year boundry!\n\t\t# localtime is beginning of year, gmt is end\n\t\t# therefore local is ahead\n\t\t$off += 86400;\n\t} else {\n\t\t$off -= 86400;\n\t}\n\n\treturn $off;\n}\n\n# constants\n\nCONFIG: {\n\tuse vars qw(%dstZone %zoneOff %dstZoneOff %Zone);\n\n\t%dstZone = (\n\t#   \"ndt\"  =>   -2*3600-1800,\t # Newfoundland Daylight   \n\t    \"adt\"  =>   -3*3600,  \t # Atlantic Daylight   \n\t    \"edt\"  =>   -4*3600,  \t # Eastern Daylight\n\t    \"cdt\"  =>   -5*3600,  \t # Central Daylight\n\t    \"mdt\"  =>   -6*3600,  \t # Mountain Daylight\n\t    \"pdt\"  =>   -7*3600,  \t # Pacific Daylight\n\t    \"ydt\"  =>   -8*3600,  \t # Yukon Daylight\n\t    \"hdt\"  =>   -9*3600,  \t # Hawaii Daylight\n\t    \"bst\"  =>   +1*3600,  \t # British Summer   \n\t    \"mest\" =>   +2*3600,  \t # Middle European Summer   \n\t    \"sst\"  =>   +2*3600,  \t # Swedish Summer\n\t    \"fst\"  =>   +2*3600,  \t # French Summer\n\t    \"wadt\" =>   +8*3600,  \t # West Australian Daylight\n\t#   \"cadt\" =>  +10*3600+1800,\t # Central Australian Daylight\n\t    \"eadt\" =>  +11*3600,  \t # Eastern Australian Daylight\n\t    \"nzdt\" =>  +13*3600,  \t # New Zealand Daylight   \n\t);\n\n\t%Zone = (\n\t    \"gmt\"\t=>   0,  \t # Greenwich Mean\n\t    \"ut\"        =>   0,  \t # Universal (Coordinated)\n\t    \"utc\"       =>   0,\n\t    \"wet\"       =>   0,  \t # Western European\n\t    \"wat\"       =>  -1*3600,\t # West Africa\n\t    \"at\"        =>  -2*3600,\t # Azores\n\t# For completeness.  BST is also British Summer, and GST is also Guam Standard.\n\t#   \"bst\"       =>  -3*3600,\t # Brazil Standard\n\t#   \"gst\"       =>  -3*3600,\t # Greenland Standard\n\t#   \"nft\"       =>  -3*3600-1800,# Newfoundland\n\t#   \"nst\"       =>  -3*3600-1800,# Newfoundland Standard\n\t    \"ast\"       =>  -4*3600,\t # Atlantic Standard\n\t    \"est\"       =>  -5*3600,\t # Eastern Standard\n\t    \"cst\"       =>  -6*3600,\t # Central Standard\n\t    \"mst\"       =>  -7*3600,\t # Mountain Standard\n\t    \"pst\"       =>  -8*3600,\t # Pacific Standard\n\t    \"yst\"\t=>  -9*3600,\t # Yukon Standard\n\t    \"hst\"\t=> -10*3600,\t # Hawaii Standard\n\t    \"cat\"\t=> -10*3600,\t # Central Alaska\n\t    \"ahst\"\t=> -10*3600,\t # Alaska-Hawaii Standard\n\t    \"nt\"\t=> -11*3600,\t # Nome\n\t    \"idlw\"\t=> -12*3600,\t # International Date Line West\n\t    \"cet\"\t=>  +1*3600, \t # Central European\n\t    \"met\"\t=>  +1*3600, \t # Middle European\n\t    \"mewt\"\t=>  +1*3600, \t # Middle European Winter\n\t    \"swt\"\t=>  +1*3600, \t # Swedish Winter\n\t    \"fwt\"\t=>  +1*3600, \t # French Winter\n\t    \"eet\"\t=>  +2*3600, \t # Eastern Europe, USSR Zone 1\n\t    \"bt\"\t=>  +3*3600, \t # Baghdad, USSR Zone 2\n\t#   \"it\"\t=>  +3*3600+1800,# Iran\n\t    \"zp4\"\t=>  +4*3600, \t # USSR Zone 3\n\t    \"zp5\"\t=>  +5*3600, \t # USSR Zone 4\n\t#   \"ist\"\t=>  +5*3600+1800,# Indian Standard\n\t    \"zp6\"\t=>  +6*3600, \t # USSR Zone 5\n\t# For completeness.  NST is also Newfoundland Stanard, and SST is also Swedish Summer.\n\t#   \"nst\"\t=>  +6*3600+1800,# North Sumatra\n\t#   \"sst\"\t=>  +7*3600, \t # South Sumatra, USSR Zone 6\n\t    \"wast\"\t=>  +7*3600, \t # West Australian Standard\n\t#   \"jt\"\t=>  +7*3600+1800,# Java (3pm in Cronusland!)\n\t    \"cct\"\t=>  +8*3600, \t # China Coast, USSR Zone 7\n\t    \"jst\"\t=>  +9*3600,\t # Japan Standard, USSR Zone 8\n\t#   \"cast\"\t=>  +9*3600+1800,# Central Australian Standard\n\t    \"east\"\t=> +10*3600,\t # Eastern Australian Standard\n\t    \"gst\"\t=> +10*3600,\t # Guam Standard, USSR Zone 9\n\t    \"nzt\"\t=> +12*3600,\t # New Zealand\n\t    \"nzst\"\t=> +12*3600,\t # New Zealand Standard\n\t    \"idle\"\t=> +12*3600,\t # International Date Line East\n\t);\n\n\t%zoneOff = reverse(%Zone);\n\t%dstZoneOff = reverse(%dstZone);\n\n\t# Preferences\n\n\t$zoneOff{0}       = 'gmt';\n\t$dstZoneOff{3600} = 'bst';\n\n}\n\nsub tz_offset (;$$)\n{\n\tmy ($zone, $time) = @_;\n\n\treturn &tz_local_offset() unless($zone);\n\n\t$time = time() unless $time;\n\tmy(@l) = localtime($time);\n\tmy $dst = $l[8];\n\n\t$zone = lc $zone;\n\n\tif($zone =~ /^(([\\-\\+])\\d\\d?)(\\d\\d)$/) {\n\t\tmy $v = $2 . $3;\n\t\treturn $1 * 3600 + $v * 60;\n\t} elsif (exists $dstZone{$zone} && ($dst || !exists $Zone{$zone})) {\n\t\treturn $dstZone{$zone};\n\t} elsif(exists $Zone{$zone}) {\n\t\treturn $Zone{$zone};\n\t}\n\tundef;\n}\n\nsub tz_name (;$$)\n{\n\tmy ($off, $dst) = @_;\n\n\t$off = tz_offset()\n\t\tunless(defined $off);\n\n\t$dst = (localtime(time))[8]\n\t\tunless(defined $dst);\n\n\tif (exists $dstZoneOff{$off} && ($dst || !exists $zoneOff{$off})) {\n\t\treturn $dstZoneOff{$off};\n\t} elsif (exists $zoneOff{$off}) {\n\t\treturn $zoneOff{$off};\n\t}\n\tsprintf(\"%+05d\", int($off / 60) * 100 + $off % 60);\n}\n\n1;\n"
  },
  {
    "path": "test/t/upsync.t",
    "content": "use warnings;\nuse strict;\n\nuse Test::More;\n\nBEGIN { use FindBin; chdir($FindBin::Bin); }\n\n\nuse lib 'lib';\nuse File::Path;\nuse Test::Nginx;\n\n\nmy $NGINX = defined $ENV{TEST_NGINX_BINARY} ? $ENV{TEST_NGINX_BINARY}\n: '../../nginx/objs/nginx';\nmy $t = Test::Nginx->new()->plan(40);\n\nsub mhttp_get($;$;$;%) {\n    my ($url, $host, $port, %extra) = @_;\n    return mhttp(<<EOF, $port, %extra);\nGET $url HTTP/1.0\nHost: $host\n\nEOF\n}\n\nsub mhttp_put($;$;$;%) {\n    my ($url, $body, $port, %extra) = @_;\n    my $len = length($body);\n    return mhttp(<<EOF, $port, %extra);\nPUT $url HTTP/1.0\nHost: localhost\nContent-Length: $len\n\n$body\nEOF\n}\n\nsub mhttp_delete($;$;%) {\n    my ($url, $port, %extra) = @_;\n    return mhttp(<<EOF, $port, %extra);\nDELETE $url HTTP/1.0\nHost: localhost\n\nEOF\n}\n\nsub get_dump_content($;%) {\n    my ($file_name, %extra) = @_;\n\n    my $content;\n    if ( open my $fd, '<', $file_name ) {\n        $content = do { local $/; <$fd> };\n\n        close $fd;\n\n    } else {\n        $content = 'The file could not be opened.';\n    }\n\n    return $content;\n}\n\nsub mrun($;$) {\n    my ($self, $conf) = @_;\n\n    my $testdir = $self->{_testdir};\n\n    if (defined $conf) {\n        my $c = `cat $conf`;\n        $self->write_file_expand('nginx.conf', $c);\n    }\n\n    my $pid = fork();\n    die \"Unable to fork(): $!\\n\" unless defined $pid;\n\n    if ($pid == 0) {\n        my @globals = $self->{_test_globals} ?\n        () : ('-g', \"pid $testdir/nginx.pid; \"\n            . \"error_log $testdir/error.log debug;\");\n        exec($NGINX, '-c', \"$testdir/nginx.conf\", '-p', \"$testdir\",\n            @globals) or die \"Unable to exec(): $!\\n\";\n    }\n\n    # wait for nginx to start\n\n    $self->waitforfile(\"$testdir/nginx.pid\")\n        or die \"Can't start nginx\";\n\n    $self->{_started} = 1;\n    return $self;\n}\n\n###############################################################################\n\nselect STDERR; $| = 1;\nselect STDOUT; $| = 1;\n\nwarn \"your test dir is \".$t->testdir();\n\n$t->write_file_expand('nginx.conf', <<'EOF');\n\n%%TEST_GLOBALS%%\n\ndaemon off;\n\nworker_processes 1;\n\nevents {\n    accept_mutex off;\n}\n\nstream {\n\n    upstream test {\n        upsync 127.0.0.1:8500/v1/kv/upstreams/test upsync_interval=50ms upsync_timeout=6m upsync_type=consul;\n        upsync_dump_path /tmp/servers_test.conf;\n\n        server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10;\n    }\n\n    upstream backend {\n        server 127.0.0.1:8090 weight=10 max_fails=3 fail_timeout=10;\n    }\n\n    server {\n        listen   8080;\n\n        upstream_show;\n    }\n\n    server {\n        listen   8081;\n\n        proxy_connect_timeout 1s;\n        proxy_timeout 3s;\n        proxy_pass test;\n    }\n\n    server {\n        listen   8082;\n\n        proxy_connect_timeout 1s;\n        proxy_timeout 3s;\n        proxy_pass backend;\n    }\n}\nEOF\n\nmrun($t);\n\n###############################################################################\nmy $rep;\nmy $dump;\n\nlike(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8089', 8500), qr/true/m, '2015-12-27 18:21:37');\nlike(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8088', 8500), qr/true/m, '2015-12-27 18:20:37');\n\nsleep(1);\n\nlike(mhttp_delete('/v1/kv/upstreams/test?recurse', 8500), qr/true/m, '2015-12-27 18:22:37');\n\nlike(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8089', '', 8500), qr/true/m, '2015-12-27 17:50:35');\n\n$rep = qr/\nUpstream name: test;Backend server counts: 1\n        server 127.0.0.1:8089 weight=1 max_fails=2 fail_timeout=10;\n/m;\n\nsleep(1);\n\nlike(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2016-04-17 17:43:30');\n\n###############################################################################\n\nlike(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');\n\n$rep = qr/\nUpstream name: test;Backend server counts: 2\n        server 127.0.0.1:8089 weight=1 max_fails=2 fail_timeout=10;\n        server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10;\n/m;\n\nsleep(1);\n\nlike(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 18:17:53');\n\n#########################\n\nlike(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8089', 8500), qr/true/m, '2015-12-27 18:24:37');\n\n$rep = qr/\nUpstream name: test;Backend server counts: 1\n        server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10;\n/m;\n\nsleep(1);\n\nlike(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2016-04-17 18:30:40');\n\n#########################\n\nlike(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');\n\n$rep = qr/\nUpstream name: test;Backend server counts: 2\n        server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10;\n        server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30;\n/m;\n\nsleep(1);\n\nlike(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2016-04-17 18:32:53');\n\n########################\n\nlike(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8088', 8500), qr/true/m, '2015-12-27 18:33:37');\n\n$rep = qr/\nUpstream name: test;Backend server counts: 1\n        server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30;\n/m;\n\nsleep(1);\n\nlike(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 18:34:40');\n\n#######################\n\nlike(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');\n\n$rep = qr/\nUpstream name: test;Backend server counts: 2\n        server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30;\n        server 127.0.0.1:8088 weight=20 max_fails=0 fail_timeout=30;\n/m;\n\nsleep(1);\n\nlike(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 18:34:40');\n\n#######################\n\nlike(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8088', '{\"weight\":40}', 8500), qr/true/m, '2015-12-27 18:42:35');\n\n$rep = qr/\nUpstream name: test;Backend server counts: 2\n        server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30;\n        server 127.0.0.1:8088 weight=40 max_fails=0 fail_timeout=30;\n/m;\n\nsleep(1);\n\nlike(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 18:43:40');\n\n#######################\n\nlike(mhttp_delete('/v1/kv/upstreams/test?recurse', 8500), qr/true/m, '2015-12-27 18:22:33');\n\nsleep(1);\n\nlike(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2016-04-14 18:44:51');\n\n#######################\n\n$dump = qr/server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s;\nserver 127.0.0.1:8088 weight=40 max_fails=0 fail_timeout=30s;\n/m;\n\nlike(get_dump_content('/tmp/servers_test.conf'), $dump, '2015-12-27 18:51:35');\n\n#######################\n\nlike(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');\n\nsleep(1);\n\n$dump = qr/server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s;\nserver 127.0.0.1:8088 weight=40 max_fails=3 fail_timeout=20s down;\n/m;\n\nlike(get_dump_content('/tmp/servers_test.conf'), $dump, '2016-03-15 18:51:35');\n\n#######################\n\nlike(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');\n\nsleep(1);\n\n$dump = qr/server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s;\nserver 127.0.0.1:8088 weight=40 max_fails=0 fail_timeout=30s;\n/m;\n\nlike(get_dump_content('/tmp/servers_test.conf'), $dump, '2016-03-15 17:51:35');\n\n$t->stop();\n\n##############################################################################\n\n$t->write_file_expand('nginx.conf', <<'EOF');\n\n%%TEST_GLOBALS%%\n\ndaemon off;\n\nworker_processes auto;\n\nevents {\n    accept_mutex off;\n}\n\nhttp {\n\n    upstream test {\n        upsync 127.0.0.1:8500/v1/kv/upstreams/test upsync_interval=50ms upsync_timeout=6m upsync_type=consul;\n        upsync_dump_path /tmp/servers_test.conf;\n\n        server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10;\n    }\n\n    upstream backend {\n        server 127.0.0.1:8090 weight=10 max_fails=3 fail_timeout=10;\n    }\n\n    server {\n        listen   8080;\n\n        upstream_show;\n    }\n\n    server {\n        listen   8081;\n\n        proxy_connect_timeout 1s;\n        proxy_timeout 3s;\n        proxy_pass test;\n    }\n\n    server {\n        listen   8082;\n\n        proxy_connect_timeout 1s;\n        proxy_timeout 3s;\n        proxy_pass backend;\n    }\n}\nEOF\n\nmrun($t);\n\n###############################################################################\n\nlike(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8088', 8500), qr/true/m, '2015-12-27 19:20:37');\nlike(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8089', 8500), qr/true/m, '2015-12-27 19:21:37');\n\nsleep(1);\n\nlike(mhttp_delete('/v1/kv/upstreams/test?recurse', 8500), qr/true/m, '2015-12-27 19:49:37');\n\nlike(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8089', '', 8500), qr/true/m, '2015-12-27 19:25:35');\n\n$rep = qr/\nUpstream name: test;Backend server counts: 1\n        server 127.0.0.1:8089 weight=1 max_fails=2 fail_timeout=10;\n/m;\n\nsleep(1);\n\nlike(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 19:20:30');\n\n###############################################################################\n\nlike(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');\n\n$rep = qr/\nUpstream name: test;Backend server counts: 2\n        server 127.0.0.1:8089 weight=1 max_fails=2 fail_timeout=10;\n        server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10;\n/m;\n\nsleep(1);\n\nlike(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 19:26:53');\n\n###########################\n\nlike(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8089', 8500), qr/true/m, '2015-12-27 19:28:37');\n\n$rep = qr/\nUpstream name: test;Backend server counts: 1\n        server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10;\n/m;\n\nsleep(1);\n\nlike(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 19:30:40');\n\n###########################\n\nlike(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');\n\n$rep = qr/\nUpstream name: test;Backend server counts: 2\n        server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10;\n        server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30;\n/m;\n\nlike(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 19:32:53');\n\n##########################\n\nlike(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8088', 8500), qr/true/m, '2015-12-27 19:35:37');\n\n$rep = qr/\nUpstream name: test;Backend server counts: 1\n        server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30;\n/m;\n\nsleep(1);\n\nlike(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2016-04-17 19:37:40');\n\n###########################\n\nlike(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');\n\n$rep = qr/\nUpstream name: test;Backend server counts: 2\n        server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30;\n        server 127.0.0.1:8088 weight=20 max_fails=0 fail_timeout=30;\n/m;\n\nsleep(1);\n\nlike(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2016-04-17 19:47:40');\n\n###########################\n\nlike(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8088', '{\"weight\":40}', 8500), qr/true/m, '2015-12-27 19:48:35');\n\n$rep = qr/\nUpstream name: test;Backend server counts: 2\n        server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30;\n        server 127.0.0.1:8088 weight=40 max_fails=0 fail_timeout=30;\n/m;\n\nsleep(1);\n\nlike(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 19:49:40');\n\n###########################\n\nlike(mhttp_delete('/v1/kv/upstreams/test?recurse', 8500), qr/true/m, '2015-12-27 19:50:37');\n\nsleep(1);\n\nlike(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 19:51:40');\n\n##########################\n\n$dump = qr/server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s;\nserver 127.0.0.1:8088 weight=40 max_fails=0 fail_timeout=30s;\n/m;\n\nlike(get_dump_content('/tmp/servers_test.conf'), $dump, '2015-12-27 19:53:35');\n\n##########################\n\nlike(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');\n\nsleep(1);\n\n$dump = qr/server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s;\nserver 127.0.0.1:8088 weight=40 max_fails=3 fail_timeout=20s down;\n/m;\n\nlike(get_dump_content('/tmp/servers_test.conf'), $dump, '2016-03-15 19:53:35');\n\n##########################\n\nlike(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');\n\nsleep(1);\n\n$dump = qr/server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s;\nserver 127.0.0.1:8088 weight=40 max_fails=3 fail_timeout=20s;\n/m;\n\nlike(get_dump_content('/tmp/servers_test.conf'), $dump, '2016-03-15 20:53:35');\n\n$t->stop();\n\n###############################################################################\n\nsub mhttp($;$;%) {\n    my ($request, $port, %extra) = @_;\n    my $reply;\n    eval {\n        local $SIG{ALRM} = sub { die \"timeout\\n\" };\n        local $SIG{PIPE} = sub { die \"sigpipe\\n\" };\n        alarm(2);\n        my $s = IO::Socket::INET->new(\n            Proto => \"tcp\",\n            PeerAddr => \"127.0.0.1:$port\"\n        );\n        log_out($request);\n        $s->print($request);\n        local $/;\n        select undef, undef, undef, $extra{sleep} if $extra{sleep};\n        return '' if $extra{aborted};\n        $reply = $s->getline();\n        alarm(0);\n    };\n    alarm(0);\n    if ($@) {\n        log_in(\"died: $@\");\n        return undef;\n    }\n    log_in($reply);\n    return $reply;\n}\n"
  },
  {
    "path": "test/test.sh",
    "content": "#!/bin/sh\n\nTEST_NGINX_USE_HUP=1 TEST_NGINX_BINARY=/usr/local/nginx/sbin/nginx prove -r t\n"
  }
]