├── .gitignore ├── GOVERNANCE.md ├── LICENSE ├── MAINTAINERS.md ├── README.md ├── TODO └── src ├── config ├── ngx_nats.c ├── ngx_nats.h ├── ngx_nats_comm.c ├── ngx_nats_comm.h ├── ngx_nats_json.c ├── ngx_nats_json.h ├── ngx_nats_protocol.c └── ngx_nats_protocol.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | 6 | # Compiled Dynamic libraries 7 | *.so 8 | 9 | # Compiled Static libraries 10 | *.lai 11 | *.la 12 | *.a 13 | -------------------------------------------------------------------------------- /GOVERNANCE.md: -------------------------------------------------------------------------------- 1 | # NATS Server Governance 2 | 3 | NATS Server (gnastd) is part of the NATS project and is subject to the [NATS Governance](https://github.com/nats-io/nats-general/blob/master/GOVERNANCE.md). -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintainers 2 | 3 | Maintainership is on a per project basis. 4 | 5 | ### Maintainers 6 | - Ivan Kozlovic [@kozlovic](https://github.com/kozlovic) 7 | - Derek Collison [@derekcollison](https://github.com/derekcollison) 8 | - Lev Brouk [@levb](https://github.com/levb) 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | nginx-nats 2 | ========== 3 | 4 | [![License Apache 2.0](https://img.shields.io/badge/License-Apache2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) 5 | 6 | Nginx module that implements the NATS client. 7 | 8 | ### Configuration: 9 | 10 | NATS configuration is a section specified at the main level (i.e. not inside 11 | the `http` section). 12 | 13 | ```nginx 14 | nats { 15 | server host1:port1; 16 | ... 17 | server hostN:portN; 18 | 19 | reconnect 2s; 20 | ping 30s; 21 | 22 | user ; 23 | password ; 24 | } 25 | 26 | http { 27 | .... 28 | } 29 | ``` 30 | 31 | * One or more NATS servers can be specified. Nginx tries them in the listed 32 | order and connects to first available ("first working", not "round-robin" or 33 | other load-balancing). 34 | * If a connection cannot be created or NATS disconnects then Nginx tries all 35 | listed servers then waits for specified `reconnect` interval before it tries 36 | to connect again. Default reconnect interval is 1 second. 37 | * `ping` specified the interval at which Nginx sends __PING__ messages to NATS 38 | server. Default is 30 seconds. 39 | * `user` and `password` are required if the NATS server has been configured to 40 | require authentication. Currently it applies to all servers; per-server 41 | user/password authentication is a possible future feature. 42 | 43 | ### Build: 44 | 45 | This module only maintains connections to NATS but does not do anything with 46 | those connections, so usually this module should be built together with some 47 | other module using the API which we export. 48 | 49 | Like any nginx module, building nginx-nats is handled by configuring an nginx 50 | build to reference this module, then building nginx. 51 | 52 | This module depends upon: 53 | 54 | * OpenSSL (currently just for randomness, but in the future for secured 55 | connections) 56 | 57 | ```console 58 | nginx-src-dir$ ./configure [...] \ 59 | --add-module=/path/to/github.com/apcera/nginx-nats/src \ 60 | --add-module=/path/to/module/which/uses/nats \ 61 | --with-cc-opt=-I${OPENSSLDIR:?}/include \ 62 | "--with-ld-opt=-L${OPENSSLDIR:?}/lib -lssl -lcrypto [...]" 63 | nginx-src-dir$ make 64 | ``` 65 | 66 | ### API 67 | 68 | There are two available header files 69 | 70 | * `ngx_nats.h` -- core of the features 71 | * `ngx_nats_json.h` -- interacting with NATS via JSON objects 72 | 73 | All JSON methods, types and macro constants begin `ngx_nats_json` (or the 74 | upper-case version of that). 75 | 76 | #### ngx\_nats.h 77 | 78 | The core type is `ngx_nats_client_t`, which is a struct type, thus variables 79 | will be of type `ngx_nats_client_t *`. The contents are outlined below, after 80 | the callback functions. 81 | 82 | The outline of the API is below, see the header for parameters and types. 83 | 84 | Basic usage: 85 | 86 | * `ngx_nats_add_client()` -- register a client which is using nginx-nats 87 | * `ngx_nats_publish()` -- publish a message, serialized; optionally in reply 88 | to another message 89 | * `ngx_nats_subscribe()` -- subscribe to receive messages 90 | * `ngx_nats_unsubscribe()` -- end a subscription 91 | * `ngx_nats_create_inbox()` -- create an end-point to receive a reply to a 92 | message 93 | 94 | There are three callback functions which are needed: 95 | 96 | * `ngx_nats_connected_pt` -- as `(ngx_nats_client_t*).connected` 97 | * `ngx_nats_disconnected_pt` -- as `(ngx_nats_client_t*).disconnected` 98 | * `ngx_nats_handle_msg_pt` -- passed as parameter to `ngx_nats_subscribe()` 99 | 100 | A `ngx_nats_client_t *client` contains three members, which should be set by 101 | the caller: 102 | 103 | * `.connected` -- callback function 104 | * `.disconnected` -- callback function 105 | * `.data` -- a `void *` callback data blob for use by the caller 106 | 107 | Utilities: 108 | 109 | * `ngx_nats_init_random()` -- initialise randomness in the nginx-nats module 110 | * `ngx_nats_next_random()` → `uint32_t`: get 32-bits of randomness 111 | * `ngx_nats_get_local_ip()` → `ngx_addr_t *` (or `NULL`) -- our local IP 112 | 113 | #### ngx\_nats\_json.h 114 | 115 | JSON structure types: `ngx_nats_json_object_t`, `ngx_nats_json_array_t`, 116 | `ngx_nats_json_value_t`, `ngx_nats_json_field_t`. 117 | 118 | * `ngx_nats_json_parse()`: parse a serialized message into JSON structure 119 | types, using the provided allocation pool 120 | * `ngx_nats_json_type_name()`: convert an `NGX_NATS_JSON_*` type constant into 121 | a string name 122 | 123 | ## License 124 | 125 | Unless otherwise noted, the NATS source files are distributed under 126 | the Apache Version 2.0 license found in the LICENSE file. 127 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 2 | - Add license when we know what license we should use. 3 | - Make sure copyright statements are correct in source files (there are some but not sure). 4 | - Programming items: 5 | - Implement user/password for each server. 6 | - Parameterize read/write timeouts. 7 | - SSL connection with NATS. 8 | - Improve write buffers management. 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_nats_module 2 | CORE_INCS="$CORE_INCS $ngx_addon_dir" 3 | USE_OPENSSL=YES 4 | 5 | if test -n "$ngx_module_link"; then 6 | ngx_module_type=CORE 7 | 8 | ngx_module_name="ngx_nats_module \ 9 | ngx_nats_core_module" 10 | 11 | ngx_module_srcs="$ngx_addon_dir/ngx_nats.c \ 12 | $ngx_addon_dir/ngx_nats_comm.c \ 13 | $ngx_addon_dir/ngx_nats_protocol.c \ 14 | $ngx_addon_dir/ngx_nats_json.c" 15 | 16 | ngx_module_deps="$ngx_addon_dir/ngx_nats.h \ 17 | $ngx_addon_dir/ngx_nats_comm.h \ 18 | $ngx_addon_dir/ngx_nats_protocol.h \ 19 | $ngx_addon_dir/ngx_nats_json.h" 20 | 21 | ngx_module_order="ngx_nats_module \ 22 | ngx_nats_core_module" 23 | 24 | . auto/module 25 | else 26 | HTTP_MODULES="$HTTP_MODULES ngx_nats_module ngx_nats_core_module" 27 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_nats.c" 28 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_nats_comm.c" 29 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_nats_protocol.c" 30 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_nats_json.c" 31 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/ngx_nats.h" 32 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/ngx_nats_comm.h" 33 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/ngx_nats_protocol.h" 34 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/ngx_nats_json.h" 35 | fi 36 | -------------------------------------------------------------------------------- /src/ngx_nats.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 (C) The NATS Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * Based on Nginx source code: 17 | * Copyright (C) Igor Sysoev 18 | * Copyright (C) Nginx, Inc. 19 | */ 20 | 21 | #include 22 | 23 | #include "ngx_nats.h" 24 | #include "ngx_nats_comm.h" 25 | 26 | /* 27 | * This file deals with the modules, setup, configuration parsing etc. 28 | * Working with NATS is in ngx_nats_comm.c. 29 | */ 30 | 31 | /* 32 | * Major TODOs: 33 | * - process exit (shutdown NATS connection). 34 | * - user/password per server. 35 | * 36 | * TODOs: 37 | * - custom log. 38 | * - ssl to NATS (?). 39 | * - improve buffers. 40 | */ 41 | 42 | 43 | /* 44 | * TODO: 45 | * I don't know about (NGX_FREEBSD) or (NGX_SOLARIS), 46 | * deal with them at some point. 47 | * 48 | * NGX_FREEBSD -- ??? 49 | * NGX_SOLARIS -- ??? 50 | * POSIX -- ??? 51 | * 52 | * For now we can work without determining the local IP, but knowing it 53 | * is better. I may be mising it but cannot find if Nginx knows the local 54 | * IP, it probably doesn't need it. We need *any* of the local IPs that 55 | * we can use for uniqueness and to reach the router in testing. 56 | */ 57 | 58 | #if (NGX_LINUX) || (NGX_DARWIN) 59 | #include 60 | #include 61 | #elif (NGX_WIN32) 62 | /* Probably don't need anything, check when porting. */ 63 | #else 64 | /* We'll go without knowing our local IP */ 65 | #endif 66 | 67 | #if nginx_version >= 1009011 68 | #define NATS_DYNAMIC_MODULE_SUPPORT (1) 69 | #define NATS_NGX_MODULES(cf) ((cf)->cycle->modules) 70 | #else 71 | #define NATS_NGX_MODULES(cf) ngx_modules 72 | #endif 73 | 74 | /*--------------------------------------------------------------------------- 75 | * Forward declarations of functions for main (root) module. 76 | *--------------------------------------------------------------------------*/ 77 | 78 | /* Parses nats{...} configuration block. */ 79 | static char * ngx_nats_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 80 | 81 | /*--------------------------------------------------------------------------- 82 | * Forward declarations of functions for core module. 83 | *--------------------------------------------------------------------------*/ 84 | 85 | static void * ngx_nats_core_create_conf(ngx_cycle_t *cycle); 86 | static char * ngx_nats_core_init_conf(ngx_conf_t *cf, void *conf); 87 | 88 | static ngx_int_t ngx_nats_core_init_module(ngx_cycle_t *cycle); 89 | static ngx_int_t ngx_nats_core_init_process(ngx_cycle_t *cycle); 90 | static void ngx_nats_core_exit_process(ngx_cycle_t *cycle); 91 | 92 | /* 93 | * Parse "server host:port" line inside the nats{...} block. 94 | * May have more than one such line. 95 | */ 96 | static char * ngx_nats_core_server(ngx_conf_t *cf, 97 | ngx_command_t *cmd, void *dummy); 98 | 99 | static void ngx_nats_init_local_ip(ngx_cycle_t *cycle); 100 | 101 | /*--------------------------------------------------------------------------- 102 | * Variables 103 | *--------------------------------------------------------------------------*/ 104 | 105 | static ngx_uint_t ngx_nats_max_module; 106 | 107 | static ngx_str_t ngx_nats_core_conf_name = ngx_string("nats_core_conf"); 108 | 109 | static ngx_int_t ngx_nats_present = 0; 110 | 111 | /*--------------------------------------------------------------------------- 112 | * Main module and command nats{...} 113 | *--------------------------------------------------------------------------*/ 114 | 115 | static ngx_command_t ngx_nats_commands[] = { 116 | 117 | { ngx_string("nats"), /* configuration section */ 118 | NGX_MAIN_CONF | NGX_CONF_BLOCK | NGX_CONF_NOARGS, 119 | ngx_nats_block, 120 | 0, 121 | 0, 122 | NULL }, 123 | 124 | ngx_null_command 125 | }; 126 | 127 | static ngx_core_module_t ngx_nats_module_ctx = { 128 | ngx_string("nats_module"), 129 | NULL, 130 | NULL 131 | }; 132 | 133 | ngx_module_t ngx_nats_module = { 134 | NGX_MODULE_V1, 135 | &ngx_nats_module_ctx, /* module context */ 136 | ngx_nats_commands, /* module directives */ 137 | NGX_CORE_MODULE, /* module type */ 138 | NULL, /* init master */ 139 | NULL, /* init module */ 140 | NULL, /* init process */ 141 | NULL, /* init thread */ 142 | NULL, /* exit thread */ 143 | NULL, /* exit process */ 144 | NULL, /* exit master */ 145 | NGX_MODULE_V1_PADDING 146 | }; 147 | 148 | 149 | /*--------------------------------------------------------------------------- 150 | * Core module processing commands inside nats{...}. 151 | *--------------------------------------------------------------------------*/ 152 | 153 | static ngx_command_t ngx_nats_core_commands[] = { 154 | 155 | { ngx_string("server"), 156 | NGX_NATS_CONF | NGX_CONF_TAKE1, 157 | ngx_nats_core_server, 158 | 0, 159 | 0, 160 | NULL }, 161 | 162 | { ngx_string("reconnect"), 163 | NGX_NATS_CONF | NGX_CONF_TAKE1, 164 | ngx_conf_set_msec_slot, 165 | 0, 166 | offsetof(ngx_nats_core_conf_t, reconnect_interval), 167 | NULL }, 168 | 169 | { ngx_string("ping"), 170 | NGX_NATS_CONF | NGX_CONF_TAKE1, 171 | ngx_conf_set_msec_slot, 172 | 0, 173 | offsetof(ngx_nats_core_conf_t, ping_interval), 174 | NULL }, 175 | 176 | { ngx_string("user"), 177 | NGX_NATS_CONF | NGX_CONF_TAKE1, 178 | ngx_conf_set_str_slot, 179 | 0, 180 | offsetof(ngx_nats_core_conf_t, user), 181 | NULL }, 182 | 183 | { ngx_string("password"), 184 | NGX_NATS_CONF | NGX_CONF_TAKE1, 185 | ngx_conf_set_str_slot, 186 | 0, 187 | offsetof(ngx_nats_core_conf_t, password), 188 | NULL }, 189 | 190 | ngx_null_command 191 | }; 192 | 193 | static ngx_nats_module_t ngx_nats_core_module_ctx = { 194 | ngx_string("nats_core_module"), 195 | ngx_nats_core_create_conf, 196 | ngx_nats_core_init_conf 197 | }; 198 | 199 | ngx_module_t ngx_nats_core_module = { 200 | NGX_MODULE_V1, 201 | &ngx_nats_core_module_ctx, /* module context */ 202 | ngx_nats_core_commands, /* module directives */ 203 | NGX_NATS_MODULE, /* module type */ 204 | NULL, /* init master */ 205 | ngx_nats_core_init_module, /* init module */ 206 | ngx_nats_core_init_process, /* init process */ 207 | NULL, /* init thread */ 208 | NULL, /* exit thread */ 209 | ngx_nats_core_exit_process, /* exit process */ 210 | NULL, /* exit master */ 211 | NGX_MODULE_V1_PADDING 212 | }; 213 | 214 | /*========================================================================== 215 | * 216 | * Main module functions 217 | * 218 | *=========================================================================*/ 219 | 220 | static ngx_uint_t ngx_nats_get_modules_count(ngx_conf_t *cf) 221 | { 222 | #if defined(NATS_DYNAMIC_MODULE_SUPPORT) 223 | return ngx_count_modules(cf->cycle, NGX_NATS_MODULE); 224 | #else 225 | ngx_uint_t i; 226 | ngx_uint_t count = 0; 227 | 228 | for (i = 0; ngx_modules[i]; i++) { 229 | if (ngx_modules[i]->type != NGX_NATS_MODULE) { 230 | continue; 231 | } 232 | 233 | ngx_modules[i]->ctx_index = count++; 234 | } 235 | 236 | return count; 237 | #endif 238 | } 239 | 240 | static char * 241 | ngx_nats_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 242 | { 243 | char *rv; 244 | void ***ctx; 245 | ngx_conf_t save_cf; 246 | ngx_uint_t i; 247 | ngx_nats_module_t *m; 248 | ngx_nats_core_conf_t *nccf; 249 | 250 | ngx_nats_present = 1; 251 | 252 | /* 253 | * Count the number of the nats modules and set up their indices 254 | */ 255 | 256 | ngx_nats_max_module = ngx_nats_get_modules_count(cf); 257 | if (ngx_nats_max_module == 0) 258 | return NGX_CONF_ERROR; 259 | 260 | /* 261 | * Setup array of configurations for NATS modules. 262 | * and call create_conf in NATS modules 263 | * before we parse inside of nats{...} block. 264 | */ 265 | 266 | ctx = ngx_pcalloc(cf->pool, sizeof(void *)); 267 | if (ctx == NULL) { 268 | return NGX_CONF_ERROR; 269 | } 270 | 271 | *ctx = ngx_pcalloc(cf->pool, ngx_nats_max_module * sizeof(void *)); 272 | if (*ctx == NULL) { 273 | return NGX_CONF_ERROR; 274 | } 275 | 276 | /* 277 | * This is Nginx ways. NGX_MAIN_CONF makes Nginx to pass us 278 | * a pointer to a pointer I need to store here. 279 | */ 280 | 281 | * (void **) conf = ctx; 282 | 283 | /* 284 | * Call create_conf in NATS modules before we parse 285 | * inside of the nats{...} block. 286 | */ 287 | 288 | for (i = 0; NATS_NGX_MODULES(cf)[i]; i++) { 289 | if (NATS_NGX_MODULES(cf)[i]->type != NGX_NATS_MODULE) { 290 | continue; 291 | } 292 | 293 | m = NATS_NGX_MODULES(cf)[i]->ctx; 294 | 295 | if (m->create_conf) { 296 | (*ctx)[NATS_NGX_MODULES(cf)[i]->ctx_index] = m->create_conf(cf->cycle); 297 | if ((*ctx)[NATS_NGX_MODULES(cf)[i]->ctx_index] == NULL) { 298 | return NGX_CONF_ERROR; 299 | } 300 | } 301 | } 302 | 303 | save_cf = *cf; 304 | 305 | /* parse the nats{...} block */ 306 | 307 | cf->ctx = ctx; 308 | cf->module_type = NGX_NATS_MODULE; 309 | cf->cmd_type = NGX_NATS_CONF; 310 | 311 | rv = ngx_conf_parse(cf, NULL); 312 | 313 | *cf = save_cf; 314 | 315 | if (rv != NGX_CONF_OK) 316 | return rv; 317 | 318 | nccf = (ngx_nats_core_conf_t*) ngx_nats_get_core_conf(cf->cycle->conf_ctx); 319 | if (nccf->servers == NULL) { 320 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 321 | "no servers defined inside nats{...} section"); 322 | return NGX_CONF_ERROR; 323 | } 324 | 325 | for (i = 0; NATS_NGX_MODULES(cf)[i]; i++) { 326 | if (NATS_NGX_MODULES(cf)[i]->type != NGX_NATS_MODULE) { 327 | continue; 328 | } 329 | 330 | m = NATS_NGX_MODULES(cf)[i]->ctx; 331 | 332 | if (m->init_conf) { 333 | rv = m->init_conf(cf, (*ctx)[NATS_NGX_MODULES(cf)[i]->ctx_index]); 334 | if (rv != NGX_CONF_OK) { 335 | return rv; 336 | } 337 | } 338 | } 339 | 340 | return NGX_CONF_OK; 341 | } 342 | 343 | 344 | /*========================================================================== 345 | * 346 | * Core module functions 347 | * 348 | *=========================================================================*/ 349 | 350 | static void * 351 | ngx_nats_core_create_conf(ngx_cycle_t *cycle) 352 | { 353 | ngx_nats_core_conf_t *nccf; 354 | 355 | nccf = ngx_pcalloc(cycle->pool, sizeof(ngx_nats_core_conf_t)); 356 | if (nccf == NULL) { 357 | return NULL; 358 | } 359 | 360 | nccf->name = &ngx_nats_core_conf_name; 361 | /*nccf->pool = cycle->pool;*/ 362 | nccf->pool = ngx_create_pool((4 * 1024), cycle->log); 363 | if (nccf->pool == NULL) { 364 | return NULL; 365 | } 366 | nccf->log = cycle->log; 367 | 368 | nccf->reconnect_interval = NGX_CONF_UNSET_MSEC; 369 | nccf->ping_interval = NGX_CONF_UNSET_MSEC; 370 | 371 | return nccf; 372 | } 373 | 374 | 375 | static void 376 | ngx_nats_conf_init_str(ngx_str_t *s, char *value) 377 | { 378 | if (s->data == NULL) { 379 | s->len = ngx_strlen(value); 380 | s->data = (u_char *) value; 381 | } 382 | } 383 | 384 | static char * 385 | ngx_nats_core_init_conf(ngx_conf_t *cf, void *conf) 386 | { 387 | ngx_nats_core_conf_t *nccf = conf; 388 | 389 | /* set default if was not in config */ 390 | ngx_conf_init_msec_value(nccf->reconnect_interval, 391 | NGX_NATS_DEFAULT_RECONNECT); 392 | ngx_conf_init_msec_value(nccf->ping_interval, NGX_NATS_DEFAULT_PING); 393 | 394 | ngx_nats_conf_init_str(&nccf->user, NGX_NATS_DEFAULT_USER); 395 | ngx_nats_conf_init_str(&nccf->password, ""); 396 | 397 | if (nccf->user.len+nccf->password.len > NGX_NATS_MAS_USER_PASS_LEN) { 398 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 399 | "NATS: user name and password are too long"); 400 | return NGX_CONF_ERROR; 401 | } 402 | 403 | return NGX_CONF_OK; 404 | } 405 | 406 | static ngx_int_t 407 | ngx_nats_core_init_module(ngx_cycle_t *cycle) 408 | { 409 | ngx_nats_core_conf_t *nccf; 410 | ngx_nats_data_t *nd; 411 | ngx_log_t *log; 412 | ngx_int_t rc; 413 | 414 | if (ngx_nats_present == 0) { 415 | /* 416 | * this means Nginx is built with these modules 417 | * but nats{...} section is not in config. Just ignore. 418 | */ 419 | return NGX_OK; 420 | } 421 | 422 | nccf = (ngx_nats_core_conf_t*) ngx_nats_get_core_conf(cycle->conf_ctx); 423 | 424 | log = nccf->pool->log; 425 | 426 | nd = ngx_pcalloc(nccf->pool, sizeof(ngx_nats_data_t)); 427 | if (nd == NULL) { 428 | return NGX_ERROR; 429 | } 430 | 431 | nd->nc_pool = ngx_create_pool(NGX_NATS_CONN_POOL_SIZE, log); 432 | if (nd->nc_pool == NULL) { 433 | return NGX_ERROR; 434 | } 435 | nd->nc_pool->log = log; 436 | 437 | /* 438 | * buffers are reused because we only have one NATS 439 | * connection at the time. 440 | */ 441 | nd->nc_read_buf = ngx_nats_buf_create(nccf->pool, 442 | NGX_NATS_READ_BUF_INIT_SIZE); 443 | if (nd->nc_read_buf == NULL) { 444 | return NGX_ERROR; 445 | } 446 | 447 | nd->nc_write_buf = ngx_nats_buf_create(nccf->pool, 448 | NGX_NATS_WRITE_BUF_INIT_SIZE); 449 | if (nd->nc_write_buf == NULL) { 450 | return NGX_ERROR; 451 | } 452 | 453 | rc = ngx_array_init(&nd->cd.clients, nccf->pool, 4, 454 | sizeof(ngx_nats_client_t *)); 455 | if (rc != NGX_OK) { 456 | return rc; 457 | } 458 | 459 | rc = ngx_array_init(&nd->cd.subs, nccf->pool, 16, 460 | sizeof(ngx_nats_subscription_t)); 461 | if (rc != NGX_OK) { 462 | return rc; 463 | } 464 | nd->cd.next_id = 0; 465 | 466 | nccf->data = nd; 467 | nd->nccf = nccf; 468 | nd->log = nccf->log; /* use diff log here ? */ 469 | nd->conn_index = -1; 470 | nd->curr_index = -1; 471 | nd->last_index = 0; 472 | 473 | ngx_nats_init_local_ip(cycle); 474 | 475 | return NGX_OK; 476 | } 477 | 478 | static ngx_int_t 479 | ngx_nats_core_init_process(ngx_cycle_t *cycle) 480 | { 481 | ngx_nats_core_conf_t *nccf; 482 | ngx_int_t rc; 483 | 484 | if (ngx_nats_present == 0) { 485 | return NGX_OK; 486 | } 487 | 488 | nccf = (ngx_nats_core_conf_t*) ngx_nats_get_core_conf(cycle->conf_ctx); 489 | 490 | nccf->log = cycle->log; 491 | 492 | ngx_nats_init_random(); 493 | 494 | if (getenv("APCERA_ROUTER_TEST_RANDOM")) { 495 | ngx_nats_test_random(); 496 | } 497 | 498 | /* 499 | * Returns error in case of some hard failure like no memory 500 | * or similar, not if could not connect to NATS, it'll retry. 501 | */ 502 | rc = ngx_nats_init(nccf); 503 | 504 | return rc; 505 | } 506 | 507 | static void 508 | ngx_nats_core_exit_process(ngx_cycle_t *cycle) 509 | { 510 | ngx_nats_core_conf_t *nccf; 511 | 512 | if (ngx_nats_present == 0) { 513 | return; 514 | } 515 | 516 | nccf = (ngx_nats_core_conf_t*) ngx_nats_get_core_conf(cycle->conf_ctx); 517 | 518 | ngx_nats_exit(nccf); 519 | } 520 | 521 | /* 522 | * Parse "server host:port" line inside of nats{...} block. 523 | */ 524 | static char * 525 | ngx_nats_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 526 | { 527 | ngx_nats_core_conf_t *nccf = conf; 528 | ngx_nats_server_t *ns; 529 | ngx_str_t *value; 530 | ngx_url_t u; 531 | 532 | /* alloc if first "server" line. */ 533 | if (nccf->servers == NULL) { 534 | nccf->servers = ngx_array_create(nccf->pool, 4, 535 | sizeof(ngx_nats_server_t)); 536 | if (nccf->servers == NULL) { 537 | return NGX_CONF_ERROR; 538 | } 539 | } 540 | 541 | /* ngx_array's push returns a pointer to where to store the new data. */ 542 | ns = ngx_array_push(nccf->servers); 543 | if (ns == NULL) { 544 | return NGX_CONF_ERROR; 545 | } 546 | 547 | ngx_memzero(ns, sizeof(ngx_nats_server_t)); 548 | 549 | value = cf->args->elts; 550 | 551 | ngx_memzero(&u, sizeof(ngx_url_t)); 552 | 553 | u.url = value[1]; 554 | u.default_port = NGX_NATS_DEFAULT_PORT; 555 | 556 | if (ngx_parse_url(nccf->pool, &u) != NGX_OK) { 557 | if (u.err) { 558 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 559 | "%s in NATS server \"%V\"", u.err, &u.url); 560 | } 561 | 562 | return NGX_CONF_ERROR; 563 | } 564 | 565 | ns->addrs = u.addrs; 566 | ns->naddrs = u.naddrs; 567 | 568 | ns->url.len = u.url.len; 569 | 570 | ns->url.data = ngx_pnalloc(nccf->pool, u.url.len + 1); 571 | if (ns->url.data == NULL) { 572 | return NGX_CONF_ERROR; 573 | } 574 | 575 | ngx_memcpy(ns->url.data, u.url.data, u.url.len + 1); 576 | 577 | return NGX_CONF_OK; 578 | } 579 | 580 | 581 | static ngx_addr_t *_ngx_nats_local_ip = NULL; 582 | 583 | static void 584 | ngx_nats_init_local_ip(ngx_cycle_t *cycle) 585 | { 586 | /* TODO: port to Windows when need it, extend to other platforms. */ 587 | 588 | #if (NGX_LINUX) || (NGX_DARWIN) 589 | 590 | struct ifaddrs *ifaddrs, *ifa; 591 | int rc, family; 592 | char host_ip4[32]; 593 | char host_ip6[96]; 594 | struct ifaddrs *ifa_ip4 = NULL; 595 | struct ifaddrs *ifa_ip6 = NULL; 596 | struct ifaddrs *store_ifa = NULL; 597 | char *store_host = NULL; 598 | size_t store_socklen = 0; 599 | ngx_int_t lev; 600 | 601 | ifaddrs = NULL; 602 | 603 | rc = getifaddrs(&ifaddrs); 604 | if (rc != 0) { 605 | ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, "getifaddrs() failed"); 606 | return; 607 | } 608 | 609 | host_ip4[0] = 0; 610 | host_ip6[0] = 0; 611 | 612 | for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { 613 | 614 | if (ifa->ifa_addr == NULL) 615 | continue; 616 | 617 | family = ifa->ifa_addr->sa_family; 618 | if (family != AF_INET && family != AF_INET6) 619 | continue; 620 | 621 | if ((ifa->ifa_flags & IFF_LOOPBACK) != 0) { 622 | continue; 623 | } 624 | 625 | if (family != AF_INET && family != AF_INET6) 626 | continue; 627 | 628 | if (family == AF_INET) { 629 | if (ifa_ip4 == NULL) { 630 | rc = getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), 631 | host_ip4, sizeof(host_ip4), NULL, 0, NI_NUMERICHOST); 632 | if (rc != 0) { 633 | ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, 634 | "getnameinfo() failed for IPv4 interface"); 635 | } else { 636 | ifa_ip4 = ifa; 637 | } 638 | } 639 | } else { /* AF_INET6 */ 640 | if (ifa_ip6 == NULL) { 641 | rc = getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in6), 642 | host_ip6, sizeof(host_ip6), NULL, 0, NI_NUMERICHOST); 643 | if (rc != 0) { 644 | ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, 645 | "getnameinfo() failed for IPv6 interface"); 646 | } else { 647 | ifa_ip6 = ifa; 648 | } 649 | } 650 | } 651 | } 652 | 653 | lev = cycle->log->log_level; 654 | cycle->log->log_level = NGX_LOG_INFO; 655 | 656 | if (ifa_ip4 == NULL && ifa_ip6 == NULL) { 657 | ngx_log_error(NGX_LOG_WARN, cycle->log, 0, 658 | "unable to detect any local IP4 or IP6 address. " 659 | "The machine may be disconnected from the network or " 660 | "the networks are disabled."); 661 | } 662 | 663 | if (ifa_ip4 != NULL) { 664 | ngx_log_error(NGX_LOG_INFO, cycle->log, 0, 665 | "found local IPv4 address '%s'", host_ip4); 666 | store_ifa = ifa_ip4; 667 | store_host = &host_ip4[0]; 668 | store_socklen = sizeof(struct sockaddr_in); 669 | } 670 | 671 | if (ifa_ip6 != NULL) { 672 | ngx_log_error(NGX_LOG_INFO, cycle->log, 0, 673 | "found local IPv6 address '%s'", host_ip6); 674 | if (store_ifa == NULL) { 675 | store_ifa = ifa_ip6; 676 | store_host = &host_ip6[0]; 677 | store_socklen = sizeof(struct sockaddr_in6); 678 | } 679 | } 680 | 681 | cycle->log->log_level = lev; 682 | 683 | /* if we failed to alloc we just go without it */ 684 | if (store_ifa != NULL) { 685 | 686 | _ngx_nats_local_ip = ngx_pcalloc(cycle->pool, sizeof(ngx_addr_t)); 687 | if (_ngx_nats_local_ip == NULL) { 688 | goto fail; 689 | } 690 | 691 | _ngx_nats_local_ip->socklen = store_socklen; 692 | _ngx_nats_local_ip->sockaddr = ngx_pcalloc(cycle->pool, _ngx_nats_local_ip->socklen); 693 | if (_ngx_nats_local_ip->sockaddr == NULL) { 694 | goto fail; 695 | } 696 | 697 | _ngx_nats_local_ip->name.len = ngx_strlen(store_host); 698 | _ngx_nats_local_ip->name.data = ngx_pnalloc(cycle->pool, 699 | _ngx_nats_local_ip->name.len + 1); 700 | if (_ngx_nats_local_ip->name.data == NULL) { 701 | goto fail; 702 | } 703 | 704 | ngx_memcpy(_ngx_nats_local_ip->sockaddr, store_ifa->ifa_addr, 705 | _ngx_nats_local_ip->socklen); 706 | ngx_memcpy(_ngx_nats_local_ip->name.data, store_host, 707 | ngx_strlen(store_host) + 1); 708 | 709 | goto end; 710 | } 711 | 712 | fail: 713 | 714 | _ngx_nats_local_ip = NULL; 715 | 716 | end: 717 | 718 | freeifaddrs(ifaddrs); 719 | 720 | #else 721 | 722 | #endif 723 | 724 | } 725 | 726 | ngx_addr_t * 727 | ngx_nats_get_local_ip(void) 728 | { 729 | return _ngx_nats_local_ip; 730 | } 731 | 732 | 733 | /* deprecated 734 | static int __random_seeded = 0; 735 | void 736 | ngx_nats_seed_random(void) 737 | { 738 | ngx_time_t *tp; 739 | ngx_addr_t *local_ip; 740 | unsigned int pid; 741 | unsigned int seed = 0; 742 | size_t i; 743 | 744 | if (__random_seeded != 0) { 745 | return; 746 | } 747 | 748 | __random_seeded = 1; 749 | 750 | ngx_time_update(); 751 | tp = ngx_timeofday(); 752 | pid = (unsigned int)ngx_pid; 753 | local_ip = ngx_nats_get_local_ip(); 754 | 755 | seed = pid * pid * pid; 756 | seed = (seed * 31) + tp->sec; 757 | seed = (seed * 31) + tp->msec; 758 | 759 | if (local_ip != NULL) { 760 | 761 | for (i = 0; i < local_ip->name.len; i++) { 762 | seed = (seed * 31) + (unsigned int)local_ip->name.data[i]; 763 | } 764 | } 765 | 766 | srand(seed); 767 | } 768 | */ 769 | 770 | /*=========================================================================== 771 | * MWC Random 772 | * 773 | * We should not rely on the system's rand(). Why Nginx does is a mystery 774 | * to me, given how much attention was paid to pretty much everything else. 775 | * Some system's rand() implementation are outright super bad, some may be 776 | * OK but it is simple enough to implement (not to invent :) a very good 777 | * PRNG like below, so we don't need to dig into what system's PRNG does 778 | * and be at its mercy. We should support a very good randomization with 779 | * very good uniformity and sufficiently large period (2^32 just won't do). 780 | * This is used by this module but also by other modules/logic, placed here 781 | * as an API that could be used by any module. 782 | * 783 | * The code below implements an extremely simple, super fast and good PRNG 784 | * with sufficient period (>2^60) invented by George Marsaglia. 785 | * http://www.math.uni-bielefeld.de/~sillke/ALGORITHMS/random/marsaglia-c 786 | * 787 | * I personally like SFMT: 788 | * http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/SFMT/index.html 789 | * 790 | * However, SFMT has more complex implementation and requires at least 2 days 791 | * to port to our code base and test (must test that it is 100% accurate 792 | * or bad things will happen). Those are the 2 days I didn't yet have :). 793 | * 794 | * Meanwhile MWC is fully sufficient for the router and its only slight 795 | * drawback is that it uses only 8 bytes seed while SFMT uses 16 bytes. 796 | * Still, for our purposes MWC is fully enough until when we possibly 797 | * transfer to SFMT. There is a variation of MWC that is CMWC using a very 798 | * log seed and with huge period but if change MWC to something else then 799 | * I'd rather just switch to SFMT. 800 | *==========================================================================*/ 801 | 802 | static uint32_t rand_z = 362436069; 803 | static uint32_t rand_w = 521288629; 804 | static uint32_t rand_z_const = 36969; 805 | static uint32_t rand_w_const = 18000; 806 | static double rand_d; 807 | static int __ngx_nats_random_init = 0; 808 | 809 | void ngx_nats_init_random(void) 810 | { 811 | uint32_t i, randomizer, n; 812 | 813 | if (__ngx_nats_random_init != 0) { 814 | return; 815 | } 816 | 817 | __ngx_nats_random_init = 1; 818 | 819 | /* 820 | * Use OpenSSL to seed the two 4-byte seeds, since we do have OpenSSL. 821 | * Ideally (for the sake of purity) we would do here without OpenSSL. 822 | * If we used SFMT that takes 16 bytes of seed data, we could use 823 | * the nanos/ip/pid to reliably seed with data unique enough for 824 | * non-secure PRNG, but it still *must* guarantee different randoms 825 | * every time we start the router. 826 | */ 827 | RAND_bytes((unsigned char *)&rand_z, sizeof(rand_z)); 828 | RAND_bytes((unsigned char *)&rand_w, sizeof(rand_w)); 829 | 830 | /* 831 | * Need only for internal use and testing. In other cases the caller 832 | * should multiply by whatever is needed, which is often not just 833 | * 1/2^32 but something else, to avoid double multiplication. 834 | */ 835 | rand_d = (double)1.0 / (double)0xffffffff; 836 | 837 | /* 838 | * Further randomize by selecting random pair of constants 839 | * and then skip random number of randoms. 840 | */ 841 | randomizer = 0; 842 | RAND_bytes((unsigned char *)&randomizer, sizeof(randomizer)); 843 | 844 | n = (uint32_t)(((double)randomizer) * rand_d * (double)4.0); 845 | 846 | /* 847 | * Values of z_const and w_const are from the post by George Marsaglia 848 | * referenced above. It is crucial to use only those he listed or as 849 | * he noted any pair satisfying the required property (see his post). 850 | */ 851 | switch (n) { 852 | case 0: 853 | rand_z_const = 31083; 854 | rand_w_const = 18030; 855 | break; 856 | case 1: 857 | rand_z_const = 31059; 858 | rand_w_const = 18513; 859 | break; 860 | case 2: 861 | rand_z_const = 30714; 862 | rand_w_const = 19098; 863 | break; 864 | case 3: 865 | rand_z_const = 30345; 866 | rand_w_const = 21723; 867 | break; 868 | default: 869 | rand_z_const = 36969; 870 | rand_w_const = 18000; 871 | break; 872 | } 873 | 874 | /* 875 | * Skip "random" number, not sure if necessary but won't harm. 876 | * "random" depends on PID only so in same router the workers, 877 | * besides using different seed, will skip different No of randoms. 878 | * Skip between 0 and 100,000 values. This runs at 200+ million/second. 879 | */ 880 | n = (uint32_t)((double)randomizer * rand_d * 100000.0); 881 | for (i = 0; i < n; i++) { 882 | ngx_nats_next_random(); 883 | } 884 | } 885 | 886 | /* 887 | * Using variables for constants slows it down a little, OK for the router. 888 | * This runs at some 100-200 MILLION times/second anyway, far faster than 889 | * we need in the router. 890 | */ 891 | uint32_t ngx_nats_next_random(void) 892 | { 893 | rand_z = rand_z_const * (rand_z & 0xffff) + (rand_z >> 16); 894 | rand_w = rand_w_const * (rand_w & 0xffff) + (rand_w >> 16); 895 | return (rand_z << 16) + (rand_w & 0xffff); 896 | } 897 | 898 | /*=========================================================================== 899 | * Testing of random. So far the results are printed and must be visually 900 | * checked :(. If will have time, will improve. 901 | *==========================================================================*/ 902 | 903 | /* 904 | * nweights must be (1 < nweights < 32), for testing only. 905 | * Sum of weights must be 1.0. In real life we'll scale. 906 | */ 907 | static void 908 | _test_random(double *weights, int nweights, int times, int print) 909 | { 910 | double d, dsum, dmulti, sweights[32]; 911 | uint32_t counts[32]; 912 | int i, j, last; 913 | 914 | if (print != 0) { 915 | fprintf(stderr,"%d weights, %-6d times:", 916 | (int)nweights, (int)times); 917 | } 918 | 919 | ngx_memset(sweights, 0, sizeof(sweights)); 920 | ngx_memset(counts, 0, sizeof(counts)); 921 | 922 | dsum = 0.0; 923 | for (i=0; isec * (uint64_t)1000 + (uint64_t)tp->msec; 1011 | 1012 | ngx_memset(counts, 0, sizeof(counts)); 1013 | 1014 | weights[0] = 0.001; 1015 | weights[1] = 0.009 + weights[0]; 1016 | weights[2] = 0.090 + weights[1]; 1017 | weights[3] = 0.200 + weights[2]; 1018 | weights[4] = 0.400 + weights[3]; 1019 | weights[5] = 0.100 + weights[4]; 1020 | weights[6] = 0.200 + weights[5]; 1021 | 1022 | nweights = 6; /* one less ! */ 1023 | 1024 | fprintf(stderr,"Testing performance of choosing out of %d weighted items...\n", 1025 | (int)(nweights+1)); 1026 | 1027 | dmulti = weights[nweights] * rand_d; 1028 | 1029 | times = 300000000; 1030 | for (i=0; isec * (uint64_t)1000 + (uint64_t)tp->msec; 1043 | d = ((double)times / (double)(tme-tms)) * 1000; 1044 | fprintf(stderr,"Time = %d millis for %d iterations\n", 1045 | (int)(tme-tms), (int)times); 1046 | fprintf(stderr,"Perf = %d times/second\n", (int)d); 1047 | 1048 | fprintf(stderr,"pcs:"); 1049 | for (i=0; i<=nweights; i++) { /* nweights is one less already */ 1050 | d = i == 0 ? 0.0 : weights[i-1]; 1051 | fprintf(stderr, " %.5f-%.5f", 1052 | weights[i]-d, ((double)counts[i] / (double)times)); 1053 | } 1054 | fprintf(stderr,"\n"); 1055 | } 1056 | -------------------------------------------------------------------------------- /src/ngx_nats.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 (C) The NATS Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * Based on Nginx source code: 17 | * Copyright (C) Igor Sysoev 18 | * Copyright (C) Nginx, Inc. 19 | */ 20 | 21 | #ifndef _NGX_NATS_H_INCLUDED_ 22 | #define _NGX_NATS_H_INCLUDED_ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | /* 30 | * Nginx reverses the characters so printing this directly on 31 | * little endian processors will print it correctly. This is 32 | * stored in an int variable. 33 | */ 34 | #define NGX_NATS_MODULE 0x5354414E /* "NATS" in reverse */ 35 | 36 | #define NGX_NATS_CONF 0x02000000 37 | 38 | #define NGX_NATS_CONN_POOL_SIZE (1024) 39 | 40 | #define NGX_NATS_DEFAULT_PORT 4222 41 | #define NGX_NATS_DEFAULT_RECONNECT 1000 /* milliseconds */ 42 | #define NGX_NATS_DEFAULT_PING 20000 /* milliseconds */ 43 | 44 | #define NGX_NATS_DEFAULT_USER "continuum.router" 45 | 46 | #define NGX_NATS_MAS_USER_PASS_LEN (512) 47 | 48 | 49 | /* 50 | * Used by ngx_nats_core_module and maybe other future NATS modules. 51 | * ngx_nats_module is not of this type, it is of type ngx_core_module_t. 52 | */ 53 | typedef struct { 54 | ngx_str_t name; 55 | void *(*create_conf)(ngx_cycle_t *cycle); 56 | char *(*init_conf)(ngx_conf_t *cf, void *conf); 57 | 58 | } ngx_nats_module_t; 59 | 60 | 61 | /* 62 | * NATS server description from the nats{...} config. 63 | */ 64 | typedef struct { 65 | ngx_str_t url; 66 | 67 | ngx_addr_t *addrs; 68 | ngx_uint_t naddrs; 69 | 70 | } ngx_nats_server_t; 71 | 72 | /* 73 | * Core configuration of NATS. 74 | */ 75 | typedef struct { 76 | ngx_str_t *name; /* module name */ 77 | 78 | ngx_pool_t *pool; /* from cycle->pool */ 79 | ngx_log_t *log; /* TODO: dedicated NATS log? */ 80 | 81 | void *data; /* pointer to ngx_nats_data_t */ 82 | 83 | /* From configuration or defaults */ 84 | 85 | ngx_array_t *servers; /* of ngx_nats_server_t elements */ 86 | 87 | ngx_msec_t reconnect_interval; /* millis */ 88 | ngx_msec_t ping_interval; /* millis */ 89 | 90 | ngx_str_t user; /* common in nats{...} section */ 91 | ngx_str_t password; 92 | 93 | } ngx_nats_core_conf_t; 94 | 95 | 96 | #define ngx_nats_get_conf(ctx, module) \ 97 | (*(ngx_get_conf(ctx, ngx_nats_module))) [module.ctx_index]; 98 | #define ngx_nats_get_core_conf(ctx) \ 99 | (*(ngx_get_conf(ctx, ngx_nats_module))) [ngx_nats_core_module.ctx_index]; 100 | 101 | 102 | /* 103 | * Main initialization called from process init function. 104 | * Implemented in ngx_nats_comm.c. 105 | */ 106 | ngx_int_t ngx_nats_init(ngx_nats_core_conf_t * nccf); 107 | void ngx_nats_exit(ngx_nats_core_conf_t * nccf); 108 | 109 | 110 | /* 111 | * Public client interface. 112 | */ 113 | typedef struct ngx_nats_client_s ngx_nats_client_t; 114 | 115 | struct ngx_nats_message_s { 116 | ngx_pool_t *pool; 117 | ngx_nats_client_t *client; 118 | ngx_int_t sid; 119 | ngx_str_t *subject; 120 | ngx_str_t *replyto; 121 | ngx_str_t data; 122 | void *client_subscription_data; 123 | }; 124 | 125 | typedef struct ngx_nats_message_s ngx_nats_message_t; 126 | 127 | typedef void (*ngx_nats_connected_pt)(ngx_nats_client_t *client); 128 | typedef void (*ngx_nats_disconnected_pt)(ngx_nats_client_t *client); 129 | typedef void (*ngx_nats_handle_msg_pt)(ngx_nats_message_t *m); 130 | 131 | struct ngx_nats_client_s { 132 | 133 | ngx_nats_connected_pt connected; 134 | ngx_nats_disconnected_pt disconnected; 135 | 136 | void *data; 137 | 138 | }; 139 | 140 | ngx_int_t ngx_nats_add_client(ngx_nats_client_t *client); 141 | 142 | ngx_int_t ngx_nats_publish(ngx_nats_client_t *client, ngx_str_t *subject, 143 | ngx_str_t *replyto, u_char *data, ngx_uint_t len); 144 | 145 | ngx_int_t ngx_nats_subscribe(ngx_nats_client_t *client, ngx_str_t *subject, 146 | ngx_int_t max, ngx_nats_handle_msg_pt handle_msg, 147 | void *client_subscription_data); 148 | 149 | ngx_int_t ngx_nats_unsubscribe(ngx_nats_client_t *client, ngx_int_t sid); 150 | 151 | ngx_int_t ngx_nats_create_inbox(u_char *buf, size_t bufsize); 152 | 153 | /* May return NULL, then we don't know local IP */ 154 | ngx_addr_t * ngx_nats_get_local_ip(void); 155 | 156 | /*void ngx_nats_seed_random(void); -- deprecated */ 157 | 158 | 159 | void ngx_nats_init_random(void); 160 | uint32_t ngx_nats_next_random(void); 161 | void ngx_nats_test_random(void); 162 | 163 | 164 | #endif /* _NGX_NATS_H_INCLUDED_ */ 165 | 166 | -------------------------------------------------------------------------------- /src/ngx_nats_comm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 (C) The NATS Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * Based on Nginx source code: 17 | * Copyright (C) Igor Sysoev 18 | * Copyright (C) Nginx, Inc. 19 | */ 20 | 21 | #include "ngx_nats_comm.h" 22 | #include "ngx_nats_protocol.h" 23 | #include "ngx_nats_json.h" 24 | 25 | /* 26 | * Implements communication with the NATS server. Connect, reconnect, 27 | * sending PINGs, sending messages and processing of the incoming messages. 28 | */ 29 | 30 | /*--------------------------------------------------------------------------- 31 | * Forward declarations. 32 | *--------------------------------------------------------------------------*/ 33 | 34 | static void ngx_nats_connect(ngx_nats_data_t *nd); 35 | static void ngx_nats_process_reconnect(ngx_nats_data_t *nd); 36 | static void ngx_nats_add_reconnect_timer(ngx_nats_data_t * nd); 37 | 38 | static void ngx_nats_connection_init(ngx_nats_connection_t *nc); 39 | static void ngx_nats_flush(ngx_nats_connection_t *nc); 40 | static ngx_int_t ngx_nats_add_message(ngx_nats_connection_t *nc, 41 | char *message, size_t size); 42 | 43 | static ngx_int_t ngx_nats_get_peer(ngx_peer_connection_t *pc, void *data); 44 | static void ngx_nats_free_peer(ngx_peer_connection_t *pc, 45 | void *data, ngx_uint_t state); 46 | 47 | static ngx_int_t ngx_nats_conn_err_reported = 0; 48 | 49 | ngx_nats_data_t *ngx_nats_data = NULL; 50 | 51 | /*--------------------------------------------------------------------------- 52 | * Implementations. 53 | *--------------------------------------------------------------------------*/ 54 | 55 | ngx_nats_buf_t * 56 | ngx_nats_buf_create(ngx_pool_t *pool, size_t size) 57 | { 58 | ngx_nats_buf_t *buf; 59 | 60 | buf = ngx_pcalloc(pool, sizeof(ngx_nats_buf_t)); 61 | if (buf == NULL) { 62 | return NULL; 63 | } 64 | 65 | buf->log = pool->log; 66 | 67 | buf->buf = ngx_alloc(size, pool->log); 68 | if (buf->buf == NULL) { 69 | return NULL; 70 | } 71 | 72 | buf->cap = size; 73 | 74 | return buf; 75 | } 76 | 77 | 78 | void 79 | ngx_nats_buf_free_buf(ngx_nats_buf_t *buf) 80 | { 81 | if (buf->buf != NULL) { 82 | ngx_free(buf->buf); 83 | } 84 | 85 | ngx_memzero(buf, sizeof(ngx_nats_buf_t)); 86 | } 87 | 88 | 89 | void 90 | ngx_nats_buf_reset(ngx_nats_buf_t *buf) 91 | { 92 | buf->pos = 0; 93 | buf->end = 0; 94 | } 95 | 96 | 97 | void 98 | ngx_nats_buf_compact(ngx_nats_buf_t *buf) 99 | { 100 | if (buf->pos == buf->end) { 101 | buf->pos = 0; 102 | buf->end = 0; 103 | return; 104 | } 105 | 106 | if (buf->pos > 0) { 107 | ngx_memmove(buf->buf, buf->buf + buf->pos, buf->end - buf->pos); 108 | buf->end -= buf->pos; 109 | buf->pos = 0; 110 | } 111 | } 112 | 113 | 114 | ngx_int_t 115 | ngx_nats_buf_ensure(ngx_nats_buf_t *buf, size_t size, ngx_int_t compact) 116 | { 117 | size_t n, tail, ns, nt; 118 | u_char *bnew; 119 | 120 | if (buf->pos == buf->end && buf->pos != 0) { 121 | buf->pos = 0; 122 | buf->end = 0; 123 | } 124 | 125 | tail = buf->cap - buf->end; 126 | if (tail >= size) { 127 | return NGX_OK; 128 | } 129 | 130 | if (compact != 0 && buf->pos > 0 && (size <= (tail + buf->pos))) { 131 | ngx_memmove(buf->buf, buf->buf + buf->pos, buf->end - buf->pos); 132 | buf->end -= buf->pos; 133 | buf->pos = 0; 134 | tail = buf->cap - buf->end; 135 | } 136 | 137 | if (tail >= size) { 138 | return NGX_OK; 139 | } 140 | 141 | if (buf->cap >= NGX_NATS_MAX_MESSAGE_SIZE) { 142 | ngx_log_error(NGX_LOG_CRIT, buf->log, 0, 143 | "attempt to increase NATS buffer size to more than maximum of %i", 144 | (ngx_int_t)NGX_NATS_MAX_MESSAGE_SIZE); 145 | return NGX_ERROR; 146 | } 147 | 148 | /* realloc */ 149 | 150 | n = buf->end - buf->pos; /* current bytes */ 151 | nt = n + size; /* total need */ 152 | 153 | ns = buf->cap * 2; 154 | while(ns < nt) { 155 | ns <<= 1; /* checked above cap is limited */ 156 | } 157 | 158 | bnew = ngx_alloc(ns, buf->log); 159 | if (bnew == NULL) { 160 | return NGX_ERROR; 161 | } 162 | 163 | ngx_memcpy(bnew, buf->buf + buf->pos, n); 164 | 165 | ngx_free(buf->buf); 166 | 167 | buf->buf = bnew; 168 | buf->cap = ns; 169 | 170 | return NGX_OK; 171 | } 172 | 173 | 174 | /* 175 | * Copied from ngx_http_upstream.c. 176 | */ 177 | static ngx_int_t 178 | ngx_nats_test_connect(ngx_connection_t *c) 179 | { 180 | int err; 181 | socklen_t len; 182 | 183 | #if (NGX_HAVE_KQUEUE) 184 | 185 | if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { 186 | if (c->write->pending_eof) { 187 | return NGX_ERROR; 188 | } 189 | 190 | } else 191 | #endif 192 | { 193 | err = 0; 194 | len = sizeof(int); 195 | 196 | /* 197 | * BSDs and Linux return 0 and set a pending error in err 198 | * Solaris returns -1 and sets errno 199 | */ 200 | 201 | if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len) 202 | == -1) 203 | { 204 | err = ngx_errno; 205 | } 206 | 207 | if (err) { 208 | return NGX_ERROR; 209 | } 210 | } 211 | 212 | return NGX_OK; 213 | } 214 | 215 | 216 | static void 217 | ngx_nats_close_connection(ngx_nats_connection_t *nc, ngx_int_t reason, 218 | ngx_int_t reconnect) 219 | { 220 | ngx_connection_t *c = nc->pc.connection; 221 | ngx_nats_data_t *nd = nc->nd; 222 | ngx_nats_client_t **pclient; 223 | ngx_int_t i, n, immediate; 224 | 225 | if (nc->ping_timer.timer_set) { 226 | ngx_del_timer(&nc->ping_timer); 227 | } 228 | 229 | if (c->fd != -1) { 230 | ngx_close_connection(c); 231 | } 232 | 233 | if (c->read->timer_set) { 234 | ngx_event_del_timer(c->read); 235 | } 236 | 237 | if (c->write->timer_set) { 238 | ngx_event_del_timer(c->write); 239 | } 240 | 241 | immediate = 0; 242 | 243 | if (!(nc->state & NGX_NATS_STATE_BYTES_EXCHANGED)) { 244 | 245 | /* reconnect immediately because we simply could not connect */ 246 | immediate = 1; 247 | 248 | ngx_log_error(NGX_LOG_DEBUG, nc->nd->log, 0, 249 | "cannot connect to NATS at '%V'", 250 | &nc->server->url); 251 | 252 | } else if (nc->state == NGX_NATS_STATE_READY) { 253 | 254 | ngx_log_error(NGX_LOG_WARN, nc->nd->log, 0, 255 | "disconnected from NATS at '%V'", 256 | &nc->server->url); 257 | 258 | /* Call disconnected in clients */ 259 | 260 | n = nd->cd.clients.nelts; 261 | pclient = nd->cd.clients.elts; 262 | 263 | for (i = 0; i < n; i++, pclient++) { 264 | if ((*pclient)->disconnected) { 265 | (*pclient)->disconnected(*pclient); 266 | } 267 | } 268 | 269 | } else { 270 | 271 | /* TODO: handle partial connect */ 272 | 273 | } 274 | 275 | nd->cd.subs.nelts = 0; /* remove all subscriptions */ 276 | nd->cd.next_id = 0; 277 | 278 | nd->nc = NULL; 279 | 280 | /* clear buffers */ 281 | ngx_nats_buf_reset(nd->nc_read_buf); 282 | ngx_nats_buf_reset(nd->nc_write_buf); 283 | 284 | if (reconnect != 0) { 285 | 286 | /* 287 | * if we could not connect at all or simply disconnected 288 | * then try to reconnect immediately. 289 | * If we did connect and connection broke because of the internal 290 | * error or bad message from NATS then wait before reconnecting 291 | * so a poison pill message, or we're out of memory, do not put 292 | * us into a tight connection loop. 293 | */ 294 | 295 | if (reason == NGX_NATS_REASON_DISCONNECTED || immediate) { 296 | 297 | /* this reconnects immediately */ 298 | ngx_nats_process_reconnect(nd); 299 | 300 | } else { 301 | 302 | /* this runs timer, then reconnects */ 303 | ngx_nats_add_reconnect_timer(nd); 304 | 305 | } 306 | } 307 | } 308 | 309 | 310 | static void 311 | ngx_nats_ping_handler(ngx_event_t *ev) 312 | { 313 | // ngx_nats_ping_handler is invoked only by the timer, OR on when nginx 314 | // does ngx_event_cancel_timers, e.g. when processing SIGHUP. We only want 315 | // it to act when invoked normally, from a timer 316 | if (!ev->timer_set) { 317 | return; 318 | } 319 | 320 | ngx_nats_connection_t *nc = ev->data; 321 | 322 | ngx_nats_add_message(nc, "PING\r\n", 6); 323 | ngx_nats_flush(nc); 324 | 325 | ngx_add_timer(&nc->ping_timer, nc->nd->nccf->ping_interval); 326 | 327 | // make sure this timer can be canceled immediately when worker processes 328 | // exit gracefully 329 | nc->ping_timer.cancelable = 1; 330 | } 331 | 332 | 333 | static void 334 | ngx_nats_check_connected(ngx_nats_connection_t *nc) 335 | { 336 | if (nc->state & NGX_NATS_STATE_BYTES_EXCHANGED) { 337 | return; 338 | } 339 | 340 | /* 341 | * Notice this only means we have successfully sent to or 342 | * received some bytes from NATS. This does not yet mean 343 | * we had successful handshake. 344 | */ 345 | 346 | nc->state |= NGX_NATS_STATE_BYTES_EXCHANGED; 347 | 348 | if (!nc->ping_timer.timer_set) { 349 | 350 | nc->ping_timer.handler = ngx_nats_ping_handler; 351 | nc->ping_timer.log = nc->nd->log; 352 | nc->ping_timer.data = nc; 353 | 354 | ngx_add_timer(&nc->ping_timer, nc->nd->nccf->ping_interval); 355 | 356 | // make sure this timer can be canceled immediately when worker 357 | // processes exit gracefully 358 | nc->ping_timer.cancelable = 1; 359 | } 360 | 361 | ngx_log_error(NGX_LOG_DEBUG, nc->nd->log, 0, 362 | "connect() to NATS at '%V' succeeded", 363 | &nc->server->url); 364 | } 365 | 366 | 367 | static void 368 | ngx_nats_flush(ngx_nats_connection_t *nc) 369 | { 370 | ngx_connection_t *c; 371 | ngx_nats_buf_t *buf = nc->write_buf; 372 | size_t slen; 373 | ssize_t n; 374 | 375 | slen = buf->end - buf->pos; 376 | if (slen == 0) { 377 | buf->pos = 0; 378 | buf->end = 0; 379 | return; 380 | } 381 | 382 | c = nc->pc.connection; 383 | 384 | n = c->send(c, (u_char *) (buf->buf + buf->pos), slen); 385 | 386 | if (n > 0) { 387 | 388 | ngx_nats_check_connected(nc); 389 | 390 | buf->pos += n; 391 | 392 | if (buf->pos == buf->end) { 393 | 394 | buf->pos = 0; 395 | buf->end = 0; 396 | 397 | if (ngx_handle_write_event(c->write, 0) != NGX_OK) { 398 | ngx_nats_close_connection(nc, 399 | NGX_NATS_REASON_INTERNAL_ERROR, 1); 400 | return; 401 | } 402 | } 403 | 404 | return; 405 | } 406 | 407 | if (n == NGX_ERROR) { 408 | ngx_nats_close_connection(nc, 409 | NGX_NATS_REASON_DISCONNECTED, 1); 410 | return; 411 | } 412 | 413 | if (n == NGX_AGAIN) { 414 | 415 | /* Will need to try send later */ 416 | 417 | ngx_add_timer(c->write, 5000); /* TODO: configurable */ 418 | 419 | if (ngx_handle_write_event(c->write, 0) != NGX_OK) { 420 | ngx_nats_close_connection(nc, NGX_NATS_REASON_INTERNAL_ERROR, 1); 421 | return; 422 | } 423 | 424 | return; 425 | } 426 | } 427 | 428 | 429 | static ngx_int_t 430 | ngx_nats_add_message(ngx_nats_connection_t *nc, char *message, size_t size) 431 | { 432 | ngx_int_t rc; 433 | ngx_nats_buf_t *buf = nc->write_buf; 434 | 435 | if (size == 0) { 436 | size = strlen(message); 437 | if (size == 0) { 438 | return NGX_OK; 439 | } 440 | } 441 | 442 | rc = ngx_nats_buf_ensure(buf, size, 1); 443 | if (rc != NGX_OK) { 444 | return rc; 445 | } 446 | 447 | ngx_memcpy(buf->buf + buf->end, message, size); 448 | buf->end += size; 449 | 450 | return NGX_OK; 451 | } 452 | 453 | 454 | static ngx_int_t 455 | ngx_nats_connection_ready(ngx_nats_connection_t *nc) 456 | { 457 | ngx_nats_data_t *nd = nc->nd; 458 | ngx_log_t *log = nd->log; 459 | ngx_nats_client_t **pclient; 460 | ngx_int_t i, n; 461 | 462 | ngx_nats_conn_err_reported = 0; 463 | 464 | /* force logging this regardless of log level */ 465 | 466 | n = log->log_level; 467 | log->log_level = NGX_LOG_INFO; 468 | 469 | ngx_log_error(NGX_LOG_INFO, nd->log, 0, 470 | "connected to NATS at '%V': version='%V'", 471 | &nc->server->url, 472 | nc->srv_version); 473 | 474 | log->log_level = n; /* restore log level */ 475 | 476 | /* Call connected in clients */ 477 | 478 | n = nd->cd.clients.nelts; 479 | pclient = nd->cd.clients.elts; 480 | 481 | for (i = 0; i < n; i++, pclient++) { 482 | (*pclient)->connected(*pclient); 483 | } 484 | 485 | return NGX_OK; 486 | } 487 | 488 | 489 | static ngx_int_t 490 | ngx_nats_parse_info(ngx_nats_connection_t *nc, ngx_str_t *bytes, 491 | ngx_nats_msg_t *msg) 492 | { 493 | ngx_nats_json_value_t *json; 494 | ngx_nats_json_field_t *f; 495 | ngx_nats_json_object_t *info; 496 | ngx_int_t rc, n, i; 497 | u_char *name; 498 | ngx_str_t msg_str = { msg->bend - msg->bstart, bytes->data + msg->bstart }; 499 | 500 | rc = ngx_nats_json_parse(nc->pool, &msg_str, &json); 501 | if (rc < 0 || json->type != NGX_NATS_JSON_OBJECT) { 502 | return NGX_ERROR; 503 | } 504 | 505 | info = (ngx_nats_json_object_t *) json->value.vobj; 506 | n = info->fields->nelts; 507 | f = (ngx_nats_json_field_t *) info->fields->elts; 508 | 509 | for (i = 0; i < n; i++, f++) { 510 | 511 | name = f->name.data; 512 | 513 | if (ngx_strcasecmp(name, (u_char *)"server_id") == 0) { 514 | if (f->value.type != NGX_NATS_JSON_STRING) { 515 | return NGX_ERROR; 516 | } 517 | nc->srv_id = f->value.value.vstr; /* in pool */ 518 | } 519 | else if (ngx_strcasecmp(name, (u_char *)"host") == 0) { 520 | if (f->value.type != NGX_NATS_JSON_STRING) { 521 | return NGX_ERROR; 522 | } 523 | nc->srv_host = f->value.value.vstr; /* in pool */ 524 | } 525 | else if (ngx_strcasecmp(name, (u_char *)"port") == 0) { 526 | if (f->value.type != NGX_NATS_JSON_INTEGER) { 527 | return NGX_ERROR; 528 | } 529 | nc->srv_port = (ngx_int_t)f->value.value.vint; 530 | } 531 | else if (ngx_strcasecmp(name, (u_char *)"version") == 0) { 532 | if (f->value.type != NGX_NATS_JSON_STRING) { 533 | return NGX_ERROR; 534 | } 535 | nc->srv_version = f->value.value.vstr; 536 | } 537 | else if (ngx_strcasecmp(name, (u_char *)"auth_required") == 0) { 538 | if (f->value.type != NGX_NATS_JSON_BOOLEAN) { 539 | return NGX_ERROR; 540 | } 541 | nc->srv_auth_required = f->value.value.vint == 0 ? 0 : 1; 542 | } 543 | else if (ngx_strcasecmp(name, (u_char *)"ssl_required") == 0) { 544 | if (f->value.type != NGX_NATS_JSON_BOOLEAN) { 545 | return NGX_ERROR; 546 | } 547 | nc->srv_ssl_required = f->value.value.vint == 0 ? 0 : 1; 548 | } 549 | else if (ngx_strcasecmp(name, (u_char *)"max_payload") == 0) { 550 | if (f->value.type != NGX_NATS_JSON_INTEGER) { 551 | return NGX_ERROR; 552 | } 553 | nc->srv_max_payload = (ngx_int_t)f->value.value.vint; 554 | } 555 | else if (ngx_strcasecmp(name, (u_char *)"go") == 0) { 556 | if (f->value.type != NGX_NATS_JSON_STRING) { 557 | return NGX_ERROR; 558 | } 559 | nc->go_version = f->value.value.vstr; /* in pool */ 560 | } 561 | } 562 | 563 | return NGX_OK; 564 | } 565 | 566 | /* 567 | * When have time switch to using hash. Currenly we have only 568 | * a few subscriptions like 4-5 at a time so linear search is 569 | * not too bad but of course using hash is in order. 570 | * One day in future, when I'm not so darn overwhelmed with work, 571 | * I still hope it'll happen :). 572 | */ 573 | 574 | ngx_nats_subscription_t * 575 | ngx_nats_get_subscription(ngx_nats_connection_t *nc, ngx_int_t sid) 576 | { 577 | ngx_nats_subscription_t *sub; 578 | ngx_int_t i, n; 579 | 580 | n = nc->nd->cd.subs.nelts; 581 | sub = nc->nd->cd.subs.elts; 582 | 583 | for (i = 0; i < n; i++, sub++) { 584 | if (sub->sid == sid) 585 | return sub; 586 | } 587 | 588 | return NULL; 589 | } 590 | 591 | ngx_nats_subscription_t * 592 | ngx_nats_add_subscription(ngx_nats_connection_t *nc, ngx_int_t sid) 593 | { 594 | ngx_nats_subscription_t *sub; 595 | ngx_int_t i, n; 596 | 597 | n = nc->nd->cd.subs.nelts; 598 | sub = nc->nd->cd.subs.elts; 599 | 600 | for (i = 0; i < n; i++, sub++) { 601 | if (sub->sid == 0) 602 | return sub; 603 | } 604 | 605 | sub = ngx_array_push(&nc->nd->cd.subs); 606 | if (sub == NULL) { 607 | return NULL; 608 | } 609 | 610 | sub->sid = sid; 611 | return sub; 612 | } 613 | 614 | ngx_int_t 615 | ngx_nats_remove_subscription(ngx_nats_connection_t *nc, ngx_int_t sid) 616 | { 617 | ngx_nats_subscription_t *sub; 618 | ngx_int_t i, n; 619 | 620 | if (sid == 0) { 621 | return sid; 622 | } 623 | 624 | n = nc->nd->cd.subs.nelts; 625 | sub = nc->nd->cd.subs.elts; 626 | 627 | for (i = 0; i < n; i++, sub++) { 628 | if (sub->sid == sid) { 629 | sub->sid = 0; 630 | return sid; 631 | } 632 | } 633 | 634 | return 0; 635 | } 636 | 637 | static void 638 | ngx_nats_process_msg(ngx_nats_connection_t *nc, ngx_nats_buf_t *buf, 639 | ngx_nats_msg_t *msg) 640 | { 641 | ngx_nats_subscription_t *sub; 642 | ngx_nats_handle_msg_pt hm; 643 | void *sub_data; 644 | ngx_nats_client_t *client; 645 | ngx_str_t *r = NULL; 646 | ngx_int_t sid; 647 | ngx_pool_t *pool; 648 | ngx_nats_message_t *m; 649 | 650 | sid = msg->sid; 651 | 652 | sub = ngx_nats_get_subscription(nc, sid); 653 | if (sub == NULL) { 654 | return; 655 | } 656 | 657 | if (msg->replyto.len > 0) { 658 | r = &msg->replyto; 659 | } 660 | 661 | /* we may remove subscription so cache these */ 662 | hm = sub->handle_msg; 663 | sub_data = sub->client_subscription_data; 664 | client = sub->client; 665 | 666 | if (sub->max > 0) { 667 | sub->recv++; 668 | if (sub->recv >= sub->max) { 669 | ngx_nats_remove_subscription(nc, sub->sid); 670 | } 671 | } 672 | 673 | pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, nc->nd->log); 674 | if (pool == NULL) { 675 | ngx_log_error(NGX_LOG_CRIT, nc->nd->log, 0, 676 | "%s: ngx_create_pool failed", __func__); 677 | return; 678 | } 679 | 680 | m = ngx_pcalloc(pool, sizeof(ngx_nats_message_t)); 681 | if (m == NULL) { 682 | ngx_log_error(NGX_LOG_CRIT, nc->nd->log, 0, 683 | "%s: ngx_pcalloc failed", __func__); 684 | goto DONE; 685 | } 686 | 687 | m->client = client; 688 | m->sid = sid; 689 | m->subject = &msg->subject; 690 | m->replyto = r; 691 | m->pool = pool; 692 | m->client_subscription_data = sub_data; 693 | m->data.data = (u_char *) (buf->buf + buf->pos + msg->bstart); 694 | m->data.len = msg->bend - msg->bstart; 695 | 696 | hm(m); 697 | 698 | DONE: 699 | ngx_destroy_pool(pool); 700 | } 701 | 702 | 703 | static ngx_int_t 704 | _nats_process_buffer_msg(ngx_nats_connection_t *nc, ngx_nats_buf_t *buf) 705 | { 706 | ngx_int_t rc, skip; 707 | ngx_nats_msg_t msg; 708 | ngx_str_t bytes; 709 | char *ce; 710 | 711 | if (buf->pos == buf->end) { 712 | ngx_nats_buf_compact(buf); 713 | return NGX_OK; 714 | } 715 | 716 | bytes.data = buf->buf + buf->pos; 717 | bytes.len = buf->end - buf->pos; 718 | rc = ngx_nats_parse(&bytes, &msg); 719 | 720 | skip = rc; /* save it */ 721 | 722 | if (rc <= 0) { 723 | 724 | if (rc == NGX_NATS_PROTO_AGAIN) { 725 | 726 | /* have incomplete message */ 727 | 728 | ngx_nats_buf_compact(buf); 729 | 730 | if (buf->end >= (buf->cap - 1)) { 731 | 732 | /* 733 | * this means we have full buffer but it doesn't fit 734 | * one message, so need to grow the buffer. 735 | * TODO: not crucial but if it is MSG message then 736 | * we may know by how much to grow the buffer. 737 | * I don't have it now so will double the buffer, 738 | * possibly several times, but it'll happen only until 739 | * the buffer gorws enough, so is OK. 740 | */ 741 | 742 | if ((nc->srv_max_payload > 0 && 743 | buf->cap >= (size_t)nc->srv_max_payload) || 744 | (buf->cap >= NGX_NATS_MAX_MESSAGE_SIZE)) { 745 | 746 | /* NATS sent message larger than promised */ 747 | ngx_log_error(NGX_LOG_CRIT, nc->nd->log, 0, 748 | "NATS sent message larger than max payload of %i", 749 | buf->cap); 750 | 751 | ngx_nats_close_connection(nc, 752 | NGX_NATS_REASON_BAD_PROTOCOL, 1); 753 | 754 | return NGX_ERROR; 755 | } 756 | 757 | /* this will double the buf */ 758 | rc = ngx_nats_buf_ensure(buf, buf->cap - 1, 0); 759 | if (rc != NGX_OK) { 760 | /* out of memory */ 761 | ngx_log_error(NGX_LOG_CRIT, nc->nd->log, 0, 762 | "out of memory receiving NATS message"); 763 | 764 | ngx_nats_close_connection(nc, 765 | NGX_NATS_REASON_NO_MEMORY, 1); 766 | 767 | return NGX_ERROR; 768 | } 769 | } 770 | 771 | return NGX_OK; 772 | } 773 | 774 | if (rc == NGX_NATS_PROTO_ERR_ERROR) { 775 | 776 | ngx_log_error(NGX_LOG_ERR, nc->nd->log, 0, 777 | "internal error processing NATS message"); 778 | 779 | } else { 780 | 781 | ngx_log_error(NGX_LOG_ERR, nc->nd->log, 0, 782 | "NATS at '%V' sent invalid message, error=%d", 783 | &nc->server->url, (int)rc); 784 | 785 | } 786 | 787 | ngx_nats_close_connection(nc, NGX_NATS_REASON_BAD_PROTOCOL, 1); 788 | 789 | return NGX_ERROR; 790 | } 791 | 792 | /* handle message */ 793 | 794 | if (msg.type == NGX_NATS_MSG_OK) { 795 | 796 | /* ignore all OKs */ 797 | 798 | } 799 | else if (msg.type == NGX_NATS_MSG_ERR) { 800 | 801 | ce = ""; 802 | 803 | if ((nc->state & NGX_NATS_STATE_CONNECT_SENT) && 804 | !(nc->state & NGX_NATS_STATE_CONNECT_OKAYED)) { 805 | ce = " connect"; 806 | } 807 | 808 | if (msg.bstart < msg.bend) { 809 | ngx_log_error(NGX_LOG_ERR, nc->nd->log, 0, 810 | "NATS at '%V' " 811 | "returned%s error: %V", 812 | &nc->server->url, ce, &bytes); 813 | } 814 | else { 815 | ngx_log_error(NGX_LOG_ERR, nc->nd->log, 0, 816 | "NATS at '%V' " 817 | "returned%s error with no message", 818 | &nc->server->url, ce); 819 | } 820 | 821 | if ((nc->state & NGX_NATS_STATE_CONNECT_SENT) && 822 | !(nc->state & NGX_NATS_STATE_CONNECT_OKAYED)) { 823 | 824 | ngx_nats_close_connection(nc, 825 | NGX_NATS_REASON_CONNECT_REFUSED, 1); 826 | 827 | return NGX_ERROR; 828 | } 829 | 830 | /* TODO: what am I supposed to do about it? */ 831 | 832 | } 833 | else if (msg.type == NGX_NATS_MSG_PING) { 834 | 835 | ngx_nats_add_message(nc, "PONG\r\n", 6); 836 | 837 | } 838 | else if (msg.type == NGX_NATS_MSG_PONG) { 839 | 840 | if ((nc->state & NGX_NATS_STATE_CONNECT_OKAYED) == 0) { 841 | nc->state |= NGX_NATS_STATE_CONNECT_OKAYED; 842 | 843 | nc->state = NGX_NATS_STATE_READY; 844 | 845 | ngx_nats_connection_ready(nc); 846 | } 847 | 848 | /* otherwise just ignore */ 849 | } 850 | else if (msg.type == NGX_NATS_MSG_INFO) { 851 | 852 | if (msg.bstart >= msg.bend) { 853 | 854 | ngx_log_error(NGX_LOG_ERR, nc->nd->log, 0, 855 | "NATS at '%V' sent INFO message with empty text", 856 | &nc->server->url); 857 | 858 | ngx_nats_close_connection(nc, 859 | NGX_NATS_REASON_BAD_PROTOCOL, 1); 860 | return NGX_ERROR; 861 | } 862 | 863 | rc = ngx_nats_parse_info(nc, &bytes, &msg); 864 | 865 | if (rc != NGX_OK) { 866 | 867 | ngx_log_error(NGX_LOG_ERR, nc->nd->log, 0, 868 | "NATS at '%V' sent invalid INFO message, error=%d", 869 | &nc->server->url, (int)rc); 870 | 871 | ngx_nats_close_connection(nc, 872 | NGX_NATS_REASON_BAD_PROTOCOL, 1); 873 | return NGX_ERROR; 874 | } 875 | } 876 | else if (msg.type == NGX_NATS_MSG_MSG) { 877 | 878 | ngx_nats_process_msg(nc, buf, &msg); 879 | 880 | } else { 881 | 882 | ngx_log_error(NGX_LOG_ERR, nc->nd->log, 0, 883 | "NATS at '%V' sent unsupported message", 884 | &nc->server->url); 885 | 886 | ngx_nats_close_connection(nc, 887 | NGX_NATS_REASON_BAD_PROTOCOL, 1); 888 | 889 | return NGX_ERROR; 890 | } 891 | 892 | return skip; 893 | } 894 | 895 | 896 | static ngx_int_t 897 | ngx_nats_process_buffer(ngx_nats_connection_t *nc, ngx_nats_buf_t *buf) 898 | { 899 | ngx_int_t rc; 900 | 901 | for ( ;; ) { 902 | 903 | rc = _nats_process_buffer_msg(nc, buf); 904 | 905 | /* Return if rc is NGX_OK or any error */ 906 | if (rc <= 0) { 907 | return rc; 908 | } 909 | 910 | /* Otherwise skip processed message and continue */ 911 | buf->pos += rc; 912 | } 913 | } 914 | 915 | static void 916 | ngx_nats_read_from_nats(ngx_connection_t *c) 917 | { 918 | ngx_nats_connection_t *nc = c->data; 919 | ngx_nats_buf_t *rbuf = nc->read_buf; 920 | ngx_nats_buf_t *wbuf = nc->write_buf; 921 | ssize_t n; 922 | size_t wlen; 923 | ngx_int_t rc; 924 | 925 | wlen = wbuf->end - wbuf->pos; 926 | 927 | for ( ;; ) { 928 | 929 | n = c->recv(c, (u_char *) (rbuf->buf + rbuf->end), 930 | rbuf->cap - rbuf->end - 1); 931 | 932 | if (n == NGX_AGAIN) { 933 | 934 | if (ngx_handle_read_event(c->read, 0) != NGX_OK) { 935 | ngx_nats_close_connection(nc, 936 | NGX_NATS_REASON_INTERNAL_ERROR, 1); 937 | return; 938 | } 939 | 940 | break; 941 | } 942 | 943 | if (n == NGX_ERROR || n <= 0) { 944 | ngx_nats_close_connection(nc, NGX_NATS_REASON_DISCONNECTED, 1); 945 | return; 946 | } 947 | 948 | ngx_nats_check_connected(nc); 949 | 950 | rbuf->end += n; 951 | 952 | rc = ngx_nats_process_buffer(nc, rbuf); 953 | 954 | if (rc != NGX_OK) { 955 | ngx_nats_close_connection(nc, NGX_NATS_REASON_BAD_PROTOCOL, 1); 956 | return; 957 | } 958 | } 959 | 960 | /* 961 | * Processing could add messages into write buffer. 962 | */ 963 | if (wlen < (wbuf->end - wbuf->pos)) { 964 | ngx_nats_flush(nc); 965 | } 966 | } 967 | 968 | 969 | static void 970 | ngx_nats_write_event_handler(ngx_connection_t *c) 971 | { 972 | ngx_nats_connection_t *nc = c->data; 973 | 974 | if (c->write->timedout) { 975 | ngx_nats_close_connection(nc, NGX_NATS_REASON_WRITE_TIMEOUT, 1); 976 | return; 977 | } 978 | 979 | if (c->write->timer_set) { 980 | ngx_del_timer(c->write); 981 | } 982 | 983 | ngx_nats_connection_init(nc); 984 | ngx_nats_flush(nc); 985 | } 986 | 987 | 988 | static void 989 | ngx_nats_read_event_handler(ngx_connection_t *c) 990 | { 991 | ngx_nats_connection_t *nc = c->data; 992 | 993 | if (c->read->timedout) { 994 | ngx_nats_close_connection(nc, NGX_NATS_REASON_READ_TIMEOUT, 1); 995 | return; 996 | } 997 | 998 | if (ngx_nats_test_connect(c) != NGX_OK) { 999 | ngx_nats_close_connection(nc, NGX_NATS_REASON_CONNECT_FAILED, 1); 1000 | return; 1001 | } 1002 | 1003 | ngx_nats_read_from_nats(c); 1004 | } 1005 | 1006 | 1007 | static void 1008 | ngx_nats_connection_handler(ngx_event_t *ev) 1009 | { 1010 | ngx_connection_t *c = ev->data; 1011 | 1012 | if (ev->write) { 1013 | ngx_nats_write_event_handler(c); 1014 | } 1015 | else { 1016 | ngx_nats_read_event_handler(c); 1017 | } 1018 | } 1019 | 1020 | 1021 | static void 1022 | ngx_nats_process_reconnect(ngx_nats_data_t *nd) 1023 | { 1024 | if (nd->reconnect_timer.timer_set) { 1025 | ngx_event_del_timer(&nd->reconnect_timer); 1026 | } 1027 | 1028 | ngx_nats_connect(nd); 1029 | } 1030 | 1031 | 1032 | static void 1033 | ngx_nats_reconnect_handler(ngx_event_t *ev) 1034 | { 1035 | ngx_nats_data_t *nd = ev->data; 1036 | 1037 | ngx_nats_process_reconnect(nd); 1038 | } 1039 | 1040 | 1041 | static void 1042 | ngx_nats_add_reconnect_timer(ngx_nats_data_t * nd) 1043 | { 1044 | nd->reconnect_timer.handler = ngx_nats_reconnect_handler; 1045 | nd->reconnect_timer.log = nd->nccf->log; 1046 | nd->reconnect_timer.data = nd; 1047 | 1048 | if (!nd->reconnect_timer.timer_set) { 1049 | 1050 | nd->curr_index = -1; 1051 | ngx_add_timer(&nd->reconnect_timer, nd->nccf->reconnect_interval); 1052 | 1053 | // make sure this timer can be canceled immediately when worker 1054 | // processes exit gracefully 1055 | nd->reconnect_timer.cancelable = 1; 1056 | } 1057 | } 1058 | 1059 | 1060 | static ngx_int_t 1061 | ngx_nats_get_peer(ngx_peer_connection_t *pc, void *data) 1062 | { 1063 | /* Must exist but I don't use it. */ 1064 | return NGX_OK; 1065 | } 1066 | 1067 | 1068 | static void 1069 | ngx_nats_free_peer(ngx_peer_connection_t *pc, void *data, ngx_uint_t state) 1070 | { 1071 | /* Must exist but I don't use it. */ 1072 | } 1073 | 1074 | static void 1075 | ngx_nats_connection_init(ngx_nats_connection_t *nc) 1076 | { 1077 | /* 128 is more than hardcoded string below. Increase when need. */ 1078 | u_char connstr[NGX_NATS_MAS_USER_PASS_LEN + 128]; 1079 | u_char *p; 1080 | 1081 | p = ngx_snprintf(connstr, sizeof(connstr), 1082 | "CONNECT {\"verbose\":false,\"pedantic\":false," 1083 | "\"user\":\"%V\",\"pass\":\"%V\",\"lang\":\"c-nginx\"}\r\nPING\r\n", 1084 | &nc->nd->nccf->user, &nc->nd->nccf->password); 1085 | 1086 | if ((nc->state & NGX_NATS_STATE_CONNECT_SENT) == 0) { 1087 | 1088 | nc->state |= NGX_NATS_STATE_CONNECT_SENT; 1089 | 1090 | ngx_nats_add_message(nc, (char*)connstr, (p - connstr)); 1091 | 1092 | return; 1093 | } 1094 | } 1095 | 1096 | 1097 | static ngx_int_t 1098 | ngx_nats_connect_loop(ngx_nats_data_t *nd) 1099 | { 1100 | ngx_nats_core_conf_t *nccf; 1101 | ngx_nats_connection_t *nc; 1102 | ngx_nats_server_t *ns; 1103 | ngx_addr_t *a; 1104 | ngx_int_t rc, n; 1105 | ngx_connection_t *c = NULL; 1106 | 1107 | nccf = nd->nccf; 1108 | 1109 | n = nccf->servers->nelts; 1110 | 1111 | if (nd->curr_index < 0) { 1112 | 1113 | /* new reconnect loop */ 1114 | nd->curr_index = nd->last_index; 1115 | nd->nconnects++; 1116 | 1117 | } else { 1118 | 1119 | if (++nd->curr_index >= n) { 1120 | nd->curr_index = 0; 1121 | } 1122 | 1123 | if (nd->curr_index == nd->last_index) { 1124 | 1125 | /* 1126 | * means we tried each server and could not connect 1127 | * to any of them, now sleep and then repeat. 1128 | */ 1129 | 1130 | if (!ngx_nats_conn_err_reported) { 1131 | 1132 | ngx_nats_conn_err_reported = 1; 1133 | 1134 | ngx_log_error(NGX_LOG_ERR, nd->log, 0, 1135 | "cannot connect to NATS server%s, " 1136 | "will try every %d milliseconds", 1137 | (u_char*)(n > 1 ? "s" : ""), 1138 | (int)nccf->reconnect_interval); 1139 | } 1140 | 1141 | ngx_nats_add_reconnect_timer(nd); 1142 | 1143 | return NGX_DECLINED; 1144 | } 1145 | } 1146 | 1147 | ngx_reset_pool(nd->nc_pool); 1148 | ngx_nats_buf_reset(nd->nc_read_buf); 1149 | ngx_nats_buf_reset(nd->nc_write_buf); 1150 | 1151 | nc = ngx_pcalloc(nd->nc_pool, sizeof(ngx_nats_connection_t)); 1152 | if (nc == NULL) { 1153 | return NGX_ERROR; 1154 | } 1155 | 1156 | nd->nc = nc; 1157 | nc->nd = nd; 1158 | 1159 | ns = (ngx_nats_server_t *) nccf->servers->elts; 1160 | ns = ns + nd->curr_index; 1161 | 1162 | a = ns->addrs; /* TODO: handle multiple addrs? */ 1163 | 1164 | nc->pc.data = nd; 1165 | nc->pool = nd->nc_pool; 1166 | nc->read_buf = nd->nc_read_buf; 1167 | nc->write_buf = nd->nc_write_buf; 1168 | nc->pc.log = nccf->log; 1169 | #if (NGX_HAVE_KQUEUE) 1170 | /* This prevents Nginx printing: 1171 | * kevent() reported about an closed connection (61: Connection refused) 1172 | * every time connection to NATS fails, it prints only if the error 1173 | * log level is set to INFO which we don't. 1174 | */ 1175 | nc->pc.log_error = NGX_ERROR_INFO; 1176 | #endif 1177 | nc->pc.sockaddr = a->sockaddr; 1178 | nc->pc.socklen = a->socklen; 1179 | nc->pc.name = &ns->url; 1180 | nc->pc.tries = 1; 1181 | nc->pc.get = ngx_nats_get_peer; 1182 | nc->pc.free = ngx_nats_free_peer; 1183 | 1184 | nc->server = ns; 1185 | 1186 | rc = ngx_event_connect_peer(&nc->pc); 1187 | 1188 | if (rc == NGX_BUSY || rc == NGX_ERROR || rc == NGX_DECLINED) { 1189 | return NGX_AGAIN; 1190 | } 1191 | 1192 | if (rc == NGX_OK || rc == NGX_AGAIN) { 1193 | 1194 | c = nc->pc.connection; 1195 | 1196 | c->data = nc; 1197 | 1198 | c->write->handler = ngx_nats_connection_handler; 1199 | c->read->handler = ngx_nats_connection_handler; 1200 | 1201 | c->log = nd->log; 1202 | 1203 | c->read->log = c->log; 1204 | c->write->log = c->log; 1205 | /* TODO: do I need SSL? c->pool is for SSL only. */ 1206 | if (c->pool != NULL) 1207 | c->pool->log = c->log; 1208 | } 1209 | 1210 | if (rc == NGX_AGAIN) { 1211 | ngx_add_timer(c->write, 5000); /* TODO: configurable? */ 1212 | return NGX_OK; 1213 | } 1214 | 1215 | if (rc == NGX_OK) { 1216 | /* Connected right here. */ 1217 | ngx_nats_connection_init(nc); 1218 | ngx_nats_flush(nc); 1219 | return NGX_OK; 1220 | } 1221 | 1222 | return NGX_OK; 1223 | } 1224 | 1225 | static void 1226 | ngx_nats_connect(ngx_nats_data_t *nd) 1227 | { 1228 | ngx_int_t rc; 1229 | 1230 | for ( ;; ) { 1231 | 1232 | /* 1233 | * If this returns NGX_AGAIN then must call again to try 1234 | * another server. Otherwise break because we either connected 1235 | * or all servers failed and the retry timer was set. 1236 | */ 1237 | rc = ngx_nats_connect_loop(nd); 1238 | 1239 | if (rc != NGX_AGAIN) { 1240 | break; 1241 | } 1242 | } 1243 | } 1244 | 1245 | /* 1246 | * Called on worker process init. Initiates connecting to NATS. 1247 | */ 1248 | ngx_int_t 1249 | ngx_nats_init(ngx_nats_core_conf_t *nccf) 1250 | { 1251 | ngx_nats_data_t *nd; 1252 | 1253 | nd = (ngx_nats_data_t *) nccf->data; 1254 | 1255 | ngx_nats_data = nd; 1256 | 1257 | nd->log = nccf->log; 1258 | 1259 | /* 1260 | * Try to connect to any NATS in the list. 1261 | * If fails it'll setup the retry timer, etc. 1262 | */ 1263 | ngx_nats_connect(nd); 1264 | 1265 | return NGX_OK; 1266 | } 1267 | 1268 | void 1269 | ngx_nats_exit(ngx_nats_core_conf_t *nccf) 1270 | { 1271 | ngx_nats_data = NULL; 1272 | } 1273 | 1274 | /*--------------------------------------------------------------------------- 1275 | * Client functions. 1276 | *--------------------------------------------------------------------------*/ 1277 | 1278 | ngx_int_t 1279 | ngx_nats_add_client(ngx_nats_client_t *client) 1280 | { 1281 | ngx_nats_data_t *nd = ngx_nats_data; 1282 | ngx_nats_client_data_t *cd; 1283 | ngx_nats_client_t **c; 1284 | 1285 | if (nd == NULL) { 1286 | return NGX_ABORT; /* nats not defined in the config */ 1287 | } 1288 | 1289 | cd = &nd->cd; 1290 | 1291 | c = ngx_array_push(&cd->clients); 1292 | if (c == NULL) { 1293 | return NGX_ERROR; 1294 | } 1295 | 1296 | *c = client; 1297 | 1298 | if (nd->nc == NULL) { 1299 | return NGX_OK; 1300 | } 1301 | 1302 | if (nd->nc->state != NGX_NATS_STATE_READY) { 1303 | return NGX_OK; 1304 | } 1305 | 1306 | client->connected(client); 1307 | 1308 | return NGX_OK; 1309 | } 1310 | 1311 | 1312 | ngx_int_t 1313 | ngx_nats_publish(ngx_nats_client_t *client, ngx_str_t *subject, 1314 | ngx_str_t *replyto, u_char *data, ngx_uint_t len) 1315 | { 1316 | ngx_nats_data_t *nd = ngx_nats_data; 1317 | ngx_nats_connection_t *nc; 1318 | u_char header[512+64]; /* TODO: !! */ 1319 | u_char *p; 1320 | ngx_int_t rc; 1321 | 1322 | if (nd == NULL) { 1323 | return NGX_ABORT; /* nats not defined in the config */ 1324 | } 1325 | 1326 | if (nd->nc == NULL) { 1327 | return NGX_ERROR; /* not connected */ 1328 | } 1329 | 1330 | nc = nd->nc; 1331 | 1332 | if (nc->state != NGX_NATS_STATE_READY) { 1333 | return NGX_ERROR; /* not connected */ 1334 | } 1335 | 1336 | if (subject == NULL) { 1337 | return NGX_ERROR; 1338 | } 1339 | 1340 | if (replyto != NULL) { 1341 | if (subject->len + replyto->len > 512) { 1342 | return NGX_DECLINED; 1343 | } 1344 | p = ngx_snprintf(header, sizeof(header), 1345 | "PUB %V %V %ui\r\n", 1346 | subject, replyto, len); 1347 | } else { 1348 | if (subject->len > 512) { 1349 | return NGX_DECLINED; 1350 | } 1351 | p = ngx_snprintf(header, sizeof(header), 1352 | "PUB %V %ui\r\n", 1353 | subject, len); 1354 | } 1355 | 1356 | rc = ngx_nats_add_message(nc, (char*)header, (p - header)); 1357 | if (rc != NGX_OK) { 1358 | return rc; 1359 | } 1360 | 1361 | rc = ngx_nats_add_message(nc, (char *)data, (size_t)len); 1362 | if (rc != NGX_OK) { 1363 | return rc; 1364 | } 1365 | 1366 | rc = ngx_nats_add_message(nc, "\r\n", 2); 1367 | if (rc != NGX_OK) { 1368 | return rc; 1369 | } 1370 | 1371 | ngx_nats_flush(nd->nc); 1372 | 1373 | return NGX_OK; 1374 | } 1375 | 1376 | 1377 | ngx_int_t 1378 | ngx_nats_subscribe(ngx_nats_client_t *client, ngx_str_t *subject, 1379 | ngx_int_t max, ngx_nats_handle_msg_pt handle_msg, 1380 | void *client_subscription_data) 1381 | { 1382 | ngx_nats_data_t *nd = ngx_nats_data; 1383 | ngx_nats_connection_t *nc; 1384 | ngx_nats_client_data_t *cd; 1385 | ngx_nats_subscription_t *sub; 1386 | u_char header[512+64]; /* TODO: !! */ 1387 | u_char *p; 1388 | ngx_int_t sid, rc; 1389 | 1390 | if (nd == NULL) { 1391 | return NGX_ABORT; /* nats not defined in the config */ 1392 | } 1393 | 1394 | if (nd->nc == NULL) { 1395 | return NGX_ERROR; /* not connected */ 1396 | } 1397 | 1398 | nc = nd->nc; 1399 | 1400 | if (nc->state != NGX_NATS_STATE_READY) { 1401 | return NGX_ERROR; /* not connected */ 1402 | } 1403 | 1404 | cd = &nd->cd; 1405 | 1406 | sid = ++cd->next_id; 1407 | sub = ngx_nats_add_subscription(nc, sid); 1408 | if (sub == NULL) { 1409 | return NGX_ERROR; 1410 | } 1411 | 1412 | sub->client = client; 1413 | sub->handle_msg = handle_msg; 1414 | sub->client_subscription_data = client_subscription_data; 1415 | sub->sid = sid; 1416 | sub->max = max; 1417 | sub->recv = 0; 1418 | 1419 | /* no queue support for now... */ 1420 | p = ngx_snprintf(header, sizeof(header), "SUB %V %ui\r\n", subject, sid); 1421 | rc = ngx_nats_add_message(nc, (char*)header, (p - header)); 1422 | if (rc != NGX_OK) { 1423 | return rc; 1424 | } 1425 | 1426 | if (max > 0) { 1427 | p = ngx_snprintf(header, sizeof(header), "UNSUB %ui %ui\r\n", sid, max); 1428 | rc = ngx_nats_add_message(nc, (char*)header, (p - header)); 1429 | if (rc != NGX_OK) { 1430 | return rc; 1431 | } 1432 | } 1433 | 1434 | ngx_nats_flush(nd->nc); 1435 | 1436 | return sid; 1437 | } 1438 | 1439 | 1440 | ngx_int_t 1441 | ngx_nats_unsubscribe(ngx_nats_client_t *client, ngx_int_t sid) 1442 | { 1443 | ngx_nats_data_t *nd = ngx_nats_data; 1444 | ngx_nats_connection_t *nc; 1445 | u_char header[512+64]; /* TODO: !! */ 1446 | u_char *p; 1447 | ngx_int_t rc, removed; 1448 | 1449 | if (sid == 0) { 1450 | return NGX_DECLINED; 1451 | } 1452 | 1453 | if (nd == NULL) { 1454 | return NGX_ABORT; /* nats not defined in the config */ 1455 | } 1456 | 1457 | if (nd->nc == NULL) { 1458 | return NGX_ERROR; /* not connected */ 1459 | } 1460 | 1461 | nc = nd->nc; 1462 | 1463 | if (nc->state != NGX_NATS_STATE_READY) { 1464 | return NGX_ERROR; /* not connected */ 1465 | } 1466 | 1467 | removed = ngx_nats_remove_subscription(nc, sid); 1468 | if (removed == 0) { 1469 | return NGX_DECLINED; 1470 | } 1471 | 1472 | p = ngx_snprintf(header, sizeof(header), "UNSUB %ui\r\n", sid); 1473 | rc = ngx_nats_add_message(nc, (char*)header, (p - header)); 1474 | if (rc != NGX_OK) { 1475 | return rc; 1476 | } 1477 | 1478 | ngx_nats_flush(nd->nc); 1479 | 1480 | return NGX_OK; 1481 | } 1482 | 1483 | 1484 | static uint32_t 1485 | _nats_rand4(uint32_t a, uint32_t b, uint32_t c, uint32_t d) 1486 | { 1487 | return ((((a * 31) + b) * 31) + c) * 31 + d; 1488 | } 1489 | 1490 | /* TODO: change impl when we'll get SFMT? */ 1491 | ngx_int_t 1492 | ngx_nats_create_inbox(u_char *buf, size_t bufsize) 1493 | { 1494 | ngx_time_t *tp; 1495 | ngx_addr_t *local_ip; 1496 | u_char *pend; 1497 | size_t i; 1498 | uint32_t partA, partB, partC, partD, ipvar; 1499 | uint32_t r1, r2, r3, r4; 1500 | 1501 | if (bufsize < 34) { 1502 | return NGX_ERROR; 1503 | } 1504 | 1505 | ngx_time_update(); 1506 | tp = ngx_timeofday(); 1507 | 1508 | local_ip = ngx_nats_get_local_ip(); 1509 | 1510 | ipvar = 0; 1511 | 1512 | if (local_ip != NULL) { 1513 | for (i = 0; i < local_ip->name.len; i++) { 1514 | ipvar = (ipvar * 31) + (uint32_t)local_ip->name.data[i]; 1515 | } 1516 | } 1517 | 1518 | ngx_nats_init_random(); 1519 | 1520 | r1 = (uint32_t) ngx_nats_next_random(); 1521 | r2 = (uint32_t) ngx_nats_next_random(); 1522 | r3 = (uint32_t) ngx_nats_next_random(); 1523 | r4 = (uint32_t) ngx_nats_next_random(); 1524 | 1525 | partA = _nats_rand4(ipvar, r1, (uint32_t)ngx_pid, (uint32_t)tp->msec); 1526 | partB = _nats_rand4(ipvar, r2, (uint32_t)ngx_pid, (uint32_t)tp->sec); 1527 | partC = _nats_rand4(ipvar, r3, (uint32_t)tp->sec, (uint32_t)tp->msec); 1528 | partD = (uint32_t) (r4 & 0x00ff); /* 1 byte only */ 1529 | 1530 | pend = ngx_snprintf(buf, bufsize, "_INBOX.%08xD%08xD%08xD%02xD", partA, partB, partC, partD); 1531 | 1532 | *pend = 0; 1533 | 1534 | return (ngx_int_t)(pend - buf); 1535 | } 1536 | 1537 | 1538 | -------------------------------------------------------------------------------- /src/ngx_nats_comm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 (C) The NATS Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * Based on Nginx source code: 17 | * Copyright (C) Igor Sysoev 18 | * Copyright (C) Nginx, Inc. 19 | */ 20 | 21 | #ifndef _NGX_NATS_COMM_H_INCLUDED_ 22 | #define _NGX_NATS_COMM_H_INCLUDED_ 23 | 24 | #include "ngx_nats.h" 25 | 26 | 27 | #define NGX_NATS_REASON_CONNECT_FAILED (1) /* unable to socket into NATS */ 28 | #define NGX_NATS_REASON_CONNECT_REFUSED (2) /* NATS refused connect (auth) */ 29 | #define NGX_NATS_REASON_READ_TIMEOUT (3) /* reading from NATS timed out */ 30 | #define NGX_NATS_REASON_WRITE_TIMEOUT (4) /* writing into NATS timed out */ 31 | #define NGX_NATS_REASON_BAD_PROTOCOL (5) /* NATS sent msg I cant parse */ 32 | #define NGX_NATS_REASON_DISCONNECTED (6) /* NATS disconnected */ 33 | #define NGX_NATS_REASON_NO_MEMORY (98) 34 | #define NGX_NATS_REASON_INTERNAL_ERROR (99) 35 | 36 | 37 | #define NGX_NATS_READ_BUF_INIT_SIZE (4096) 38 | #define NGX_NATS_WRITE_BUF_INIT_SIZE (4096) 39 | 40 | 41 | #define NGX_NATS_STATE_BYTES_EXCHANGED 0x01 42 | #define NGX_NATS_STATE_CONNECT_SENT 0x02 43 | #define NGX_NATS_STATE_CONNECT_OKAYED 0x04 44 | #define NGX_NATS_STATE_INFO_RECEIVED 0x08 45 | #define NGX_NATS_STATE_READY 0x0f 46 | 47 | 48 | #define NGX_NATS_MAX_MESSAGE_SIZE (256 * 1024 * 1024) /* 256MB */ 49 | 50 | typedef struct ngx_nats_data_s ngx_nats_data_t; 51 | 52 | 53 | typedef struct { 54 | ngx_log_t *log; 55 | 56 | /* 57 | * The readable/sendable portion of the buffer is between 58 | * pos and end, 0<=pos<=end<=cap. "cap" is allocated length. 59 | */ 60 | u_char *buf; 61 | size_t pos; 62 | size_t end; 63 | size_t cap; 64 | 65 | } ngx_nats_buf_t; 66 | 67 | 68 | typedef struct { 69 | 70 | ngx_pool_t *pool; 71 | 72 | ngx_nats_data_t *nd; 73 | ngx_nats_server_t *server; /* from ngx_nats_core_conf_t */ 74 | 75 | ngx_peer_connection_t pc; 76 | ngx_event_t ping_timer; 77 | 78 | ngx_nats_buf_t *read_buf; 79 | ngx_nats_buf_t *write_buf; 80 | 81 | /* Sent by NATS in INFO message */ 82 | ngx_str_t *srv_id; 83 | ngx_str_t *srv_host; 84 | ngx_int_t srv_port; 85 | ngx_str_t *srv_version; 86 | ngx_str_t *go_version; 87 | ngx_int_t srv_max_payload; 88 | unsigned srv_auth_required:1; 89 | unsigned srv_ssl_required:1; 90 | 91 | /* Stages/state of connecting to NATS */ 92 | u_char state; 93 | 94 | } ngx_nats_connection_t; 95 | 96 | 97 | typedef struct { 98 | 99 | ngx_nats_client_t *client; 100 | ngx_nats_handle_msg_pt handle_msg; 101 | void *client_subscription_data; 102 | ngx_int_t sid; 103 | ngx_int_t max; 104 | ngx_int_t recv; 105 | 106 | } ngx_nats_subscription_t; 107 | 108 | 109 | typedef struct { 110 | 111 | ngx_array_t clients; /* ngx_nats_client_t* */ 112 | 113 | /* 114 | * TODO: use ngx_hash? array more economical if just a few 115 | * subscriptions. 116 | */ 117 | ngx_array_t subs; /* ngx_nats_subscription_t */ 118 | ngx_int_t next_id; 119 | 120 | } ngx_nats_client_data_t; 121 | 122 | 123 | struct ngx_nats_data_s { 124 | 125 | ngx_nats_core_conf_t *nccf; 126 | ngx_log_t *log; 127 | 128 | ngx_event_t reconnect_timer; 129 | ngx_int_t nconnects; /* unsuccessful connects */ 130 | 131 | ngx_nats_client_data_t cd; 132 | 133 | ngx_nats_connection_t *nc; /* currently one only */ 134 | ngx_pool_t *nc_pool; 135 | ngx_nats_buf_t *nc_read_buf; 136 | ngx_nats_buf_t *nc_write_buf; 137 | 138 | ngx_int_t conn_index; /* connected in nccf->servers */ 139 | ngx_int_t curr_index; /* for loop-through connect */ 140 | ngx_int_t last_index; /* last connected to */ 141 | 142 | }; 143 | 144 | 145 | /* 146 | * The entire buf story needs improvement, especially write buffers. 147 | * Current impl will do for low rate of sends to NATS. If NATS is used 148 | * to process HTTP requests or in similar high-performance fashion 149 | * then this has to change, but no plans for now. 150 | */ 151 | ngx_nats_buf_t * ngx_nats_buf_create(ngx_pool_t *pool, size_t size); 152 | void ngx_nats_buf_free_buf(ngx_nats_buf_t *buf); 153 | ngx_int_t ngx_nats_buf_ensure(ngx_nats_buf_t *buf, size_t size, 154 | ngx_int_t compact); 155 | void ngx_nats_buf_reset(ngx_nats_buf_t *buf); 156 | void ngx_nats_buf_compact(ngx_nats_buf_t *buf); 157 | 158 | 159 | #endif /* _NGX_NATS_COMM_H_INCLUDED_ */ 160 | 161 | -------------------------------------------------------------------------------- /src/ngx_nats_json.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 (C) The NATS Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | #include "ngx_nats_json.h" 19 | 20 | 21 | /* 22 | * JSON parsing. Doesn't support \u in strings otherwise should be complete. 23 | * Fields in object are kept in an array as opposed to normaly as a hashtable. 24 | * This is for first version to bootstrap it, using ngx_hash is a little 25 | * complicated. Works OK for limited No of fields in objects. 26 | * Need to revisit this. 27 | */ 28 | 29 | 30 | typedef struct 31 | { 32 | ngx_pool_t *pool; 33 | 34 | u_char *text; /* JSON string */ 35 | size_t len; /* length of string in "s" */ 36 | size_t pos; /* current parse position */ 37 | size_t istart; /* start position of the item we parse */ 38 | 39 | u_char *str; /* for parsing string values */ 40 | size_t str_cap; /* allocated size (capacity) */ 41 | size_t str_pos; 42 | 43 | size_t err_pos; 44 | 45 | } ngx_nats_json_parse_ctx_t; 46 | 47 | 48 | /*--------------------------------------------------------------------------- 49 | * Forward declarations. 50 | *--------------------------------------------------------------------------*/ 51 | 52 | static ngx_int_t ngx_nats_json_parse_object(ngx_nats_json_parse_ctx_t *pc, 53 | ngx_nats_json_object_t *o); 54 | static ngx_int_t ngx_nats_json_parse_array(ngx_nats_json_parse_ctx_t *pc, 55 | ngx_nats_json_array_t *a); 56 | static ngx_int_t ngx_nats_json_parse_string(ngx_nats_json_parse_ctx_t *pc, 57 | ngx_str_t *s, u_char q); 58 | static ngx_int_t ngx_nats_json_parse_hex(ngx_nats_json_parse_ctx_t *pc, 59 | ngx_nats_json_value_t *v); 60 | static ngx_int_t ngx_nats_json_parse_double(ngx_nats_json_parse_ctx_t *pc, 61 | ngx_nats_json_value_t *v); 62 | static ngx_int_t ngx_nats_json_parse_int(ngx_nats_json_parse_ctx_t *pc, 63 | ngx_nats_json_value_t *v); 64 | static ngx_int_t ngx_nats_json_parse_value(ngx_nats_json_parse_ctx_t *pc, 65 | ngx_nats_json_value_t *v); 66 | 67 | 68 | /*--------------------------------------------------------------------------- 69 | * Implementations. 70 | *--------------------------------------------------------------------------*/ 71 | 72 | const u_char * 73 | ngx_nats_json_type_name(ngx_int_t type) 74 | { 75 | switch(type) 76 | { 77 | case NGX_NATS_JSON_NULL: return (u_char *)"Null"; 78 | case NGX_NATS_JSON_OBJECT: return (u_char *)"Object"; 79 | case NGX_NATS_JSON_ARRAY: return (u_char *)"Array"; 80 | case NGX_NATS_JSON_BOOLEAN: return (u_char *)"Boolean"; 81 | case NGX_NATS_JSON_INTEGER: return (u_char *)"Integer"; 82 | case NGX_NATS_JSON_DOUBLE: return (u_char *)"Double"; 83 | case NGX_NATS_JSON_STRING: return (u_char *)"String"; 84 | default: return (u_char *)""; 85 | } 86 | } 87 | 88 | 89 | static ngx_int_t 90 | _skipSpaces(ngx_nats_json_parse_ctx_t *pc) 91 | { 92 | u_char *t; 93 | size_t p; 94 | size_t m = pc->len; 95 | 96 | t = pc->text; 97 | pc->istart = -1; 98 | 99 | for (p = pc->pos; p < m; p++) { 100 | u_char c = t[p]; 101 | if (c != ' ' && c != '\r' && c != '\n' && c != '\t') { 102 | pc->pos = p; 103 | return 0; 104 | } 105 | } 106 | 107 | pc->err_pos = p; 108 | return NGX_NATS_JSON_ERR_EOF; 109 | } 110 | 111 | static ngx_int_t 112 | ngx_nats_json_parse_object(ngx_nats_json_parse_ctx_t *pc, 113 | ngx_nats_json_object_t *o) 114 | { 115 | /* pc->pos is right after '{' */ 116 | 117 | ngx_nats_json_field_t *f; 118 | u_char *t; 119 | ngx_int_t rc; 120 | size_t psave = 0; 121 | u_char c, need_field=0; 122 | 123 | t = pc->text; 124 | pc->istart = pc->pos - 1; 125 | 126 | while(pc->pos < pc->len) { 127 | 128 | psave = pc->pos; 129 | 130 | rc = _skipSpaces(pc); 131 | if (rc != 0) { 132 | pc->err_pos = psave; 133 | return NGX_NATS_JSON_ERR_EOF; 134 | } 135 | 136 | pc->istart = pc->pos; 137 | c = t[pc->pos++]; 138 | 139 | if (c == '}') { 140 | if (need_field) { 141 | pc->err_pos = pc->pos - 1; 142 | return NGX_NATS_JSON_ERR_SYNTAX; 143 | } 144 | return 0; 145 | } 146 | 147 | 148 | /* must have field name in quotes */ 149 | if (c != '\'' && c != '\"') { 150 | pc->err_pos = pc->pos - 1; 151 | return NGX_NATS_JSON_ERR_SYNTAX; 152 | } 153 | 154 | f = (ngx_nats_json_field_t *) ngx_array_push(o->fields); 155 | if (f == NULL) { 156 | return NGX_NATS_JSON_ERR_ERROR; 157 | } 158 | ngx_memzero(f, sizeof(ngx_nats_json_field_t)); 159 | 160 | rc = ngx_nats_json_parse_string(pc, &f->name, c); 161 | if (rc != 0) { 162 | return rc; 163 | } 164 | 165 | if (f->name.len == 0) { 166 | pc->err_pos = pc->pos - 1; 167 | return NGX_NATS_JSON_ERR_SYNTAX; 168 | } 169 | 170 | psave = pc->pos; 171 | 172 | rc = _skipSpaces(pc); 173 | if (rc != 0) { 174 | pc->err_pos = psave; 175 | return NGX_NATS_JSON_ERR_EOF; 176 | } 177 | 178 | c = t[pc->pos++]; 179 | 180 | if (c != ':') { 181 | pc->err_pos = pc->pos - 1; 182 | return NGX_NATS_JSON_ERR_SYNTAX; 183 | } 184 | 185 | rc = ngx_nats_json_parse_value(pc, &f->value); 186 | if (rc != 0) { 187 | return rc; 188 | } 189 | 190 | psave = pc->pos; 191 | 192 | rc = _skipSpaces(pc); 193 | if (rc != 0) { 194 | pc->err_pos = psave; 195 | return NGX_NATS_JSON_ERR_EOF; 196 | } 197 | 198 | psave = pc->pos; 199 | 200 | c = t[pc->pos++]; 201 | if (c == '}') 202 | return 0; 203 | 204 | if (c == ',') { 205 | need_field = 1; 206 | continue; 207 | } 208 | 209 | pc->err_pos = psave; 210 | return NGX_NATS_JSON_ERR_SYNTAX; 211 | } 212 | 213 | pc->err_pos = psave; 214 | return NGX_NATS_JSON_ERR_EOF; 215 | } 216 | 217 | 218 | static ngx_int_t 219 | ngx_nats_json_parse_array(ngx_nats_json_parse_ctx_t *pc, 220 | ngx_nats_json_array_t *a) 221 | { 222 | /* pc->pos is right after '[' */ 223 | 224 | ngx_nats_json_value_t *v; 225 | u_char *t; 226 | ngx_int_t rc; 227 | size_t psave; 228 | u_char c, need_field; 229 | 230 | t = pc->text; 231 | pc->istart = pc->pos - 1; 232 | 233 | psave = 0; 234 | need_field = 0; 235 | 236 | while(pc->pos < pc->len) { 237 | 238 | psave = pc->pos; 239 | rc = _skipSpaces(pc); 240 | if (rc != 0) { 241 | pc->err_pos = psave; 242 | return NGX_NATS_JSON_ERR_EOF; 243 | } 244 | 245 | pc->istart = pc->pos; 246 | c = t[pc->pos]; 247 | 248 | if (c == ']') { 249 | if (need_field) { 250 | pc->err_pos = pc->pos; 251 | return NGX_NATS_JSON_ERR_SYNTAX; 252 | } 253 | pc->pos++; 254 | return 0; 255 | } 256 | 257 | v = (ngx_nats_json_value_t *) ngx_array_push(a->values); 258 | if (v == NULL) { 259 | return NGX_NATS_JSON_ERR_ERROR; 260 | } 261 | ngx_memzero(v, sizeof(ngx_nats_json_value_t)); 262 | 263 | rc = ngx_nats_json_parse_value(pc, v); 264 | if (rc != 0) { 265 | return rc; 266 | } 267 | 268 | psave = pc->pos; 269 | 270 | rc = _skipSpaces(pc); 271 | if (rc != 0) { 272 | pc->err_pos = psave; 273 | return NGX_NATS_JSON_ERR_EOF; 274 | } 275 | 276 | psave = pc->pos; 277 | 278 | c = t[pc->pos++]; 279 | if (c == ']') 280 | return 0; 281 | 282 | if (c == ',') { 283 | need_field = 1; 284 | continue; 285 | } 286 | 287 | pc->err_pos = psave; 288 | return NGX_NATS_JSON_ERR_SYNTAX; 289 | } 290 | 291 | pc->err_pos = psave; 292 | return NGX_NATS_JSON_ERR_EOF; 293 | } 294 | 295 | /* 296 | * Called only with n=2 or n=1 maybe, never too large so 2*cap 297 | * is always sufficient expansion because we start with 1024. 298 | */ 299 | static ngx_int_t 300 | ngx_nats_json_ensure_str(ngx_nats_json_parse_ctx_t *pc, size_t n) 301 | { 302 | u_char *ns; 303 | size_t sz = pc->str_cap - pc->str_pos; /* available bytes */ 304 | 305 | if (sz < n) { 306 | 307 | /* 308 | * Limit string length, if single string value wants to be 309 | * more than 1MB then something is wrong. 310 | */ 311 | if (pc->str_cap >= (size_t)(1024*1024)) { 312 | /* TODO: log JSON string value too large */ 313 | return NGX_NATS_JSON_ERR_ERROR; 314 | } 315 | 316 | sz = pc->str_cap * 2; 317 | 318 | ns = ngx_pnalloc(pc->pool, sz); 319 | if (ns == NULL) { 320 | /* ngx_pnalloc did log out of memory... */ 321 | return NGX_NATS_JSON_ERR_ERROR; 322 | } 323 | 324 | ngx_memcpy(ns, pc->str, pc->str_pos); 325 | 326 | ngx_pfree(pc->pool, pc->str); 327 | 328 | pc->str = ns; 329 | pc->str_cap = sz; 330 | } 331 | 332 | return 0; 333 | } 334 | 335 | 336 | static ngx_int_t 337 | ngx_nats_json_parse_string(ngx_nats_json_parse_ctx_t *pc, 338 | ngx_str_t *s, u_char q) 339 | { 340 | /* pc->pos is right after the first quote (I allow ' or ") */ 341 | 342 | u_char *p; 343 | u_char *t; 344 | size_t n, max, nout, mspos; 345 | u_char quote_found = 0; 346 | u_char c; 347 | 348 | t = pc->text; 349 | pc->istart = pc->pos - 1; 350 | 351 | n = pc->pos; 352 | max = pc->len; 353 | p = t + pc->pos; /* after " */ 354 | mspos = pc->str_cap - 2; 355 | 356 | nout = 0; 357 | 358 | while(n < max) { 359 | 360 | pc->str_pos = nout; 361 | 362 | if (pc->str_pos >= mspos) { 363 | if (ngx_nats_json_ensure_str(pc, 2) != 0) { 364 | return NGX_NATS_JSON_ERR_ERROR; 365 | } 366 | mspos = pc->str_cap - 2; 367 | } 368 | 369 | c = *p++; 370 | n++; 371 | 372 | if (c == '\\') { 373 | 374 | if (n >= max) { 375 | pc->err_pos = pc->istart; 376 | return NGX_NATS_JSON_ERR_EOF; 377 | } 378 | 379 | c = *p++; 380 | n++; 381 | 382 | switch (c) { 383 | 384 | case 'b': pc->str[nout++] = '\b'; break; 385 | case 't': pc->str[nout++] = '\t'; break; 386 | case 'n': pc->str[nout++] = '\n'; break; 387 | case 'f': pc->str[nout++] = '\f'; break; 388 | case 'r': pc->str[nout++] = '\r'; break; 389 | 390 | case '\"': 391 | case '\'': 392 | case '\\': 393 | case '/': pc->str[nout++] = c; break; 394 | 395 | case 'u': 396 | pc->err_pos = n - 2; 397 | return NGX_NATS_JSON_ERR_NOT_SUPPORTED; 398 | 399 | default: 400 | pc->err_pos = n - 2; 401 | return NGX_NATS_JSON_ERR_SYNTAX; 402 | } 403 | 404 | continue; 405 | } 406 | 407 | if (c == q) 408 | { 409 | quote_found = 1; 410 | break; 411 | } 412 | 413 | pc->str[nout++] = c; 414 | } 415 | 416 | pc->pos = n; 417 | 418 | if (quote_found == 0) { 419 | pc->err_pos = pc->istart; 420 | return NGX_NATS_JSON_ERR_EOF; 421 | } 422 | 423 | /* +1 for trailing 0, "nout" does not count it. */ 424 | s->data = ngx_pnalloc(pc->pool, nout + 1); 425 | if (s->data == NULL) { 426 | return NGX_NATS_JSON_ERR_ERROR; 427 | } 428 | if (nout > 0) { 429 | ngx_memcpy(s->data, pc->str, nout); 430 | } 431 | s->data[nout] = 0; 432 | s->len = nout; 433 | 434 | return 0; 435 | } 436 | 437 | 438 | static ngx_int_t 439 | ngx_nats_json_parse_hex(ngx_nats_json_parse_ctx_t *pc, 440 | ngx_nats_json_value_t *v) 441 | { 442 | /* pc->istart is at the start of "0X...." text, pc->pos is after value */ 443 | 444 | size_t ilen = pc->pos - pc->istart; 445 | uint64_t u64; 446 | u_char *p; 447 | u_char *end; 448 | u_char c; 449 | 450 | pc->err_pos = pc->istart; 451 | 452 | /* skip "0x" that was already found */ 453 | 454 | p = pc->text + pc->istart + 2; 455 | end = pc->text + pc->pos; 456 | ilen -= 2; 457 | 458 | if (ilen > 16) { 459 | return NGX_NATS_JSON_ERR_SYNTAX; 460 | } 461 | 462 | u64 = 0; 463 | 464 | while(p < end) { 465 | 466 | c = *p++; 467 | 468 | if (c >= '0' && c <= '9') { 469 | u64 = (u64 << 4) + (c-'0'); 470 | } 471 | else if (c >= 'a' && c <= 'f') { 472 | u64 = (u64 << 4) + (10 + (c-'a')); 473 | } 474 | else if (c >= 'A' && c <= 'F') { 475 | u64 = (u64 << 4) + (10 + (c-'A')); 476 | } 477 | else { 478 | pc->err_pos = pc->istart + (size_t)(p - pc->istart); 479 | return NGX_NATS_JSON_ERR_SYNTAX; 480 | } 481 | } 482 | 483 | v->type = NGX_NATS_JSON_INTEGER; 484 | v->value.vint = (int64_t)u64; 485 | 486 | return 0; 487 | } 488 | 489 | 490 | static ngx_int_t 491 | ngx_nats_json_parse_int(ngx_nats_json_parse_ctx_t *pc, 492 | ngx_nats_json_value_t *v) 493 | { 494 | /* pc->istart is at the start of number text, pc->pos is right after */ 495 | 496 | size_t ilen; 497 | uint64_t u64; 498 | u_char *p; 499 | u_char *end; 500 | u_char neg = 0; 501 | u_char c; 502 | 503 | /* 504 | * pre-set this, if we return OK then it doesn't matter, 505 | * in all other cases we'll point to start of the number. 506 | */ 507 | pc->err_pos = pc->istart; 508 | 509 | ilen = pc->pos - pc->istart; 510 | p = pc->text + pc->istart; 511 | end = pc->text + pc->pos; 512 | 513 | if ((*p) == '-') 514 | { 515 | neg = 1; 516 | p++; 517 | ilen--; 518 | } 519 | 520 | if (ilen <= 0) { 521 | return NGX_NATS_JSON_ERR_SYNTAX; 522 | } 523 | 524 | u64 = 0; 525 | 526 | while(p < end) { 527 | c = *p++; 528 | 529 | if (c < '0' || c > '9') { 530 | return NGX_NATS_JSON_ERR_SYNTAX; 531 | } 532 | 533 | /* check can still multiply by 10 */ 534 | if (u64 > 922337203685477580LL) { 535 | return NGX_NATS_JSON_ERR_SYNTAX; 536 | } 537 | 538 | u64 = (u64 * 10) + (c - '0'); 539 | } 540 | 541 | v->type = NGX_NATS_JSON_INTEGER; 542 | 543 | /* check range */ 544 | if (neg) { 545 | if ((u64 - 1) > 9223372036854775807LL) { 546 | return NGX_NATS_JSON_ERR_SYNTAX; 547 | } 548 | v->value.vint = -((int64_t) u64); 549 | } 550 | else { 551 | if (u64 > 9223372036854775807LL) { 552 | return NGX_NATS_JSON_ERR_SYNTAX; 553 | } 554 | v->value.vint = (int64_t) u64; 555 | } 556 | 557 | return 0; 558 | } 559 | 560 | 561 | static ngx_int_t 562 | ngx_nats_json_parse_double(ngx_nats_json_parse_ctx_t *pc, 563 | ngx_nats_json_value_t *v) 564 | { 565 | /* pc->istart is at the start of number text, pc->pos is right after */ 566 | 567 | double d; 568 | u_char *p; 569 | u_char *endptr = NULL; 570 | size_t ilen = pc->pos - pc->istart; 571 | u_char temp[32]; 572 | 573 | if (ilen > 31) { 574 | pc->err_pos = pc->istart; 575 | return NGX_NATS_JSON_ERR_SYNTAX; 576 | } 577 | 578 | p = &temp[0]; 579 | 580 | ngx_memcpy(p, pc->text + pc->istart, ilen); 581 | p[ilen] = 0; 582 | 583 | d = strtod((char *)p, (char **)&endptr); 584 | 585 | if (endptr != (p+ilen)) { 586 | pc->err_pos = pc->istart + (size_t)(endptr - p); 587 | return NGX_NATS_JSON_ERR_SYNTAX; 588 | } 589 | 590 | v->type = NGX_NATS_JSON_DOUBLE; 591 | v->value.vdec = d; 592 | return 0; 593 | } 594 | 595 | 596 | static ngx_int_t 597 | ngx_nats_json_parse_value(ngx_nats_json_parse_ctx_t *pc, 598 | ngx_nats_json_value_t *v) 599 | { 600 | ngx_int_t rc; 601 | u_char c, c2; 602 | size_t ilen, n; 603 | u_char *p; 604 | u_char *t; 605 | 606 | t = pc->text; 607 | 608 | rc = _skipSpaces(pc); 609 | if (rc != 0) { 610 | return rc; 611 | } 612 | 613 | c = t[pc->pos]; 614 | 615 | if (c == '{') { 616 | 617 | pc->pos++; 618 | 619 | v->type = NGX_NATS_JSON_OBJECT; 620 | v->value.vobj = ngx_pcalloc(pc->pool, sizeof(ngx_nats_json_object_t)); 621 | if (v->value.vobj == NULL) { 622 | return NGX_NATS_JSON_ERR_ERROR; 623 | } 624 | v->value.vobj->fields = ngx_array_create(pc->pool, 8, 625 | sizeof(ngx_nats_json_field_t)); 626 | if (v->value.vobj->fields == NULL) { 627 | return NGX_NATS_JSON_ERR_ERROR; 628 | } 629 | 630 | rc = ngx_nats_json_parse_object(pc, v->value.vobj); 631 | return rc; 632 | } 633 | 634 | if (c == '[') { 635 | 636 | pc->pos++; 637 | 638 | v->type = NGX_NATS_JSON_ARRAY; 639 | v->value.varr = ngx_pcalloc(pc->pool, sizeof(ngx_nats_json_array_t)); 640 | if (v->value.varr == NULL) { 641 | return NGX_NATS_JSON_ERR_ERROR; 642 | } 643 | 644 | v->value.varr->values = ngx_array_create(pc->pool, 8, 645 | sizeof(ngx_nats_json_value_t)); 646 | if (v->value.varr->values == NULL) { 647 | return NGX_NATS_JSON_ERR_ERROR; 648 | } 649 | 650 | return ngx_nats_json_parse_array(pc, v->value.varr); 651 | } 652 | 653 | if (c == '\'' || c == '\"') { 654 | v->type = NGX_NATS_JSON_STRING; 655 | pc->pos++; 656 | 657 | v->value.vstr = ngx_pcalloc(pc->pool, sizeof(ngx_str_t)); 658 | if (v->value.vstr == NULL) { 659 | return NGX_NATS_JSON_ERR_ERROR; 660 | } 661 | 662 | return ngx_nats_json_parse_string(pc, (ngx_str_t *)v->value.vstr, c); 663 | } 664 | 665 | /* Primitive value -- null, boolean, integer, double. */ 666 | 667 | pc->istart = pc->pos; 668 | 669 | while(pc->pos < pc->len) { 670 | 671 | c = t[pc->pos]; 672 | 673 | if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || 674 | c == ',' || c == ':' || 675 | c == '[' || c == ']' || c == '{' || c == '}') { 676 | break; 677 | } 678 | 679 | pc->pos++; 680 | } 681 | 682 | ilen = pc->pos - pc->istart; /* length of item text */ 683 | p = t + pc->istart; 684 | 685 | /* TODO: I don't support NaN for now, should I? */ 686 | 687 | if (ilen == 4) { 688 | 689 | if (ngx_strncasecmp(p, (u_char *)"null", 4) == 0) { 690 | v->type = NGX_NATS_JSON_NULL; 691 | return 0; 692 | } 693 | 694 | if (ngx_strncasecmp(p, (u_char *)"true", 4) == 0) { 695 | v->type = NGX_NATS_JSON_BOOLEAN; 696 | v->value.vint = 1; 697 | return 0; 698 | } 699 | } 700 | else if (ilen == 5 && 701 | (ngx_strncasecmp(p, (u_char *)"false", 5)) == 0) { 702 | v->type = NGX_NATS_JSON_BOOLEAN; 703 | v->value.vint = 0; 704 | return 0; 705 | } 706 | 707 | /* 708 | * Now the only option it's a number -- integer or double. 709 | * Notice pc->pos is AFTER the number, pc->istart is start of the number. 710 | */ 711 | 712 | c = t[pc->istart]; 713 | 714 | /* Check first character is a valid number start. */ 715 | if ((c < '0' || c > '9') && c != '.' && c != '+' && c != '-') { 716 | pc->err_pos = pc->istart; 717 | return NGX_NATS_JSON_ERR_SYNTAX; 718 | } 719 | 720 | if (c == '0') { 721 | 722 | if (ilen == 1) { 723 | v->type = NGX_NATS_JSON_INTEGER; 724 | v->value.vint = 0; 725 | return 0; 726 | } 727 | 728 | if (ilen > 2 && (t[pc->istart+1] == 'x' || t[pc->istart+1] == 'X')) { 729 | v->type = NGX_NATS_JSON_INTEGER; 730 | return ngx_nats_json_parse_hex(pc, v); 731 | } 732 | } 733 | 734 | for (n = pc->istart; n < pc->pos; n++) { 735 | c2 = t[n]; 736 | if (c2 == '.' || c2 == 'e' || c2 == 'E') { 737 | v->type = NGX_NATS_JSON_DOUBLE; 738 | return ngx_nats_json_parse_double(pc, v); 739 | } 740 | } 741 | 742 | if (c == '0') { 743 | pc->err_pos = pc->istart; 744 | return NGX_NATS_JSON_ERR_SYNTAX; 745 | } 746 | 747 | v->type = NGX_NATS_JSON_INTEGER; 748 | return ngx_nats_json_parse_int(pc, v); 749 | } 750 | 751 | 752 | /* 753 | * Public use. All above is private (static) only. 754 | * 755 | * Pool must be reset before calling this! Can't do here because 756 | * "json" may be allocated in that same pool. 757 | */ 758 | ngx_int_t 759 | ngx_nats_json_parse( 760 | ngx_pool_t *pool, 761 | ngx_str_t *bytes, 762 | ngx_nats_json_value_t **json) 763 | { 764 | ngx_nats_json_value_t *js; 765 | ngx_nats_json_parse_ctx_t pc; 766 | ngx_int_t rc; 767 | 768 | if (bytes->len < 1) { 769 | /* TODO: log JSON error with full string and the error pos */ 770 | return NGX_NATS_JSON_ERR_SYNTAX; 771 | } 772 | 773 | js = ngx_pcalloc(pool, sizeof(ngx_nats_json_value_t)); 774 | if (js == NULL) { 775 | return NGX_ERROR; 776 | } 777 | 778 | ngx_memzero(&pc, sizeof(ngx_nats_json_parse_ctx_t)); 779 | 780 | /* Empty JSON string is not valid, caller shouldn't call then. */ 781 | 782 | pc.pool = pool; 783 | pc.text = bytes->data; 784 | pc.len = bytes->len; 785 | 786 | pc.str_cap = 1024; /* will grow if need */ 787 | pc.str = ngx_pnalloc(pool, pc.str_cap); /* non-aligned pool alloc */ 788 | if (pc.str == NULL) { 789 | return NGX_NATS_JSON_ERR_ERROR; 790 | } 791 | 792 | pc.err_pos = -1; 793 | 794 | rc = ngx_nats_json_parse_value(&pc, js); 795 | 796 | if (rc != 0) { 797 | /* TODO: log JSON error with full string and the error pos */ 798 | return rc; 799 | } 800 | 801 | *json = js; 802 | return pc.pos; /* count of parsed bytes */ 803 | } 804 | 805 | /* 806 | * For testing only. 807 | */ 808 | static void 809 | _json_debug_print_value(ngx_nats_json_value_t *v) 810 | { 811 | uint64_t u64; 812 | ngx_uint_t n, lim; 813 | ngx_nats_json_value_t *v2; 814 | ngx_nats_json_field_t *f; 815 | u_char numbuf[28]; 816 | int pos; 817 | 818 | switch(v->type) 819 | { 820 | case NGX_NATS_JSON_NULL: 821 | fprintf(stderr,"null"); 822 | return; 823 | 824 | case NGX_NATS_JSON_BOOLEAN: 825 | fprintf(stderr,"%s", 826 | (v->value.vint == 0 ? "false" : "true")); 827 | return; 828 | 829 | case NGX_NATS_JSON_INTEGER: 830 | if (v->value.vint < 0) { 831 | fprintf(stderr,"-"); 832 | u64 = (uint64_t) -(v->value.vint); 833 | } else { 834 | u64 = (uint64_t) v->value.vint; 835 | } 836 | numbuf[sizeof(numbuf)-1] = 0; 837 | pos = sizeof(numbuf)-1; 838 | do { 839 | numbuf[--pos] = u64 % 10 + '0'; 840 | } while (u64 /= 10); 841 | fprintf(stderr,"%s",numbuf + pos); 842 | return; 843 | 844 | case NGX_NATS_JSON_DOUBLE: 845 | fprintf(stderr,"%f",v->value.vdec); 846 | return; 847 | 848 | /* as-is, do not escape chars, this is debugging only. */ 849 | case NGX_NATS_JSON_STRING: 850 | fprintf(stderr,"\"%s\"",v->value.vstr->data); 851 | return; 852 | 853 | case NGX_NATS_JSON_OBJECT: 854 | fprintf(stderr,"{"); 855 | f = (ngx_nats_json_field_t *) v->value.vobj->fields->elts; 856 | lim = v->value.vobj->fields->nelts; 857 | for (n = 0; n < lim; n++) { 858 | fprintf(stderr,"\"%s\":",f->name.data); 859 | _json_debug_print_value(&f->value); 860 | if (n < lim-1) { 861 | fprintf(stderr,","); 862 | } 863 | f++; 864 | } 865 | fprintf(stderr,"}"); 866 | return; 867 | 868 | case NGX_NATS_JSON_ARRAY: 869 | fprintf(stderr,"["); 870 | v2 = (ngx_nats_json_value_t *) v->value.varr->values->elts; 871 | lim = v->value.varr->values->nelts; 872 | for (n = 0; n < lim; n++) { 873 | _json_debug_print_value(v2); 874 | if (n < lim-1) { 875 | fprintf(stderr,","); 876 | } 877 | v2++; 878 | } 879 | fprintf(stderr,"]"); 880 | return; 881 | 882 | default: 883 | fprintf(stderr,"",(int)v->type); 884 | return; 885 | } 886 | 887 | } 888 | 889 | 890 | void 891 | ngx_nats_json_debug_print(ngx_nats_json_value_t *v) 892 | { 893 | if (v->type != NGX_NATS_JSON_OBJECT && v->type != NGX_NATS_JSON_ARRAY) { 894 | fprintf(stderr, 895 | "\n"); 896 | return; 897 | } 898 | _json_debug_print_value(v); 899 | } 900 | 901 | 902 | 903 | -------------------------------------------------------------------------------- /src/ngx_nats_json.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 (C) The NATS Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | #ifndef _NGX_NATS_JSON_H_INCLUDED_ 19 | #define _NGX_NATS_JSON_H_INCLUDED_ 20 | 21 | #include 22 | #include 23 | 24 | 25 | /* 26 | * Error codes must be negative. 27 | */ 28 | #define NGX_NATS_JSON_ERR_ERROR (-1) /* out of memory etc. */ 29 | #define NGX_NATS_JSON_ERR_SYNTAX (-2) /* JSON syntax error */ 30 | #define NGX_NATS_JSON_ERR_EOF (-3) /* unexpected end of JSON */ 31 | #define NGX_NATS_JSON_ERR_NOT_SUPPORTED (-4) /* not supported syntax */ 32 | 33 | 34 | /* 35 | * JSON value types. 36 | */ 37 | #define NGX_NATS_JSON_NULL (1) 38 | #define NGX_NATS_JSON_OBJECT (2) 39 | #define NGX_NATS_JSON_ARRAY (3) 40 | #define NGX_NATS_JSON_BOOLEAN (4) 41 | #define NGX_NATS_JSON_INTEGER (5) 42 | #define NGX_NATS_JSON_DOUBLE (6) 43 | #define NGX_NATS_JSON_STRING (7) 44 | 45 | 46 | typedef struct { 47 | 48 | /* TODO: use ngx_hash_t when have time to figure it out? */ 49 | 50 | ngx_array_t *fields; /* of ngx_nats_json_field */ 51 | 52 | } ngx_nats_json_object_t; 53 | 54 | 55 | typedef struct { 56 | 57 | ngx_array_t *values; /* of ngx_nats_json_value */ 58 | 59 | } ngx_nats_json_array_t; 60 | 61 | 62 | typedef struct { 63 | 64 | ngx_int_t type; 65 | 66 | union { 67 | ngx_nats_json_object_t *vobj; 68 | ngx_nats_json_array_t *varr; 69 | ngx_str_t *vstr; 70 | int64_t vint; /* also keeps boolean */ 71 | double vdec; /* decimal */ 72 | } value; 73 | 74 | } ngx_nats_json_value_t; 75 | 76 | 77 | typedef struct { 78 | 79 | ngx_str_t name; 80 | ngx_nats_json_value_t value; 81 | 82 | } ngx_nats_json_field_t; 83 | 84 | 85 | /* 86 | * If returns < 0 then it's an error code. 87 | * If returns > 0 then parsing was successful and return value is the number 88 | * of parsed bytes, which may be less than the value of "len" param. 89 | * We do not allow empty JSON string. Notice this never returns 0 value. 90 | * This may parse an object, array or a primitive value. The caller 91 | * must check the type if it only expects an object or array, etc. 92 | * 93 | * Parsed objects/arrays/values are allocated in a provided pool. 94 | */ 95 | ngx_int_t ngx_nats_json_parse( 96 | ngx_pool_t *pool, ngx_str_t *bytes, ngx_nats_json_value_t **json); 97 | 98 | const u_char * ngx_nats_json_type_name(ngx_int_t type); 99 | 100 | /* 101 | * For testing only. 102 | */ 103 | void ngx_nats_json_debug_print(ngx_nats_json_value_t *v); 104 | 105 | #endif /* _NGX_NATS_JSON_H_INCLUDED_ */ 106 | 107 | -------------------------------------------------------------------------------- /src/ngx_nats_protocol.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 (C) The NATS Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | #include "ngx_nats_protocol.h" 19 | 20 | 21 | /* 22 | * This parses incoming NATS messages. 23 | */ 24 | 25 | /*--------------------------------------------------------------------------- 26 | * Forward declarations of functions. 27 | *--------------------------------------------------------------------------*/ 28 | 29 | /* 30 | * Top-level function "ngx_nats_parse(...)" and all below return: 31 | * - If return value is positive then it's the total number of bytes 32 | * in a message and "msg" is formed. 33 | * - If returns NGX_NATS_PROTO_AGAIN then message is incomplete, 34 | * caller must read more bytes from NATS and call parse again. 35 | * - Otherwise an error NGX_NATS_PROTO_ERR_...., connection to NATS 36 | * was closed because of protocol violation. 37 | */ 38 | 39 | static ngx_int_t 40 | ngx_nats_parse_ok( 41 | ngx_str_t *bytes, size_t header_len, ngx_nats_msg_t *m); 42 | static ngx_int_t 43 | ngx_nats_parse_err( 44 | ngx_str_t *bytes, size_t header_len, ngx_nats_msg_t *m); 45 | static ngx_int_t 46 | ngx_nats_parse_ping( 47 | ngx_str_t *bytes, size_t header_len, ngx_nats_msg_t *m); 48 | static ngx_int_t 49 | ngx_nats_parse_pong( 50 | ngx_str_t *bytes, size_t header_len, ngx_nats_msg_t *m); 51 | static ngx_int_t 52 | ngx_nats_parse_info( 53 | ngx_str_t *bytes, size_t header_len, ngx_nats_msg_t *m); 54 | static ngx_int_t 55 | ngx_nats_parse_msg ( 56 | ngx_str_t *bytes, size_t header_len, ngx_nats_msg_t *m); 57 | 58 | 59 | #define _NATS_PARSE_ERR ((size_t)-1) 60 | 61 | /*--------------------------------------------------------------------------- 62 | * Implementations. 63 | *--------------------------------------------------------------------------*/ 64 | 65 | char* 66 | ngx_nats_protocol_msg_name(ngx_int_t type) 67 | { 68 | switch(type) { 69 | case NGX_NATS_MSG_OK: return "+OK"; 70 | case NGX_NATS_MSG_ERR: return "-ERR"; 71 | case NGX_NATS_MSG_PING: return "PING"; 72 | case NGX_NATS_MSG_PONG: return "PONG"; 73 | case NGX_NATS_MSG_INFO: return "INFO"; 74 | case NGX_NATS_MSG_MSG: return "MSG"; 75 | } 76 | return ""; 77 | } 78 | 79 | 80 | static ngx_int_t 81 | _nats_atoi(u_char *s, size_t n, ngx_int_t *result) 82 | { 83 | ngx_int_t value; 84 | 85 | if (n == 0) { 86 | return NGX_ERROR; 87 | } 88 | 89 | for (value = 0; n--; s++) { 90 | if (*s < '0' || *s > '9') { 91 | return NGX_ERROR; 92 | } 93 | 94 | value = value * 10 + (*s - '0'); 95 | } 96 | 97 | *result = value; 98 | return NGX_OK; 99 | } 100 | 101 | static size_t 102 | _nats_token(u_char *s, size_t pos, size_t max, size_t *ns, size_t *ne) 103 | { 104 | char q = 0; 105 | 106 | *ns = max; 107 | *ne = max; 108 | 109 | /* skip spaces */ 110 | for ( ; pos < max && s[pos] == ' '; pos++); 111 | if (pos == max) { 112 | return max; 113 | } 114 | 115 | *ns = pos; 116 | if (s[pos] == '\'' || s[pos] == '\"') { 117 | if (pos >= max-1) { 118 | return _NATS_PARSE_ERR; 119 | } 120 | q = s[pos++]; 121 | for ( ;; ) { 122 | if (s[pos] == '\\') { 123 | if (pos >= max-1) { 124 | return _NATS_PARSE_ERR; 125 | } 126 | pos += 2; 127 | } else if (s[pos] == q) { 128 | *ne = pos+1; 129 | return pos + 1; 130 | } else if (pos >= max-1) { 131 | return _NATS_PARSE_ERR; 132 | } else { 133 | pos++; 134 | } 135 | } 136 | } 137 | 138 | for ( ; pos < max && s[pos] != ' ' && s[pos] != '\r'; pos++); 139 | 140 | *ne = pos; 141 | return pos; 142 | } 143 | 144 | 145 | ngx_int_t 146 | ngx_nats_parse(ngx_str_t *bytes, ngx_nats_msg_t *m) 147 | { 148 | size_t p, header_len; 149 | 150 | ngx_memzero(m, sizeof(ngx_nats_msg_t)); 151 | 152 | /* 153 | * Parse header if possible and then call appropriate parser. 154 | * May not have enough bytes to even parse the header. 155 | */ 156 | 157 | for (p = 0; p < bytes->len; p++) { 158 | 159 | if (bytes->data[p] == '\n') { 160 | return NGX_NATS_PROTO_ERR_INVALID_HEADER; 161 | } 162 | 163 | if (bytes->data[p] == ' ' || bytes->data[p] == '\r') { 164 | break; 165 | } 166 | } 167 | 168 | if (p >= bytes->len) { 169 | 170 | /* 171 | * TODO: return error if no complete header in first 4096 bytes. 172 | * Only "-ERR" may be quite long due to error message, others can't. 173 | */ 174 | 175 | return NGX_NATS_PROTO_AGAIN; 176 | } 177 | 178 | header_len = p; /* header length */ 179 | 180 | if (p == 3) { 181 | 182 | if (ngx_strncasecmp(bytes->data, (u_char *) "+OK", 3) == 0) { 183 | return ngx_nats_parse_ok(bytes, header_len, m); 184 | } 185 | 186 | if (ngx_strncasecmp(bytes->data, (u_char *) "MSG", 3) == 0) { 187 | return ngx_nats_parse_msg(bytes, header_len, m); 188 | } 189 | 190 | return NGX_NATS_PROTO_ERR_INVALID_HEADER; 191 | } 192 | 193 | if (p == 4) { 194 | 195 | if (ngx_strncasecmp(bytes->data, (u_char *) "-ERR", 4) == 0) { 196 | return ngx_nats_parse_err(bytes, header_len, m); 197 | } 198 | 199 | if (ngx_strncasecmp(bytes->data, (u_char *) "PING", 4) == 0) { 200 | return ngx_nats_parse_ping(bytes, header_len, m); 201 | } 202 | 203 | if (ngx_strncasecmp(bytes->data, (u_char *) "PONG", 4) == 0) { 204 | return ngx_nats_parse_pong(bytes, header_len, m); 205 | } 206 | 207 | if (ngx_strncasecmp(bytes->data, (u_char *) "INFO", 4) == 0) { 208 | return ngx_nats_parse_info(bytes, header_len, m); 209 | } 210 | 211 | return NGX_NATS_PROTO_ERR_INVALID_HEADER; 212 | } 213 | 214 | /* Modify above and here if more NATS message types. */ 215 | 216 | return NGX_NATS_PROTO_ERR_INVALID_HEADER; 217 | } 218 | 219 | 220 | ngx_int_t 221 | ngx_nats_parse_ok( 222 | ngx_str_t *bytes, size_t header_len, ngx_nats_msg_t *m) 223 | { 224 | /* 225 | * "+OK\r\n" 226 | */ 227 | 228 | m->type = NGX_NATS_MSG_OK; 229 | 230 | if (bytes->len < 4) { 231 | return NGX_NATS_PROTO_AGAIN; 232 | } 233 | 234 | if (bytes->data[3] != '\r') { 235 | return NGX_NATS_PROTO_ERR_INVALID_MSG; 236 | } 237 | 238 | if (bytes->len < 5) { 239 | return NGX_NATS_PROTO_AGAIN; 240 | } 241 | 242 | if (bytes->data[4] != '\n') { 243 | return NGX_NATS_PROTO_ERR_INVALID_MSG; 244 | } 245 | 246 | return 5; 247 | } 248 | 249 | 250 | ngx_int_t 251 | ngx_nats_parse_ping( 252 | ngx_str_t *bytes, size_t header_len, ngx_nats_msg_t *m) 253 | { 254 | /* 255 | * "PING\r\n" 256 | */ 257 | 258 | m->type = NGX_NATS_MSG_PING; 259 | 260 | if (bytes->len < 5) { 261 | return NGX_NATS_PROTO_AGAIN; 262 | } 263 | 264 | if (bytes->data[4] != '\r') { 265 | return NGX_NATS_PROTO_ERR_INVALID_MSG; 266 | } 267 | 268 | if (bytes->len < 6) { 269 | return NGX_NATS_PROTO_AGAIN; 270 | } 271 | 272 | if (bytes->data[5] != '\n') { 273 | return NGX_NATS_PROTO_ERR_INVALID_MSG; 274 | } 275 | 276 | return 6; 277 | } 278 | 279 | 280 | ngx_int_t 281 | ngx_nats_parse_pong( 282 | ngx_str_t *bytes, size_t header_len, ngx_nats_msg_t *m) 283 | { 284 | /* 285 | * "PONG\r\n" 286 | */ 287 | 288 | m->type = NGX_NATS_MSG_PONG; 289 | 290 | if (bytes->len < 5) { 291 | return NGX_NATS_PROTO_AGAIN; 292 | } 293 | 294 | if (bytes->data[4] != '\r') { 295 | return NGX_NATS_PROTO_ERR_INVALID_MSG; 296 | } 297 | 298 | if (bytes->len < 6) { 299 | return NGX_NATS_PROTO_AGAIN; 300 | } 301 | 302 | if (bytes->data[5] != '\n') { 303 | return NGX_NATS_PROTO_ERR_INVALID_MSG; 304 | } 305 | 306 | return 6; 307 | } 308 | 309 | 310 | ngx_int_t 311 | ngx_nats_parse_err( 312 | ngx_str_t *bytes, size_t header_len, ngx_nats_msg_t *m) 313 | { 314 | /* 315 | * "-ERR error-text\r\n". 316 | * 317 | * "error-text" is optional (???) 318 | */ 319 | 320 | size_t n; 321 | 322 | m->type = NGX_NATS_MSG_ERR; 323 | 324 | for (n = header_len; n < bytes->len && bytes->data[n] == ' '; n++); 325 | 326 | if (n >= bytes->len) { 327 | return NGX_NATS_PROTO_AGAIN; 328 | } 329 | 330 | if (n == header_len) { 331 | 332 | /* Must be \r\n, if possible that NATS sends -ERR with no message */ 333 | if (bytes->data[n] != '\r') { 334 | return NGX_NATS_PROTO_ERR_INVALID_MSG; 335 | } 336 | 337 | if (n == bytes->len - 1) { 338 | return NGX_NATS_PROTO_AGAIN; 339 | } 340 | 341 | if (bytes->data[n+1] != '\n') { 342 | return NGX_NATS_PROTO_ERR_INVALID_MSG; 343 | } 344 | 345 | m->bstart = header_len; 346 | m->bend = header_len; 347 | bytes->data[m->bend] = 0; 348 | 349 | return (ngx_int_t)(n + 2); 350 | } 351 | 352 | m->bstart = n; /* first non-space char after -ERR */ 353 | 354 | for (n++; n < bytes->len && bytes->data[n] != '\r'; n++); 355 | 356 | /* Means we didn't find or found as very last available char */ 357 | if (n >= bytes->len-1) { 358 | return NGX_NATS_PROTO_AGAIN; 359 | } 360 | 361 | if (bytes->data[n+1] != '\n') { 362 | return NGX_NATS_PROTO_ERR_INVALID_MSG; 363 | } 364 | 365 | m->bend = n; 366 | bytes->data[m->bend] = 0; 367 | 368 | return (ngx_int_t)(n + 2); 369 | } 370 | 371 | 372 | ngx_int_t 373 | ngx_nats_parse_info( 374 | ngx_str_t *bytes, size_t header_len, ngx_nats_msg_t *m) 375 | { 376 | /* 377 | * "INFO {...fields...}\r\n" 378 | */ 379 | 380 | /* TODO: return error if no complete header in first 1024 bytes */ 381 | 382 | size_t n; 383 | 384 | m->type = NGX_NATS_MSG_INFO; 385 | 386 | for (n = header_len; n < bytes->len && bytes->data[n] == ' '; n++); 387 | 388 | if (n >= bytes->len) { 389 | return NGX_NATS_PROTO_AGAIN; 390 | } 391 | 392 | if (n == header_len) { /* no space(s) after INFO */ 393 | return NGX_NATS_PROTO_ERR_INVALID_MSG; 394 | } 395 | 396 | m->bstart = n; /* first non-space char after INFO */ 397 | 398 | for (n++; n < bytes->len && bytes->data[n] != '\r'; n++); 399 | 400 | /* Means we didn't find or found as very last available char */ 401 | if (n >= bytes->len-1) { 402 | return NGX_NATS_PROTO_AGAIN; 403 | } 404 | 405 | if (bytes->data[n+1] != '\n') { 406 | return NGX_NATS_PROTO_ERR_INVALID_MSG; 407 | } 408 | 409 | m->bend = n; 410 | bytes->data[m->bend] = 0; 411 | 412 | return (ngx_int_t)(n + 2); 413 | } 414 | 415 | 416 | 417 | ngx_int_t 418 | ngx_nats_parse_msg( 419 | ngx_str_t *bytes, size_t header_len, ngx_nats_msg_t *m) 420 | { 421 | /* 422 | * "MSG Subject Sid [reply-to] Len\r\n...payload...\r\n". 423 | * "reply-to" is optional, may not have spaces. 424 | * We do have at least one char after "MSG". 425 | */ 426 | 427 | /* TODO: return error if no complete header in first 1024 bytes */ 428 | 429 | size_t n, max, ns, ne, ns2, ne2; 430 | ngx_int_t rc; 431 | 432 | m->type = NGX_NATS_MSG_MSG; 433 | 434 | if (bytes->data[header_len] != ' ') { 435 | return NGX_NATS_PROTO_ERR_INVALID_MSG; 436 | } 437 | 438 | for (n = header_len; n < bytes->len-1 && bytes->data[n] != '\r'; n++); 439 | if (n >= bytes->len-1) { 440 | return NGX_NATS_PROTO_AGAIN; 441 | } 442 | if (bytes->data[n+1] != '\n') { 443 | return NGX_NATS_PROTO_ERR_INVALID_MSG; 444 | } 445 | 446 | /* have full header */ 447 | max = n; 448 | 449 | n = header_len; 450 | 451 | /* get subject */ 452 | n = _nats_token(bytes->data, n, max, &ns, &ne); 453 | if (n == _NATS_PARSE_ERR || n == max || ns >= ne) { 454 | return NGX_NATS_PROTO_ERR_INVALID_MSG; 455 | } 456 | m->subject.data = (u_char *)(bytes->data + ns); 457 | m->subject.len = ne - ns; 458 | 459 | /* get Sid */ 460 | n = _nats_token(bytes->data, n, max, &ns, &ne); 461 | if (n == _NATS_PARSE_ERR || n == max || ns >= ne) { 462 | return NGX_NATS_PROTO_ERR_INVALID_MSG; 463 | } 464 | rc = _nats_atoi((u_char *)(bytes->data + ns), ne - ns, &m->sid); 465 | if (rc != NGX_OK) { 466 | return NGX_NATS_PROTO_ERR_INVALID_MSG; 467 | } 468 | 469 | /* get next one or two tokens */ 470 | n = _nats_token(bytes->data, n, max, &ns, &ne); 471 | if (n == _NATS_PARSE_ERR || ns >= ne) { /* may be last token */ 472 | return NGX_NATS_PROTO_ERR_INVALID_MSG; 473 | } 474 | 475 | if (n < max) { /* may have one more token */ 476 | n = _nats_token(bytes->data, n, max, &ns2, &ne2); 477 | if (n == _NATS_PARSE_ERR) { 478 | return NGX_NATS_PROTO_ERR_INVALID_MSG; 479 | } 480 | if (ns2 < ne2) { 481 | /* have token */ 482 | m->replyto.data = (u_char *)(bytes->data + ns); 483 | m->replyto.len = ne - ns; 484 | ns = ns2; 485 | ne = ne2; 486 | } 487 | /* check no more tokens */ 488 | n = _nats_token(bytes->data, n, max, &ns2, &ne2); 489 | if (n == _NATS_PARSE_ERR || n < max || ns2 < ne2) { 490 | return NGX_NATS_PROTO_ERR_INVALID_MSG; 491 | } 492 | } 493 | 494 | rc = _nats_atoi((u_char *)(bytes->data + ns), ne - ns, &m->len); 495 | if (rc != NGX_OK) { 496 | return NGX_NATS_PROTO_ERR_INVALID_MSG; 497 | } 498 | 499 | ns = max + 2; /* start of payload */ 500 | 501 | if ((ns + m->len + 2) > bytes->len) { 502 | return NGX_NATS_PROTO_AGAIN; 503 | } 504 | 505 | if (bytes->data[ns + m->len] != '\r' || bytes->data[ns + m->len + 1] != '\n') { 506 | return NGX_NATS_PROTO_ERR_INVALID_MSG; 507 | } 508 | 509 | m->bstart = ns; 510 | m->bend = ns + m->len; 511 | 512 | m->subject.data[m->subject.len] = 0; 513 | 514 | if (m->replyto.len > 0) { 515 | m->replyto.data[m->replyto.len] = 0; 516 | } 517 | 518 | bytes->data[m->bend] = 0; 519 | 520 | return (ngx_int_t) (ns + m->len + 2); 521 | } 522 | 523 | 524 | -------------------------------------------------------------------------------- /src/ngx_nats_protocol.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 (C) The NATS Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | #ifndef _NGX_NATS_PROTOCOL_H_INCLUDED_ 19 | #define _NGX_NATS_PROTOCOL_H_INCLUDED_ 20 | 21 | #include 22 | 23 | 24 | /* 25 | * Must be negative. 26 | */ 27 | #define NGX_NATS_PROTO_AGAIN (-1) /* incomplete message */ 28 | #define NGX_NATS_PROTO_ERR_ERROR (-2) /* no memory etc. */ 29 | #define NGX_NATS_PROTO_ERR_INVALID_HEADER (-3) 30 | #define NGX_NATS_PROTO_ERR_INVALID_MSG (-4) 31 | #define NGX_NATS_PROTO_ERR_INVALID_JSON (-5) 32 | 33 | 34 | /* 35 | * Types of protocol messages sent by NATS server to the client. 36 | */ 37 | #define NGX_NATS_MSG_OK (1) /* "+OK" messge, not OK return code */ 38 | #define NGX_NATS_MSG_ERR (2) 39 | #define NGX_NATS_MSG_PING (3) 40 | #define NGX_NATS_MSG_PONG (4) 41 | #define NGX_NATS_MSG_INFO (5) 42 | #define NGX_NATS_MSG_MSG (6) 43 | 44 | 45 | // TODO: combine ngx_nats_msg_t and ngx_nats_message_t, no reason to have 2 46 | // is len == bend - bstart? 47 | typedef struct { 48 | 49 | ngx_int_t type; 50 | 51 | /* For "MSG" */ 52 | ngx_int_t sid; /* sids are integers only */ 53 | ngx_int_t len; /* payload length */ 54 | ngx_str_t subject; /* subject */ 55 | ngx_str_t replyto; /* replyTo.len==0 means no replyTo */ 56 | 57 | /* 58 | * Used for "INFO", "-ERR" and "MSG". "+OK", "PING" and "PONG" 59 | * have no body. "-ERR" body is a string in quotes. 60 | * "INFO" body is a JSON. "MSG" body is anything. 61 | * There is a 0 after bend if the caller needs it. 62 | */ 63 | size_t bstart; /* start of message body */ 64 | size_t bend; /* end of message body */ 65 | 66 | } ngx_nats_msg_t; 67 | 68 | 69 | char* ngx_nats_protocol_msg_name(ngx_int_t type); 70 | 71 | /* See comments in ngx_nats_protocol.c for meaning of return value. */ 72 | ngx_int_t ngx_nats_parse(ngx_str_t *bytes, ngx_nats_msg_t *msg); 73 | 74 | #endif /* _NGX_NATS_PROTOCOL_H_INCLUDED_ */ 75 | 76 | 77 | --------------------------------------------------------------------------------