@openziti/ziti-sdk-nodejs 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/build.yml +220 -0
- package/.github/workflows/codeql-analysis.yml +71 -0
- package/.github/workflows/mattermost-ziti-webhook.yml +26 -0
- package/.gitmodules +4 -0
- package/.travis.yml-obsolete +99 -0
- package/CODE_OF_CONDUCT.md +17 -0
- package/CONTRIBUTING.md +6 -0
- package/LICENSE +201 -0
- package/README.md +155 -0
- package/appveyor.yml-obsolete +32 -0
- package/binding.gyp +227 -0
- package/lib/index.js +17 -0
- package/lib/ziti.js +40 -0
- package/package.json +56 -0
- package/scripts/build-appveyor.bat +198 -0
- package/scripts/install_node.sh +99 -0
- package/scripts/validate_tag.sh +24 -0
- package/src/Ziti_https_request.c +960 -0
- package/src/Ziti_https_request_data.c +250 -0
- package/src/Ziti_https_request_end.c +79 -0
- package/src/stack_traces.c +334 -0
- package/src/utils.c +108 -0
- package/src/utils.h +85 -0
- package/src/ziti-add-on.c +88 -0
- package/src/ziti-nodejs.h +209 -0
- package/src/ziti_close.c +79 -0
- package/src/ziti_dial.c +375 -0
- package/src/ziti_enroll.c +245 -0
- package/src/ziti_hello.c +52 -0
- package/src/ziti_init.c +315 -0
- package/src/ziti_service_available.c +222 -0
- package/src/ziti_shutdown.c +47 -0
- package/src/ziti_websocket_connect.c +458 -0
- package/src/ziti_websocket_write.c +235 -0
- package/src/ziti_write.c +223 -0
- package/tests/enroll-test.js +38 -0
- package/tests/hello.js +12 -0
- package/tests/https-test.js +206 -0
- package/tests/mattermost-test.js +124 -0
- package/tests/websocket-test.js +119 -0
- package/ziti.js +1 -0
- package/ziti.png +0 -0
|
@@ -0,0 +1,960 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2019-2020 Netfoundry, Inc.
|
|
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
|
+
https://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
|
+
#include "ziti-nodejs.h"
|
|
18
|
+
#include <ziti/ziti_src.h>
|
|
19
|
+
|
|
20
|
+
static const unsigned int U1 = 1;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Number of hosts we can have client pools for
|
|
24
|
+
*/
|
|
25
|
+
enum { listMapCapacity = 50 };
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Number of simultaneous HTTP requests we can have active against a single host before queueing
|
|
29
|
+
*/
|
|
30
|
+
enum { perKeyListMapCapacity = 25 };
|
|
31
|
+
|
|
32
|
+
struct ListMap* HttpsClientListMap;
|
|
33
|
+
|
|
34
|
+
struct key_value {
|
|
35
|
+
char* key;
|
|
36
|
+
void* value;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
struct ListMap {
|
|
40
|
+
struct key_value kvPairs[listMapCapacity];
|
|
41
|
+
size_t count;
|
|
42
|
+
uv_sem_t sem;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
struct ListMap* newListMap() {
|
|
46
|
+
struct ListMap* listMap = calloc(1, sizeof *listMap);
|
|
47
|
+
return listMap;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
uv_mutex_t client_pool_lock;
|
|
51
|
+
|
|
52
|
+
bool listMapInsert(struct ListMap* collection, char* key, void* value) {
|
|
53
|
+
|
|
54
|
+
if (collection->count == listMapCapacity) {
|
|
55
|
+
ZITI_NODEJS_LOG(ERROR, "collection->count already at capacity [%d], insert FAIL", listMapCapacity);
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
collection->kvPairs[collection->count].key = strdup(key);
|
|
60
|
+
collection->kvPairs[collection->count].value = value;
|
|
61
|
+
collection->count++;
|
|
62
|
+
ZITI_NODEJS_LOG(DEBUG, "collection->count total is: [%zd]", collection->count);
|
|
63
|
+
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
struct ListMap* getInnerListMapValueForKey(struct ListMap* collection, char* key) {
|
|
69
|
+
struct ListMap* value = NULL;
|
|
70
|
+
for (size_t i = 0 ; i < collection->count && value == NULL ; ++i) {
|
|
71
|
+
if (strcmp(collection->kvPairs[i].key, key) == 0) {
|
|
72
|
+
value = collection->kvPairs[i].value;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
HttpsClient* getHttpsClientForKey(struct ListMap* collection, char* key) {
|
|
79
|
+
HttpsClient* value = NULL;
|
|
80
|
+
int busyCount = 0;
|
|
81
|
+
for (size_t i = 0 ; i < collection->count && value == NULL ; ++i) {
|
|
82
|
+
if (strcmp(collection->kvPairs[i].key, key) == 0) {
|
|
83
|
+
value = collection->kvPairs[i].value;
|
|
84
|
+
if (value->active) { // if it's in use
|
|
85
|
+
value = NULL; // then keep searching
|
|
86
|
+
}
|
|
87
|
+
else if (value->purge) { // if it's broken
|
|
88
|
+
value = NULL; // then keep searching
|
|
89
|
+
} else {
|
|
90
|
+
value->active = true; // mark the one we will return as 'in use
|
|
91
|
+
}
|
|
92
|
+
busyCount++;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
ZITI_NODEJS_LOG(DEBUG, "returning value '%p', collection->count is: [%zd], busy-count is: [%d]", value, collection->count, busyCount);
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
void freeListMap(struct ListMap* collection) {
|
|
100
|
+
if (collection == NULL) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
for (size_t i = 0 ; i < collection->count ; ++i) {
|
|
104
|
+
free(collection->kvPairs[i].value);
|
|
105
|
+
}
|
|
106
|
+
free(collection);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
*
|
|
111
|
+
*/
|
|
112
|
+
static int purge_and_replace_bad_clients(struct ListMap* clientListMap, HttpsAddonData* addon_data) {
|
|
113
|
+
int numReplaced = 0;
|
|
114
|
+
for (int i = 0; i < perKeyListMapCapacity; i++) {
|
|
115
|
+
|
|
116
|
+
HttpsClient* httpsClient = clientListMap->kvPairs[i].value;
|
|
117
|
+
|
|
118
|
+
if (httpsClient->purge) {
|
|
119
|
+
|
|
120
|
+
ZITI_NODEJS_LOG(DEBUG, "*********** purging client [%p] from slot [%d]", httpsClient, i);
|
|
121
|
+
|
|
122
|
+
// FIXME: find out why the following free causes things to eventually crash in the uv_loop
|
|
123
|
+
// free(httpsClient->scheme_host_port);
|
|
124
|
+
// free(httpsClient);
|
|
125
|
+
|
|
126
|
+
httpsClient = calloc(1, sizeof *httpsClient);
|
|
127
|
+
httpsClient->scheme_host_port = strdup(addon_data->scheme_host_port);
|
|
128
|
+
ziti_src_init(thread_loop, &(httpsClient->ziti_src), addon_data->service, ztx );
|
|
129
|
+
um_http_init_with_src(thread_loop, &(httpsClient->client), addon_data->scheme_host_port, (um_src_t *)&(httpsClient->ziti_src) );
|
|
130
|
+
|
|
131
|
+
clientListMap->kvPairs[i].value = httpsClient;
|
|
132
|
+
|
|
133
|
+
ZITI_NODEJS_LOG(DEBUG, "*********** new client [%p] now occupying slot [%d]", httpsClient, i);
|
|
134
|
+
|
|
135
|
+
numReplaced++;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return numReplaced;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
*
|
|
143
|
+
*/
|
|
144
|
+
static void allocate_client(uv_work_t* req) {
|
|
145
|
+
|
|
146
|
+
ZITI_NODEJS_LOG(DEBUG, "allocate_client() entered, uv_work_t is: %p", req);
|
|
147
|
+
|
|
148
|
+
HttpsAddonData* addon_data = (HttpsAddonData*) req->data;
|
|
149
|
+
|
|
150
|
+
ZITI_NODEJS_LOG(DEBUG, "addon_data is: %p", addon_data);
|
|
151
|
+
|
|
152
|
+
// if (uv_mutex_trylock(&client_pool_lock)) {
|
|
153
|
+
// ZITI_NODEJS_LOG(ERROR, "uv_mutex_lock failure");
|
|
154
|
+
// }
|
|
155
|
+
|
|
156
|
+
struct ListMap* clientListMap = getInnerListMapValueForKey(HttpsClientListMap, addon_data->scheme_host_port);
|
|
157
|
+
|
|
158
|
+
if (NULL == clientListMap) { // If first time seeing this key, spawn a pool of clients for it
|
|
159
|
+
|
|
160
|
+
clientListMap = newListMap();
|
|
161
|
+
listMapInsert(HttpsClientListMap, addon_data->scheme_host_port, (void*)clientListMap);
|
|
162
|
+
|
|
163
|
+
uv_sem_init(&(clientListMap->sem), perKeyListMapCapacity);
|
|
164
|
+
|
|
165
|
+
for (int i = 0; i < perKeyListMapCapacity; i++) {
|
|
166
|
+
|
|
167
|
+
HttpsClient* httpsClient = calloc(1, sizeof *httpsClient);
|
|
168
|
+
httpsClient->scheme_host_port = strdup(addon_data->scheme_host_port);
|
|
169
|
+
ziti_src_init(thread_loop, &(httpsClient->ziti_src), addon_data->service, ztx );
|
|
170
|
+
um_http_init_with_src(thread_loop, &(httpsClient->client), addon_data->scheme_host_port, (um_src_t *)&(httpsClient->ziti_src) );
|
|
171
|
+
|
|
172
|
+
listMapInsert(clientListMap, addon_data->scheme_host_port, (void*)httpsClient);
|
|
173
|
+
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
|
|
178
|
+
purge_and_replace_bad_clients(clientListMap, addon_data);
|
|
179
|
+
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
ZITI_NODEJS_LOG(DEBUG, "----------> acquiring sem");
|
|
183
|
+
uv_sem_wait(&(clientListMap->sem));
|
|
184
|
+
ZITI_NODEJS_LOG(DEBUG, "----------> successfully acquired sem");
|
|
185
|
+
|
|
186
|
+
addon_data->httpsClient = getHttpsClientForKey(clientListMap, addon_data->scheme_host_port);
|
|
187
|
+
ZITI_NODEJS_LOG(DEBUG, "----------> client is: [%p]", addon_data->httpsClient);
|
|
188
|
+
|
|
189
|
+
if (NULL == addon_data->httpsClient) {
|
|
190
|
+
ZITI_NODEJS_LOG(DEBUG, "----------> client is NULL, so we must purge_and_replace_bad_clients");
|
|
191
|
+
purge_and_replace_bad_clients(clientListMap, addon_data);
|
|
192
|
+
|
|
193
|
+
addon_data->httpsClient = getHttpsClientForKey(clientListMap, addon_data->scheme_host_port);
|
|
194
|
+
ZITI_NODEJS_LOG(DEBUG, "----------> client is: [%p]", addon_data->httpsClient);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (NULL == addon_data->httpsClient) {
|
|
198
|
+
ZITI_NODEJS_LOG(DEBUG, "----------> client is NULL, so we are in an unrecoverable state!");
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* This function is responsible for calling the JavaScript on_resp_body callback function
|
|
206
|
+
* that was specified when the Ziti_https_request(...) was called from JavaScript.
|
|
207
|
+
*/
|
|
208
|
+
static void CallJs_on_resp_body(napi_env env, napi_value js_cb, void* context, void* data) {
|
|
209
|
+
|
|
210
|
+
ZITI_NODEJS_LOG(DEBUG, "entered");
|
|
211
|
+
|
|
212
|
+
// This parameter is not used.
|
|
213
|
+
(void) context;
|
|
214
|
+
|
|
215
|
+
// Retrieve the HttpsRespBodyItem created by the worker thread.
|
|
216
|
+
HttpsRespBodyItem* item = (HttpsRespBodyItem*)data;
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
// env and js_cb may both be NULL if Node.js is in its cleanup phase, and
|
|
220
|
+
// items are left over from earlier thread-safe calls from the worker thread.
|
|
221
|
+
// When env is NULL, we simply skip over the call into Javascript
|
|
222
|
+
if (env != NULL) {
|
|
223
|
+
|
|
224
|
+
napi_value undefined;
|
|
225
|
+
|
|
226
|
+
// Retrieve the JavaScript `undefined` value so we can use it as the `this`
|
|
227
|
+
// value of the JavaScript function call.
|
|
228
|
+
napi_get_undefined(env, &undefined);
|
|
229
|
+
|
|
230
|
+
// const obj = {}
|
|
231
|
+
napi_value js_http_item, js_req, js_len, js_buffer;
|
|
232
|
+
void* result_data;
|
|
233
|
+
int rc = napi_create_object(env, &js_http_item);
|
|
234
|
+
if (rc != napi_ok) {
|
|
235
|
+
napi_throw_error(env, "EINVAL", "failure to create object");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// obj.req = req
|
|
239
|
+
napi_create_int64(env, (int64_t)item->req, &js_req);
|
|
240
|
+
if (rc != napi_ok) {
|
|
241
|
+
napi_throw_error(env, "EINVAL", "failure to create resp.req");
|
|
242
|
+
}
|
|
243
|
+
rc = napi_set_named_property(env, js_http_item, "req", js_req);
|
|
244
|
+
if (rc != napi_ok) {
|
|
245
|
+
napi_throw_error(env, "EINVAL", "failure to set named property req");
|
|
246
|
+
}
|
|
247
|
+
ZITI_NODEJS_LOG(DEBUG, "js_req: %p", item->req);
|
|
248
|
+
|
|
249
|
+
// obj.len = len
|
|
250
|
+
rc = napi_create_int32(env, item->len, &js_len);
|
|
251
|
+
if (rc != napi_ok) {
|
|
252
|
+
napi_throw_error(env, "EINVAL", "failure to create resp.len");
|
|
253
|
+
}
|
|
254
|
+
rc = napi_set_named_property(env, js_http_item, "len", js_len);
|
|
255
|
+
if (rc != napi_ok) {
|
|
256
|
+
napi_throw_error(env, "EINVAL", "failure to set named property len");
|
|
257
|
+
}
|
|
258
|
+
ZITI_NODEJS_LOG(DEBUG, "len: %zd", item->len);
|
|
259
|
+
|
|
260
|
+
// obj.body = body
|
|
261
|
+
if (NULL != item->body) {
|
|
262
|
+
rc = napi_create_buffer_copy(env, item->len, (const void*)item->body, (void**)&result_data, &js_buffer);
|
|
263
|
+
if (rc != napi_ok) {
|
|
264
|
+
napi_throw_error(env, "EINVAL", "failure to create resp.data");
|
|
265
|
+
}
|
|
266
|
+
rc = napi_set_named_property(env, js_http_item, "body", js_buffer);
|
|
267
|
+
if (rc != napi_ok) {
|
|
268
|
+
napi_throw_error(env, "EINVAL", "failure to set named property body");
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
rc = napi_set_named_property(env, js_http_item, "body", undefined);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Call the JavaScript function and pass it the HttpsRespItem
|
|
275
|
+
rc = napi_call_function(
|
|
276
|
+
env,
|
|
277
|
+
undefined,
|
|
278
|
+
js_cb,
|
|
279
|
+
1,
|
|
280
|
+
&js_http_item,
|
|
281
|
+
NULL
|
|
282
|
+
);
|
|
283
|
+
if (rc != napi_ok) {
|
|
284
|
+
napi_throw_error(env, "EINVAL", "failure to invoke JS callback");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
*
|
|
293
|
+
*/
|
|
294
|
+
void on_resp_body(um_http_req_t *req, const char *body, ssize_t len) {
|
|
295
|
+
|
|
296
|
+
// ZITI_NODEJS_LOG(DEBUG, "len: %zd, body is: \n>>>>>%s<<<<<", len, body);
|
|
297
|
+
ZITI_NODEJS_LOG(DEBUG, "body: %p", body);
|
|
298
|
+
ZITI_NODEJS_LOG(DEBUG, "len: %zd", len);
|
|
299
|
+
|
|
300
|
+
HttpsAddonData* addon_data = (HttpsAddonData*) req->data;
|
|
301
|
+
ZITI_NODEJS_LOG(DEBUG, "addon_data->httpsClient is: %p", addon_data->httpsClient);
|
|
302
|
+
|
|
303
|
+
HttpsRespBodyItem* item = calloc(1, sizeof(*item));
|
|
304
|
+
ZITI_NODEJS_LOG(DEBUG, "new HttpsRespBodyItem is: %p", item);
|
|
305
|
+
|
|
306
|
+
// Grab everything off the um_http_resp_t that we need to eventually pass on to the JS on_resp_body callback.
|
|
307
|
+
// If we wait until CallJs_on_resp_body is invoked to do that work, the um_http_resp_t may have already been free'd by the C-SDK
|
|
308
|
+
|
|
309
|
+
item->req = req;
|
|
310
|
+
|
|
311
|
+
if (NULL != body) {
|
|
312
|
+
item->body = calloc(1, len);
|
|
313
|
+
memcpy((void*)item->body, body, len);
|
|
314
|
+
} else {
|
|
315
|
+
item->body = NULL;
|
|
316
|
+
}
|
|
317
|
+
item->len = len;
|
|
318
|
+
|
|
319
|
+
if ((NULL == body) && (UV_EOF == len)) {
|
|
320
|
+
ZITI_NODEJS_LOG(DEBUG, "<--------- returning httpsClient [%p] back to pool", addon_data->httpsClient);
|
|
321
|
+
addon_data->httpsClient->active = false;
|
|
322
|
+
|
|
323
|
+
struct ListMap* clientListMap = getInnerListMapValueForKey(HttpsClientListMap, addon_data->scheme_host_port);
|
|
324
|
+
|
|
325
|
+
ZITI_NODEJS_LOG(DEBUG, "<-------- returning sem for client: [%p] ", addon_data->httpsClient);
|
|
326
|
+
uv_sem_post(&(clientListMap->sem));
|
|
327
|
+
ZITI_NODEJS_LOG(DEBUG, " after returning sem for client: [%p] ", addon_data->httpsClient);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
ZITI_NODEJS_LOG(DEBUG, "calling tsfn_on_resp_body: %p", addon_data->tsfn_on_resp_body);
|
|
331
|
+
|
|
332
|
+
// Initiate the call into the JavaScript callback.
|
|
333
|
+
int rc = napi_call_threadsafe_function(
|
|
334
|
+
addon_data->tsfn_on_resp_body,
|
|
335
|
+
item,
|
|
336
|
+
napi_tsfn_blocking);
|
|
337
|
+
if (rc != napi_ok) {
|
|
338
|
+
napi_throw_error(addon_data->env, "EINVAL", "failure to invoke JS callback");
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* This function is responsible for calling the JavaScript on_resp callback function
|
|
346
|
+
* that was specified when the Ziti_https_request(...) was called from JavaScript.
|
|
347
|
+
*/
|
|
348
|
+
static void CallJs_on_resp(napi_env env, napi_value js_cb, void* context, void* data) {
|
|
349
|
+
|
|
350
|
+
ZITI_NODEJS_LOG(DEBUG, "entered");
|
|
351
|
+
|
|
352
|
+
// This parameter is not used.
|
|
353
|
+
(void) context;
|
|
354
|
+
|
|
355
|
+
// Retrieve the HttpsRespItem created by the worker thread.
|
|
356
|
+
HttpsRespItem* item = (HttpsRespItem*)data;
|
|
357
|
+
ZITI_NODEJS_LOG(DEBUG, "HttpsRespItem is: %p", item);
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
// env and js_cb may both be NULL if Node.js is in its cleanup phase, and
|
|
361
|
+
// items are left over from earlier thread-safe calls from the worker thread.
|
|
362
|
+
// When env is NULL, we simply skip over the call into Javascript
|
|
363
|
+
if (env != NULL) {
|
|
364
|
+
|
|
365
|
+
napi_value undefined;
|
|
366
|
+
|
|
367
|
+
// Retrieve the JavaScript `undefined` value so we can use it as the `this`
|
|
368
|
+
// value of the JavaScript function call.
|
|
369
|
+
napi_get_undefined(env, &undefined);
|
|
370
|
+
|
|
371
|
+
// const obj = {}
|
|
372
|
+
napi_value js_http_item, js_req, js_code, js_status;
|
|
373
|
+
int rc = napi_create_object(env, &js_http_item);
|
|
374
|
+
if (rc != napi_ok) {
|
|
375
|
+
napi_throw_error(env, "EINVAL", "failure to create object");
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// obj.req = req
|
|
379
|
+
napi_create_int64(env, (int64_t)item->req, &js_req);
|
|
380
|
+
if (rc != napi_ok) {
|
|
381
|
+
napi_throw_error(env, "EINVAL", "failure to create resp.req");
|
|
382
|
+
}
|
|
383
|
+
rc = napi_set_named_property(env, js_http_item, "req", js_req);
|
|
384
|
+
if (rc != napi_ok) {
|
|
385
|
+
napi_throw_error(env, "EINVAL", "failure to set named property req");
|
|
386
|
+
}
|
|
387
|
+
ZITI_NODEJS_LOG(DEBUG, "js_req: %p", item->req);
|
|
388
|
+
|
|
389
|
+
// obj.code = code
|
|
390
|
+
rc = napi_create_int32(env, item->code, &js_code);
|
|
391
|
+
if (rc != napi_ok) {
|
|
392
|
+
napi_throw_error(env, "EINVAL", "failure to create resp.code");
|
|
393
|
+
}
|
|
394
|
+
rc = napi_set_named_property(env, js_http_item, "code", js_code);
|
|
395
|
+
if (rc != napi_ok) {
|
|
396
|
+
napi_throw_error(env, "EINVAL", "failure to set named property code");
|
|
397
|
+
}
|
|
398
|
+
ZITI_NODEJS_LOG(DEBUG, "code: %d", item->code);
|
|
399
|
+
|
|
400
|
+
// obj.status = status
|
|
401
|
+
rc = napi_create_string_utf8(env, (const char*)item->status, strlen(item->status), &js_status);
|
|
402
|
+
if (rc != napi_ok) {
|
|
403
|
+
napi_throw_error(env, "EINVAL", "failure to create resp.status");
|
|
404
|
+
}
|
|
405
|
+
rc = napi_set_named_property(env, js_http_item, "status", js_status);
|
|
406
|
+
if (rc != napi_ok) {
|
|
407
|
+
napi_throw_error(env, "EINVAL", "failure to set named property status");
|
|
408
|
+
}
|
|
409
|
+
ZITI_NODEJS_LOG(DEBUG, "status: %s", item->status);
|
|
410
|
+
|
|
411
|
+
// const headers = {}
|
|
412
|
+
um_http_hdr *h;
|
|
413
|
+
int i = 0;
|
|
414
|
+
napi_value js_headers, js_header_value;
|
|
415
|
+
napi_value js_cookies_array = NULL;
|
|
416
|
+
|
|
417
|
+
rc = napi_create_object(env, &js_headers);
|
|
418
|
+
if (rc != napi_ok) {
|
|
419
|
+
napi_throw_error(env, "EINVAL", "failure to create js_headers object");
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
for (h = item->headers; h != NULL && h->name != NULL; h++) {
|
|
423
|
+
|
|
424
|
+
if (strcasecmp(h->name, "set-cookie") == 0) {
|
|
425
|
+
if (NULL == js_cookies_array) {
|
|
426
|
+
rc = napi_create_array(env, &js_cookies_array);
|
|
427
|
+
if (rc != napi_ok) {
|
|
428
|
+
napi_throw_error(env, "EINVAL", "failure to create js_cookies_array");
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
rc = napi_create_string_utf8(env, (const char*)h->value, strlen(h->value), &js_header_value);
|
|
432
|
+
if (rc != napi_ok) {
|
|
433
|
+
napi_throw_error(env, "EINVAL", "failure to create js_header_value");
|
|
434
|
+
}
|
|
435
|
+
rc = napi_set_element(env, js_cookies_array, i++, js_header_value);
|
|
436
|
+
if (rc != napi_ok) {
|
|
437
|
+
napi_throw_error(env, "EINVAL", "failure to set js_header_element");
|
|
438
|
+
}
|
|
439
|
+
} else {
|
|
440
|
+
rc = napi_create_string_utf8(env, (const char*)h->value, strlen(h->value), &js_header_value);
|
|
441
|
+
if (rc != napi_ok) {
|
|
442
|
+
napi_throw_error(env, "EINVAL", "failure to create js_header_value");
|
|
443
|
+
}
|
|
444
|
+
rc = napi_set_named_property(env, js_headers, h->name, js_header_value);
|
|
445
|
+
if (rc != napi_ok) {
|
|
446
|
+
napi_throw_error(env, "EINVAL", "failure to set header");
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (NULL != js_cookies_array) {
|
|
451
|
+
rc = napi_set_named_property(env, js_headers, "Set-Cookie", js_cookies_array);
|
|
452
|
+
if (rc != napi_ok) {
|
|
453
|
+
napi_throw_error(env, "EINVAL", "failure to set set-cookie header");
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// obj.headers = headers
|
|
458
|
+
rc = napi_set_named_property(env, js_http_item, "headers", js_headers);
|
|
459
|
+
if (rc != napi_ok) {
|
|
460
|
+
napi_throw_error(env, "EINVAL", "failure to set named property headers");
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Call the JavaScript function and pass it the HttpsRespItem
|
|
464
|
+
rc = napi_call_function(
|
|
465
|
+
env,
|
|
466
|
+
undefined,
|
|
467
|
+
js_cb,
|
|
468
|
+
1,
|
|
469
|
+
&js_http_item,
|
|
470
|
+
NULL
|
|
471
|
+
);
|
|
472
|
+
if (rc != napi_ok) {
|
|
473
|
+
napi_throw_error(env, "EINVAL", "failure to invoke JS callback");
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
*
|
|
482
|
+
*/
|
|
483
|
+
void on_resp(um_http_resp_t *resp, void *data) {
|
|
484
|
+
ZITI_NODEJS_LOG(DEBUG, "resp: %p", resp);
|
|
485
|
+
|
|
486
|
+
HttpsAddonData* addon_data = (HttpsAddonData*) data;
|
|
487
|
+
ZITI_NODEJS_LOG(DEBUG, "addon_data->httpsReq is: %p", addon_data->httpsReq);
|
|
488
|
+
|
|
489
|
+
addon_data->httpsReq->on_resp_has_fired = true;
|
|
490
|
+
addon_data->httpsReq->respCode = resp->code;
|
|
491
|
+
|
|
492
|
+
HttpsRespItem* item = calloc(1, sizeof(*item));
|
|
493
|
+
ZITI_NODEJS_LOG(DEBUG, "new HttpsRespItem is: %p", item);
|
|
494
|
+
|
|
495
|
+
// Grab everything off the um_http_resp_t that we need to eventually pass on to the JS on_resp callback.
|
|
496
|
+
// If we wait until CallJs_on_resp is invoked to do that work, the um_http_resp_t may have already been free'd by the C-SDK
|
|
497
|
+
|
|
498
|
+
item->req = resp->req;
|
|
499
|
+
ZITI_NODEJS_LOG(DEBUG, "item->req: %p", item->req);
|
|
500
|
+
|
|
501
|
+
item->code = resp->code;
|
|
502
|
+
ZITI_NODEJS_LOG(DEBUG, "item->code: %d", item->code);
|
|
503
|
+
|
|
504
|
+
item->status = strdup(resp->status);
|
|
505
|
+
ZITI_NODEJS_LOG(DEBUG, "item->status: %s", item->status);
|
|
506
|
+
|
|
507
|
+
int header_cnt = 0;
|
|
508
|
+
um_http_hdr *h;
|
|
509
|
+
LIST_FOREACH(h, &resp->headers, _next) {
|
|
510
|
+
header_cnt++;
|
|
511
|
+
}
|
|
512
|
+
ZITI_NODEJS_LOG(DEBUG, "header_cnt: %d", header_cnt);
|
|
513
|
+
|
|
514
|
+
item->headers = calloc(header_cnt + 1, sizeof(um_http_hdr));
|
|
515
|
+
header_cnt = 0;
|
|
516
|
+
LIST_FOREACH(h, &resp->headers, _next) {
|
|
517
|
+
item->headers[header_cnt].name = strdup(h->name);
|
|
518
|
+
item->headers[header_cnt].value = strdup(h->value);
|
|
519
|
+
ZITI_NODEJS_LOG(DEBUG, "item->headers[%d]: %s : %s", header_cnt, item->headers[header_cnt].name, item->headers[header_cnt].value);
|
|
520
|
+
header_cnt++;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if ((UV_EOF == resp->code) || (resp->code < 0)) {
|
|
524
|
+
ZITI_NODEJS_LOG(ERROR, "<--------- returning httpsClient [%p] back to pool due to error: [%d]", addon_data->httpsClient, resp->code);
|
|
525
|
+
addon_data->httpsClient->active = false;
|
|
526
|
+
|
|
527
|
+
// Before we fully release this client (via semaphore post) let's indicate purge is needed, because after errs happen on a client,
|
|
528
|
+
// subsequent requests using that client never get processed.
|
|
529
|
+
ZITI_NODEJS_LOG(DEBUG, "*********** due to error, purge now necessary for client: [%p]", addon_data->httpsClient);
|
|
530
|
+
addon_data->httpsClient->purge = true;
|
|
531
|
+
|
|
532
|
+
struct ListMap* clientListMap = getInnerListMapValueForKey(HttpsClientListMap, addon_data->scheme_host_port);
|
|
533
|
+
|
|
534
|
+
ZITI_NODEJS_LOG(DEBUG, "<-------- returning sem for client: [%p] ", addon_data->httpsClient);
|
|
535
|
+
uv_sem_post(&(clientListMap->sem));
|
|
536
|
+
ZITI_NODEJS_LOG(DEBUG, " after returning sem for client: [%p] ", addon_data->httpsClient);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Initiate the call into the JavaScript callback. The call into JavaScript will not have happened when this function returns, but it will be queued.
|
|
540
|
+
int rc = napi_call_threadsafe_function(
|
|
541
|
+
addon_data->tsfn_on_resp,
|
|
542
|
+
item,
|
|
543
|
+
napi_tsfn_blocking);
|
|
544
|
+
if (rc != napi_ok) {
|
|
545
|
+
napi_throw_error(addon_data->env, "EINVAL", "failure to invoke JS callback");
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (UV_EOF != resp->code) {
|
|
549
|
+
// We need body of the HTTP response, so wire up that callback now
|
|
550
|
+
resp->body_cb = on_resp_body;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* This function is responsible for calling the JavaScript on_resp callback function
|
|
557
|
+
* that was specified when the Ziti_https_request(...) was called from JavaScript.
|
|
558
|
+
*/
|
|
559
|
+
static void CallJs_on_req(napi_env env, napi_value js_cb, void* context, void* data) {
|
|
560
|
+
|
|
561
|
+
ZITI_NODEJS_LOG(DEBUG, "entered");
|
|
562
|
+
|
|
563
|
+
// This parameter is not used.
|
|
564
|
+
(void) context;
|
|
565
|
+
|
|
566
|
+
// Retrieve the addon_data created by the worker thread.
|
|
567
|
+
HttpsAddonData* addon_data = (HttpsAddonData*) data;
|
|
568
|
+
|
|
569
|
+
// env and js_cb may both be NULL if Node.js is in its cleanup phase, and
|
|
570
|
+
// items are left over from earlier thread-safe calls from the worker thread.
|
|
571
|
+
// When env is NULL, we simply skip over the call into Javascript
|
|
572
|
+
if (env != NULL) {
|
|
573
|
+
|
|
574
|
+
napi_value undefined;
|
|
575
|
+
|
|
576
|
+
// Retrieve the JavaScript `undefined` value so we can use it as the `this`
|
|
577
|
+
// value of the JavaScript function call.
|
|
578
|
+
napi_get_undefined(env, &undefined);
|
|
579
|
+
|
|
580
|
+
// const obj = {}
|
|
581
|
+
napi_value js_http_item, js_req;
|
|
582
|
+
int rc = napi_create_object(env, &js_http_item);
|
|
583
|
+
if (rc != napi_ok) {
|
|
584
|
+
napi_throw_error(env, "EINVAL", "failure to create object");
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// obj.req = req
|
|
588
|
+
napi_create_int64(env, (int64_t)addon_data->httpsReq, &js_req);
|
|
589
|
+
if (rc != napi_ok) {
|
|
590
|
+
napi_throw_error(env, "EINVAL", "failure to create resp.req");
|
|
591
|
+
}
|
|
592
|
+
rc = napi_set_named_property(env, js_http_item, "req", js_req);
|
|
593
|
+
if (rc != napi_ok) {
|
|
594
|
+
napi_throw_error(env, "EINVAL", "failure to set named property req");
|
|
595
|
+
}
|
|
596
|
+
ZITI_NODEJS_LOG(DEBUG, "js_req: %p", addon_data->httpsReq);
|
|
597
|
+
|
|
598
|
+
// Call the JavaScript function and pass it the req
|
|
599
|
+
rc = napi_call_function(
|
|
600
|
+
env,
|
|
601
|
+
undefined,
|
|
602
|
+
js_cb,
|
|
603
|
+
1,
|
|
604
|
+
&js_http_item,
|
|
605
|
+
NULL
|
|
606
|
+
);
|
|
607
|
+
if (rc != napi_ok) {
|
|
608
|
+
napi_throw_error(env, "EINVAL", "failure to invoke JS callback");
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
*
|
|
617
|
+
*/
|
|
618
|
+
void on_client(uv_work_t* req, int status) {
|
|
619
|
+
|
|
620
|
+
ZITI_NODEJS_LOG(DEBUG, "on_client() entered, uv_work_t is: %p, status is: %d", req, status);
|
|
621
|
+
|
|
622
|
+
HttpsAddonData* addon_data = (HttpsAddonData*) req->data;
|
|
623
|
+
ZITI_NODEJS_LOG(DEBUG, "client is: [%p]", addon_data->httpsClient);
|
|
624
|
+
|
|
625
|
+
// Initiate the request: HTTP -> TLS -> Ziti -> Service
|
|
626
|
+
um_http_req_t *r = um_http_req(
|
|
627
|
+
&(addon_data->httpsClient->client),
|
|
628
|
+
addon_data->method,
|
|
629
|
+
addon_data->path,
|
|
630
|
+
on_resp,
|
|
631
|
+
addon_data /* Pass our addon data around so we can eventually find our way back to the JS callback */
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
ZITI_NODEJS_LOG(DEBUG, "req: %p", r);
|
|
635
|
+
addon_data->httpsReq->req = r;
|
|
636
|
+
|
|
637
|
+
// Add headers to request
|
|
638
|
+
for (int i = 0; i < (int)addon_data->headers_array_length; i++) {
|
|
639
|
+
um_http_req_header(r, addon_data->header_name[i], addon_data->header_value[i]);
|
|
640
|
+
// free(addon_data->header_name[i]);
|
|
641
|
+
// free(addon_data->header_value[i]);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Initiate the call into the JavaScript callback. The call into JavaScript will not have happened when this function returns, but it will be queued.
|
|
645
|
+
int rc = napi_call_threadsafe_function(
|
|
646
|
+
addon_data->tsfn_on_req,
|
|
647
|
+
addon_data,
|
|
648
|
+
napi_tsfn_blocking);
|
|
649
|
+
if (rc != napi_ok) {
|
|
650
|
+
napi_throw_error(addon_data->env, "EINVAL", "failure to invoke JS callback");
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Initiate an HTTPS request
|
|
659
|
+
*
|
|
660
|
+
* @param {string} [0] url
|
|
661
|
+
* @param {string} [1] method
|
|
662
|
+
* @param {string[]} [2] headers; Array of strings of the form "name:value"
|
|
663
|
+
* @param {func} [3] JS on_req callback; This is invoked from 'on_client' function above
|
|
664
|
+
* @param {func} [4] JS on_resp callback; This is invoked from 'on_resp' function above
|
|
665
|
+
* @param {func} [5] JS on_resp_data callback; This is invoked from 'on_resp_data' function above
|
|
666
|
+
*
|
|
667
|
+
* @returns {um_http_req_t} req This allows the JS to subsequently write the Body to the request (see _Ziti_http_request_data)
|
|
668
|
+
|
|
669
|
+
*/
|
|
670
|
+
napi_value _Ziti_http_request(napi_env env, const napi_callback_info info) {
|
|
671
|
+
|
|
672
|
+
napi_status status;
|
|
673
|
+
size_t result;
|
|
674
|
+
napi_value jsRetval;
|
|
675
|
+
char* query = "";
|
|
676
|
+
int rc;
|
|
677
|
+
|
|
678
|
+
ZITI_NODEJS_LOG(DEBUG, "entered");
|
|
679
|
+
|
|
680
|
+
if (NULL == HttpsClientListMap) {
|
|
681
|
+
HttpsClientListMap = newListMap();
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
size_t argc = 6;
|
|
685
|
+
napi_value args[6];
|
|
686
|
+
status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
|
|
687
|
+
if (status != napi_ok) {
|
|
688
|
+
napi_throw_error(env, NULL, "Failed to parse arguments");
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
if (argc < 6) {
|
|
692
|
+
napi_throw_error(env, "EINVAL", "Too few arguments");
|
|
693
|
+
return NULL;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
HttpsAddonData* addon_data = calloc(1, sizeof(HttpsAddonData));
|
|
697
|
+
ZITI_NODEJS_LOG(DEBUG, "allocated addon_data : %p", addon_data);
|
|
698
|
+
addon_data->env = env;
|
|
699
|
+
|
|
700
|
+
// Obtain url length
|
|
701
|
+
size_t url_len;
|
|
702
|
+
status = napi_get_value_string_utf8(env, args[0], NULL, 0, &url_len);
|
|
703
|
+
if (status != napi_ok) {
|
|
704
|
+
napi_throw_error(env, "EINVAL", "url is not a string");
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// Obtain url
|
|
708
|
+
char* url = calloc(1, url_len+2);
|
|
709
|
+
status = napi_get_value_string_utf8(env, args[0], url, url_len+1, &result);
|
|
710
|
+
if (status != napi_ok) {
|
|
711
|
+
napi_throw_error(env, "EINVAL", "Failed to obtain url");
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
ZITI_NODEJS_LOG(DEBUG, "url: %s", url);
|
|
715
|
+
|
|
716
|
+
struct http_parser_url url_parse = {0};
|
|
717
|
+
rc = http_parser_parse_url(url, strlen(url), false, &url_parse);
|
|
718
|
+
if (rc != 0) {
|
|
719
|
+
napi_throw_error(env, "EINVAL", "Failed to parse url");
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
if (url_parse.field_set & (U1 << (unsigned int) UF_HOST)) {
|
|
723
|
+
addon_data->service = strndup(url + url_parse.field_data[UF_HOST].off, url_parse.field_data[UF_HOST].len);
|
|
724
|
+
}
|
|
725
|
+
else {
|
|
726
|
+
ZITI_NODEJS_LOG(ERROR, "invalid URL: no host");
|
|
727
|
+
napi_throw_error(env, "EINVAL", "invalid URL: no host");
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
ZITI_NODEJS_LOG(DEBUG, "service: %s", addon_data->service);
|
|
731
|
+
|
|
732
|
+
if (url_parse.field_set & (U1 << (unsigned int) UF_PATH)) {
|
|
733
|
+
addon_data->path = strndup(url + url_parse.field_data[UF_PATH].off, url_parse.field_data[UF_PATH].len);
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
ZITI_NODEJS_LOG(ERROR, "invalid URL: no path");
|
|
737
|
+
napi_throw_error(env, "EINVAL", "invalid URL: no path");
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
ZITI_NODEJS_LOG(DEBUG, "path: [%s]", addon_data->path);
|
|
741
|
+
|
|
742
|
+
if (url_parse.field_set & (U1 << (unsigned int) UF_QUERY)) {
|
|
743
|
+
query = strndup(url + url_parse.field_data[UF_QUERY].off, url_parse.field_data[UF_QUERY].len);
|
|
744
|
+
ZITI_NODEJS_LOG(DEBUG, "query: [%s]", query);
|
|
745
|
+
char* expanded_path = calloc(1, strlen(addon_data->path)+strlen(query)+2);
|
|
746
|
+
|
|
747
|
+
strcat(expanded_path, addon_data->path);
|
|
748
|
+
strcat(expanded_path, "?");
|
|
749
|
+
strcat(expanded_path, query);
|
|
750
|
+
ZITI_NODEJS_LOG(DEBUG, "expanded_path: [%s]", expanded_path);
|
|
751
|
+
free(addon_data->path);
|
|
752
|
+
addon_data->path = expanded_path;
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
ZITI_NODEJS_LOG(DEBUG, "URL: no query found");
|
|
756
|
+
}
|
|
757
|
+
ZITI_NODEJS_LOG(DEBUG, "adjusted path: [%s]", addon_data->path);
|
|
758
|
+
|
|
759
|
+
addon_data->scheme_host_port = strndup(url + url_parse.field_data[UF_SCHEMA].off, url_parse.field_data[UF_PATH].off);
|
|
760
|
+
|
|
761
|
+
ZITI_NODEJS_LOG(DEBUG, "scheme_host_port: %s", addon_data->scheme_host_port);
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
// Obtain method length
|
|
765
|
+
size_t method_len;
|
|
766
|
+
status = napi_get_value_string_utf8(env, args[1], NULL, 0, &method_len);
|
|
767
|
+
if (status != napi_ok) {
|
|
768
|
+
napi_throw_error(env, "EINVAL", "method is not a string");
|
|
769
|
+
}
|
|
770
|
+
// Obtain method
|
|
771
|
+
addon_data->method = calloc(1, method_len+2);
|
|
772
|
+
status = napi_get_value_string_utf8(env, args[1], addon_data->method, method_len+1, &result);
|
|
773
|
+
if (status != napi_ok) {
|
|
774
|
+
napi_throw_error(env, "EINVAL", "Failed to obtain method");
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
ZITI_NODEJS_LOG(DEBUG, "method: %s", addon_data->method);
|
|
778
|
+
|
|
779
|
+
// Obtain ptr to JS on_req callback function
|
|
780
|
+
napi_value js_cb = args[3];
|
|
781
|
+
napi_value work_name;
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
HttpsReq* httpsReq = calloc(1, sizeof(HttpsReq));
|
|
785
|
+
addon_data->httpsReq = httpsReq;
|
|
786
|
+
httpsReq->addon_data = addon_data;
|
|
787
|
+
|
|
788
|
+
// Create a string to describe this asynchronous operation.
|
|
789
|
+
rc = napi_create_string_utf8(env, "on_req", NAPI_AUTO_LENGTH, &work_name);
|
|
790
|
+
if (rc != napi_ok) {
|
|
791
|
+
napi_throw_error(env, "EINVAL", "Failed to create string");
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// Convert the callback retrieved from JavaScript into a thread-safe function (tsfn)
|
|
795
|
+
// which we can call from a worker thread.
|
|
796
|
+
rc = napi_create_threadsafe_function(
|
|
797
|
+
env,
|
|
798
|
+
js_cb,
|
|
799
|
+
NULL,
|
|
800
|
+
work_name,
|
|
801
|
+
0,
|
|
802
|
+
1,
|
|
803
|
+
NULL,
|
|
804
|
+
NULL,
|
|
805
|
+
NULL,
|
|
806
|
+
CallJs_on_req,
|
|
807
|
+
&(addon_data->tsfn_on_req)
|
|
808
|
+
);
|
|
809
|
+
if (rc != napi_ok) {
|
|
810
|
+
napi_throw_error(env, "EINVAL", "Failed to create threadsafe_function");
|
|
811
|
+
}
|
|
812
|
+
ZITI_NODEJS_LOG(DEBUG, "napi_create_threadsafe_function addon_data->tsfn_on_req() : %p", addon_data->tsfn_on_req);
|
|
813
|
+
|
|
814
|
+
// Obtain ptr to JS on_resp callback function
|
|
815
|
+
napi_value js_cb2 = args[4];
|
|
816
|
+
|
|
817
|
+
// Create a string to describe this asynchronous operation.
|
|
818
|
+
rc = napi_create_string_utf8(env, "on_resp", NAPI_AUTO_LENGTH, &work_name);
|
|
819
|
+
if (rc != napi_ok) {
|
|
820
|
+
napi_throw_error(env, "EINVAL", "Failed to create string");
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Convert the callback retrieved from JavaScript into a thread-safe function (tsfn)
|
|
824
|
+
// which we can call from a worker thread.
|
|
825
|
+
rc = napi_create_threadsafe_function(
|
|
826
|
+
env,
|
|
827
|
+
js_cb2,
|
|
828
|
+
NULL,
|
|
829
|
+
work_name,
|
|
830
|
+
0,
|
|
831
|
+
1,
|
|
832
|
+
NULL,
|
|
833
|
+
NULL,
|
|
834
|
+
NULL,
|
|
835
|
+
CallJs_on_resp,
|
|
836
|
+
&(addon_data->tsfn_on_resp)
|
|
837
|
+
);
|
|
838
|
+
if (rc != napi_ok) {
|
|
839
|
+
napi_throw_error(env, "EINVAL", "Failed to create threadsafe_function");
|
|
840
|
+
}
|
|
841
|
+
ZITI_NODEJS_LOG(DEBUG, "napi_create_threadsafe_function addon_data->tsfn_on_resp() : %p", addon_data->tsfn_on_resp);
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
// Obtain ptr to JS on_resp_data callback function
|
|
845
|
+
napi_value js_cb3 = args[5];
|
|
846
|
+
|
|
847
|
+
// Create a string to describe this asynchronous operation.
|
|
848
|
+
rc = napi_create_string_utf8(env, "on_resp_data", NAPI_AUTO_LENGTH, &work_name);
|
|
849
|
+
if (rc != napi_ok) {
|
|
850
|
+
napi_throw_error(env, "EINVAL", "Failed to create string");
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// Convert the callback retrieved from JavaScript into a thread-safe function (tsfn)
|
|
854
|
+
// which we can call from a worker thread.
|
|
855
|
+
rc = napi_create_threadsafe_function(
|
|
856
|
+
env,
|
|
857
|
+
js_cb3,
|
|
858
|
+
NULL,
|
|
859
|
+
work_name,
|
|
860
|
+
0,
|
|
861
|
+
1,
|
|
862
|
+
NULL,
|
|
863
|
+
NULL,
|
|
864
|
+
NULL,
|
|
865
|
+
CallJs_on_resp_body,
|
|
866
|
+
&(addon_data->tsfn_on_resp_body)
|
|
867
|
+
);
|
|
868
|
+
if (rc != napi_ok) {
|
|
869
|
+
napi_throw_error(env, "EINVAL", "Failed to create threadsafe_function");
|
|
870
|
+
}
|
|
871
|
+
ZITI_NODEJS_LOG(DEBUG, "napi_create_threadsafe_function addon_data->tsfn_on_resp_body() : %p", addon_data->tsfn_on_resp_body);
|
|
872
|
+
|
|
873
|
+
//
|
|
874
|
+
// Capture headers
|
|
875
|
+
//
|
|
876
|
+
uint32_t i;
|
|
877
|
+
status = napi_get_array_length(env, args[2], &addon_data->headers_array_length);
|
|
878
|
+
if (status != napi_ok) {
|
|
879
|
+
napi_throw_error(env, "EINVAL", "Failed to obtain headers array");
|
|
880
|
+
}
|
|
881
|
+
ZITI_NODEJS_LOG(DEBUG, "headers_array_length: %d", addon_data->headers_array_length);
|
|
882
|
+
for (i = 0; i < addon_data->headers_array_length; i++) {
|
|
883
|
+
|
|
884
|
+
napi_value headers_array_element;
|
|
885
|
+
status = napi_get_element(env, args[2], i, &headers_array_element);
|
|
886
|
+
if (status != napi_ok) {
|
|
887
|
+
napi_throw_error(env, "EINVAL", "Failed to obtain headers element");
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// Obtain element length
|
|
891
|
+
size_t element_len;
|
|
892
|
+
status = napi_get_value_string_utf8(env, headers_array_element, NULL, 0, &element_len);
|
|
893
|
+
if (status != napi_ok) {
|
|
894
|
+
napi_throw_error(env, "EINVAL", "header arry element is not a string");
|
|
895
|
+
}
|
|
896
|
+
ZITI_NODEJS_LOG(DEBUG, "element_len: %zd", element_len);
|
|
897
|
+
|
|
898
|
+
if (element_len > 8*1024) {
|
|
899
|
+
ZITI_NODEJS_LOG(DEBUG, "skipping header element; length too long");
|
|
900
|
+
addon_data->httpsReq->on_resp_has_fired = true;
|
|
901
|
+
break;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Obtain element
|
|
905
|
+
char* header_element = calloc(1, element_len+2);
|
|
906
|
+
status = napi_get_value_string_utf8(env, headers_array_element, header_element, element_len+1, &result);
|
|
907
|
+
if (status != napi_ok) {
|
|
908
|
+
napi_throw_error(env, "EINVAL", "Failed to obtain element");
|
|
909
|
+
}
|
|
910
|
+
ZITI_NODEJS_LOG(DEBUG, "header_element: %s", header_element);
|
|
911
|
+
|
|
912
|
+
char * header_name = strtok(header_element, ":");
|
|
913
|
+
if (NULL == header_name) {
|
|
914
|
+
napi_throw_error(env, "EINVAL", "Failed to split header element");
|
|
915
|
+
}
|
|
916
|
+
char * header_value = strtok(NULL, ":");
|
|
917
|
+
if (strlen(header_value) < 1) {
|
|
918
|
+
napi_throw_error(env, "EINVAL", "Failed to split header element");
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
addon_data->header_name[i] = strdup(header_name);
|
|
922
|
+
addon_data->header_value[i] = strdup(header_value);
|
|
923
|
+
|
|
924
|
+
free(header_element);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
//
|
|
928
|
+
// Queue the HTTP request. First thing that happens in the flow is to allocate a client from the pool
|
|
929
|
+
//
|
|
930
|
+
addon_data->uv_req.data = addon_data;
|
|
931
|
+
uv_queue_work(thread_loop, &addon_data->uv_req, allocate_client, on_client);
|
|
932
|
+
|
|
933
|
+
ZITI_NODEJS_LOG(DEBUG, "uv_queue_work of allocate_client returned req: %p", &(addon_data->uv_req));
|
|
934
|
+
|
|
935
|
+
//
|
|
936
|
+
// We always return zero. The real results/status are returned via the multiple callbacks
|
|
937
|
+
//
|
|
938
|
+
status = napi_create_int64(env, (int64_t)0, &jsRetval);
|
|
939
|
+
if (status != napi_ok) {
|
|
940
|
+
napi_throw_error(env, NULL, "Unable to create return value");
|
|
941
|
+
}
|
|
942
|
+
return jsRetval;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
void expose_ziti_https_request(napi_env env, napi_value exports) {
|
|
947
|
+
napi_status status;
|
|
948
|
+
napi_value fn;
|
|
949
|
+
|
|
950
|
+
status = napi_create_function(env, NULL, 0, _Ziti_http_request, NULL, &fn);
|
|
951
|
+
if (status != napi_ok) {
|
|
952
|
+
napi_throw_error(env, NULL, "Unable to wrap native function '_Ziti_http_request");
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
status = napi_set_named_property(env, exports, "Ziti_http_request", fn);
|
|
956
|
+
if (status != napi_ok) {
|
|
957
|
+
napi_throw_error(env, NULL, "Unable to populate exports for 'Ziti_http_request");
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
}
|