@crawlee/http 4.0.0-beta.80 → 4.0.0-beta.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,8 @@
1
1
  import type { BasicCrawlerOptions, CrawlingContext, ErrorHandler, GetUserDataFromRequest, Request as CrawleeRequest, RequestHandler, RequireContextPipeline, RouterHandler, RouterRoutes, RouteSchemas, RoutesFromSchemas } from '@crawlee/basic';
2
2
  import { BasicCrawler, ContextPipeline } from '@crawlee/basic';
3
3
  import { type LoadedRequest } from '@crawlee/core';
4
- import type { Awaitable, Dictionary, ISession } from '@crawlee/types';
4
+ import type { Awaitable, Dictionary } from '@crawlee/types';
5
5
  import { type CheerioRoot } from '@crawlee/utils';
6
- import type { RequestLike, ResponseLike } from 'content-type';
7
6
  import type { JsonValue } from 'type-fest';
8
7
  export type HttpErrorHandler<UserData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
9
8
  JSONData extends JsonValue = any, // with default to Dictionary we cant use a typed router in untyped crawler
@@ -24,6 +23,11 @@ export interface HttpCrawlerOptions<Context extends InternalHttpCrawlingContext
24
23
  *
25
24
  * A hook may optionally return a partial object whose properties are merged into the crawling context,
26
25
  * allowing the hook to override context members for subsequent hooks and pipeline stages.
26
+ *
27
+ * The context is built up in the following order: base context (`request`, `session`, helpers, ...) ->
28
+ * `extendContext` -> `preNavigationHooks` -> navigation -> `postNavigationHooks` -> `requestHandler`.
29
+ * This means the members added by `extendContext` are already available here, but navigation-dependent
30
+ * members (e.g. `response`, `body`, `$`) are not.
27
31
  * Example:
28
32
  * ```
29
33
  * preNavigationHooks: [
@@ -33,7 +37,7 @@ export interface HttpCrawlerOptions<Context extends InternalHttpCrawlingContext
33
37
  * ]
34
38
  * ```
35
39
  */
36
- preNavigationHooks?: InternalHttpHook<CrawlingContext>[];
40
+ preNavigationHooks?: InternalHttpHook<CrawlingContext, ContextExtension>[];
37
41
  /**
38
42
  * Async functions that are sequentially evaluated after the navigation. Good for checking if the navigation was successful.
39
43
  * The function accepts `crawlingContext` as the only parameter.
@@ -51,7 +55,7 @@ export interface HttpCrawlerOptions<Context extends InternalHttpCrawlingContext
51
55
  * ]
52
56
  * ```
53
57
  */
54
- postNavigationHooks?: ((crawlingContext: CrawlingContextWithResponse) => Awaitable<void | Partial<CrawlingContextWithResponse>>)[];
58
+ postNavigationHooks?: ((crawlingContext: CrawlingContextWithResponse & ContextExtension) => Awaitable<void | Partial<CrawlingContextWithResponse>>)[];
55
59
  /**
56
60
  * An array of [MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types)
57
61
  * you want the crawler to load and process. By default, only `text/html`, `application/xhtml+xml`, `text/xml`, `application/xml`,
@@ -91,7 +95,7 @@ export interface HttpCrawlerOptions<Context extends InternalHttpCrawlingContext
91
95
  /**
92
96
  * @internal
93
97
  */
94
- export type InternalHttpHook<Context> = (crawlingContext: Context) => Awaitable<void | Partial<Context>>;
98
+ export type InternalHttpHook<Context, ContextExtension = {}> = (crawlingContext: Context & ContextExtension) => Awaitable<void | Partial<Context>>;
95
99
  export type HttpHook<UserData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
96
100
  JSONData extends JsonValue = any> = InternalHttpHook<HttpCrawlingContext<UserData, JSONData>>;
97
101
  interface CrawlingContextWithResponse<UserData extends Dictionary = any> extends CrawlingContext<UserData> {
@@ -228,14 +232,14 @@ JSONData extends JsonValue = any> = RequestHandler<HttpCrawlingContext<UserData,
228
232
  * @category Crawlers
229
233
  */
230
234
  export declare class HttpCrawler<Context extends InternalHttpCrawlingContext<any, any> = InternalHttpCrawlingContext, ContextExtension = Dictionary<never>, ExtendedContext extends Context = Context & ContextExtension> extends BasicCrawler<Context, ContextExtension, ExtendedContext> {
231
- protected preNavigationHooks: InternalHttpHook<CrawlingContext>[];
232
- protected postNavigationHooks: ((crawlingContext: CrawlingContextWithResponse) => Awaitable<void | Partial<CrawlingContextWithResponse>>)[];
233
- protected saveResponseCookies: boolean;
234
- protected navigationTimeoutMillis: number;
235
- protected ignoreSslErrors: boolean;
236
- protected suggestResponseEncoding?: string;
237
- protected forceResponseEncoding?: string;
238
- protected readonly supportedMimeTypes: Set<string>;
235
+ private preNavigationHooks;
236
+ private postNavigationHooks;
237
+ private saveResponseCookies;
238
+ private navigationTimeoutMillis;
239
+ private ignoreSslErrors;
240
+ private suggestResponseEncoding?;
241
+ private forceResponseEncoding?;
242
+ private readonly supportedMimeTypes;
239
243
  protected static optionsShape: {
240
244
  // @ts-ignore optional peer dependency or compatibility with es2022
241
245
  navigationTimeoutSecs: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
@@ -337,64 +341,30 @@ export declare class HttpCrawler<Context extends InternalHttpCrawlingContext<any
337
341
  * on the request such as only downloading the request body if the
338
342
  * received content type matches text/html, application/xml, application/xhtml+xml.
339
343
  */
340
- protected _requestFunction({ request, session, proxyUrl }: RequestFunctionOptions): Promise<Response>;
344
+ private requestFunction;
341
345
  /**
342
346
  * Encodes and parses response according to the provided content type
343
347
  */
344
- protected _parseResponse(request: CrawleeRequest, response: Response): Promise<{
345
- response: Response;
346
- contentType: {
347
- type: string;
348
- encoding: BufferEncoding;
349
- };
350
- body: string;
351
- } | {
352
- body: Buffer<ArrayBuffer>;
353
- response: Response;
354
- contentType: {
355
- type: string;
356
- encoding: BufferEncoding;
357
- };
358
- }>;
348
+ private parseResponse;
359
349
  /**
360
350
  * Combines the provided `requestOptions` with mandatory (non-overridable) values.
361
351
  */
362
- protected _getRequestOptions(request: CrawleeRequest, session: ISession, proxyUrl?: string): {
363
- url: string;
364
- // @ts-ignore optional peer dependency or compatibility with es2022
365
- method: import("@crawlee/types").AllowedHttpMethods;
366
- proxyUrl: string | undefined;
367
- timeout: number;
368
- sessionToken: ISession;
369
- headers: Record<string, string> | undefined;
370
- https: {
371
- rejectUnauthorized: boolean;
372
- };
373
- body: string | undefined;
374
- };
375
- protected _encodeResponse(request: CrawleeRequest, response: Response, encoding: BufferEncoding): {
376
- encoding: BufferEncoding;
377
- response: Response;
378
- };
352
+ private getRequestOptions;
353
+ private encodeResponse;
379
354
  /**
380
355
  * Checks and extends supported mime types
381
356
  */
382
- protected _extendSupportedMimeTypes(additionalMimeTypes: (string | RequestLike | ResponseLike)[]): void;
357
+ private extendSupportedMimeTypes;
383
358
  /**
384
359
  * Handles timeout request
385
360
  */
386
- protected _handleRequestTimeout(session: ISession): void;
361
+ private handleRequestTimeout;
387
362
  private _abortDownloadOfBody;
388
363
  /**
389
364
  * @internal wraps public utility for mocking purposes
390
365
  */
391
366
  private _requestAsBrowser;
392
367
  }
393
- interface RequestFunctionOptions {
394
- request: CrawleeRequest;
395
- session: ISession;
396
- proxyUrl?: string;
397
- }
398
368
  /**
399
369
  * Creates new {@link Router} instance that works based on request labels.
400
370
  * This instance can then serve as a `requestHandler` of your {@link HttpCrawler}.
@@ -94,6 +94,10 @@ const HTTP_OPTIMIZED_AUTOSCALED_POOL_OPTIONS = {
94
94
  * @category Crawlers
95
95
  */
96
96
  export class HttpCrawler extends BasicCrawler {
97
+ // Internal storage uses the base (non-extended) context types. The public option types are
98
+ // extension-aware for consumer DX, but internally the pipeline composes hooks against the
99
+ // concrete crawling context, which does not statically carry `ContextExtension`. The members
100
+ // added by `extendContext` are present at runtime regardless.
97
101
  preNavigationHooks;
98
102
  postNavigationHooks;
99
103
  saveResponseCookies;
@@ -129,7 +133,7 @@ export class HttpCrawler extends BasicCrawler {
129
133
  });
130
134
  this.supportedMimeTypes = new Set([...HTML_AND_XML_MIME_TYPES, APPLICATION_JSON_MIME_TYPE]);
131
135
  if (additionalMimeTypes.length)
132
- this._extendSupportedMimeTypes(additionalMimeTypes);
136
+ this.extendSupportedMimeTypes(additionalMimeTypes);
133
137
  if (suggestResponseEncoding && forceResponseEncoding) {
134
138
  this.log.warning('Both forceResponseEncoding and suggestResponseEncoding options are set. Using forceResponseEncoding.');
135
139
  }
@@ -137,6 +141,9 @@ export class HttpCrawler extends BasicCrawler {
137
141
  this.ignoreSslErrors = ignoreSslErrors;
138
142
  this.suggestResponseEncoding = suggestResponseEncoding;
139
143
  this.forceResponseEncoding = forceResponseEncoding;
144
+ // Cast away the extension-aware option types to the base internal storage types (see the field
145
+ // declarations above). This is sound - the hooks only ever receive the base context plus the
146
+ // members `extendContext` added at runtime.
140
147
  this.preNavigationHooks = preNavigationHooks;
141
148
  this.postNavigationHooks = [
142
149
  ({ request, response }) => this._abortDownloadOfBody(request, response),
@@ -188,7 +195,7 @@ export class HttpCrawler extends BasicCrawler {
188
195
  tryCancel();
189
196
  const { request, session } = crawlingContext;
190
197
  const proxyUrl = crawlingContext.proxyInfo?.url;
191
- const httpResponse = await addTimeoutToPromise(async () => this._requestFunction({ request, session, proxyUrl }), this.navigationTimeoutMillis, `request timed out after ${this.navigationTimeoutMillis / 1000} seconds.`);
198
+ const httpResponse = await addTimeoutToPromise(async () => this.requestFunction({ request, session, proxyUrl }), this.navigationTimeoutMillis, `request timed out after ${this.navigationTimeoutMillis / 1000} seconds.`);
192
199
  tryCancel();
193
200
  request.loadedUrl = httpResponse?.url;
194
201
  request.state = RequestState.AFTER_NAV;
@@ -215,7 +222,7 @@ export class HttpCrawler extends BasicCrawler {
215
222
  };
216
223
  }
217
224
  tryCancel();
218
- const parsed = await this._parseResponse(crawlingContext.request, crawlingContext.response);
225
+ const parsed = await this.parseResponse(crawlingContext.request, crawlingContext.response);
219
226
  tryCancel();
220
227
  const response = parsed.response;
221
228
  const contentType = parsed.contentType;
@@ -291,15 +298,15 @@ export class HttpCrawler extends BasicCrawler {
291
298
  * on the request such as only downloading the request body if the
292
299
  * received content type matches text/html, application/xml, application/xhtml+xml.
293
300
  */
294
- async _requestFunction({ request, session, proxyUrl }) {
295
- const opts = this._getRequestOptions(request, session, proxyUrl);
301
+ async requestFunction({ request, session, proxyUrl }) {
302
+ const opts = this.getRequestOptions(request, session, proxyUrl);
296
303
  try {
297
304
  return await this._requestAsBrowser(opts, session);
298
305
  }
299
306
  catch (e) {
300
307
  if (e instanceof Error && e.constructor.name === 'TimeoutError') {
301
- this._handleRequestTimeout(session);
302
- return new Response(); // this will never happen, as _handleRequestTimeout always throws
308
+ this.handleRequestTimeout(session);
309
+ return new Response(); // this will never happen, as handleRequestTimeout always throws
303
310
  }
304
311
  if (this.isProxyError(e)) {
305
312
  throw new SessionError(this._getMessageFromError(e));
@@ -312,10 +319,10 @@ export class HttpCrawler extends BasicCrawler {
312
319
  /**
313
320
  * Encodes and parses response according to the provided content type
314
321
  */
315
- async _parseResponse(request, response) {
322
+ async parseResponse(request, response) {
316
323
  const { status } = response;
317
324
  const { type, charset } = parseContentTypeFromResponse(response);
318
- const { response: reencodedResponse, encoding } = this._encodeResponse(request, response, charset);
325
+ const { response: reencodedResponse, encoding } = this.encodeResponse(request, response, charset);
319
326
  const contentType = { type, encoding };
320
327
  if (status >= 400 && status <= 599) {
321
328
  this.stats.registerStatusCode(status);
@@ -361,7 +368,7 @@ export class HttpCrawler extends BasicCrawler {
361
368
  /**
362
369
  * Combines the provided `requestOptions` with mandatory (non-overridable) values.
363
370
  */
364
- _getRequestOptions(request, session, proxyUrl) {
371
+ getRequestOptions(request, session, proxyUrl) {
365
372
  const requestOptions = {
366
373
  url: request.url,
367
374
  method: request.method,
@@ -389,7 +396,7 @@ export class HttpCrawler extends BasicCrawler {
389
396
  requestOptions.body = request.payload ?? '';
390
397
  return requestOptions;
391
398
  }
392
- _encodeResponse(request, response, encoding) {
399
+ encodeResponse(request, response, encoding) {
393
400
  if (this.forceResponseEncoding) {
394
401
  encoding = this.forceResponseEncoding;
395
402
  }
@@ -425,7 +432,7 @@ export class HttpCrawler extends BasicCrawler {
425
432
  /**
426
433
  * Checks and extends supported mime types
427
434
  */
428
- _extendSupportedMimeTypes(additionalMimeTypes) {
435
+ extendSupportedMimeTypes(additionalMimeTypes) {
429
436
  for (const mimeType of additionalMimeTypes) {
430
437
  if (mimeType === '*/*') {
431
438
  this.supportedMimeTypes.add(mimeType);
@@ -443,7 +450,7 @@ export class HttpCrawler extends BasicCrawler {
443
450
  /**
444
451
  * Handles timeout request
445
452
  */
446
- _handleRequestTimeout(session) {
453
+ handleRequestTimeout(session) {
447
454
  session.markBad();
448
455
  throw new Error(`request timed out after ${this.navigationTimeoutMillis / 1000} seconds.`);
449
456
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/http",
3
- "version": "4.0.0-beta.80",
3
+ "version": "4.0.0-beta.82",
4
4
  "description": "The scalable web crawling and scraping library for JavaScript/Node.js. Enables development of data extraction and web automation jobs (not only) with headless Chrome and Puppeteer.",
5
5
  "engines": {
6
6
  "node": ">=22.0.0"
@@ -49,11 +49,11 @@
49
49
  "dependencies": {
50
50
  "@apify/timeout": "^0.3.2",
51
51
  "@apify/utilities": "^2.15.5",
52
- "@crawlee/basic": "4.0.0-beta.80",
53
- "@crawlee/core": "4.0.0-beta.80",
54
- "@crawlee/http-client": "4.0.0-beta.80",
55
- "@crawlee/types": "4.0.0-beta.80",
56
- "@crawlee/utils": "4.0.0-beta.80",
52
+ "@crawlee/basic": "4.0.0-beta.82",
53
+ "@crawlee/core": "4.0.0-beta.82",
54
+ "@crawlee/http-client": "4.0.0-beta.82",
55
+ "@crawlee/types": "4.0.0-beta.82",
56
+ "@crawlee/utils": "4.0.0-beta.82",
57
57
  "@types/content-type": "^1.1.8",
58
58
  "cheerio": "^1.0.0",
59
59
  "content-type": "^1.0.5",
@@ -70,5 +70,5 @@
70
70
  }
71
71
  }
72
72
  },
73
- "gitHead": "96c57b4a0c999e4b2bd198792490af28db7aa42d"
73
+ "gitHead": "eb1096f7c7743d124375ef011fbbadb19476822e"
74
74
  }