Skip to content

Commit

Permalink
Merge branch 'tuckbick-customClient'
Browse files Browse the repository at this point in the history
  • Loading branch information
spanditcaa committed Apr 9, 2019
2 parents 952420f + e3fdb08 commit b820e44
Showing 3 changed files with 82 additions and 29 deletions.
29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -40,7 +40,7 @@ _**NOTE**: h2o2 is included with and loaded by default in Hapi < 9.0._

## Options

The plugin can be registered with an optional object specifying defaults to be applied to the proxy handler object.
The plugin can be registered with an optional object specifying defaults to be applied to the proxy handler object.

The proxy handler object has the following properties:

@@ -50,6 +50,7 @@ The proxy handler object has the following properties:
* 'http'
* 'https'
* `uri` - absolute URI used instead of host, port, protocol, path, and query. Cannot be used with `host`, `port`, `protocol`, or `mapUri`.
* `httpClient` - an http client that abides by the Wreck interface. Defaults to [`wreck`](https://github.com/hapijs/wreck).
* `passThrough` - if set to `true`, it forwards the headers from the client to the upstream service, headers sent from the upstream service will also be forwarded to the client. Defaults to `false`.
* `localStatePassThrough` - if set to`false`, any locally defined state is removed from incoming requests before being sent to the upstream service. This value can be overridden on a per state basis via the `server.state()` `passThrough` option. Defaults to `false`
* `acceptEncoding` - if set to `false`, does not pass-through the 'Accept-Encoding' HTTP header which is useful for the `onResponse` post-processing to avoid receiving an encoded response. Can only be used together with `passThrough`. Defaults to `true` (passing header).
@@ -75,7 +76,7 @@ The proxy handler object has the following properties:
* `maxSockets` - sets the maximum number of sockets available per outgoing proxy host connection. `false` means use the **wreck** module default value (`Infinity`). Does not affect non-proxy outgoing client connections. Defaults to `Infinity`.
* `secureProtocol` - [TLS](http://nodejs.org/api/tls.html) flag indicating the SSL method to use, e.g. `SSLv3_method`
to force SSL version 3. The possible values depend on your installation of OpenSSL. Read the official OpenSSL docs for possible [SSL_METHODS](https://www.openssl.org/docs/man1.0.2/ssl/ssl.html).
* `ciphers` - [TLS](https://nodejs.org/api/tls.html#tls_modifying_the_default_tls_cipher_suite) list of TLS ciphers to override node's default.
* `ciphers` - [TLS](https://nodejs.org/api/tls.html#tls_modifying_the_default_tls_cipher_suite) list of TLS ciphers to override node's default.
The possible values depend on your installation of OpenSSL. Read the official OpenSSL docs for possible [TLS_CIPHERS](https://www.openssl.org/docs/man1.0.2/apps/ciphers.html#CIPHER-LIST-FORMAT).
* `downstreamResponseTime` - logs the time spent processing the downstream request using [process.hrtime](https://nodejs.org/api/process.html#process_process_hrtime_time). Defaults to `false`.

@@ -204,3 +205,27 @@ server.route({
});

```


### Using a custom http client

By default, `h2o2` uses Wreck to perform requests. A custom http client can be provided by passing a client to `httpClient`, as long as it abides by the [`wreck`](https://github.com/hapijs/wreck) interface. The two functions that `h2o2` utilizes are `request()` and `parseCacheControl()`.

```javascript
server.route({
method: 'GET',
path: '/',
handler: {
proxy: {
httpClient: {
request(method, uri, options) {
return axios({
method,
url: 'https://some.upstream.service.com/'
})
}
}
}
}
});
```
13 changes: 11 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -17,6 +17,10 @@ const NS_PER_SEC = 1e9;


internals.defaults = {
httpClient: {
request: Wreck.request.bind(Wreck),
parseCacheControl: Wreck.parseCacheControl.bind(Wreck)
},
xforward: false,
passThrough: false,
redirects: false,
@@ -28,6 +32,10 @@ internals.defaults = {


internals.schema = Joi.object({
httpClient: Joi.object({
request: Joi.func(),
parseCacheControl: Joi.func()
}),
host: Joi.string(),
port: Joi.number().integer(),
protocol: Joi.string().valid('http', 'https', 'http:', 'https:'),
@@ -154,7 +162,8 @@ internals.handler = function (route, handlerOptions) {
if (settings.downstreamResponseTime) {
downstreamStartTime = process.hrtime();
}
const promise = Wreck.request(request.method, uri, options);

const promise = settings.httpClient.request(request.method, uri, options);

if (settings.onRequest) {
settings.onRequest(promise.req);
@@ -183,7 +192,7 @@ internals.handler = function (route, handlerOptions) {
if (settings._upstreamTtl) {
const cacheControlHeader = res.headers['cache-control'];
if (cacheControlHeader) {
const cacheControl = Wreck.parseCacheControl(cacheControlHeader);
const cacheControl = settings.httpClient.parseCacheControl(cacheControlHeader);
if (cacheControl) {
ttl = cacheControl['max-age'] * 1000;
}
69 changes: 44 additions & 25 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -50,35 +50,35 @@ describe('H2o2', () => {
it('overrides maxSockets', { parallel: false }, async () => {

let maxSockets;
const orig = Wreck.request;
Wreck.request = function (method, uri, options, callback) {
const httpClient = {
request(method, uri, options, callback) {

Wreck.request = orig;
maxSockets = options.agent.maxSockets;
maxSockets = options.agent.maxSockets;

return { statusCode: 200 };
return { statusCode: 200 };
}
};

const server = await provisionServer();
server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', maxSockets: 213 } } });
server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', httpClient, maxSockets: 213 } } });
await server.inject('/');
expect(maxSockets).to.equal(213);
});

it('uses node default with maxSockets set to false', { parallel: false }, async () => {

let agent;
const orig = Wreck.request;
Wreck.request = function (method, uri, options) {
const httpClient = {
request(method, uri, options) {

Wreck.request = orig;
agent = options.agent;
agent = options.agent;

return { statusCode: 200 };
return { statusCode: 200 };
}
};

const server = await provisionServer();
server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', maxSockets: false } } });
server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', httpClient, maxSockets: false } } });
await server.inject('/');
expect(agent).to.equal(undefined);
});
@@ -1470,15 +1470,15 @@ describe('H2o2', () => {

const server = await provisionServer();

const requestFn = Wreck.request;
Wreck.request = function (method, url, options) {
const httpClient = {
request(method, uri, options, callback) {

Wreck.request = requestFn;
expect(options.headers['content-type']).to.equal('application/json');
expect(options.headers['Content-Type']).to.not.exist();
throw new Error('placeholder');
expect(options.headers['content-type']).to.equal('application/json');
expect(options.headers['Content-Type']).to.not.exist();
throw new Error('placeholder');
}
};
server.route({ method: 'GET', path: '/test', handler: { proxy: { uri: 'http://localhost', passThrough: true } } });
server.route({ method: 'GET', path: '/test', handler: { proxy: { uri: 'http://localhost', httpClient, passThrough: true } } });
await server.inject({ method: 'GET', url: '/test', headers: { 'Content-Type': 'application/json' } });
});

@@ -1487,14 +1487,14 @@ describe('H2o2', () => {
const server = await provisionServer();
const agent = { name: 'myagent' };

const requestFn = Wreck.request;
Wreck.request = function (method, url, options) {
const httpClient = {
request(method, uri, options, callback) {

Wreck.request = requestFn;
expect(options.agent).to.equal(agent);
return { statusCode: 200 };
expect(options.agent).to.equal(agent);
return { statusCode: 200 };
}
};
server.route({ method: 'GET', path: '/agenttest', handler: { proxy: { uri: 'http://localhost', agent } } });
server.route({ method: 'GET', path: '/agenttest', handler: { proxy: { uri: 'http://localhost', httpClient, agent } } });
await server.inject({ method: 'GET', url: '/agenttest', headers: {} }, (res) => { });
});

@@ -1844,4 +1844,23 @@ describe('H2o2', () => {
const res = await server.inject('/failureResponse');
expect(res.statusCode).to.equal(502);
});

it('uses a custom http-client', async () => {

const upstream = Hapi.server();
upstream.route({ method: 'GET', path: '/', handler: () => 'ok' });
await upstream.start();

const httpClient = {
request: Wreck.request.bind(Wreck),
parseCacheControl: Wreck.parseCacheControl.bind(Wreck)
};

const server = await provisionServer();
server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.info.port, httpClient } } });

const res = await server.inject('/');

expect(res.payload).to.equal('ok');
});
});

0 comments on commit b820e44

Please # to comment.