Skip to content

Commit c22c63c

Browse files
committed
feat(core): added support for absolute redirections
Also fixed async ref issue with http2 relative redirections fix #107
1 parent fbc90bd commit c22c63c

11 files changed

Lines changed: 243 additions & 96 deletions

File tree

lib/context.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,21 @@ import {
1919
HttpProtocols,
2020
parsePerOrigin,
2121
PerOrigin,
22+
RetryError,
23+
} from "./core";
24+
import {
2225
SimpleSession,
2326
SimpleSessionHttp1,
2427
SimpleSessionHttp2,
25-
RetryError,
26-
} from "./core";
28+
} from "./simple-session";
2729
import { fetch as fetchHttp1 } from "./fetch-http1";
2830
import { fetch as fetchHttp2 } from "./fetch-http2";
2931
import { version } from "./generated/version";
3032
import { Request } from "./request";
3133
import { Response } from "./response";
3234
import { parseInput } from "./utils";
3335
import OriginCache from "./origin-cache";
36+
import { FetchExtra } from "./fetch-common";
3437

3538

3639
function makeDefaultUserAgent( ): string
@@ -174,7 +177,7 @@ export class Context
174177

175178
public async fetch( input: string | Request, init?: Partial< FetchInit > )
176179
{
177-
return this.retryFetch( input, init, 0 );
180+
return this.retryFetch( input, init );
178181
}
179182

180183
public async disconnect( url: string )
@@ -201,25 +204,27 @@ export class Context
201204
private async retryFetch(
202205
input: string | Request,
203206
init: Partial< FetchInit > | undefined,
204-
count: number
207+
extra?: FetchExtra,
208+
count: number = 0
205209
)
206210
: Promise< Response >
207211
{
208212
++count;
209213

210-
return this.retryableFetch( input, init )
214+
return this.retryableFetch( input, init, extra )
211215
.catch( specific( RetryError, err =>
212216
{
213217
// TODO: Implement a more robust retry logic
214218
if ( count > 10 )
215219
throw err;
216-
return this.retryFetch( input, init, count );
220+
return this.retryFetch( input, init, extra, count );
217221
} ) );
218222
}
219223

220224
private async retryableFetch(
221225
input: string | Request,
222-
init?: Partial< FetchInit >
226+
init?: Partial< FetchInit >,
227+
extra?: FetchExtra
223228
)
224229
: Promise< Response >
225230
{
@@ -243,6 +248,7 @@ export class Context
243248
cookieJar: this._cookieJar,
244249
protocol,
245250
userAgent: ( ) => this.userAgent( origin ),
251+
newFetch: this.retryFetch.bind( this ),
246252
} );
247253

248254
const doFetchHttp1 = ( socket: Socket, cleanup: ( ) => void ) =>
@@ -259,7 +265,7 @@ export class Context
259265
} ),
260266
...makeSimpleSession( "http1" ),
261267
};
262-
return fetchHttp1( sessionGetterHttp1, request, init );
268+
return fetchHttp1( sessionGetterHttp1, request, init, extra );
263269
};
264270

265271
const doFetchHttp2 = async ( cacheableSession: CacheableH2Session ) =>
@@ -273,7 +279,9 @@ export class Context
273279
get: ( ) => ( { session, cleanup } ),
274280
...makeSimpleSession( "http2" ),
275281
};
276-
return await fetchHttp2( sessionGetterHttp2, request, init );
282+
return await fetchHttp2(
283+
sessionGetterHttp2, request, init, extra
284+
);
277285
}
278286
catch ( err )
279287
{

lib/core.ts

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
import { ClientRequest } from "http";
2-
import { ClientHttp2Session } from "http2";
3-
41
import { AbortSignal } from "./abort";
5-
import { CookieJar } from "./cookie-jar";
62
import { Headers, RawHeaders } from "./headers";
73

84

@@ -241,37 +237,3 @@ export interface Http1Options
241237
maxFreeSockets: number | PerOrigin< number >;
242238
timeout: void | number | PerOrigin< void | number >;
243239
}
244-
245-
export interface SimpleSession
246-
{
247-
protocol: HttpProtocols;
248-
249-
cookieJar: CookieJar;
250-
251-
userAgent( ): string;
252-
accept( ): string;
253-
254-
contentDecoders( ): ReadonlyArray< Decoder >;
255-
}
256-
257-
export interface SimpleSessionHttp1Request
258-
{
259-
req: ClientRequest;
260-
cleanup: ( ) => void;
261-
}
262-
263-
export interface SimpleSessionHttp2Session
264-
{
265-
session: Promise< ClientHttp2Session >;
266-
cleanup: ( ) => void;
267-
}
268-
269-
export interface SimpleSessionHttp1 extends SimpleSession
270-
{
271-
get( url: string ): SimpleSessionHttp1Request;
272-
}
273-
274-
export interface SimpleSessionHttp2 extends SimpleSession
275-
{
276-
get( ): SimpleSessionHttp2Session;
277-
}

lib/fetch-common.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,8 @@ import { URL } from "url";
44
import { Finally, rethrow } from "already";
55

66
import { BodyInspector } from "./body";
7-
import {
8-
AbortError,
9-
Decoder,
10-
FetchInit,
11-
SimpleSession,
12-
TimeoutError,
13-
} from "./core";
7+
import { AbortError, Decoder, FetchInit, TimeoutError } from "./core";
8+
import { SimpleSession } from "./simple-session";
149
import { Headers, RawHeaders } from "./headers";
1510
import { Request } from "./request";
1611
import { Response } from "./response";

lib/fetch-http1.ts

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@ import { Socket } from "net";
55
import { syncGuard } from "callguard";
66

77
import { AbortController } from "./abort";
8-
import {
9-
FetchInit,
10-
SimpleSessionHttp1,
11-
} from "./core";
8+
import { FetchInit } from "./core";
9+
import { SimpleSessionHttp1 } from "./simple-session";
1210
import {
1311
FetchExtra,
1412
handleSignalAndTimeout,
@@ -23,7 +21,13 @@ import {
2321
import { GuardedHeaders } from "./headers";
2422
import { Request } from "./request";
2523
import { Response, StreamResponse } from "./response";
26-
import { arrayify, isRedirectStatus, parseLocation, pipeline } from "./utils";
24+
import {
25+
arrayify,
26+
isRedirectStatus,
27+
parseLocation,
28+
pipeline,
29+
ParsedLocation,
30+
} from "./utils";
2731

2832
const {
2933
// Responses, these are the same in HTTP/1.1 and HTTP/2
@@ -215,8 +219,11 @@ export async function fetchImpl(
215219
)
216220
);
217221

222+
const { url: locationUrl, isRelative } =
223+
location as ParsedLocation;
224+
218225
if ( redirect === "error" )
219-
return reject( makeRedirectionError( location ) );
226+
return reject( makeRedirectionError( locationUrl ) );
220227

221228
// redirect is 'follow'
222229

@@ -225,24 +232,36 @@ export async function fetchImpl(
225232
// body). The concept is fundementally broken anyway...
226233
if ( !endStream )
227234
return reject(
228-
makeRedirectionMethodError( location, method )
235+
makeRedirectionMethodError( locationUrl, method )
229236
);
230237

231-
if ( !location )
232-
return reject( makeIllegalRedirectError( ) );
233-
234238
res.destroy( );
235-
resolve(
236-
fetchImpl(
237-
session,
238-
request.clone( location ),
239-
{ signal, onTrailers },
239+
240+
if ( isRelative )
241+
{
242+
resolve(
243+
fetchImpl(
244+
session,
245+
request.clone( locationUrl ),
246+
{ signal, onTrailers },
247+
{
248+
redirected: redirected.concat( url ),
249+
timeoutAt,
250+
}
251+
)
252+
);
253+
}
254+
else
255+
{
256+
resolve( session.newFetch(
257+
request.clone( locationUrl ),
258+
init,
240259
{
241-
redirected: redirected.concat( url ),
242260
timeoutAt,
261+
redirected: redirected.concat( url ),
243262
}
244-
)
245-
);
263+
) );
264+
}
246265
} ) );
247266
} );
248267

@@ -274,13 +293,15 @@ export async function fetchImpl(
274293
export function fetch(
275294
session: SimpleSessionHttp1,
276295
input: Request,
277-
init?: Partial< FetchInit >
296+
init?: Partial< FetchInit >,
297+
extra?: FetchExtra
278298
)
279299
: Promise< Response >
280300
{
281-
const timeoutAt = void 0;
282-
283-
const extra = { timeoutAt, redirected: [ ] };
301+
extra = {
302+
timeoutAt: extra?.timeoutAt,
303+
redirected: extra?.redirected ?? [ ],
304+
};
284305

285306
return fetchImpl( session, input, init, extra );
286307
}

lib/fetch-http2.ts

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import {
1111
AbortError,
1212
RetryError,
1313
FetchInit,
14-
SimpleSessionHttp2,
1514
} from "./core";
15+
import { SimpleSessionHttp2 } from "./simple-session";
1616
import {
1717
FetchExtra,
1818
handleSignalAndTimeout,
@@ -27,7 +27,13 @@ import {
2727
import { GuardedHeaders } from "./headers";
2828
import { Request } from "./request";
2929
import { Response, StreamResponse } from "./response";
30-
import { arrayify, isRedirectStatus, parseLocation, pipeline } from "./utils";
30+
import {
31+
arrayify,
32+
isRedirectStatus,
33+
parseLocation,
34+
pipeline,
35+
ParsedLocation,
36+
} from "./utils";
3137
import { hasGotGoaway } from "./utils-http2";
3238

3339
const {
@@ -121,6 +127,7 @@ async function fetchImpl(
121127
};
122128

123129
let stream: ClientHttp2Stream;
130+
let shouldCleanupSocket = true;
124131
try
125132
{
126133
stream = h2session.request( headersToSend, { endStream } );
@@ -177,7 +184,8 @@ async function fetchImpl(
177184

178185
stream.on( "close", guard( ( ) =>
179186
{
180-
socketCleanup( );
187+
if ( shouldCleanupSocket )
188+
socketCleanup( );
181189

182190
// We'll get an 'error' event if there actually is an
183191
// error, but not if we got NGHTTP2_NO_ERROR.
@@ -313,8 +321,11 @@ async function fetchImpl(
313321
)
314322
);
315323

324+
const { url: locationUrl, isRelative } =
325+
location as ParsedLocation;
326+
316327
if ( redirect === "error" )
317-
return reject( makeRedirectionError( location ) );
328+
return reject( makeRedirectionError( locationUrl ) );
318329

319330
// redirect is 'follow'
320331

@@ -323,25 +334,38 @@ async function fetchImpl(
323334
// body). The concept is fundementally broken anyway...
324335
if ( !endStream )
325336
return reject(
326-
makeRedirectionMethodError( location, method )
337+
makeRedirectionMethodError( locationUrl, method )
327338
);
328339

329340
if ( !location )
330341
return reject( makeIllegalRedirectError( ) );
331342

332-
stream.destroy( );
333-
resolve(
334-
fetchImpl(
343+
if ( isRelative )
344+
{
345+
shouldCleanupSocket = false;
346+
stream.destroy( );
347+
resolve( fetchImpl(
335348
session,
336-
request.clone( location ),
337-
{ signal, onTrailers },
349+
request.clone( locationUrl ),
350+
init,
338351
{
339352
raceConditionedGoaway,
340353
redirected: redirected.concat( url ),
341354
timeoutAt,
342355
}
343-
)
344-
);
356+
) );
357+
}
358+
else
359+
{
360+
resolve( session.newFetch(
361+
request.clone( locationUrl ),
362+
init,
363+
{
364+
timeoutAt,
365+
redirected: redirected.concat( url ),
366+
}
367+
) );
368+
}
345369
} ) );
346370
} );
347371

@@ -371,14 +395,16 @@ async function fetchImpl(
371395
export function fetch(
372396
session: SimpleSessionHttp2,
373397
input: Request,
374-
init?: Partial< FetchInit >
398+
init?: Partial< FetchInit >,
399+
extra?: FetchExtra
375400
)
376401
: Promise< Response >
377402
{
378-
const timeoutAt = void 0;
379-
380-
const raceConditionedGoaway = new Set< string>( );
381-
const extra = { timeoutAt, redirected: [ ], raceConditionedGoaway };
403+
const http2Extra: FetchExtraHttp2 = {
404+
timeoutAt: extra?.timeoutAt,
405+
redirected: extra?.redirected ?? [ ],
406+
raceConditionedGoaway: new Set< string>( ),
407+
};
382408

383-
return fetchImpl( session, input, init, extra );
409+
return fetchImpl( session, input, init, http2Extra );
384410
}

0 commit comments

Comments
 (0)