diff --git a/common/transport/http/devdoc/rest_api_client_requirements.md b/common/transport/http/devdoc/rest_api_client_requirements.md index 1ba01e6df..eecdac29f 100644 --- a/common/transport/http/devdoc/rest_api_client_requirements.md +++ b/common/transport/http/devdoc/rest_api_client_requirements.md @@ -56,7 +56,11 @@ The `executeApiCall` method builds the HTTP request using the passed arguments a **SRS_NODE_IOTHUB_REST_API_CLIENT_13_003: [** If `requestOptions` is not falsy then it shall be passed to the `buildRequest` function. **]** -**SRS_NODE_IOTHUB_REST_API_CLIENT_16_009: [** If the HTTP request is successful the `executeApiCall` method shall parse the JSON response received and call the `done` callback with a `null` first argument, the parsed result as a second argument and the HTTP response object itself as a third argument. **]** +**SRS_NODE_IOTHUB_REST_API_CLIENT_16_009: [** If the HTTP request is successful the `executeApiCall` method shall call the `done` callback with a `null` first argument, the parsed result according to **SRS_NODE_IOTHUB_REST_API_CLIENT_16_037** and **SRS_NODE_IOTHUB_REST_API_CLIENT_16_038** as a second argument and the HTTP response object itself as a third argument. **]** + +**SRS_NODE_IOTHUB_REST_API_CLIENT_16_037: [** If the HTTP request is successful and the `content-type` header of the response starts with `application/json` the `executeApiCall` method shall parse the body of the response and provide the `result` as an object. **]** + +**SRS_NODE_IOTHUB_REST_API_CLIENT_16_038: [** If the HTTP request is successful and the `content-type` is not set or is set to something else than `application/json`, the `executeApiCall` method shall use the body of the response as is for the `result` object. **]** **SRS_NODE_IOTHUB_REST_API_CLIENT_16_010: [** If the HTTP request fails with an error code >= 300 the `executeApiCall` method shall translate the HTTP error into a transport-agnostic error using the `translateError` method and call the `done` callback with the resulting error as the only argument. **]** diff --git a/common/transport/http/src/rest_api_client.ts b/common/transport/http/src/rest_api_client.ts index 91b5b40f2..da984f8b0 100644 --- a/common/transport/http/src/rest_api_client.ts +++ b/common/transport/http/src/rest_api_client.ts @@ -151,8 +151,13 @@ export class RestApiClient { } } else { /*Codes_SRS_NODE_IOTHUB_REST_API_CLIENT_16_009: [If the HTTP request is successful the `executeApiCall` method shall parse the JSON response received and call the `done` callback with a `null` first argument, the parsed result as a second argument and the HTTP response object itself as a third argument.]*/ - const result = responseBody ? JSON.parse(responseBody) : ''; - done(null, result, response); + /*Codes_SRS_NODE_IOTHUB_REST_API_CLIENT_16_038: [If the HTTP request is successful and the `content-type` is not set or is set to something else than `application/json`, the `executeApiCall` method shall use the body of the response as is for the `result` object.]*/ + let result = responseBody; + if (responseBody && response.headers['content-type'].indexOf('application/json') >= 0) { + /*Codes_SRS_NODE_IOTHUB_REST_API_CLIENT_16_037: [If the HTTP request is successful and the `content-type` header of the response starts with `application/json` the `executeApiCall` method shall parse the body of the response and provide the `result` as an object.]*/ + result = JSON.parse(responseBody); + } + done(null, result || '', response); } }; diff --git a/common/transport/http/test/_rest_api_client_test.js b/common/transport/http/test/_rest_api_client_test.js index 595054556..2f29997b7 100644 --- a/common/transport/http/test/_rest_api_client_test.js +++ b/common/transport/http/test/_rest_api_client_test.js @@ -300,8 +300,9 @@ describe('RestApiClient', function() { }); /*Tests_SRS_NODE_IOTHUB_REST_API_CLIENT_16_009: [If the HTTP request is successful the `executeApiCall` method shall parse the JSON response received and call the `done` callback with a `null` first argument, the parsed result as a second argument and the HTTP response object itself as a third argument.]*/ - it('calls the done callback with null, a result and a response if the request succeeds', function (testCallback) { - var fakeResponse = { statusCode: 200 }; + it('calls the done callback with null, a parsed result and a response if the request succeeds and the content-type response headers starts with application/json', function (testCallback) { + /*Tests_SRS_NODE_IOTHUB_REST_API_CLIENT_16_037: [If the HTTP request is successful and the `content-type` header of the response starts with `application/json` the `executeApiCall` method shall parse the body of the response and provide the `result` as an object.]*/ + var fakeResponse = { statusCode: 200, headers: { 'content-type' : 'application/json; charset=utf-8' } }; var fakeResponseBody = { foo: 'bar' }; @@ -327,6 +328,33 @@ describe('RestApiClient', function() { }); }); + /*Tests_SRS_NODE_IOTHUB_REST_API_CLIENT_16_038: [If the HTTP request is successful and the `content-type` is not set or is set to something else than `application/json`, the `executeApiCall` method shall use the body of the response as is for the `result` object.]*/ + it('calls the done callback with null, an unparsed result and a response if the request succeeds and the headers is not application/json', function (testCallback) { + /*Tests_SRS_NODE_IOTHUB_REST_API_CLIENT_16_037: [If the HTTP request is successful and the `content-type` header of the response starts with `application/json` the `executeApiCall` method shall parse the body of the response and provide the `result` as an object.]*/ + var fakeResponse = { statusCode: 200, headers: { 'content-type' : 'text/plain; charset=utf-8' } }; + var fakeResponseBody = "Hello world"; + + var fakeHttpHelper = { + buildRequest: function(method, path, headers, host, requestCallback) { + return { + setTimeout: function() {}, + write: function() {}, + end: function() { + requestCallback(null, JSON.stringify(fakeResponseBody), fakeResponse); + } + }; + } + }; + + var client = new RestApiClient(fakeConfig, fakeAgent, fakeHttpHelper); + client.executeApiCall('GET', '/test/path', null, null, function(err, result, response) { + assert.isNull(err); + assert.strictEqual(result, fakeResponseBody); + assert.equal(response, fakeResponse); + testCallback(); + }); + }); + /*Tests_SRS_NODE_IOTHUB_REST_API_CLIENT_16_010: [If the HTTP request fails with an error code >= 300 the `executeApiCall` method shall translate the HTTP error into a transport-agnostic error using the `translateError` method and call the `done` callback with the resulting error as the only argument.]*/ it('calls the done callback with a translated error if the request fails with an HTTP error', function (testCallback) { var fakeResponse = { statusCode: 401 }; diff --git a/service/samples/c2d_tcp_streaming.js b/service/samples/c2d_tcp_streaming.js index ceb63874c..9fd4132ef 100644 --- a/service/samples/c2d_tcp_streaming.js +++ b/service/samples/c2d_tcp_streaming.js @@ -18,7 +18,7 @@ var streamInit = { var client = ServiceClient.fromConnectionString(process.env.IOTHUB_CONNECTION_STRING); console.log('initiating stream'); -client.initiateStream('', streamInit, function(err, result) { +client.initiateStream(process.env.STREAMING_TARGET_DEVICE, streamInit, function(err, result) { if (err) { console.error(err.toString()); process.exit(-1); diff --git a/service/samples/tcp_streaming_proxy.js b/service/samples/tcp_streaming_proxy.js new file mode 100644 index 000000000..f37d72223 --- /dev/null +++ b/service/samples/tcp_streaming_proxy.js @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +'use strict'; + +var websocket = require('websocket-stream') +var ServiceClient = require('azure-iothub').Client; +var net = require('net'); + +var streamInit = { + streamName: 'TestStream', + contentType: 'text/plain', + contentEncoding: 'utf-8', + connectTimeoutInSeconds: 30, + responseTimeoutInSeconds: 30, + payload: undefined +} + +var client = ServiceClient.fromConnectionString(process.env.IOTHUB_CONNECTION_STRING); + +console.log('initiating stream'); +client.initiateStream(process.env.STREAMING_TARGET_DEVICE, streamInit, function(err, result) { + if (err) { + console.error(err.toString()); + process.exit(-1); + } else { + console.log(JSON.stringify(result, null, 2)); + + var ws = websocket(result.uri, { headers: { 'Authorization': 'Bearer ' + result.authorizationToken} }); + console.log('Got websocket - creating local server on port ' + process.env.PROXY_PORT); + var proxyServer = net.createServer(function (socket) { + socket.on('end', function () { + console.log('client disconnected'); + process.exit(0); + }) + + socket.pipe(ws); + ws.pipe(socket); + }); + + proxyServer.on('error', function (err) { + console.error('error on the proxy server socket: ' + err.toString()); + process.exit(-1); + }) + + proxyServer.listen(process.env.PROXY_PORT, function () { + console.log('listening on port: ' + process.env.PROXY_PORT + '...'); + }) + } +}); \ No newline at end of file