From 0d534ed90f442c0955058155855ba46f311965f2 Mon Sep 17 00:00:00 2001 From: Szymon Marczak <36894700+szmarczak@users.noreply.github.com> Date: Wed, 4 Sep 2019 13:05:04 +0200 Subject: [PATCH] Replace `baseUrl` option with `prefixUrl` (#829) Co-authored-by: Sindre Sorhus --- advanced-creation.md | 6 +++--- migration-guides.md | 3 +-- readme.md | 35 ++++++++++++++----------------- source/normalize-arguments.ts | 39 ++++++++++++++++++++++------------- source/utils/types.ts | 4 ++-- test/arguments.ts | 38 ++++++++++++++-------------------- test/cache.ts | 2 +- test/create.ts | 21 +++++++++++-------- test/helpers/with-server.ts | 4 ++-- test/hooks.ts | 2 +- test/merge-instances.ts | 4 ++-- test/progress.ts | 2 +- test/stream.ts | 2 +- test/unix-socket.ts | 24 +++++++++++++-------- 14 files changed, 96 insertions(+), 90 deletions(-) diff --git a/advanced-creation.md b/advanced-creation.md index fb09aedd3..ef75966a2 100644 --- a/advanced-creation.md +++ b/advanced-creation.md @@ -220,7 +220,7 @@ const noUserAgent = got.extend({ ```js const httpbin = got.extend({ - baseUrl: 'https://httpbin.org/' + prefixUrl: 'https://httpbin.org/' }); ``` @@ -261,10 +261,10 @@ const merged = got.mergeInstances(controlRedirects, limitDownloadUpload, httpbin */ const MEGABYTE = 1048576; - await merged('http://ipv4.download.thinkbroadband.com/5MB.zip', {downloadLimit: MEGABYTE}); + await merged('http://ipv4.download.thinkbroadband.com/5MB.zip', {downloadLimit: MEGABYTE, prefixUrl: ''}); // CancelError: Exceeded the download limit of 1048576 bytes - await merged('https://jigsaw.w3.org/HTTP/300/301.html', {allowedHosts: ['google.com']}); + await merged('https://jigsaw.w3.org/HTTP/300/301.html', {allowedHosts: ['google.com'], prefixUrl: ''}); // CancelError: Redirection to jigsaw.w3.org is not allowed })(); ``` diff --git a/migration-guides.md b/migration-guides.md index 99fed8ee3..b8e88796a 100644 --- a/migration-guides.md +++ b/migration-guides.md @@ -68,8 +68,6 @@ The [`timeout` option](https://github.com/sindresorhus/got#timeout) has some ext The [`searchParams` option](https://github.com/sindresorhus/got#searchParams) is always serialized using [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) unless it's a `string`. -The [`baseUrl` option](https://github.com/sindresorhus/got#baseurl) appends the ending slash if it's not present. - There's no `maxRedirects` option. It's always set to `10`. To use streams, just call `got.stream(url, options)` or `got(url, {stream: true, ...}`). @@ -82,6 +80,7 @@ To use streams, just call `got.stream(url, options)` or `got(url, {stream: true, - No `agentClass`/`agentOptions`/`pool` option. - No `forever` option. You need to use [forever-agent](https://github.com/request/forever-agent). - No `proxy` option. You need to [pass a custom agent](readme.md#proxies). +- No `baseUrl` option. Instead, there is `prefixUrl` which appends a trailing slash if not present. It will be always prepended unless `url` is an instance of URL. - No `removeRefererHeader` option. You can remove the referer header in a [`beforeRequest` hook](https://github.com/sindresorhus/got#hooksbeforeRequest): ```js diff --git a/readme.md b/readme.md index 615bf0ce4..619c5c0c2 100644 --- a/readme.md +++ b/readme.md @@ -143,28 +143,23 @@ Type: `object` Any of the [`https.request`](https://nodejs.org/api/https.html#https_https_request_options_callback) options. -###### baseUrl +###### prefixUrl -Type: `string | object` - -When specified, `url` will be prepended by `baseUrl`.
-If you specify an absolute URL, it will skip the `baseUrl`. +Type: `string | URL` -Very useful when used with `got.extend()` to create niche-specific Got instances. +When specified, `prefixUrl` will be prepended to `url`. The prefix can be any valid URL, either relative or absolute. A trailing slash `/` is optional, one will be added automatically, if needed, when joining `prefixUrl` and `url`. The `url` argument cannot start with a `/` when using this option. -Can be a string or a [WHATWG `URL`](https://nodejs.org/api/url.html#url_class_url). +Useful when used with `got.extend()` to create niche-specific Got-instances. -Slash at the end of `baseUrl` and at the beginning of the `url` argument is optional: +**Note:** `prefixUrl` will be ignored if the `url` argument is a URL instance. ```js -await got('hello', {baseUrl: 'https://example.com/v1'}); -//=> 'https://example.com/v1/hello' - -await got('/hello', {baseUrl: 'https://example.com/v1/'}); -//=> 'https://example.com/v1/hello' +const got = require('got'); -await got('/hello', {baseUrl: 'https://example.com/v1'}); -//=> 'https://example.com/v1/hello' +(async () => { + await ky('unicorn', {prefixUrl: 'https://cats.com'}); + //=> 'https://cats.com/unicorn' +})(); ``` ###### headers @@ -746,7 +741,7 @@ Configure a new `got` instance with default `options`. The `options` are merged ```js const client = got.extend({ - baseUrl: 'https://example.com', + prefixUrl: 'https://example.com', headers: { 'x-unicorn': 'rainbow' } @@ -764,7 +759,7 @@ client.get('/demo'); ```js (async () => { const client = got.extend({ - baseUrl: 'httpbin.org', + prefixUrl: 'httpbin.org', headers: { 'x-foo': 'bar' } @@ -1059,7 +1054,7 @@ const chain = new AWS.CredentialProviderChain(); // Create a Got instance to use relative paths and signed requests const awsClient = got.extend({ - baseUrl: 'https://.execute-api..amazonaws.com//', + prefixUrl: 'https://.execute-api..amazonaws.com//', hooks: { beforeRequest: [ async options => { @@ -1184,7 +1179,7 @@ Bear in mind; if you send an `if-modified-since` header and receive a `304 Not M ### Custom endpoints -Use `got.extend()` to make it nicer to work with REST APIs. Especially if you use the `baseUrl` option. +Use `got.extend()` to make it nicer to work with REST APIs. Especially if you use the `prefixUrl` option. **Note:** Not to be confused with [`got.create()`](advanced-creation.md), which has no defaults. @@ -1193,7 +1188,7 @@ const got = require('got'); const pkg = require('./package.json'); const custom = got.extend({ - baseUrl: 'example.com', + prefixUrl: 'example.com', responseType: 'json', headers: { 'user-agent': `my-package/${pkg.version} (https://github.com/username/my-package)` diff --git a/source/normalize-arguments.ts b/source/normalize-arguments.ts index bec64f170..e0e74944a 100644 --- a/source/normalize-arguments.ts +++ b/source/normalize-arguments.ts @@ -44,8 +44,12 @@ export const preNormalizeArguments = (options: Options, defaults?: Options): Nor options.headers = lowercaseKeys(options.headers); } - if (options.baseUrl && !options.baseUrl.toString().endsWith('/')) { - options.baseUrl += '/'; + if (options.prefixUrl) { + options.prefixUrl = options.prefixUrl.toString(); + + if (!options.prefixUrl.toString().endsWith('/')) { + options.prefixUrl += '/'; + } } if (is.nullOrUndefined(options.hooks)) { @@ -125,7 +129,7 @@ export const normalizeArguments = (url: URLOrOptions, options: NormalizedOptions let urlArgument: URLArgument; if (is.plainObject(url)) { options = {...url, ...options}; - urlArgument = options.url || ''; + urlArgument = options.url || {}; delete options.url; } else { urlArgument = url; @@ -143,17 +147,24 @@ export const normalizeArguments = (url: URLOrOptions, options: NormalizedOptions let urlObj: https.RequestOptions | URLOptions; if (is.string(urlArgument)) { - if (options.baseUrl) { - if (urlArgument.startsWith('/')) { - urlArgument = urlArgument.slice(1); - } - } else { - urlArgument = urlArgument.replace(/^unix:/, 'http://$&'); + if (options.prefixUrl && urlArgument.startsWith('/')) { + throw new Error('`url` must not begin with a slash when using `prefixUrl`'); } - urlObj = urlArgument || options.baseUrl ? urlToOptions(new URL(urlArgument, options.baseUrl)) : {}; + if (options.prefixUrl) { + urlArgument = options.prefixUrl + urlArgument; + } + + urlArgument = urlArgument.replace(/^unix:/, 'http://$&'); + + urlObj = urlToOptions(new URL(urlArgument)); } else if (is.urlInstance(urlArgument)) { urlObj = urlToOptions(urlArgument); + } else if (options.prefixUrl) { + urlObj = { + ...urlToOptions(new URL(options.prefixUrl)), + ...urlArgument + }; } else { urlObj = urlArgument; } @@ -169,12 +180,12 @@ export const normalizeArguments = (url: URLOrOptions, options: NormalizedOptions } } - const {baseUrl} = options; - Object.defineProperty(options, 'baseUrl', { + const {prefixUrl} = options; + Object.defineProperty(options, 'prefixUrl', { set: () => { - throw new Error('Failed to set baseUrl. Options are normalized already.'); + throw new Error('Failed to set prefixUrl. Options are normalized already.'); }, - get: () => baseUrl + get: () => prefixUrl }); let {searchParams} = options; diff --git a/source/utils/types.ts b/source/utils/types.ts index 5ba70f6ca..b07b55f05 100644 --- a/source/utils/types.ts +++ b/source/utils/types.ts @@ -134,7 +134,7 @@ export interface Options extends Omit | Keyv | false; url?: URL | string; @@ -151,7 +151,7 @@ export interface NormalizedOptions extends Omit, 'timeout' | ' gotTimeout: Required; retry: NormalizedRetryOptions; lookup?: CacheableLookup['lookup']; - readonly baseUrl: string; + readonly prefixUrl: string; path: string; hostname: string; host: string; diff --git a/test/arguments.ts b/test/arguments.ts index d52047828..1db6325c5 100644 --- a/test/arguments.ts +++ b/test/arguments.ts @@ -39,9 +39,9 @@ test('throws an error if the protocol is not specified', async t => { test('string url with searchParams is preserved', withServer, async (t, server, got) => { server.get('/', echoUrl); - const path = '/?test=http://example.com?foo=bar'; + const path = '?test=http://example.com?foo=bar'; const {body} = await got(path); - t.is(body, path); + t.is(body, `/${path}`); }); test('options are optional', withServer, async (t, server, got) => { @@ -161,7 +161,7 @@ test('accepts `url` as an option', withServer, async (t, server, got) => { await t.notThrowsAsync(got({url: 'test'})); }); -test('can omit `url` option if using `baseUrl`', withServer, async (t, server, got) => { +test('can omit `url` option if using `prefixUrl`', withServer, async (t, server, got) => { server.get('/', echoUrl); await t.notThrowsAsync(got({})); @@ -206,49 +206,41 @@ test('allows extra keys in `options.hooks`', withServer, async (t, server, got) await t.notThrowsAsync(got('test', {hooks: {extra: {}}})); }); -test('`baseUrl` option works', withServer, async (t, server, got) => { +test('`prefixUrl` option works', withServer, async (t, server, got) => { server.get('/test/foobar', echoUrl); - const instanceA = got.extend({baseUrl: `${server.url}/test`}); - const {body} = await instanceA('/foobar'); - t.is(body, '/test/foobar'); -}); - -test('accepts WHATWG URL as the `baseUrl` option', withServer, async (t, server, got) => { - server.get('/test/foobar', echoUrl); - - const instanceA = got.extend({baseUrl: new URL(`${server.url}/test`)}); - const {body} = await instanceA('/foobar'); + const instanceA = got.extend({prefixUrl: `${server.url}/test`}); + const {body} = await instanceA('foobar'); t.is(body, '/test/foobar'); }); -test('backslash in the end of `baseUrl` option is optional', withServer, async (t, server) => { +test('accepts WHATWG URL as the `prefixUrl` option', withServer, async (t, server, got) => { server.get('/test/foobar', echoUrl); - const instanceA = got.extend({baseUrl: `${server.url}/test/`}); - const {body} = await instanceA('/foobar'); + const instanceA = got.extend({prefixUrl: new URL(`${server.url}/test`)}); + const {body} = await instanceA('foobar'); t.is(body, '/test/foobar'); }); -test('backslash in the beginning of `url` is optional when using `baseUrl` option', withServer, async (t, server) => { +test('backslash in the end of `prefixUrl` option is optional', withServer, async (t, server) => { server.get('/test/foobar', echoUrl); - const instanceA = got.extend({baseUrl: `${server.url}/test`}); + const instanceA = got.extend({prefixUrl: `${server.url}/test/`}); const {body} = await instanceA('foobar'); t.is(body, '/test/foobar'); }); -test('throws when trying to modify `baseUrl` after options got normalized', async t => { +test('throws when trying to modify `prefixUrl` after options got normalized', async t => { const instanceA = got.create({ methods: [], - options: {baseUrl: 'https://example.com'}, + options: {prefixUrl: 'https://example.com'}, handler: (options, next) => { - options.baseUrl = 'https://google.com'; + options.prefixUrl = 'https://google.com'; return next(options); } }); - await t.throwsAsync(instanceA('/'), 'Failed to set baseUrl. Options are normalized already.'); + await t.throwsAsync(instanceA(''), 'Failed to set prefixUrl. Options are normalized already.'); }); test('throws if the `searchParams` value is invalid', async t => { diff --git a/test/cache.ts b/test/cache.ts index cb4498219..228b50d93 100644 --- a/test/cache.ts +++ b/test/cache.ts @@ -134,7 +134,7 @@ test('doesn\'t cache response when received HTTP error', withServer, async (t, s test('DNS cache works', withServer, async (t, _server, got) => { const map = new Map(); - await t.notThrowsAsync(got('https://example.com', {dnsCache: map})); + await t.notThrowsAsync(got('https://example.com', {dnsCache: map, prefixUrl: ''})); t.is(map.size, 1); }); diff --git a/test/create.ts b/test/create.ts index 06fceea9e..8804985f7 100644 --- a/test/create.ts +++ b/test/create.ts @@ -95,9 +95,12 @@ test('extend keeps the old value if the new one is undefined', t => { }); test('extend merges URL instances', t => { - const a = got.extend({baseUrl: new URL('https://example.com')}); - const b = a.extend({baseUrl: '/foo'}); - t.is(b.defaults.options.baseUrl.toString(), 'https://example.com/foo/'); + // @ts-ignore Custom instance. + const a = got.extend({custom: new URL('https://example.com')}); + // @ts-ignore Custom instance. + const b = a.extend({custom: '/foo'}); + // @ts-ignore Custom instance. + t.is(b.defaults.options.custom.toString(), 'https://example.com/foo'); }); test('create', withServer, async (t, server) => { @@ -128,8 +131,8 @@ test('hooks are merged on got.extend()', t => { test('custom endpoint with custom headers (extend)', withServer, async (t, server) => { server.all('/', echoHeaders); - const instance = got.extend({headers: {unicorn: 'rainbow'}, baseUrl: server.url}); - const headers = await instance('/').json(); + const instance = got.extend({headers: {unicorn: 'rainbow'}, prefixUrl: server.url}); + const headers = await instance('').json(); t.is(headers.unicorn, 'rainbow'); t.not(headers['user-agent'], undefined); }); @@ -138,7 +141,7 @@ test('no tampering with defaults', t => { const instance = got.create({ handler: got.defaults.handler, options: got.mergeOptions(got.defaults.options, { - baseUrl: 'example/' + prefixUrl: 'example/' }) }); @@ -149,11 +152,11 @@ test('no tampering with defaults', t => { // Tamper Time t.throws(() => { - instance.defaults.options.baseUrl = 'http://google.com'; + instance.defaults.options.prefixUrl = 'http://google.com'; }); - t.is(instance.defaults.options.baseUrl, 'example/'); - t.is(instance2.defaults.options.baseUrl, 'example/'); + t.is(instance.defaults.options.prefixUrl, 'example/'); + t.is(instance2.defaults.options.prefixUrl, 'example/'); }); test('defaults can be mutable', t => { diff --git a/test/helpers/with-server.ts b/test/helpers/with-server.ts index 4fab383db..9e5f931b9 100644 --- a/test/helpers/with-server.ts +++ b/test/helpers/with-server.ts @@ -13,9 +13,9 @@ export default async (t, run) => { }); // @ts-ignore Ignore errors for extending got, for the tests - const preparedGot = got.extend({baseUrl: server.url, avaTest: t.title}); + const preparedGot = got.extend({prefixUrl: server.url, avaTest: t.title}); // @ts-ignore Ignore errors for extending got, for the tests - preparedGot.secure = got.extend({baseUrl: server.sslUrl, avaTest: t.title}); + preparedGot.secure = got.extend({prefixUrl: server.sslUrl, avaTest: t.title}); server.hostname = (new URL(server.url)).hostname; server.sslHostname = (new URL(server.sslUrl)).hostname; diff --git a/test/hooks.ts b/test/hooks.ts index c623e9218..6699a9655 100644 --- a/test/hooks.ts +++ b/test/hooks.ts @@ -487,7 +487,7 @@ test('beforeError allows modifications', async t => { test('does not break on `afterResponse` hook with JSON mode', withServer, async (t, server, got) => { server.get('/foobar', echoHeaders); - await t.notThrowsAsync(got('/', { + await t.notThrowsAsync(got('', { hooks: { afterResponse: [ (response, retryWithMergedOptions) => { diff --git a/test/merge-instances.ts b/test/merge-instances.ts index 923a86ce5..f8bc02940 100644 --- a/test/merge-instances.ts +++ b/test/merge-instances.ts @@ -13,10 +13,10 @@ test('merging instances', withServer, async (t, server) => { server.get('/', echoHeaders); const instanceA = got.extend({headers: {unicorn: 'rainbow'}}); - const instanceB = got.extend({baseUrl: server.url}); + const instanceB = got.extend({prefixUrl: server.url}); const merged = got.mergeInstances(instanceA, instanceB); - const headers = await merged('/').json(); + const headers = await merged('').json(); t.is(headers.unicorn, 'rainbow'); t.not(headers['user-agent'], undefined); }); diff --git a/test/progress.ts b/test/progress.ts index 2a0006cfe..676ccda8a 100644 --- a/test/progress.ts +++ b/test/progress.ts @@ -72,7 +72,7 @@ test('download progress - missing total size', withServer, async (t, server, got const events = []; - await got('/').on('downloadProgress', event => events.push(event)); + await got('').on('downloadProgress', event => events.push(event)); checkEvents(t, events); }); diff --git a/test/stream.ts b/test/stream.ts index 635fcb2c2..206d26c54 100644 --- a/test/stream.ts +++ b/test/stream.ts @@ -106,7 +106,7 @@ test('has error event', withServer, async (t, server, got) => { }); test('has error event #2', withServer, async (t, _server, got) => { - const stream = got.stream('http://doesntexist'); + const stream = got.stream('http://doesntexist', {prefixUrl: null}); await t.throwsAsync(pEvent(stream, 'response'), {code: 'ENOTFOUND'}); }); diff --git a/test/unix-socket.ts b/test/unix-socket.ts index 9ae89d4c8..b31d0d278 100644 --- a/test/unix-socket.ts +++ b/test/unix-socket.ts @@ -3,29 +3,27 @@ import test from 'ava'; import got from '../source'; import {withSocketServer} from './helpers/with-server'; +const okHandler = (_request, response) => { + response.end('ok'); +}; + if (process.platform !== 'win32') { test('works', withSocketServer, async (t, server) => { - server.on('/', (_request, response) => { - response.end('ok'); - }); + server.on('/', okHandler); const url = format('http://unix:%s:%s', server.socketPath, '/'); t.is((await got(url)).body, 'ok'); }); test('protocol-less works', withSocketServer, async (t, server) => { - server.on('/', (_request, response) => { - response.end('ok'); - }); + server.on('/', okHandler); const url = format('unix:%s:%s', server.socketPath, '/'); t.is((await got(url)).body, 'ok'); }); test('address with : works', withSocketServer, async (t, server) => { - server.on('/foo:bar', (_request, response) => { - response.end('ok'); - }); + server.on('/foo:bar', okHandler); const url = format('unix:%s:%s', server.socketPath, '/foo:bar'); t.is((await got(url)).body, 'ok'); @@ -37,4 +35,12 @@ if (process.platform !== 'win32') { code: 'ENOTFOUND' }); }); + + test('works when extending instances', withSocketServer, async (t, server) => { + server.on('/', okHandler); + + const url = format('unix:%s:%s', server.socketPath, '/'); + const instance = got.extend({prefixUrl: url}); + t.is((await instance('')).body, 'ok'); + }); }