Skip to content

Commit

Permalink
Make it work
Browse files Browse the repository at this point in the history
  • Loading branch information
szmarczak committed Dec 1, 2019
1 parent 2ec5c4d commit 779b610
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 28 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"decompress-response": "^5.0.0",
"duplexer3": "^0.1.4",
"get-stream": "^5.0.0",
"http2-wrapper": "^1.0.0-beta.2",
"lowercase-keys": "^2.0.0",
"mimic-response": "^2.0.0",
"p-cancelable": "^2.0.0",
Expand Down
23 changes: 12 additions & 11 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ For browser usage, we recommend [Ky](https://github.com/sindresorhus/ky) by the
## Highlights

- [Promise & stream API](#api)
- [HTTP2 support](#http2-support)
- [Request cancelation](#aborting-the-request)
- [RFC compliant caching](#cache-adapters)
- [Follows redirects](#followredirect)
Expand Down Expand Up @@ -445,10 +446,10 @@ Default: `false`

###### request

Type: `Function`\
Default: `http.request | https.request` *(Depending on the protocol)*
Type: `Function | AsyncFunction`\
Default: `http2wrapper.auto`

Custom request function. The main purpose of this is to [support HTTP2 using a wrapper](#experimental-http2-support).
Custom request function. The main purpose of this is to [support HTTP2 using a wrapper](#http2-support).

###### useElectronNet

Expand Down Expand Up @@ -1395,19 +1396,19 @@ const custom = got.extend({
})();
```

### Experimental HTTP2 support
### HTTP2 support

Got provides an experimental support for HTTP2 using the [`http2-wrapper`](https://github.com/szmarczak/http2-wrapper) package:
Got supports HTTP2 via the [`http2-wrapper`](https://github.com/szmarczak/http2-wrapper) package.

**Note:** Overriding `options.request` will disable HTTP2 support.

```js
const got = require('got');
const {request} = require('http2-wrapper');

const h2got = got.extend({request});

(async () => {
const {body} = await h2got('https://nghttp2.org/httpbin/headers');
console.log(body);
const {headers} = await got('https://nghttp2.org/httpbin/anything');
console.log(headers.via);
//=> '2 nghttpx'
})();
```

Expand All @@ -1421,7 +1422,7 @@ Got was created because the popular [`request`](https://github.com/request/reque

| | `got` | [`request`][r0] | [`node-fetch`][n0] | [`ky`][k0] | [`axios`][a0] | [`superagent`][s0] |
|-----------------------|:----------------:|:---------------:|:--------------------:|:-----------------:|:----------------:|:--------------------:|
| HTTP/2 support | ||||| ✔️\*\* |
| HTTP/2 support | ✔️ ||||| ✔️\*\* |
| Browser support ||| ✔️\* | ✔️ | ✔️ | ✔️ |
| Electron support | ✔️ ||||||
| Promise API | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
Expand Down
17 changes: 5 additions & 12 deletions source/normalize-arguments.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {promisify} from 'util';
import CacheableRequest = require('cacheable-request');
import http = require('http');
// @ts-ignore
import http2 = require('../../http2-wrapper');
import https = require('https');
import Keyv = require('keyv');
import lowercaseKeys = require('lowercase-keys');
Expand All @@ -18,12 +19,10 @@ import merge from './utils/merge';
import optionsToUrl from './utils/options-to-url';
import supportsBrotli from './utils/supports-brotli';
import {
AgentByProtocol,
Defaults,
Method,
NormalizedOptions,
Options,
RequestFunction,
URLOrOptions
} from './utils/types';

Expand All @@ -39,8 +38,6 @@ const nonEnumerableProperties: NonEnumerableProperty[] = [
'form'
];

const isAgentByProtocol = (agent: Options['agent']): agent is AgentByProtocol => is.object(agent);

// TODO: `preNormalizeArguments` should merge `options` & `defaults`
export const preNormalizeArguments = (options: Options, defaults?: NormalizedOptions): NormalizedOptions => {
// `options.headers`
Expand Down Expand Up @@ -302,8 +299,8 @@ const withoutBody: ReadonlySet<string> = new Set(['GET', 'HEAD']);

export type NormalizedRequestArguments = Merge<https.RequestOptions, {
body?: stream.Readable;
request: RequestFunction;
url: Pick<NormalizedOptions, 'url'>;
request: NormalizedOptions['request'];
url: NormalizedOptions['url'];
}>;

export const normalizeRequestArguments = async (options: NormalizedOptions): Promise<NormalizedRequestArguments> => {
Expand Down Expand Up @@ -402,7 +399,7 @@ export const normalizeRequestArguments = async (options: NormalizedOptions): Pro

// Normalize request function
if (!is.function_(options.request)) {
options.request = options.url.protocol === 'https:' ? https.request : http.request;
options.request = http2.auto;
}

// UNIX sockets
Expand All @@ -421,10 +418,6 @@ export const normalizeRequestArguments = async (options: NormalizedOptions): Pro
}
}

if (isAgentByProtocol(options.agent)) {
options.agent = options.agent[options.url.protocol.slice(0, -1) as keyof AgentByProtocol] ?? options.agent;
}

if (options.dnsCache) {
options.lookup = options.dnsCache.lookup;
}
Expand Down
40 changes: 38 additions & 2 deletions source/request-as-event-emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import stream = require('stream');
import {promisify} from 'util';
import is from '@sindresorhus/is';
import timer from '@szmarczak/http-timer';
import {Promisable} from 'type-fest';
import {ProxyStream} from './as-stream';
import calculateRetryDelay from './calculate-retry-delay';
import {CacheError, GotError, MaxRedirectsError, RequestError, TimeoutError} from './errors';
Expand Down Expand Up @@ -207,6 +208,35 @@ export default (options: NormalizedOptions): RequestAsEventEmitter => {
...urlToOptions(options.url)
};

const request = httpOptions.request!;
httpOptions.request = (url, options, callback) => {
// @ts-ignore Type URL is not assignable to type URL
const result = request(url, options, callback);

if (is.promise(result)) {
const emitter = new EventEmitter();

// @ts-ignore `cacheable-request` doesn't support async request function
result.once = emitter.once.bind(emitter);

// @ts-ignore This is a TS bug, because `result` is `Promise<http.ClientRequest>`
// eslint-disable-next-line promise/prefer-await-to-then
result.then((request: http.ClientRequest) => {
request.once('abort', () => {
emitter.emit('abort');
});

request.once('error', error => {
emitter.emit('error', error);
});

return request;
});
}

return result;
};

// @ts-ignore ResponseLike missing socket field, should be fixed upstream
const cacheRequest = options.cacheableRequest!(httpOptions, handleResponse);

Expand All @@ -218,12 +248,18 @@ export default (options: NormalizedOptions): RequestAsEventEmitter => {
}
});

cacheRequest.once('request', handleRequest);
cacheRequest.once('request', async (request: Promisable<http.ClientRequest>) => {
try {
handleRequest(await request);
} catch (error) {
emitError(new RequestError(error, options));
}
});
} else {
// Catches errors thrown by calling `requestFn(…)`
try {
// @ts-ignore URLSearchParams does not equal URLSearchParams
handleRequest(httpOptions.request(options.url, httpOptions, handleResponse));
handleRequest(await httpOptions.request(options.url, httpOptions, handleResponse));
} catch (error) {
emitError(new RequestError(error, options));
}
Expand Down
4 changes: 2 additions & 2 deletions source/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export interface RetryOptions extends Partial<DefaultRetryOptions> {
retries?: number;
}

export type RequestFunction = typeof http.request;
export type RequestFunction = (url: string | URL, options: http.RequestOptions, callback?: (res: http.IncomingMessage) => void) => http.ClientRequest | Promise<http.ClientRequest>;

export interface AgentByProtocol {
http?: http.Agent;
Expand Down Expand Up @@ -175,7 +175,7 @@ export interface GotOptions {
throwHttpErrors?: boolean;
cookieJar?: ToughCookieJar | PromiseCookieJar;
ignoreInvalidCookies?: boolean;
request?: RequestFunction;
request?: RequestFunction | typeof http.request;
agent?: http.Agent | https.Agent | boolean | AgentByProtocol;
cache?: string | CacheableRequest.StorageAdapter | false;
headers?: Headers;
Expand Down
4 changes: 3 additions & 1 deletion test/timeout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import EventEmitter = require('events');
import {PassThrough as PassThroughStream} from 'stream';
import stream = require('stream');
import http = require('http');
import https = require('https');
import net = require('net');
import getStream = require('get-stream');
import test from 'ava';
Expand Down Expand Up @@ -285,7 +286,8 @@ test.serial('secureConnect timeout', withServerAndLolex, async (t, _server, got,
return socket;
},
timeout: {secureConnect: 0},
retry: 0
retry: 0,
request: https.request
}).on('request', (request: http.ClientRequest) => {
request.on('socket', () => {
clock.runAll();
Expand Down

0 comments on commit 779b610

Please sign in to comment.