From 548e6f344f26d0b74dffdb45e723c27e7f267da8 Mon Sep 17 00:00:00 2001 From: Akhil Kedia Date: Tue, 13 Jun 2017 15:20:26 +0900 Subject: [PATCH] HTTPS Module for Tizen. IoT.js-DCO-1.0-Signed-off-by: Akhil Kedia akhil.kedia@samsung.com Change-Id: I75c80c5c50a91f42caacdcdd46f1619230aa5b12 --- build.config | 13 +- docs/api/IoT.js-API-HTTPS.md | 133 +++ src/iotjs_magic_strings.h | 19 + src/iotjs_module.h | 3 +- src/js/https.js | 30 + src/js/https_client.js | 156 ++++ src/js/https_incoming.js | 242 ++++++ src/modules/iotjs_module_https.c | 862 +++++++++++++++++++ src/modules/iotjs_module_https.h | 151 ++++ test/run_pass/test_https_get.js | 85 ++ test/run_pass/test_https_request_response.js | 119 +++ test/run_pass/test_https_timeout.js | 39 + test/testsets.json | 3 + tools/module_analyzer.py | 2 - 14 files changed, 1848 insertions(+), 9 deletions(-) create mode 100644 docs/api/IoT.js-API-HTTPS.md create mode 100755 src/js/https.js create mode 100644 src/js/https_client.js create mode 100644 src/js/https_incoming.js create mode 100644 src/modules/iotjs_module_https.c create mode 100644 src/modules/iotjs_module_https.h create mode 100644 test/run_pass/test_https_get.js create mode 100644 test/run_pass/test_https_request_response.js create mode 100644 test/run_pass/test_https_timeout.js diff --git a/build.config b/build.config index 342c82e..034cf46 100644 --- a/build.config +++ b/build.config @@ -98,19 +98,20 @@ "linux": ["m", "rt"], "darwin": [], "nuttx": [], - "tizen": ["m", "rt"], + "tizen": ["m", "rt", "curl"], "tizenrt": [] } }, "module": { "always": ["buffer", "console", "events", "fs", "module", "timers"], - "include": ["assert", "dns", "http", "net", "stream", "testdriver"], + "include": ["assert", "dns", "http", "net", "stream", "testdriver", "https"], "exclude": { "all": [], - "linux": ["adc", "ble", "dgram", "gpio", "i2c", "pwm", "spi", "uart"], - "nuttx": ["adc", "dgram", "gpio", "i2c", "pwm", "stm32f4dis", "uart"], - "darwin": [], - "tizenrt": ["gpio", "pwm"] + "linux": ["adc", "ble", "dgram", "gpio", "i2c", "pwm", "spi", "uart", "https"], + "nuttx": ["adc", "dgram", "gpio", "i2c", "pwm", "stm32f4dis", "uart", "https"], + "darwin": ["https"], + "tizen": ["adc", "ble", "dgram", "gpio", "i2c", "pwm", "spi", "uart"], + "tizenrt": ["gpio", "pwm", "https"] } } } diff --git a/docs/api/IoT.js-API-HTTPS.md b/docs/api/IoT.js-API-HTTPS.md new file mode 100644 index 0000000..5ed3aae --- /dev/null +++ b/docs/api/IoT.js-API-HTTPS.md @@ -0,0 +1,133 @@ +### Platform Support + + The following shows Https module APIs available for each platform. + +| | Linux
(Ubuntu) | Raspbian
(Raspberry Pi) | Nuttx
(STM32F4-Discovery) | Tizen
(Artik 10) | +| :---: | :---: | :---: | :---: | :---: | +| https.request | X | X | X | O | +| https.get | X | X | X | O | + + +# Https + +IoT.js provides HTTPS to support HTTPS clients enabling users to send HTTPS request easily. + +### https.request(options[, callback]) +* `options` {Object} + * `host` {string} A domain name or IP address of the server to issue the request to. Defaults to 'localhost'. + * `hostname` {string} Alias for host. + * `port` {number} Port of remote server. Defaults to 80. + * `method` {string} A string specifying the HTTPS request method. Defaults to 'GET'. + * `path` {string} Request path. Defaults to '/'. Should include query string if any. E.G. '/index.html?page=12'. An exception is thrown when the request path contains illegal characters. Currently, only spaces are rejected but that may change in the future. + * `headers` {Object} An object containing request headers. + * `auth` {string} Optional Basic Authentication in the form `username:password`. Used to compute HTTPS Basic Authentication header. + * `ca` {string} Optional file path to CA certificate. Allows to override system trusted CA certificates. + * `cert` {string} Optional file path to client authentication certificate in PEM format. + * `key1` {string} Optional file path to private keys for client cert in PEM format. +* `callback` {Function} + * `response` {https.IncomingMessage} +* Returns: {https.ClientRequest} + +Example: +```javascript +var https = require('https'); + +var request = https.request({ + method: 'POST', + port: 443, + headers: {'Content-Length': 3} +}); + +... + +request.end(); +``` + +Note that in the example `req.end()` was called. With `https.request()` one must always call `req.end()` to signify that you're done with the request - even if there is no data being written to the request body. + +### https.get(options[, callback]) +* `options` {Object} +* `callback` {Function} + * `response` {https.IncomingMessage} +* Returns: {https.ClientRequest} + +Same as `https.request` except that `https.get` automatically call `req.end()` at the end. + +Example: +```javascript +var https = require('https'); + +https.get({ + port: 80, +}, function(res) { +... +}); +``` + + +### https.METHODS +A list of HTTPS methods supported by the parser as `string` properties of an `Object`. Currently supported methods are `'DELETE'`, `'GET'`, `'HEAD'`, `'POST'`, `'PUT'`, `'CONNECT'`, `'OPTIONS'`, `'TRACE'`. + + +## Class: https.ClientRequest + +This object is created internally and returned from https.request(). It represents an in-progress request whose header has already been queued. + +https.ClientRequest inherits [`Stream.writable`](IoT.js-API-Stream.md). See it's documentation to write data to an outgoing HTTP request. Notable methods are `'writable.write()'` (to send data as request body), `'writable.end()'` (to signal an end and flush remaining request body), and the event `'finish'`. + +### request.aborted +If the request has been aborted, this contains the time at which the request was aborted in milliseconds since epoch as `Number`. + +### Event: 'close' +This event is fired when the underlying socket is closed. + +### Event: 'response' +* `response` {https.IncomingMessage} + +This event is emitted when server's response header is parsed. ` https.IncomingMessage` object is passed as argument to handler. + +### Event: 'socket' +This event is emitted when a socket is assigned to this request. + +### request.abort() +Will abort the outgoing request, dropping any data to be sent/received and destroying the underlying socket. + +### request.setTimeout(ms, cb) +* `ms` {number} +* `cb` {Function} + +Registers cb for 'timeout' event and set socket's timeout value to ms. This event will be triggered by the underlying socket's 'timeout' event. + +If cb is not provided, the socket will be destroyed automatically after timeout. +If you provides cb, you should handle the socket's timeout. + + +## Class: https.IncomingMessage + +This object is created internally and returned to the callback in https.request(). It represents the response sent by a server to a request. + +https.IncomingMessage inherits [`Stream.readable`](IoT.js-API-Stream.md). See it's documentation to read incoming data from an HTTP request. Notable events are `'data'` (fired when there is data to read), `'close'`, `'end'` (Request has ended) and the method `readable.read()`. + +### message.headers +HTTPS header object. + +### message.method +Request's method as `string` + +### message.statusCode +HTTPS response status code as `number` of 3-digit. + +### message.statusMessage +HTTPS response status message as `string` + +### message.url +Requests URL as `string` + +### message.setTimeout(ms, cb) +* `ms` {number} +* `cb` {Function} + +Registers cb for 'timeout' event set socket's timeout value to ms. This event will be triggered by the underlying socket's 'timeout' event. + +If cb is not provided, the socket will be destroyed automatically after timeout. +If you provides cb, you should handle the socket's timeout. diff --git a/src/iotjs_magic_strings.h b/src/iotjs_magic_strings.h index 3334073..89c9e20 100644 --- a/src/iotjs_magic_strings.h +++ b/src/iotjs_magic_strings.h @@ -20,7 +20,9 @@ #define IOTJS_MAGIC_STRING_1 "1" #define IOTJS_MAGIC_STRING_2 "2" #define IOTJS_MAGIC_STRING_3 "3" +#define IOTJS_MAGIC_STRING_ABORT "abort" #define IOTJS_MAGIC_STRING_ADC "Adc" +#define IOTJS_MAGIC_STRING_ADDHEADER "addHeader" #define IOTJS_MAGIC_STRING_ADDMEMBERSHIP "addMembership" #define IOTJS_MAGIC_STRING_ADDRESS "address" #define IOTJS_MAGIC_STRING_ARCH "arch" @@ -41,6 +43,8 @@ #define IOTJS_MAGIC_STRING__BUILTIN "_builtin" #define IOTJS_MAGIC_STRING_BYTELENGTH "byteLength" #define IOTJS_MAGIC_STRING_BYTEPARSED "byteParsed" +#define IOTJS_MAGIC_STRING_CA "ca" +#define IOTJS_MAGIC_STRING_CERT "cert" #define IOTJS_MAGIC_STRING_CHDIR "chdir" #define IOTJS_MAGIC_STRING_CHIP "chip" #define IOTJS_MAGIC_STRING_CHIPSELECT "chipSelect" @@ -53,6 +57,7 @@ #define IOTJS_MAGIC_STRING_COMPILENATIVEPTR "compileNativePtr" #define IOTJS_MAGIC_STRING_CONNECT "connect" #define IOTJS_MAGIC_STRING_COPY "copy" +#define IOTJS_MAGIC_STRING_CREATEREQUEST "createRequest" #define IOTJS_MAGIC_STRING__CREATESTAT "_createStat" #define IOTJS_MAGIC_STRING_CREATETCP "createTCP" #define IOTJS_MAGIC_STRING_CWD "cwd" @@ -74,6 +79,7 @@ #define IOTJS_MAGIC_STRING_FALLING_U "FALLING" #define IOTJS_MAGIC_STRING_FAMILY "family" #define IOTJS_MAGIC_STRING_FINISH "finish" +#define IOTJS_MAGIC_STRING_FINISHREQUEST "finishRequest" #define IOTJS_MAGIC_STRING_FLOAT "FLOAT" #define IOTJS_MAGIC_STRING_FSTAT "fstat" #define IOTJS_MAGIC_STRING_GETADDRINFO "getaddrinfo" @@ -85,8 +91,10 @@ #define IOTJS_MAGIC_STRING_HEXWRITE "hexWrite" #define IOTJS_MAGIC_STRING_HIGH "HIGH" #define IOTJS_MAGIC_STRING_HOME "HOME" +#define IOTJS_MAGIC_STRING_HOST "host" #define IOTJS_MAGIC_STRING_HTTPPARSER "HTTPParser" #define IOTJS_MAGIC_STRING_IN "IN" +#define IOTJS_MAGIC_STRING__INCOMING "_incoming" #define IOTJS_MAGIC_STRING__INITARGV "_initArgv" #define IOTJS_MAGIC_STRING_IOTJS_PATH "IOTJS_PATH" #define IOTJS_MAGIC_STRING_IOTJS "iotjs" @@ -94,6 +102,7 @@ #define IOTJS_MAGIC_STRING_IPV6 "IPv6" #define IOTJS_MAGIC_STRING_ISALIVEEXCEPTFOR "isAliveExceptFor" #define IOTJS_MAGIC_STRING_ISDEVUP "isDevUp" +#define IOTJS_MAGIC_STRING_KEY "key" #define IOTJS_MAGIC_STRING_LENGTH "length" #define IOTJS_MAGIC_STRING_LISTEN "listen" #define IOTJS_MAGIC_STRING_LOOPBACK "loopback" @@ -109,14 +118,21 @@ #define IOTJS_MAGIC_STRING_NONE "NONE" #define IOTJS_MAGIC_STRING_ONBODY "OnBody" #define IOTJS_MAGIC_STRING_ONCLOSE "onclose" +#define IOTJS_MAGIC_STRING_ONCLOSED "onClosed" #define IOTJS_MAGIC_STRING_ONCONNECTION "onconnection" +#define IOTJS_MAGIC_STRING_ONDATA "onData" +#define IOTJS_MAGIC_STRING_ONEND "onEnd" +#define IOTJS_MAGIC_STRING_ONERROR "onError" #define IOTJS_MAGIC_STRING_ONHEADERSCOMPLETE "OnHeadersComplete" #define IOTJS_MAGIC_STRING_ONHEADERS "OnHeaders" #define IOTJS_MAGIC_STRING_ONMESSAGECOMPLETE "OnMessageComplete" #define IOTJS_MAGIC_STRING_ONMESSAGE "onmessage" #define IOTJS_MAGIC_STRING__ONNEXTTICK "_onNextTick" #define IOTJS_MAGIC_STRING_ONREAD "onread" +#define IOTJS_MAGIC_STRING_ONSOCKET "onSocket" +#define IOTJS_MAGIC_STRING_ONTIMEOUT "onTimeout" #define IOTJS_MAGIC_STRING__ONUNCAUGHTEXCEPTION "_onUncaughtException" +#define IOTJS_MAGIC_STRING_ONWRITABLE "onWritable" #define IOTJS_MAGIC_STRING_OPENDRAIN "OPENDRAIN" #define IOTJS_MAGIC_STRING_OPEN "open" #define IOTJS_MAGIC_STRING_OUT "OUT" @@ -150,6 +166,7 @@ #define IOTJS_MAGIC_STRING_RISING_U "RISING" #define IOTJS_MAGIC_STRING_RMDIR "rmdir" #define IOTJS_MAGIC_STRING_SEND "send" +#define IOTJS_MAGIC_STRING_SENDREQUEST "sendRequest" #define IOTJS_MAGIC_STRING_SETADDRESS "setAddress" #define IOTJS_MAGIC_STRING_SETBROADCAST "setBroadcast" #define IOTJS_MAGIC_STRING_SETDUTYCYCLE "setDutyCycle" @@ -160,6 +177,7 @@ #define IOTJS_MAGIC_STRING_SETMULTICASTLOOPBACK "setMulticastLoopback" #define IOTJS_MAGIC_STRING_SETMULTICASTTTL "setMulticastTTL" #define IOTJS_MAGIC_STRING_SETPERIOD "setPeriod" +#define IOTJS_MAGIC_STRING_SETTIMEOUT "setTimeout" #define IOTJS_MAGIC_STRING_SETTTL "setTTL" #define IOTJS_MAGIC_STRING_SHOULDKEEPALIVE "shouldkeepalive" #define IOTJS_MAGIC_STRING_SHUTDOWN "shutdown" @@ -186,5 +204,6 @@ #define IOTJS_MAGIC_STRING_WRITESYNC "writeSync" #define IOTJS_MAGIC_STRING_WRITEUINT8 "writeUInt8" #define IOTJS_MAGIC_STRING_WRITE "write" +#define IOTJS_MAGIC_STRING__WRITE "_write" #endif /* IOTJS_STRING_CONSTANTS_H */ diff --git a/src/iotjs_module.h b/src/iotjs_module.h index fb880b1..73b10ad 100644 --- a/src/iotjs_module.h +++ b/src/iotjs_module.h @@ -52,7 +52,8 @@ typedef iotjs_jval_t (*register_func)(); E(F, TCP, Tcp, tcp) \ E(F, TIMER, Timer, timer) \ E(F, UART, Uart, uart) \ - E(F, UDP, Udp, udp) + E(F, UDP, Udp, udp) \ + E(F, HTTPS, Https, https) #define ENUMDEF_MODULE_LIST(upper, Camel, lower) MODULE_##upper, diff --git a/src/js/https.js b/src/js/https.js new file mode 100755 index 0000000..7719092 --- /dev/null +++ b/src/js/https.js @@ -0,0 +1,30 @@ +/* Copyright 2017-present Samsung Electronics Co., Ltd. and other contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var client = require('https_client'); + +var ClientRequest = exports.ClientRequest = client.ClientRequest; + +exports.request = function(options, cb) { + return new ClientRequest(options, cb); +}; + +exports.METHODS = client.METHODS; + +exports.get = function(options, cb) { + var req = exports.request(options, cb); + req.end(); + return req; +}; diff --git a/src/js/https_client.js b/src/js/https_client.js new file mode 100644 index 0000000..02246a6 --- /dev/null +++ b/src/js/https_client.js @@ -0,0 +1,156 @@ +/* Copyright 2017-present Samsung Electronics Co., Ltd. and other contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var util = require('util'); +var incoming = require('https_incoming'); +var stream = require('stream'); +var Buffer = require('buffer'); +var httpsNative = process.binding(process.binding.https); + +var methods = {'0': 'DELETE', '1': 'GET', '2': 'HEAD', '3': 'POST', + '4': 'PUT', '5': 'CONNECT', '6': 'OPTIONS', '7': 'TRACE'}; +exports.METHODS = methods; + +function ClientRequest(options, cb) { + this.stream = stream.Writable.call(this, options); + + // get port, host and method. + var port = options.port = options.port || 443; + var host = options.host = options.hostname || options.host || '127.0.0.1'; + var path = options.path || '/'; + var protocol = options.protocol || 'https:'; + + this.host = protocol + '//' + host + ':' + port + path; + this.method = options.method || 'GET'; + this.ca = options.ca || ''; + this.cert = options.cert || ''; + this.key = options.key || ''; + + var isMethodGood = false; + for (var key in methods) { + if (methods.hasOwnProperty(key)) { + if(this.method == methods[key]) { + isMethodGood = true; + } + } + } + + if(!isMethodGood) { + var err = new Error('Incorrect options.method.') + this.emit('error', err); + return; + } + + this._incoming = new incoming.IncomingMessage(this); + this._incoming.url = this.host; + this._incoming.method = this.method; + this.aborted = null; + + // Register response event handler. + if (cb) { + this.once('response', cb); + } + this.once('finish', this.onFinish); + + httpsNative.createRequest(this); + + if (options.auth) { + var headerString = 'Authorization: Basic ' + toBase64(options.auth); + httpsNative.addHeader(headerString, this); + } + if (options.headers) { + var keys = Object.keys(options.headers); + for (var i = 0, l = keys.length; i < l; i++) { + var key = keys[i]; + httpsNative.addHeader(key + ': ' + options.headers[key], this); + } + } + httpsNative.sendRequest(this); +} + +util.inherits(ClientRequest, stream.Writable); + +// Concrete stream overriding the empty underlying _write method. +ClientRequest.prototype._write = function(chunk, callback, onwrite) { + httpsNative._write(this, chunk.toString(), callback, onwrite); +}; + +ClientRequest.prototype.headersComplete = function() { + var self = this; + self.emit('response', self._incoming); + return (self.method == 'HEAD'); +}; + +ClientRequest.prototype.onError = function(ret) { + this.emit('error', ret); +}; + +ClientRequest.prototype.onFinish = function() { + httpsNative.finishRequest(this); +}; + +ClientRequest.prototype.setTimeout = function(ms, cb) { + this._incoming.setTimeout(ms, cb); +}; + +ClientRequest.prototype.abort = function(doNotEmit) { + if (!this.aborted) { + httpsNative.abort(this); + var date = new Date(); + this.aborted = date.getTime(); + + if (this._incoming.parser) { + this._incoming.parser.finish(); + this._incoming.parser = null; + } + + if (!doNotEmit) { + this.emit('abort'); + } + } +}; + +exports.ClientRequest = ClientRequest; + +function toBase64(input) { + var output = ''; + var chr1, chr2, chr3, enc1, enc2, enc3, enc4; + var i = 0; + //Convert to UTF-8 + input = Buffer(input).toString(); + var _keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + + '0123456789+/='; + while (i < input.length) { + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + + output = output + + _keyStr.charAt(enc1) + _keyStr.charAt(enc2) + + _keyStr.charAt(enc3) + _keyStr.charAt(enc4); + } + return output; +} diff --git a/src/js/https_incoming.js b/src/js/https_incoming.js new file mode 100644 index 0000000..9570582 --- /dev/null +++ b/src/js/https_incoming.js @@ -0,0 +1,242 @@ +/* Copyright 2017-present Samsung Electronics Co., Ltd. and other contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var util = require('util'); +var stream = require('stream'); +var Buffer = require('buffer'); +var httpsNative = process.binding(process.binding.https); +var HTTPParser = process.binding(process.binding.httpparser).HTTPParser; + +function IncomingMessage(clientRequest) { + stream.Readable.call(this); + this.clientRequest = clientRequest; + + this.headers = {}; + this.started = false; + this.completed = false; + this.timeoutCallback = null; + // for response (client) + this.statusCode = null; + this.statusMessage = null; + this.url = null; + this.method = null; + + this.parser = createHTTPParser(this); + + this.onData = cbOnData; + this.onClosed = cbOnClosed; + this.onEnd = cbOnEnd; + this.onError = cbOnError; + this.onSocket = cbOnSocket; + this.onTimeout = cbOnTimeout; + this.onWritable = cbOnWritable; +} + +util.inherits(IncomingMessage, stream.Readable); +exports.IncomingMessage = IncomingMessage; + +IncomingMessage.prototype.read = function(n) { + this.read = stream.Readable.prototype.read; + return this.read(n); +}; + +IncomingMessage.prototype.setTimeout = function(ms, cb) { + if (cb) { + this.timeoutCallback = cb; + this.once('timeout', cb); + } + httpsNative.setTimeout(ms, this.clientRequest); +}; + +IncomingMessage.prototype.addHeaders = function(headers) { + if (!this.headers) { + this.headers = {}; + } + if (!headers) { + return; + } + + for (var i = 0; i < headers.length; i = i + 2) { + this.headers[headers[i]] = headers[i + 1]; + } +}; + +// HTTP PARSER Constructor +var createHTTPParser = function(incoming) { + var parser = new HTTPParser(HTTPParser.RESPONSE); + parser.incoming = incoming; + // cb during http parsing from C side(http_parser) + parser.OnHeaders = parserOnHeaders; + parser.OnHeadersComplete = parserOnHeadersComplete; + parser.OnBody = parserOnBody; + parser.OnMessageComplete = parserOnMessageComplete; + return parser; +}; + +// ------------- HTTP PARSER CALLBACKS ------------- +// This is called when http header is fragmented and +// HTTPParser sends it to JS in separate pieces. +function parserOnHeaders(headers, url) { + var parser = this; + parser.incoming.addHeaders(headers); +} + +// This is called when header part in http msg is parsed. +function parserOnHeadersComplete(info) { + var parser = this; + var headers = info.headers; + + if (!headers) { + headers = parser._headers; + parser.incoming.addHeaders(headers); + parser._headers = {}; + } else { + parser.incoming.addHeaders(headers); + } + + // add header fields of headers to incoming.headers + parser.incoming.addHeaders(headers); + parser.incoming.statusCode = info.status; + parser.incoming.statusMessage = info.status_msg; + parser.incoming.started = true; + + // For client side, if response to 'HEAD' request, we will skip parsing body + return parser.incoming.clientRequest.headersComplete(); +} + +// parserOnBody is called when HTTPParser parses http msg(incoming) and +// get body part(buf from start at length of len) +function parserOnBody(buf, start, len) { + var parser = this; + var incoming = parser.incoming; + + if (!incoming) { + return; + } + + // Push body part into incoming stream, which will emit 'data' event + var body = buf.slice(start, start + len); + incoming.push(body); +} + +// This is called when parsing of incoming http msg done +function parserOnMessageComplete() { + var parser = this; + var incoming = parser.incoming; + + if (incoming) { + incoming.completed = true; + // no more data from incoming, stream will emit 'end' event + incoming.push(null); + } +} + +//------------ LIBCURL PARSER CALLBACKS ----------------- +// Called by libcurl when Request is Done. Finish parser and unref +function cbOnEnd() { + var incoming = this; + var parser = incoming.parser; + if (parser) { + // unref all links to parser, make parser GCed + parser.finish(); + parser = null; + incoming.parser = null; + } +} + +function cbOnClosed() { + var incoming = this; + var parser = incoming.parser; + var clientRequest = incoming.clientRequest; + + if (incoming.started && !incoming.completed) { + // Socket closed before we emitted 'end' + incoming.on('end', function() { + incoming.emit('close'); + clientRequest.emit('close'); + }); + incoming.push(null); + } else if (!incoming.started) { + incoming.emit('close'); + clientRequest.emit('close'); + // socket closed before response starts. + var err = new Error('Could Not Start Connection'); + clientRequest.onError(err); + } else { + clientRequest.emit('close'); + } + + if (parser) { + // unref all links to parser, make parser GCed + parser.finish(); + parser = null; + incoming.parser = null; + } +} + +// Called by libcurl when Request is Done. Finish parser and unref +function cbOnData(chunk) { + var incoming = this; + + if (!incoming) { + return false; + } + + var parser = incoming.parser; + var clientRequest = incoming.clientRequest; + + chunk = new Buffer(chunk); + var ret = parser.execute(chunk); + + if (ret instanceof Error) { + parser.finish(); + // unref all links to parser, make parser GCed + parser = null; + clientRequest.onError(ret); + return false; + } + return true; +} + +function cbOnError(er) { + var incoming = this; + var clientRequest = incoming.clientRequest; + var err = new Error(er); + clientRequest.onError(err); + clientRequest.abort(true); + incoming.emit('error', err); +} + +function cbOnTimeout() { + var incoming = this; + var clientRequest = incoming.clientRequest; + incoming.emit('timeout'); + if (!incoming.timeoutCallback) { + clientRequest.abort.call(clientRequest, false); + } + incoming.emit('aborted'); +} + +function cbOnSocket() { + var incoming = this; + var clientRequest = incoming.clientRequest; + clientRequest.emit('socket'); +} + +function cbOnWritable() { + var incoming = this; + var clientRequest = incoming.clientRequest; + clientRequest._readyToWrite(); +} diff --git a/src/modules/iotjs_module_https.c b/src/modules/iotjs_module_https.c new file mode 100644 index 0000000..1a0a9c3 --- /dev/null +++ b/src/modules/iotjs_module_https.c @@ -0,0 +1,862 @@ +/* Copyright 2017-present Samsung Electronics Co., Ltd. and other contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "iotjs_module_https.h" +#include "iotjs_objectwrap.h" +#include +#include +#include +#include +#include +#include +#include + +void iotjs_https_destroy(iotjs_https_t* https_data); +IOTJS_DEFINE_NATIVE_HANDLE_INFO(https); + +//-------------Constructor------------ +iotjs_https_t* iotjs_https_create(const char* URL, const char* method, + const char* ca, const char* cert, + const char* key, const iotjs_jval_t* jthis) { + iotjs_https_t* https_data = IOTJS_ALLOC(iotjs_https_t); + IOTJS_VALIDATED_STRUCT_CONSTRUCTOR(iotjs_https_t, https_data); + + // Original Request Details + _this->URL = URL; + _this->header_list = NULL; + if (strcmp(method, STRING_GET) == 0) + _this->method = HTTPS_GET; + else if (strcmp(method, STRING_POST) == 0) + _this->method = HTTPS_POST; + else if (strcmp(method, STRING_PUT) == 0) + _this->method = HTTPS_PUT; + else if (strcmp(method, STRING_DELETE) == 0) + _this->method = HTTPS_DELETE; + else if (strcmp(method, STRING_HEAD) == 0) + _this->method = HTTPS_HEAD; + else if (strcmp(method, STRING_CONNECT) == 0) + _this->method = HTTPS_CONNECT; + else if (strcmp(method, STRING_OPTIONS) == 0) + _this->method = HTTPS_OPTIONS; + else if (strcmp(method, STRING_TRACE) == 0) + _this->method = HTTPS_TRACE; + else { + // Will never reach here cuz checked in JS + } + + // TLS certs stuff + _this->ca = ca; + _this->cert = cert; + _this->key = key; + // Content Length stuff + _this->content_length = -1; + + // Handles + _this->loop = iotjs_environment_loop(iotjs_environment_get()); + _this->jthis_native = iotjs_jval_create_copied(jthis); + iotjs_jval_set_object_native_handle(&(_this->jthis_native), + (uintptr_t)https_data, + &https_native_info); + _this->curl_multi_handle = curl_multi_init(); + _this->curl_easy_handle = curl_easy_init(); + _this->timeout.data = (void*)https_data; + uv_timer_init(_this->loop, &(_this->timeout)); + _this->request_done = false; + _this->closing_handles = 3; + _this->poll_data = NULL; + + // Timeout stuff + _this->timeout_ms = -1; + _this->last_bytes_num = -1; + _this->last_bytes_time = 0; + _this->socket_timeout.data = (void*)https_data; + uv_timer_init(_this->loop, &(_this->socket_timeout)); + + // ReadData stuff + _this->cur_read_index = 0; + _this->is_stream_writable = false; + _this->stream_ended = false; + _this->data_to_read = false; + _this->to_destroy_read_onwrite = false; + _this->async_read_onwrite.data = (void*)https_data; + uv_timer_init(_this->loop, &(_this->async_read_onwrite)); + // No Need to read data for following types of requests + if (_this->method == HTTPS_GET || _this->method == HTTPS_DELETE || + _this->method == HTTPS_HEAD || _this->method == HTTPS_OPTIONS || + _this->method == HTTPS_TRACE) + _this->stream_ended = true; + + return https_data; +} + +// Destructor +void iotjs_https_destroy(iotjs_https_t* https_data) { + IOTJS_VALIDATED_STRUCT_DESTRUCTOR(iotjs_https_t, https_data); + // To shutup unused variable _this warning + _this->URL = NULL; + IOTJS_RELEASE(https_data); +} + +//----------------Utility Functions------------------ +void iotjs_https_check_done(iotjs_https_t* https_data) { + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_t, https_data); + char* done_url; + CURLMsg* message; + int pending; + bool error = false; + + while ((message = curl_multi_info_read(_this->curl_multi_handle, &pending))) { + switch (message->msg) { + case CURLMSG_DONE: + curl_easy_getinfo(message->easy_handle, CURLINFO_EFFECTIVE_URL, + &done_url); + break; + default: + error = true; + } + if (error) { + iotjs_jargs_t jarg = iotjs_jargs_create(1); + char error[] = "Unknown Error has occured."; + iotjs_string_t jresult_string = + iotjs_string_create_with_size(error, strlen(error)); + iotjs_jargs_append_string(&jarg, &jresult_string); + iotjs_https_jcallback(https_data, IOTJS_MAGIC_STRING_ONERROR, &jarg, + false); + iotjs_string_destroy(&jresult_string); + iotjs_jargs_destroy(&jarg); + } + if (_this->stream_ended) { + iotjs_https_cleanup(https_data); + } else { + if (_this->to_destroy_read_onwrite) { + iotjs_https_call_read_onwrite_async(https_data); + } + _this->request_done = true; + } + break; + } +} + +// Cleanup before destructor +void iotjs_https_cleanup(iotjs_https_t* https_data) { + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_t, https_data); + _this->loop = NULL; + + uv_close((uv_handle_t*)&_this->timeout, + (uv_close_cb)iotjs_https_uv_close_callback); + uv_close((uv_handle_t*)&_this->socket_timeout, + (uv_close_cb)iotjs_https_uv_close_callback); + uv_close((uv_handle_t*)&_this->async_read_onwrite, + (uv_close_cb)iotjs_https_uv_close_callback); + + iotjs_https_jcallback(https_data, IOTJS_MAGIC_STRING_ONEND, + iotjs_jargs_get_empty(), false); + iotjs_https_jcallback(https_data, IOTJS_MAGIC_STRING_ONCLOSED, + iotjs_jargs_get_empty(), false); + + curl_multi_remove_handle(_this->curl_multi_handle, _this->curl_easy_handle); + curl_easy_cleanup(_this->curl_easy_handle); + _this->curl_easy_handle = NULL; + curl_multi_cleanup(_this->curl_multi_handle); + _this->curl_multi_handle = NULL; + curl_slist_free_all(_this->header_list); + + if (_this->poll_data != NULL) + iotjs_https_poll_close_all(_this->poll_data); + + + if (_this->to_destroy_read_onwrite) { + const iotjs_jargs_t* jarg = iotjs_jargs_get_empty(); + const iotjs_jval_t* jthis = &(_this->jthis_native); + IOTJS_ASSERT(iotjs_jval_is_function(&(_this->read_onwrite))); + + if (!iotjs_jval_is_undefined(&(_this->read_callback))) + iotjs_make_callback(&(_this->read_callback), jthis, jarg); + + iotjs_make_callback(&(_this->read_onwrite), jthis, jarg); + _this->to_destroy_read_onwrite = false; + iotjs_string_destroy(&(_this->read_chunk)); + iotjs_jval_destroy(&(_this->read_onwrite)); + iotjs_jval_destroy(&(_this->read_callback)); + } + return; +} + +CURLM* iotjs_https_get_multi_handle(iotjs_https_t* https_data) { + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_t, https_data); + return _this->curl_multi_handle; +} + +// Set various parameters of curl handles +void iotjs_https_initialize_curl_opts(iotjs_https_t* https_data) { + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_t, https_data); + + // Setup Some parameters for multi handle + curl_multi_setopt(_this->curl_multi_handle, CURLMOPT_SOCKETFUNCTION, + iotjs_https_curl_socket_callback); + curl_multi_setopt(_this->curl_multi_handle, CURLMOPT_SOCKETDATA, + (void*)https_data); + curl_multi_setopt(_this->curl_multi_handle, CURLMOPT_TIMERFUNCTION, + iotjs_https_curl_start_timeout_callback); + curl_multi_setopt(_this->curl_multi_handle, CURLMOPT_TIMERDATA, + (void*)https_data); + + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_PROXY, ""); + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_HEADERDATA, + (void*)https_data); + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_WRITEFUNCTION, + iotjs_https_curl_write_callback); + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_WRITEDATA, + (void*)https_data); + + // Read and send data to server only for some request types + if (_this->method == HTTPS_POST || _this->method == HTTPS_PUT || + _this->method == HTTPS_CONNECT) { + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_READFUNCTION, + iotjs_https_curl_read_callback); + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_READDATA, + (void*)https_data); + } + + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_SOCKOPTFUNCTION, + iotjs_https_curl_sockopt_callback); + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_SOCKOPTDATA, + (void*)https_data); + + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_URL, _this->URL); + _this->URL = NULL; + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_PROTOCOLS, + CURLPROTO_HTTP | CURLPROTO_HTTPS); + + if (strlen(_this->ca) > 0) + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_CAINFO, _this->ca); + _this->ca = NULL; + if (strlen(_this->cert) > 0) + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_SSLCERT, _this->cert); + _this->cert = NULL; + if (strlen(_this->key) > 0) + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_SSLKEY, _this->key); + _this->key = NULL; + + // Various request types + switch (_this->method) { + case HTTPS_GET: + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_HTTPGET, 1L); + break; + case HTTPS_POST: + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_POST, 1L); + break; + case HTTPS_PUT: + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_UPLOAD, 1L); + break; + case HTTPS_DELETE: + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_CUSTOMREQUEST, + "DELETE"); + break; + case HTTPS_HEAD: + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_NOBODY, 1L); + break; + case HTTPS_CONNECT: + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_CUSTOMREQUEST, + "CONNECT"); + break; + case HTTPS_OPTIONS: + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_CUSTOMREQUEST, + "OPTIONS"); + break; + case HTTPS_TRACE: + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_CUSTOMREQUEST, "TRACE"); + break; + } + + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_HTTP_TRANSFER_DECODING, 0L); +} + +// Get https.ClientRequest from struct +iotjs_jval_t* iotjs_https_jthis_from_https(iotjs_https_t* https_data) { + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_t, https_data); + return &(_this->jthis_native); +} + +// Call any property of ClientRequest._Incoming +bool iotjs_https_jcallback(iotjs_https_t* https_data, const char* property, + const iotjs_jargs_t* jarg, bool resultvalue) { + iotjs_jval_t* jthis = iotjs_https_jthis_from_https(https_data); + bool retval = true; + if (iotjs_jval_is_null(jthis)) + return retval; + + iotjs_jval_t jincoming = + iotjs_jval_get_property(jthis, IOTJS_MAGIC_STRING__INCOMING); + iotjs_jval_t cb = iotjs_jval_get_property(&jincoming, property); + + IOTJS_ASSERT(iotjs_jval_is_function(&cb)); + if (!resultvalue) { + iotjs_make_callback(&cb, &jincoming, jarg); + } else { + iotjs_jval_t result = + iotjs_make_callback_with_result(&cb, &jincoming, jarg); + retval = iotjs_jval_as_boolean(&result); + iotjs_jval_destroy(&result); + } + + iotjs_jval_destroy(&jincoming); + iotjs_jval_destroy(&cb); + return retval; +} + +// Call onWrite and callback after ClientRequest._write +void iotjs_https_call_read_onwrite(uv_timer_t* timer) { + iotjs_https_t* https_data = (iotjs_https_t*)(timer->data); + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_t, https_data); + + uv_timer_stop(&(_this->async_read_onwrite)); + if (iotjs_jval_is_null(&_this->jthis_native)) + return; + const iotjs_jargs_t* jarg = iotjs_jargs_get_empty(); + const iotjs_jval_t* jthis = &(_this->jthis_native); + IOTJS_ASSERT(iotjs_jval_is_function(&(_this->read_onwrite))); + + if (!iotjs_jval_is_undefined(&(_this->read_callback))) + iotjs_make_callback(&(_this->read_callback), jthis, jarg); + + iotjs_make_callback(&(_this->read_onwrite), jthis, jarg); +} + +// Call the above method Asynchronously +void iotjs_https_call_read_onwrite_async(iotjs_https_t* https_data) { + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_t, https_data); + uv_timer_start(&(_this->async_read_onwrite), iotjs_https_call_read_onwrite, 0, + 0); +} + +// ------------Functions almost directly called by JS---------- +// Add a header to outgoing request +void iotjs_https_add_header(iotjs_https_t* https_data, + const char* char_header) { + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_t, https_data); + _this->header_list = curl_slist_append(_this->header_list, char_header); + if (_this->method == HTTPS_POST || _this->method == HTTPS_PUT) { + if (strncmp(char_header, "Content-Length: ", strlen("Content-Length: ")) == + 0) { + const char* numberString = char_header + strlen("Content-Length: "); + _this->content_length = strtol(numberString, NULL, 10); + } + } +} + +// Recieved data to write from ClientRequest._write +void iotjs_https_data_to_write(iotjs_https_t* https_data, + iotjs_string_t read_chunk, + const iotjs_jval_t* callback, + const iotjs_jval_t* onwrite) { + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_t, https_data); + + if (_this->to_destroy_read_onwrite) { + _this->to_destroy_read_onwrite = false; + iotjs_string_destroy(&(_this->read_chunk)); + iotjs_jval_destroy(&(_this->read_onwrite)); + iotjs_jval_destroy(&(_this->read_callback)); + } + + _this->read_chunk = read_chunk; + _this->data_to_read = true; + + _this->read_callback = iotjs_jval_create_copied(callback); + _this->read_onwrite = iotjs_jval_create_copied(onwrite); + _this->to_destroy_read_onwrite = true; + + if (_this->request_done) { + iotjs_https_call_read_onwrite_async(https_data); + } else if (_this->is_stream_writable) { + curl_easy_pause(_this->curl_easy_handle, CURLPAUSE_CONT); + uv_timer_stop(&(_this->timeout)); + uv_timer_start(&(_this->timeout), iotjs_https_uv_timeout_callback, 1, 0); + } +} + +// Finish writing all data from ClientRequest Stream +void iotjs_https_finish_request(iotjs_https_t* https_data) { + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_t, https_data); + _this->stream_ended = true; + if (_this->request_done) { + iotjs_https_cleanup(https_data); + } else if (_this->is_stream_writable) { + curl_easy_pause(_this->curl_easy_handle, CURLPAUSE_CONT); + uv_timer_stop(&(_this->timeout)); + uv_timer_start(&(_this->timeout), iotjs_https_uv_timeout_callback, 1, 0); + } +} + +// Start sending the request +void iotjs_https_send_request(iotjs_https_t* https_data) { + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_t, https_data); + // Add all the headers to the easy handle + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_HTTPHEADER, + _this->header_list); + + if (_this->method == HTTPS_POST && _this->content_length != -1) + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_POSTFIELDSIZE, + _this->content_length); + else if (_this->method == HTTPS_PUT && _this->content_length != -1) + curl_easy_setopt(_this->curl_easy_handle, CURLOPT_INFILESIZE, + _this->content_length); + + curl_multi_add_handle(_this->curl_multi_handle, _this->curl_easy_handle); +} + +// Set timeout for request. +void iotjs_https_set_timeout(long ms, iotjs_https_t* https_data) { + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_t, https_data); + if (ms < 0) + return; + _this->timeout_ms = ms; + uv_timer_start(&(_this->socket_timeout), + iotjs_https_uv_socket_timeout_callback, 1, (uint64_t)ms); +} + + +//--------------CURL Callbacks------------------ +// Read callback is actually to write data to outgoing request +size_t iotjs_https_curl_read_callback(void* contents, size_t size, size_t nmemb, + void* userp) { + iotjs_https_t* https_data = (iotjs_https_t*)userp; + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_t, https_data); + + // If stream wasnt made writable yet, make it so. + if (!_this->is_stream_writable) { + _this->is_stream_writable = true; + iotjs_https_jcallback(https_data, IOTJS_MAGIC_STRING_ONWRITABLE, + iotjs_jargs_get_empty(), false); + } + + if (_this->data_to_read) { + size_t real_size = size * nmemb; + size_t chunk_size = iotjs_string_size(&(_this->read_chunk)); + size_t left_to_copy_size = chunk_size - _this->cur_read_index; + + if (real_size < 1) + return 0; + + // send some data + if (_this->cur_read_index < chunk_size) { + size_t num_to_copy = + (left_to_copy_size < real_size) ? left_to_copy_size : real_size; + const char* buf = iotjs_string_data(&(_this->read_chunk)); + buf = &buf[_this->cur_read_index]; + strncpy((char*)contents, buf, num_to_copy); + _this->cur_read_index = _this->cur_read_index + num_to_copy; + return num_to_copy; + } + + // Finished sending one chunk of data + _this->cur_read_index = 0; + _this->data_to_read = false; + iotjs_https_call_read_onwrite_async(https_data); + } + + // If the data is sent, and stream hasn't ended, wait for more data + if (!_this->stream_ended) { + return CURL_READFUNC_PAUSE; + } + + // All done, end the transfer + return 0; +} + +// Pass Curl events on its fd sockets +int iotjs_https_curl_socket_callback(CURL* easy, curl_socket_t sockfd, + int action, void* userp, void* socketp) { + iotjs_https_t* https_data = (iotjs_https_t*)userp; + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_t, https_data); + if (action == CURL_POLL_IN || action == CURL_POLL_OUT || + action == CURL_POLL_INOUT) { + iotjs_https_poll_t* poll_data = NULL; + + if (!socketp) { + poll_data = iotjs_https_poll_create(_this->loop, sockfd, https_data); + curl_multi_assign(_this->curl_multi_handle, sockfd, (void*)poll_data); + _this->closing_handles = _this->closing_handles + 1; + if (_this->poll_data == NULL) + _this->poll_data = poll_data; + else + iotjs_https_poll_append(_this->poll_data, poll_data); + } else + poll_data = (iotjs_https_poll_t*)socketp; + + if (action == CURL_POLL_IN) + uv_poll_start(iotjs_https_poll_get_poll_handle(poll_data), UV_READABLE, + iotjs_https_uv_poll_callback); + else if (action == CURL_POLL_OUT) + uv_poll_start(iotjs_https_poll_get_poll_handle(poll_data), UV_WRITABLE, + iotjs_https_uv_poll_callback); + else if (action == CURL_POLL_INOUT) + uv_poll_start(iotjs_https_poll_get_poll_handle(poll_data), + UV_READABLE | UV_WRITABLE, iotjs_https_uv_poll_callback); + } else { + if (socketp) { + iotjs_https_poll_t* poll_data = (iotjs_https_poll_t*)socketp; + iotjs_https_poll_close(poll_data); + curl_multi_assign(_this->curl_multi_handle, sockfd, NULL); + } + } + return 0; +} + +// Socket Assigned Callback +int iotjs_https_curl_sockopt_callback(void* userp, curl_socket_t curlfd, + curlsocktype purpose) { + iotjs_https_t* https_data = (iotjs_https_t*)userp; + iotjs_https_jcallback(https_data, IOTJS_MAGIC_STRING_ONSOCKET, + iotjs_jargs_get_empty(), false); + return CURL_SOCKOPT_OK; +} + +// Curl wants us to signal after timeout +int iotjs_https_curl_start_timeout_callback(CURLM* multi, long timeout_ms, + void* userp) { + iotjs_https_t* https_data = (iotjs_https_t*)userp; + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_t, https_data); + if (timeout_ms < 0) + uv_timer_stop(&(_this->timeout)); + else { + if (timeout_ms == 0) + timeout_ms = 1; + if ((_this->timeout_ms != -1) && (timeout_ms > _this->timeout_ms)) + timeout_ms = _this->timeout_ms; + uv_timer_start(&(_this->timeout), iotjs_https_uv_timeout_callback, + (uint64_t)timeout_ms, 0); + } + return 0; +} + +// Write Callback is actually to read data from incoming response +size_t iotjs_https_curl_write_callback(void* contents, size_t size, + size_t nmemb, void* userp) { + iotjs_https_t* https_data = (iotjs_https_t*)userp; + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_t, https_data); + size_t real_size = size * nmemb; + if (iotjs_jval_is_null(&_this->jthis_native)) + return real_size - 1; + iotjs_jargs_t jarg = iotjs_jargs_create(1); + iotjs_jval_t jresult_arr = iotjs_jval_create_byte_array(real_size, contents); + // iotjs_string_t jresult_string = + // iotjs_string_create_with_size(contents, real_size); + // iotjs_jargs_append_string(&jarg, &jresult_string); + // Use the jresult_arr Byte Array in production, but in testing use + // string. Uncomment out above line in testing. + iotjs_jargs_append_jval(&jarg, &jresult_arr); + + bool result = + iotjs_https_jcallback(https_data, IOTJS_MAGIC_STRING_ONDATA, &jarg, true); + + iotjs_jval_destroy(&jresult_arr); + // iotjs_string_destroy(&jresult_string); + iotjs_jargs_destroy(&jarg); + + if (!result) { + return real_size - 1; + } + + return real_size; +} + + +//--------------LibTUV Callbacks------------------ +// Callback called on closing handles during cleanup +void iotjs_https_uv_close_callback(uv_handle_t* handle) { + iotjs_https_t* https_data = (iotjs_https_t*)handle->data; + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_t, https_data); + _this->closing_handles = _this->closing_handles - 1; + if (_this->closing_handles <= 0) { + if (_this->poll_data != NULL) + iotjs_https_poll_destroy(_this->poll_data); + iotjs_jval_destroy(&_this->jthis_native); + } +} + +// Callback called when poll detects actions on FD +void iotjs_https_uv_poll_callback(uv_poll_t* poll, int status, int events) { + iotjs_https_poll_t* poll_data = (iotjs_https_poll_t*)poll->data; + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_poll_t, poll_data); + iotjs_https_t* https_data = (iotjs_https_t*)_this->https_data; + + int flags = 0; + if (status < 0) + flags = CURL_CSELECT_ERR; + if (!status && events & UV_READABLE) + flags |= CURL_CSELECT_IN; + if (!status && events & UV_WRITABLE) + flags |= CURL_CSELECT_OUT; + int running_handles; + curl_multi_socket_action(iotjs_https_get_multi_handle(https_data), + _this->sockfd, flags, &running_handles); + iotjs_https_check_done(https_data); +} + +// This function is for signalling to curl a given time has passed. +// This timeout is usually given by curl itself. +void iotjs_https_uv_timeout_callback(uv_timer_t* timer) { + iotjs_https_t* https_data = (iotjs_https_t*)(timer->data); + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_t, https_data); + uv_timer_stop(timer); + curl_multi_socket_action(_this->curl_multi_handle, CURL_SOCKET_TIMEOUT, 0, + &_this->running_handles); + iotjs_https_check_done(https_data); +} + +// Callback called to check if request has timed out +void iotjs_https_uv_socket_timeout_callback(uv_timer_t* timer) { + iotjs_https_t* https_data = (iotjs_https_t*)(timer->data); + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_t, https_data); + double download_bytes = 0; + double upload_bytes = 0; + uint64_t total_time_ms = 0; + + if (_this->timeout_ms != -1) { + curl_easy_getinfo(_this->curl_easy_handle, CURLINFO_SIZE_DOWNLOAD, + &download_bytes); + curl_easy_getinfo(_this->curl_easy_handle, CURLINFO_SIZE_UPLOAD, + &upload_bytes); + total_time_ms = uv_now(_this->loop); + double total_bytes = download_bytes + upload_bytes; + + if (_this->last_bytes_num == total_bytes) { + if (total_time_ms > + ((uint64_t)_this->timeout_ms + _this->last_bytes_time)) { + if (!_this->request_done) { + iotjs_https_jcallback(https_data, IOTJS_MAGIC_STRING_ONTIMEOUT, + iotjs_jargs_get_empty(), false); + } + uv_timer_stop(&(_this->socket_timeout)); + } + } else { + _this->last_bytes_num = total_bytes; + _this->last_bytes_time = total_time_ms; + } + } +} + +//--------------https_poll Functions------------------ +iotjs_https_poll_t* iotjs_https_poll_create(uv_loop_t* loop, + curl_socket_t sockfd, + iotjs_https_t* https_data) { + iotjs_https_poll_t* poll_data = IOTJS_ALLOC(iotjs_https_poll_t); + IOTJS_VALIDATED_STRUCT_CONSTRUCTOR(iotjs_https_poll_t, poll_data); + _this->sockfd = sockfd; + _this->poll_handle.data = poll_data; + _this->https_data = https_data; + _this->closing = false; + _this->next = NULL; + uv_poll_init_socket(loop, &_this->poll_handle, sockfd); + return poll_data; +} + +void iotjs_https_poll_append(iotjs_https_poll_t* head, + iotjs_https_poll_t* poll_data) { + iotjs_https_poll_t* current = head; + iotjs_https_poll_t* next = iotjs_https_poll_get_next(current); + while (next != NULL) { + current = next; + next = iotjs_https_poll_get_next(current); + } + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_poll_t, current); + _this->next = poll_data; +} + +iotjs_https_poll_t* iotjs_https_poll_get_next(iotjs_https_poll_t* poll_data) { + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_poll_t, poll_data); + return _this->next; +} + +uv_poll_t* iotjs_https_poll_get_poll_handle(iotjs_https_poll_t* poll_data) { + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_poll_t, poll_data); + return &_this->poll_handle; +} + +void iotjs_https_poll_close(iotjs_https_poll_t* poll_data) { + IOTJS_VALIDATED_STRUCT_METHOD(iotjs_https_poll_t, poll_data); + if (_this->closing == false) { + _this->closing = true; + uv_poll_stop(&_this->poll_handle); + _this->poll_handle.data = _this->https_data; + uv_close((uv_handle_t*)&_this->poll_handle, iotjs_https_uv_close_callback); + } + return; +} + +void iotjs_https_poll_close_all(iotjs_https_poll_t* head) { + iotjs_https_poll_t* current = head; + while (current != NULL) { + iotjs_https_poll_close(current); + current = iotjs_https_poll_get_next(current); + } +} + +void iotjs_https_poll_destroy(iotjs_https_poll_t* poll_data) { + IOTJS_VALIDATED_STRUCT_DESTRUCTOR(iotjs_https_poll_t, poll_data); + if (_this->next != NULL) { + iotjs_https_poll_destroy(_this->next); + } + IOTJS_RELEASE(poll_data); +} + +// ------------JHANDLERS---------------- + +JHANDLER_FUNCTION(createRequest) { + DJHANDLER_CHECK_THIS(object); + DJHANDLER_CHECK_ARGS(1, object); + + const iotjs_jval_t* jthis = JHANDLER_GET_ARG(0, object); + + iotjs_jval_t jhost = iotjs_jval_get_property(jthis, IOTJS_MAGIC_STRING_HOST); + iotjs_string_t host = iotjs_jval_as_string(&jhost); + iotjs_jval_destroy(&jhost); + + iotjs_jval_t jmethod = + iotjs_jval_get_property(jthis, IOTJS_MAGIC_STRING_METHOD); + iotjs_string_t method = iotjs_jval_as_string(&jmethod); + iotjs_jval_destroy(&jmethod); + + iotjs_jval_t jca = iotjs_jval_get_property(jthis, IOTJS_MAGIC_STRING_CA); + iotjs_string_t ca = iotjs_jval_as_string(&jca); + iotjs_jval_destroy(&jca); + + iotjs_jval_t jcert = iotjs_jval_get_property(jthis, IOTJS_MAGIC_STRING_CERT); + iotjs_string_t cert = iotjs_jval_as_string(&jcert); + iotjs_jval_destroy(&jcert); + + iotjs_jval_t jkey = iotjs_jval_get_property(jthis, IOTJS_MAGIC_STRING_KEY); + iotjs_string_t key = iotjs_jval_as_string(&jkey); + iotjs_jval_destroy(&jkey); + + if (curl_global_init(CURL_GLOBAL_SSL)) { + return; + } + iotjs_https_t* https_data = + iotjs_https_create(iotjs_string_data(&host), iotjs_string_data(&method), + iotjs_string_data(&ca), iotjs_string_data(&cert), + iotjs_string_data(&key), jthis); + + iotjs_https_initialize_curl_opts(https_data); + + iotjs_string_destroy(&host); + iotjs_string_destroy(&method); + iotjs_string_destroy(&ca); + iotjs_string_destroy(&cert); + iotjs_string_destroy(&key); + iotjs_jhandler_return_null(jhandler); +} + +JHANDLER_FUNCTION(addHeader) { + DJHANDLER_CHECK_THIS(object); + + DJHANDLER_CHECK_ARGS(2, string, object); + iotjs_string_t header = JHANDLER_GET_ARG(0, string); + const char* char_header = iotjs_string_data(&header); + + const iotjs_jval_t* jthis = JHANDLER_GET_ARG(1, object); + iotjs_https_t* https_data = + (iotjs_https_t*)iotjs_jval_get_object_native_handle(jthis); + iotjs_https_add_header(https_data, char_header); + + iotjs_string_destroy(&header); + iotjs_jhandler_return_null(jhandler); +} + +JHANDLER_FUNCTION(sendRequest) { + DJHANDLER_CHECK_THIS(object); + + DJHANDLER_CHECK_ARG(0, object); + const iotjs_jval_t* jthis = JHANDLER_GET_ARG(0, object); + iotjs_https_t* https_data = + (iotjs_https_t*)iotjs_jval_get_object_native_handle(jthis); + iotjs_https_send_request(https_data); + iotjs_jhandler_return_null(jhandler); +} + +JHANDLER_FUNCTION(setTimeout) { + DJHANDLER_CHECK_THIS(object); + DJHANDLER_CHECK_ARGS(2, number, object); + + double ms = JHANDLER_GET_ARG(0, number); + const iotjs_jval_t* jthis = JHANDLER_GET_ARG(1, object); + + iotjs_https_t* https_data = + (iotjs_https_t*)iotjs_jval_get_object_native_handle(jthis); + iotjs_https_set_timeout((long)ms, https_data); + + iotjs_jhandler_return_null(jhandler); +} + +JHANDLER_FUNCTION(_write) { + DJHANDLER_CHECK_THIS(object); + DJHANDLER_CHECK_ARGS(2, object, string); + // Argument 3 can be null, so not checked directly, checked later + DJHANDLER_CHECK_ARG(3, function); + + const iotjs_jval_t* jthis = JHANDLER_GET_ARG(0, object); + iotjs_string_t read_chunk = JHANDLER_GET_ARG(1, string); + + const iotjs_jval_t* callback = iotjs_jhandler_get_arg(jhandler, 2); + const iotjs_jval_t* onwrite = JHANDLER_GET_ARG(3, function); + + iotjs_https_t* https_data = + (iotjs_https_t*)iotjs_jval_get_object_native_handle(jthis); + iotjs_https_data_to_write(https_data, read_chunk, callback, onwrite); + + // readchunk was copied to https_data, hence not destroyed. + iotjs_jhandler_return_null(jhandler); +} + +JHANDLER_FUNCTION(finishRequest) { + DJHANDLER_CHECK_THIS(object); + DJHANDLER_CHECK_ARG(0, object); + + const iotjs_jval_t* jthis = JHANDLER_GET_ARG(0, object); + iotjs_https_t* https_data = + (iotjs_https_t*)iotjs_jval_get_object_native_handle(jthis); + iotjs_https_finish_request(https_data); + + iotjs_jhandler_return_null(jhandler); +} + +JHANDLER_FUNCTION(Abort) { + DJHANDLER_CHECK_THIS(object); + DJHANDLER_CHECK_ARG(0, object); + + const iotjs_jval_t* jthis = JHANDLER_GET_ARG(0, object); + iotjs_https_t* https_data = + (iotjs_https_t*)iotjs_jval_get_object_native_handle(jthis); + iotjs_https_cleanup(https_data); + + iotjs_jhandler_return_null(jhandler); +} + +iotjs_jval_t InitHttps() { + iotjs_jval_t https = iotjs_jval_create_object(); + + iotjs_jval_set_method(&https, IOTJS_MAGIC_STRING_CREATEREQUEST, + createRequest); + iotjs_jval_set_method(&https, IOTJS_MAGIC_STRING_ADDHEADER, addHeader); + iotjs_jval_set_method(&https, IOTJS_MAGIC_STRING_SENDREQUEST, sendRequest); + iotjs_jval_set_method(&https, IOTJS_MAGIC_STRING_SETTIMEOUT, setTimeout); + iotjs_jval_set_method(&https, IOTJS_MAGIC_STRING__WRITE, _write); + iotjs_jval_set_method(&https, IOTJS_MAGIC_STRING_FINISHREQUEST, + finishRequest); + iotjs_jval_set_method(&https, IOTJS_MAGIC_STRING_ABORT, Abort); + + return https; +} diff --git a/src/modules/iotjs_module_https.h b/src/modules/iotjs_module_https.h new file mode 100644 index 0000000..65f8965 --- /dev/null +++ b/src/modules/iotjs_module_https.h @@ -0,0 +1,151 @@ +/* Copyright 2017-present Samsung Electronics Co., Ltd. and other contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IOTJS_MODULE_HTTPS_H +#define IOTJS_MODULE_HTTPS_H + +#include "iotjs_def.h" +#include +#include + +typedef enum { + HTTPS_GET = 0, + HTTPS_POST, + HTTPS_PUT, + HTTPS_DELETE, + HTTPS_HEAD, + HTTPS_CONNECT, + HTTPS_OPTIONS, + HTTPS_TRACE +} HTTPS_Methods; + +#define STRING_GET "GET" +#define STRING_POST "POST" +#define STRING_PUT "PUT" +#define STRING_DELETE "DELETE" +#define STRING_HEAD "HEAD" +#define STRING_CONNECT "CONNECT" +#define STRING_OPTIONS "OPTIONS" +#define STRING_TRACE "TRACE" + +// A Per-Request Struct, native bound to https.ClientRequest +typedef struct { + // Original Request Details + const char* URL; + HTTPS_Methods method; + struct curl_slist* header_list; + // TLS certs Options + const char* ca; + const char* cert; + const char* key; + // Content-Length for Post and Put + long content_length; + + // Handles + uv_loop_t* loop; + iotjs_jval_t jthis_native; + CURLM* curl_multi_handle; + uv_timer_t timeout; + CURL* curl_easy_handle; + // Curl Context + int running_handles; + int closing_handles; + bool request_done; + struct iotjs_https_poll_t* poll_data; + + // For SetTimeOut + uv_timer_t socket_timeout; + long timeout_ms; + double last_bytes_num; + uint64_t last_bytes_time; + + // For Writable Stream ClientRequest + size_t cur_read_index; + bool is_stream_writable; + bool data_to_read; + bool stream_ended; + bool to_destroy_read_onwrite; + iotjs_string_t read_chunk; + iotjs_jval_t read_callback; + iotjs_jval_t read_onwrite; + uv_timer_t async_read_onwrite; + +} IOTJS_VALIDATED_STRUCT(iotjs_https_t); + +iotjs_https_t* iotjs_https_create(const char* URL, const char* method, + const char* ca, const char* cert, + const char* key, const iotjs_jval_t* jthis); + +#define THIS iotjs_https_t* https_data +// Some utility functions +void iotjs_https_check_done(THIS); +void iotjs_https_cleanup(THIS); +CURLM* iotjs_https_get_multi_handle(THIS); +void iotjs_https_initialize_curl_opts(THIS); +iotjs_jval_t* iotjs_https_jthis_from_https(THIS); +bool iotjs_https_jcallback(THIS, const char* property, + const iotjs_jargs_t* jarg, bool resultvalue); +void iotjs_https_call_read_onwrite(uv_timer_t* timer); +void iotjs_https_call_read_onwrite_async(THIS); + +// Functions almost directly called by JS via JHANDLER +void iotjs_https_add_header(THIS, const char* char_header); +void iotjs_https_data_to_write(THIS, iotjs_string_t read_chunk, + const iotjs_jval_t* callback, + const iotjs_jval_t* onwrite); +void iotjs_https_finish_request(THIS); +void iotjs_https_send_request(THIS); +void iotjs_https_set_timeout(long ms, THIS); +#undef THIS + + +// CURL callbacks +size_t iotjs_https_curl_read_callback(void* contents, size_t size, size_t nmemb, + void* userp); +int iotjs_https_curl_socket_callback(CURL* easy, curl_socket_t sockfd, + int action, void* userp, void* socketp); +int iotjs_https_curl_sockopt_callback(void* userp, curl_socket_t curlfd, + curlsocktype purpose); +int iotjs_https_curl_start_timeout_callback(CURLM* multi, long timeout_ms, + void* userp); +size_t iotjs_https_curl_write_callback(void* contents, size_t size, + size_t nmemb, void* userp); + +// UV Callbacks +void iotjs_https_uv_close_callback(uv_handle_t* handle); +void iotjs_https_uv_poll_callback(uv_poll_t* poll, int status, int events); +void iotjs_https_uv_socket_timeout_callback(uv_timer_t* timer); +void iotjs_https_uv_timeout_callback(uv_timer_t* timer); + +typedef struct { + uv_poll_t poll_handle; + struct iotjs_https_poll_t* next; + struct iotjs_https_t* https_data; + curl_socket_t sockfd; + bool closing; +} IOTJS_VALIDATED_STRUCT(iotjs_https_poll_t); + +iotjs_https_poll_t* iotjs_https_poll_create(uv_loop_t* loop, + curl_socket_t sockfd, + iotjs_https_t* https_data); +void iotjs_https_poll_append(iotjs_https_poll_t* head, + iotjs_https_poll_t* poll_data); +iotjs_https_poll_t* iotjs_https_poll_get_next(iotjs_https_poll_t* poll_data); +uv_poll_t* iotjs_https_poll_get_poll_handle(iotjs_https_poll_t* poll_data); +void iotjs_https_poll_close(iotjs_https_poll_t* poll_data); +void iotjs_https_poll_destroy(iotjs_https_poll_t* poll_data); +void iotjs_https_poll_close_all(iotjs_https_poll_t* head); + +#endif /* IOTJS_MODULE_HTTPS_H */ diff --git a/test/run_pass/test_https_get.js b/test/run_pass/test_https_get.js new file mode 100644 index 0000000..3ef93e2 --- /dev/null +++ b/test/run_pass/test_https_get.js @@ -0,0 +1,85 @@ +/* Copyright 2017-present Samsung Electronics Co., Ltd. and other contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +var assert = require('assert'); +var https = require('https'); + + +var isRequest1Finished = false; +// 1. GET req +options = { + method: 'GET', + host: "httpbin.org", + path: '/user-agent', + headers: {'user-agent': 'iotjs'} +}; + +var getResponseHandler = function (res) { + var res_body = ''; + + assert.equal(200, res.statusCode); + + var endHandler = function(){ + var response = JSON.parse(res_body); + assert.equal('iotjs', response['user-agent']); + isRequest1Finished = true; + }; + res.on('end', endHandler); + + res.on('data', function(chunk){ + res_body += chunk.toString(); + }); +}; + +https.get(options, getResponseHandler); + +// 2. close server req +var testMsg = 'Hello IoT.js'; +var finalOptions = { + method: 'POST', + host: "httpbin.org", + path: '/post', + headers: {'Content-Length': testMsg.length, + 'Content-Type': 'application/json'} +}; +var isRequest2Finished = false; + +var finalResponseHandler = function (res) { + var res_body = ''; + + assert.equal(200, res.statusCode); + + var endHandler = function(){ + var response = JSON.parse(res_body); + assert.equal(testMsg, response['data']); + isRequest2Finished = true; + }; + res.on('end', endHandler); + + res.on('data', function(chunk){ + res_body += chunk.toString(); + }); +}; + +var finalReq = https.request(finalOptions, finalResponseHandler); +finalReq.write(testMsg); +finalReq.end(); + + +process.on('exit', function() { + assert.equal(isRequest1Finished, true); + assert.equal(isRequest2Finished, true); +}); diff --git a/test/run_pass/test_https_request_response.js b/test/run_pass/test_https_request_response.js new file mode 100644 index 0000000..5054afd --- /dev/null +++ b/test/run_pass/test_https_request_response.js @@ -0,0 +1,119 @@ +/* Copyright 2017-present Samsung Electronics Co., Ltd. and other contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var assert = require('assert'); +var http = require('http'); +var https = require('https'); +var net = require('net'); + +// Messages for further requests. +var message = 'Hello IoT.js'; + +// Options for further requests. +var options = { + method: 'POST', + host: "httpbin.org", + path: '/post', + headers: {'Content-Length': message.length, + 'Content-Type': 'application/json'} +}; + +// Simple request with valid utf-8 message. +var isRequest1Finished = false; +var request1 = https.request(options, function(response) { + var str = ''; + + response.on('data', function(chunk) { + str += chunk.toString(); + }); + + response.on('end', function() { + var response = JSON.parse(str); + assert.equal(message, response['data']); + isRequest1Finished = true; + }); +}); +request1.end(message); + + +// Simple request with multiple end callback. +var isRequest2Finished = false; +var request2 = https.request(options, function(response) { + var str = ''; + + response.on('data', function(chunk) { + str += chunk.toString(); + }); + + response.on('end', function() { + var response = JSON.parse(str); + assert.equal(message, response['data']); + }); +}); + +request2.end(message, function() { + isRequest2Finished = true; +}); + +// Call the request2 end again to test the finish state. +request2.end(message, function() { + // This clabback should never be called. + assert.equal(isRequest2Finished, false); +}); + + +// Simple request with buffer chunk as message parameter. +var isRequest3Finished = false; +var request3 = https.request(options, function(response) { + var str = ''; + + response.on('data', function(chunk) { + str += chunk; + }); + + response.on('end', function() { + var response = JSON.parse(str); + assert.equal(message, response['data']); + isRequest3Finished = true; + }); +}); +request3.end(new Buffer(message)); + + +// Test the IncomingMessage read function. +var isRequest4Finished = false; +var readRequest = https.request({ + method: 'GET', + host: "httpbin.org", + path: '/get' +}); + +readRequest.on('response', function(incomingMessage) { + incomingMessage.on('readable', function() { + var inc = incomingMessage.read(); + assert.equal(inc instanceof Buffer, true); + assert(inc.toString('utf8').length > 0); + isRequest4Finished = true; + }); +}); +readRequest.end(); + + +process.on('exit', function() { + assert.equal(isRequest1Finished, true); + assert.equal(isRequest2Finished, true); + assert.equal(isRequest3Finished, true); + assert.equal(isRequest4Finished, true); +}); diff --git a/test/run_pass/test_https_timeout.js b/test/run_pass/test_https_timeout.js new file mode 100644 index 0000000..465fa24 --- /dev/null +++ b/test/run_pass/test_https_timeout.js @@ -0,0 +1,39 @@ +/* Copyright 2015-present Samsung Electronics Co., Ltd. and other contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + + +var assert = require('assert'); +var https = require('https'); + +options = { + method: 'GET', + host: 'httpbin.org', + path: '/delay/10' +}; + +var getReq = https.get(options); + +getReq.on('error', function(){}); + +var timeouted = false; +getReq.setTimeout(5000, function() { + timeouted = true; + getReq.abort(); +}); + +process.on('exit', function(code) { + assert.equal(timeouted, true); +}); diff --git a/test/testsets.json b/test/testsets.json index 44b8063..0d43ffe 100644 --- a/test/testsets.json +++ b/test/testsets.json @@ -41,6 +41,9 @@ { "name": "test_fs_open_read_sync_3.js", "skip": ["nuttx"], "reason": "not implemented for nuttx" }, { "name": "test_gpio_input.js", "skip": ["all"], "reason": "needs hardware" }, { "name": "test_gpio_output.js", "skip": ["all"], "reason": "need user input"}, + { "name": "test_https_get.js", "timeout": 40 }, + { "name": "test_https_request_response.js", "timeout": 40 }, + { "name": "test_https_timeout.js", "timeout": 40 }, { "name": "test_i2c.js", "skip": ["all"], "reason": "need to setup test environment" }, { "name": "test_iotjs_promise.js", "skip": ["all"], "reason": "es2015 is off by default" }, { "name": "test_module_cache.js", "skip": ["nuttx"], "reason": "not implemented for nuttx" }, diff --git a/tools/module_analyzer.py b/tools/module_analyzer.py index 1da91a0..b051268 100644 --- a/tools/module_analyzer.py +++ b/tools/module_analyzer.py @@ -34,8 +34,6 @@ def resolve_modules(options): if options.target_os: system_os = options.target_os - if system_os == 'tizen': - system_os = 'linux' build_modules_excludes |= set( options.config['module']['exclude'][system_os]) -- 2.34.1