1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19var util = require('util'); 20var EventEmitter = require('events').EventEmitter; 21var thrift = require('./thrift'); 22 23var TBufferedTransport = require('./buffered_transport'); 24var TBinaryProtocol = require('./binary_protocol'); 25var InputBufferUnderrunError = require('./input_buffer_underrun_error'); 26 27var createClient = require('./create_client'); 28 29/** 30 * @class 31 * @name ConnectOptions 32 * @property {string} transport - The Thrift layered transport to use (TBufferedTransport, etc). 33 * @property {string} protocol - The Thrift serialization protocol to use (TBinaryProtocol, etc.). 34 * @property {string} path - The URL path to POST to (e.g. "/", "/mySvc", "/thrift/quoteSvc", etc.). 35 * @property {object} header - A standard Node.js header hash, an object hash containing key/value 36 * pairs where the key is the header name string and the value is the header value string. 37 * @property {object} requestOptions - Options passed on to http request. Details: 38 * https://developer.harmonyos.com/en/docs/documentation/doc-references/js-apis-net-http-0000001168304341#section12262183471518 39 * @example 40 * //Use a connection that requires ssl/tls, closes the connection after each request, 41 * // uses the buffered transport layer, uses the JSON protocol and directs RPC traffic 42 * // to https://thrift.example.com:9090/hello 43 * import http from '@ohos.net.http' // HTTP module of OpenHarmonyOS 44 * var thrift = require('thrift'); 45 * var options = { 46 * transport: thrift.TBufferedTransport, 47 * protocol: thrift.TJSONProtocol, 48 * path: "/hello", 49 * headers: {"Connection": "close"} 50 * }; 51 * // With OpenHarmonyOS HTTP module, HTTPS is supported by default. To support HTTP, See: 52 * // https://developer.harmonyos.com/en/docs/documentation/doc-references/js-apis-net-http-0000001168304341#EN-US_TOPIC_0000001171944450__s56d19203690d4782bfc74069abb6bd71 53 * var con = thrift.createOhosConnection(http.createHttp, "thrift.example.com", 9090, options); 54 * var client = thrift.createOhosClient(myService, connection); 55 * client.myServiceFunction(); 56 */ 57 58/** 59 * Initializes a Thrift HttpConnection instance (use createHttpConnection() rather than 60 * instantiating directly). 61 * @constructor 62 * @param {ConnectOptions} options - The configuration options to use. 63 * @throws {error} Exceptions other than InputBufferUnderrunError are rethrown 64 * @event {error} The "error" event is fired when a Node.js error event occurs during 65 * request or response processing, in which case the node error is passed on. An "error" 66 * event may also be fired when the connection can not map a response back to the 67 * appropriate client (an internal error), generating a TApplicationException. 68 * @classdesc OhosConnection objects provide Thrift end point transport 69 * semantics implemented over the OpenHarmonyOS http.request() method. 70 * @see {@link createOhosConnection} 71 */ 72var OhosConnection = exports.OhosConnection = function(options) { 73 //Initialize the emitter base object 74 EventEmitter.call(this); 75 76 //Set configuration 77 var self = this; 78 this.options = options || {}; 79 this.host = this.options.host; 80 this.port = this.options.port; 81 this.path = this.options.path || '/'; 82 //OpenHarmonyOS needs URL for initiating an HTTP request. 83 this.url = 84 this.port === 80 85 ? this.host.replace(/\/$/, '') + this.path 86 : this.host.replace(/\/$/, '') + ':' + this.port + this.path; 87 this.transport = this.options.transport || TBufferedTransport; 88 this.protocol = this.options.protocol || TBinaryProtocol; 89 //Inherit method from OpenHarmonyOS HTTP module 90 this.createHttp = this.options.createHttp; 91 92 //Prepare HTTP request options 93 this.requestOptions = { 94 method: 'POST', 95 header: this.options.header || {}, 96 readTimeout: this.options.readTimeout || 60000, 97 connectTimeout: this.options.connectTimeout || 60000 98 }; 99 for (var attrname in this.options.requestOptions) { 100 this.requestOptions[attrname] = this.options.requestOptions[attrname]; 101 } 102 /*jshint -W069 */ 103 if (!this.requestOptions.header['Connection']) { 104 this.requestOptions.header['Connection'] = 'keep-alive'; 105 } 106 /*jshint +W069 */ 107 108 //The sequence map is used to map seqIDs back to the 109 // calling client in multiplexed scenarios 110 this.seqId2Service = {}; 111 112 function decodeCallback(transport_with_data) { 113 var proto = new self.protocol(transport_with_data); 114 try { 115 while (true) { 116 var header = proto.readMessageBegin(); 117 var dummy_seqid = header.rseqid * -1; 118 var client = self.client; 119 //The Multiplexed Protocol stores a hash of seqid to service names 120 // in seqId2Service. If the SeqId is found in the hash we need to 121 // lookup the appropriate client for this call. 122 // The client var is a single client object when not multiplexing, 123 // when using multiplexing it is a service name keyed hash of client 124 // objects. 125 //NOTE: The 2 way interdependencies between protocols, transports, 126 // connections and clients in the Node.js implementation are irregular 127 // and make the implementation difficult to extend and maintain. We 128 // should bring this stuff inline with typical thrift I/O stack 129 // operation soon. 130 // --ra 131 var service_name = self.seqId2Service[header.rseqid]; 132 if (service_name) { 133 client = self.client[service_name]; 134 delete self.seqId2Service[header.rseqid]; 135 } 136 /*jshint -W083 */ 137 client._reqs[dummy_seqid] = function(err, success){ 138 transport_with_data.commitPosition(); 139 var clientCallback = client._reqs[header.rseqid]; 140 delete client._reqs[header.rseqid]; 141 if (clientCallback) { 142 process.nextTick(function() { 143 clientCallback(err, success); 144 }); 145 } 146 }; 147 /*jshint +W083 */ 148 if(client['recv_' + header.fname]) { 149 client['recv_' + header.fname](proto, header.mtype, dummy_seqid); 150 } else { 151 delete client._reqs[dummy_seqid]; 152 self.emit("error", 153 new thrift.TApplicationException( 154 thrift.TApplicationExceptionType.WRONG_METHOD_NAME, 155 "Received a response to an unknown RPC function")); 156 } 157 } 158 } 159 catch (e) { 160 if (e instanceof InputBufferUnderrunError) { 161 transport_with_data.rollbackPosition(); 162 } else { 163 self.emit('error', e); 164 } 165 } 166 } 167 168 169 //Response handler 170 ////////////////////////////////////////////////// 171 this.responseCallback = function(error, response) { 172 //Response will be a struct like: 173 // https://developer.harmonyos.com/en/docs/documentation/doc-references/js-apis-net-http-0000001168304341#section15920192914312 174 var data = []; 175 var dataLen = 0; 176 177 if (error) { 178 self.emit('error', error); 179 return; 180 } 181 182 if (!response || response.responseCode !== 200) { 183 self.emit('error', new THTTPException(response)); 184 } 185 186 // With OpenHarmonyOS running in a Browser (e.g. Browserify), chunk 187 // will be a string or an ArrayBuffer. 188 if ( 189 typeof response.result == 'string' || 190 Object.prototype.toString.call(response.result) == '[object Uint8Array]' 191 ) { 192 // Wrap ArrayBuffer/string in a Buffer so data[i].copy will work 193 data.push(Buffer.from(response.result)); 194 } 195 dataLen += response.result.length; 196 197 var buf = Buffer.alloc(dataLen); 198 for (var i = 0, len = data.length, pos = 0; i < len; i++) { 199 data[i].copy(buf, pos); 200 pos += data[i].length; 201 } 202 //Get the receiver function for the transport and 203 // call it with the buffer 204 self.transport.receiver(decodeCallback)(buf); 205 }; 206 207 /** 208 * Writes Thrift message data to the connection 209 * @param {Buffer} data - A Node.js Buffer containing the data to write 210 * @returns {void} No return value. 211 * @event {error} the "error" event is raised upon request failure passing the 212 * Node.js error object to the listener. 213 */ 214 this.write = function(data) { 215 //To initiate multiple HTTP requests, we must create an HttpRequest object 216 // for each HTTP request 217 var http = self.createHttp(); 218 var opts = self.requestOptions; 219 opts.header["Content-length"] = data.length; 220 if (!opts.header["Content-Type"]) 221 opts.header["Content-Type"] = "application/x-thrift"; 222 // extraData not support array data currently 223 opts.extraData = data.toString(); 224 http.request(self.url, opts, self.responseCallback); 225 }; 226}; 227util.inherits(OhosConnection, EventEmitter); 228 229/** 230 * Creates a new OhosConnection object, used by Thrift clients to connect 231 * to Thrift HTTP based servers. 232 * @param {Function} createHttp - OpenHarmonyOS method to initiate or destroy an HTTP request. 233 * @param {string} host - The host name or IP to connect to. 234 * @param {number} port - The TCP port to connect to. 235 * @param {ConnectOptions} options - The configuration options to use. 236 * @returns {OhosConnection} The connection object. 237 * @see {@link ConnectOptions} 238 */ 239exports.createOhosConnection = function(createHttp, host, port, options) { 240 options.createHttp = createHttp; 241 options.host = host; 242 options.port = port || 80; 243 return new OhosConnection(options); 244}; 245 246exports.createOhosClient = createClient; 247 248function THTTPException(response) { 249 thrift.TApplicationException.call(this); 250 if (Error.captureStackTrace !== undefined) { 251 Error.captureStackTrace(this, this.constructor); 252 } 253 254 this.name = this.constructor.name; 255 this.responseCode = response.responseCode; 256 this.response = response; 257 this.type = thrift.TApplicationExceptionType.PROTOCOL_ERROR; 258 this.message = 259 'Received a response with a bad HTTP status code: ' + response.responseCode; 260} 261util.inherits(THTTPException, thrift.TApplicationException); 262