Skip to content

Commit

Permalink
Merge branch 'feature/http-client-timeouts' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
etki committed Aug 14, 2017
2 parents 1671a22 + 599f7ea commit d2c3541
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 40 deletions.
38 changes: 28 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,13 @@ var call = client

Basic client provides you with following methods:

- `get(url, [query, headers])`
- `head(url, [query, headers])`
- `post(url, [payload, headers])`
- `put(url, [payload, headers])`
- `patch(url, [payload, headers])`
- `delete(url, [payload, headers])`
- `request(method, url, [query, payload, headers])`
- `get(url, [query, headers, timeout])`
- `head(url, [query, headers, timeout])`
- `post(url, [payload, headers, timeout])`
- `put(url, [payload, headers, timeout])`
- `patch(url, [payload, headers, timeout])`
- `delete(url, [payload, headers, timeout])`
- `request(method, url, [query, payload, headers, timeout])`

with following rules:

Expand Down Expand Up @@ -161,6 +161,8 @@ var settings = {
headers: {},
// maximum amount of request retries
retries: 4,
// default timeout in milliseconds
timeout: 1000,
// alternative logger options
logger: {
level: SDK.Logger.Level.Info,
Expand Down Expand Up @@ -191,6 +193,15 @@ in promise, treating 404 as false and any 2xx as true
trigger `http.NotFoundException`
- There are fallback methods `.request()` and `.execute()` in case you have
some logic depending on response codes.

The signatures are:

- `get(resource, [query, headers, timeout])`
- `exists(resource, [query, headers, timeout])`
- `create(resource, [payload, headers, query, timeout])`
- `set(resource, [payload, headers, query, timeout]) : PUT`
- `modify(resource, [payload, headers, query, timeout]) : PATCH`
- `delete(resource, [payload, headers, query, timeout])`

So you may talk to your backend like that:

Expand Down Expand Up @@ -223,6 +234,8 @@ var options = {
headers: {
'Content-Type': 'application/json'
},
// default timeout
timeout: 1000,
// same logger options
logger: {}
}
Expand Down Expand Up @@ -362,9 +375,14 @@ feelings for semicolons.
## Testing

This package is using Mocha with Chai for test running, Istanbul for
recording coverage metrics and Allure framework for reporting. If you
want full-blown feedback, use `npx jake test:with-report` to generate
Allure report (don't forget to install
recording coverage metrics and Allure framework for reporting. To run
tests, simply invoke jake:

```bash
npx jake test
```
If you want full-blown feedback, use `npx jake test:with-report` to
generate Allure report (don't forget to install
[allure-commandline][allure-commandline] before) and coverage report,
which will be placed in `tmp/report` directory.

Expand Down
9 changes: 9 additions & 0 deletions lib/http/_schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
* @property {QueryBag|null} query Request query
* @property {HeaderBag|null} headers Request headers
* @property {string} payload Serialized request payload
* @property {int} timeout
*/

/**
Expand Down Expand Up @@ -156,6 +157,7 @@ var Method = {
* @property {QueryBag} query
* @property {*} payload
* @property {HeaderBag} headers
* @property {int} timeout
*/

/**
Expand Down Expand Up @@ -190,6 +192,7 @@ var Method = {
* @param {*|undefined} [payload]
* @param {QueryBag|undefined} [query]
* @param {HeaderBag|undefined} [headers]
* @param {int} [timeout]
*
* @return {RestResponsePromise}
*/
Expand All @@ -200,6 +203,7 @@ var Method = {
* @param {string} route
* @param {QueryBag} [query]
* @param {HeaderBag} [headers]
* @param {int} [timeout]
*
* @return {Thenable.<boolean|Error>}
*/
Expand All @@ -210,6 +214,7 @@ var Method = {
* @param {string} route
* @param {QueryBag} [query]
* @param {HeaderBag} [headers]
* @param {int} [timeout]
*
* @return {Thenable.<*|Error>}
*/
Expand All @@ -220,6 +225,7 @@ var Method = {
* @param {string} route
* @param {*} [payload]
* @param {HeaderBag} [headers]
* @param {int} [timeout]
*
* @return {Thenable.<*|Error>}
*/
Expand All @@ -230,6 +236,7 @@ var Method = {
* @param {string} route
* @param {*} [payload]
* @param {HeaderBag} [headers]
* @param {int} [timeout]
*
* @return {Thenable.<*|Error>}
*/
Expand All @@ -240,6 +247,7 @@ var Method = {
* @param {string} route
* @param {*} [payload]
* @param {HeaderBag} [headers]
* @param {int} [timeout]
*
* @return {Thenable.<*|Error>}
*/
Expand All @@ -250,6 +258,7 @@ var Method = {
* @param {string} route
* @param {*} [payload]
* @param {HeaderBag} [headers]
* @param {int} [timeout]
*
* @return {Thenable.<*|Error>}
*/
Expand Down
35 changes: 22 additions & 13 deletions lib/http/basic.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/* global Net */

var timeout = require('../concurrent').timeout
var C = require('./_common')
var Q = C.Query
var H = C.Headers
var Slf4j = require('../logger').slf4j.Slf4j
var Slf4j = require('../logger').Slf4j

/**
* Provides client configuration defaults.
Expand Down Expand Up @@ -41,18 +42,18 @@ var RS = {
Ok: 'Ok',
fromCode: function (code) {
if (code < 200 || !code) {
return this.NetworkError
return RS.NetworkError
}
if (code < 400) {
return this.Ok
return RS.Ok
}
if (code === 404) {
return this.NotFound
return RS.NotFound
}
if (code < 500) {
return this.ClientError
return RS.ClientError
}
return this.ServerError
return RS.ServerError
}
}

Expand All @@ -71,6 +72,7 @@ var RS = {
* @property {HeaderBag} headers Headers to be used on every request.
* @property {int} retries Maximum number of retries allowed for request.
* @property {LoggerOptions} logger Logger instance or context and/or name.
* @property {int} timeout
*/

/**
Expand Down Expand Up @@ -129,6 +131,7 @@ function BasicHttpClient (settings, transport) {
*/
function execute (request) {
var message
request.timeout = typeof request.timeout === 'number' ? request.timeout : settings.timeout
if (!request.method) {
message = 'Request method hasn\'t been specified'
return Promise.reject(new C.InvalidConfigurationException(message))
Expand Down Expand Up @@ -171,7 +174,7 @@ function BasicHttpClient (settings, transport) {
opts.postData = request.payload
opts.headers = H.encode(headers)
logger.trace('Executing request #{} `{} {}`, attempt #{}', request.id, request.method, url, attempt)
return transport(url, opts).then(function (raw) {
return timeout(transport(url, opts), request.timeout).then(function (raw) {
var response = {code: raw.code, headers: H.decode(raw.headers), payload: raw.text}
var status = RS.fromCode(response.code)
var toRetry = shouldRetry(status, attempt)
Expand All @@ -189,8 +192,8 @@ function BasicHttpClient (settings, transport) {
})
}

function request (method, url, query, payload, headers) {
return execute({url: url, method: method, headers: headers, query: query, payload: payload})
function request (method, url, query, payload, headers, timeout) {
return execute({url: url, method: method, headers: headers, query: query, payload: payload, timeout: timeout})
}

// noinspection JSUnusedGlobalSymbols
Expand All @@ -199,14 +202,14 @@ function BasicHttpClient (settings, transport) {

var methods = ['get', 'head']
methods.forEach(function (method) {
self[method] = function (url, query, headers) {
return request(method, url, query, null, headers)
self[method] = function (url, query, headers, timeout) {
return request(method, url, query, null, headers, timeout)
}
})
methods = ['post', 'put', 'patch', 'delete']
methods.forEach(function (method) {
self[method] = function (url, payload, headers, query) {
return request(method, url, query, payload, headers)
self[method] = function (url, payload, headers, query, timeout) {
return request(method, url, query, payload, headers, timeout)
}
})

Expand All @@ -218,6 +221,7 @@ function BasicHttpClient (settings, transport) {
* @param {string} url
* @param {QueryBag} [query]
* @param {HeaderBag} [headers]
* @param {int} [timeout]
*
* @return {HttpResponsePromise}
*/
Expand All @@ -230,6 +234,7 @@ function BasicHttpClient (settings, transport) {
* @param {string} url
* @param {QueryBag} [query]
* @param {HeaderBag} [headers]
* @param {int} [timeout]
*
* @return {HttpResponsePromise}
*/
Expand All @@ -242,6 +247,7 @@ function BasicHttpClient (settings, transport) {
* @param {string} url
* @param {string} [payload]
* @param {HeaderBag} [headers]
* @param {int} [timeout]
*
* @return {HttpResponsePromise}
*/
Expand All @@ -254,6 +260,7 @@ function BasicHttpClient (settings, transport) {
* @param {string} url
* @param {string} [payload]
* @param {HeaderBag} [headers]
* @param {int} [timeout]
*
* @return {HttpResponsePromise}
*/
Expand All @@ -266,6 +273,7 @@ function BasicHttpClient (settings, transport) {
* @param {string} url
* @param {string} [payload]
* @param {HeaderBag} [headers]
* @param {int} [timeout]
*
* @return {HttpResponsePromise}
*/
Expand All @@ -278,6 +286,7 @@ function BasicHttpClient (settings, transport) {
* @param {string} url
* @param {string} [payload]
* @param {HeaderBag} [headers]
* @param {int} [timeout]
*
* @return {HttpResponsePromise}
*/
Expand Down
43 changes: 26 additions & 17 deletions lib/http/rest.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
var C = require('./_common')
var M = C.Method
var Client = require('./basic').Client
var Slf4j = require('../logger').slf4j.Slf4j
var Slf4j = require('../logger').Slf4j

/**
* @interface Serializer
Expand Down Expand Up @@ -41,15 +41,24 @@ function JsonSerializer () {
* @class RestClientSettings
*
* @property {string|undefined} url String to prepend to every request route.
* @property {Serializer|undefined} serializer Serializer to encode/decode messages, default: JsonSerializer
* @property {number|undefined} retries Maximum number of retries for each request, default: `4`
* @property {string|undefined} methodOverrideHeader Name of header that should conceal real request HTTP method,
* @property {Serializer|undefined} serializer Serializer to encode/decode
* messages, default: JsonSerializer
* @property {number|undefined} retries Maximum number of retries for each
* request, default: `4`
* @property {string|undefined} methodOverrideHeader Name of header that should
* conceal real request HTTP method,
* look up `X-HTTP-Method-Override` in nearest search engine. Default: none
* @property {boolean|undefined} retryOnNetworkError Whether request should be retried on connection error,
* @property {boolean|undefined} retryOnNetworkError Whether request should be
* retried on connection error,
* default: `true`
* @property {boolean|undefined} retryOnServerError Whether request should be retried on server error (5xx),
* @property {boolean|undefined} retryOnServerError Whether request should be
* retried on server error (5xx),
* default: `true`
* @property {HeaderBag|undefined} headers Object containing headers for every emitted request, default: `{}`
* @property {HeaderBag|undefined} headers Object containing headers for every
* emitted request, default: `{}`
* @property {int|undefined} timeout Default request timeout in milliseconds,
* may be overriden on per-request level. Falsey values will use no timeout
* at all.
* @property {LoggerOptions} logger Logger options.
* @property {IHttpClient} client Underlying http client
*/
Expand Down Expand Up @@ -81,7 +90,6 @@ function RestClient (settings, transport) {
}
})
client = settings.client || new Client(opts, transport)
// noinspection JSUnusedAssignment
logger = Slf4j.factory(opts.logger, 'ama-team.voxengine-sdk.http.rest')

function execute (request) {
Expand All @@ -91,7 +99,8 @@ function RestClient (settings, transport) {
url: request.resource || request.route, // 0.2.0 compatibility
method: request.method,
query: request.query,
headers: request.headers
headers: request.headers,
timeout: typeof request.timeout === 'number' ? request.timeout : settings.timeout
}
logger.debug('Executing request #{} `{} {}`', request.id, request.method, request.resource)
basicRequest.payload = request.payload ? opts.serializer.serialize(request.payload) : null
Expand All @@ -116,8 +125,8 @@ function RestClient (settings, transport) {
/**
* @inheritDoc
*/
function request (method, resource, payload, query, headers) {
return execute({resource: resource, method: method, query: query, payload: payload, headers: headers})
function request (method, resource, payload, query, headers, timeout) {
return execute({resource: resource, method: method, query: query, payload: payload, headers: headers, timeout: timeout})
}

/**
Expand All @@ -133,8 +142,8 @@ function RestClient (settings, transport) {
/**
* @inheritDoc
*/
this.exists = function (resource, query, headers) {
return request(M.Head, resource, null, query, headers)
this.exists = function (resource, query, headers, timeout) {
return request(M.Head, resource, null, query, headers, timeout)
.then(function (response) {
return response.code !== 404
})
Expand All @@ -143,17 +152,17 @@ function RestClient (settings, transport) {
/**
* @inheritDoc
*/
this.get = function (resource, query, headers) {
return request(M.Get, resource, null, query, headers)
this.get = function (resource, query, headers, timeout) {
return request(M.Get, resource, null, query, headers, timeout)
.then(function (response) {
return response.code === 404 ? null : response.payload
})
}

var methods = {create: M.Post, set: M.Put, modify: M.Patch, delete: M.Delete}
Object.keys(methods).forEach(function (method) {
self[method] = function (resource, payload, headers, query) {
return request(methods[method], resource, payload, query, headers)
self[method] = function (resource, payload, headers, query, timeout) {
return request(methods[method], resource, payload, query, headers, timeout)
.then(function (response) {
if (response.code === 404) {
var message = 'Modification request has returned 404 status code'
Expand Down

0 comments on commit d2c3541

Please sign in to comment.