@crawlee/http 4.0.0-beta.105 → 4.0.0-beta.107

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.
@@ -242,14 +242,8 @@ JSONData extends JsonValue = any> = RequestHandler<HttpCrawlingContext<UserData,
242
242
  * @category Crawlers
243
243
  */
244
244
  export declare class HttpCrawler<Context extends InternalHttpCrawlingContext<any, any> = InternalHttpCrawlingContext, ContextExtension = Dictionary<never>, ExtendedContext extends Context = Context & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>> extends BasicCrawler<Context, ContextExtension, ExtendedContext, Routes> {
245
- private preNavigationHooks;
246
- private postNavigationHooks;
247
- private saveResponseCookies;
248
- private navigationTimeoutMillis;
245
+ #private;
249
246
  private ignoreSslErrors;
250
- private suggestResponseEncoding?;
251
- private forceResponseEncoding?;
252
- private readonly supportedMimeTypes;
253
247
  protected static optionsShape: {
254
248
  // @ts-ignore optional peer dependency or compatibility with es2022
255
249
  navigationTimeoutSecs: import("ow").NumberPredicate & import("ow").BasePredicate<number | undefined>;
@@ -381,11 +375,11 @@ export declare class HttpCrawler<Context extends InternalHttpCrawlingContext<any
381
375
  * Handles timeout request
382
376
  */
383
377
  private handleRequestTimeout;
384
- private _abortDownloadOfBody;
378
+ private abortDownloadOfBody;
385
379
  /**
386
380
  * @internal wraps public utility for mocking purposes
387
381
  */
388
- private _requestAsBrowser;
382
+ private requestAsBrowser;
389
383
  }
390
384
  /**
391
385
  * Creates new {@link Router} instance that works based on request labels.
@@ -109,14 +109,15 @@ export class HttpCrawler extends BasicCrawler {
109
109
  // extension-aware for consumer DX, but internally the pipeline composes hooks against the
110
110
  // concrete crawling context, which does not statically carry `ContextExtension`. The members
111
111
  // added by `extendContext` are present at runtime regardless.
112
- preNavigationHooks;
113
- postNavigationHooks;
114
- saveResponseCookies;
115
- navigationTimeoutMillis;
112
+ #preNavigationHooks;
113
+ #postNavigationHooks;
114
+ #saveResponseCookies;
115
+ #navigationTimeoutMillis;
116
+ // kept as TS-private: tests read it at runtime
116
117
  ignoreSslErrors;
117
- suggestResponseEncoding;
118
- forceResponseEncoding;
119
- supportedMimeTypes;
118
+ #suggestResponseEncoding;
119
+ #forceResponseEncoding;
120
+ #supportedMimeTypes;
120
121
  static optionsShape = {
121
122
  ...BasicCrawler.optionsShape,
122
123
  navigationTimeoutSecs: ow.optional.number,
@@ -141,28 +142,28 @@ export class HttpCrawler extends BasicCrawler {
141
142
  contextPipelineBuilder: contextPipelineBuilder ??
142
143
  (() => this.buildContextPipeline()),
143
144
  });
144
- this.supportedMimeTypes = new Set([...HTML_AND_XML_MIME_TYPES, APPLICATION_JSON_MIME_TYPE]);
145
+ this.#supportedMimeTypes = new Set([...HTML_AND_XML_MIME_TYPES, APPLICATION_JSON_MIME_TYPE]);
145
146
  if (additionalMimeTypes.length)
146
147
  this.extendSupportedMimeTypes(additionalMimeTypes);
147
148
  if (suggestResponseEncoding && forceResponseEncoding) {
148
149
  this.log.warning('Both forceResponseEncoding and suggestResponseEncoding options are set. Using forceResponseEncoding.');
149
150
  }
150
- this.navigationTimeoutMillis = navigationTimeoutSecs * 1000;
151
+ this.#navigationTimeoutMillis = navigationTimeoutSecs * 1000;
151
152
  this.ignoreSslErrors = ignoreSslErrors;
152
- this.suggestResponseEncoding = suggestResponseEncoding;
153
- this.forceResponseEncoding = forceResponseEncoding;
153
+ this.#suggestResponseEncoding = suggestResponseEncoding;
154
+ this.#forceResponseEncoding = forceResponseEncoding;
154
155
  // Cast away the extension-aware option types to the base internal storage types (see the field
155
156
  // declarations above). This is sound - the hooks only ever receive the base context plus the
156
157
  // members `extendContext` added at runtime.
157
- this.preNavigationHooks = preNavigationHooks;
158
- this.postNavigationHooks = [
159
- ({ request, response }) => this._abortDownloadOfBody(request, response),
158
+ this.#preNavigationHooks = preNavigationHooks;
159
+ this.#postNavigationHooks = [
160
+ ({ request, response }) => this.abortDownloadOfBody(request, response),
160
161
  ...postNavigationHooks,
161
162
  ];
162
- this.saveResponseCookies = saveResponseCookies;
163
+ this.#saveResponseCookies = saveResponseCookies;
163
164
  }
164
165
  getNavigationTimeoutMillis() {
165
- return this.navigationTimeoutMillis;
166
+ return this.#navigationTimeoutMillis;
166
167
  }
167
168
  /**
168
169
  * Folds {@link HTTP_OPTIMIZED_CONCURRENCY_SYSTEM_OPTIONS} into the default system, keeping the user's
@@ -185,9 +186,9 @@ export class HttpCrawler extends BasicCrawler {
185
186
  // A single navigation window covers the pre-navigation hooks, the navigation, and the post-navigation
186
187
  // hooks: the whole phase shares one `navigationTimeoutSecs` budget, so a slow hook eats into the same
187
188
  // window the navigation uses instead of each step being timed on its own.
188
- const navigationTimedOut = `Navigation timed out after ${this.navigationTimeoutMillis / 1000} seconds.`;
189
+ const navigationTimedOut = `Navigation timed out after ${this.#navigationTimeoutMillis / 1000} seconds.`;
189
190
  const windowGuard = (step) => skipGuard(async (ctx) => {
190
- const remaining = remainingNavigationWindowMillis(ctx, this.navigationTimeoutMillis);
191
+ const remaining = remainingNavigationWindowMillis(ctx, this.#navigationTimeoutMillis);
191
192
  if (remaining <= 0) {
192
193
  throw new TimeoutError(navigationTimedOut);
193
194
  }
@@ -196,11 +197,11 @@ export class HttpCrawler extends BasicCrawler {
196
197
  let pipeline = ContextPipeline.create().compose({
197
198
  action: this.prepareHttpRequest.bind(this),
198
199
  });
199
- for (const hook of this.preNavigationHooks) {
200
+ for (const hook of this.#preNavigationHooks) {
200
201
  pipeline = pipeline.compose(windowGuard(hook));
201
202
  }
202
203
  let pipelineWithNavigation = pipeline.compose(skipGuard(this.makeHttpRequest.bind(this)));
203
- for (const hook of this.postNavigationHooks) {
204
+ for (const hook of this.#postNavigationHooks) {
204
205
  pipelineWithNavigation = pipelineWithNavigation.compose(windowGuard(hook));
205
206
  }
206
207
  return pipelineWithNavigation
@@ -234,7 +235,7 @@ export class HttpCrawler extends BasicCrawler {
234
235
  // Bound the request by whatever is left of the shared navigation window (the pre-navigation hooks may
235
236
  // have already spent part of it), so it produces a clean navigation-timeout error rather than the raw
236
237
  // client abort.
237
- const httpResponse = await addTimeoutToPromise(async () => this.requestFunction({ request, session, proxyUrl }), Math.max(1, remainingNavigationWindowMillis(crawlingContext, this.navigationTimeoutMillis)), `Navigation timed out after ${this.navigationTimeoutMillis / 1000} seconds.`);
238
+ const httpResponse = await addTimeoutToPromise(async () => this.requestFunction({ request, session, proxyUrl }), Math.max(1, remainingNavigationWindowMillis(crawlingContext, this.#navigationTimeoutMillis)), `Navigation timed out after ${this.#navigationTimeoutMillis / 1000} seconds.`);
238
239
  tryCancel();
239
240
  request.loadedUrl = httpResponse?.url;
240
241
  request.state = RequestState.AFTER_NAV;
@@ -264,11 +265,11 @@ export class HttpCrawler extends BasicCrawler {
264
265
  // Reading the body is still part of the navigation, so it draws from the same shared window: on a server
265
266
  // that streams the body slowly the request completes (headers arrive) but the body read would otherwise
266
267
  // run unbounded. `extendTimeout` from a post-navigation hook has already pushed this deadline out if asked.
267
- const remaining = remainingNavigationWindowMillis(crawlingContext, this.navigationTimeoutMillis);
268
+ const remaining = remainingNavigationWindowMillis(crawlingContext, this.#navigationTimeoutMillis);
268
269
  if (remaining <= 0) {
269
- throw new TimeoutError(`Navigation timed out after ${this.navigationTimeoutMillis / 1000} seconds.`);
270
+ throw new TimeoutError(`Navigation timed out after ${this.#navigationTimeoutMillis / 1000} seconds.`);
270
271
  }
271
- const parsed = await addTimeoutToPromise(async () => this.parseResponse(crawlingContext.request, crawlingContext.response), remaining, `Navigation timed out after ${this.navigationTimeoutMillis / 1000} seconds.`);
272
+ const parsed = await addTimeoutToPromise(async () => this.parseResponse(crawlingContext.request, crawlingContext.response), remaining, `Navigation timed out after ${this.#navigationTimeoutMillis / 1000} seconds.`);
272
273
  tryCancel();
273
274
  const response = parsed.response;
274
275
  const contentType = parsed.contentType;
@@ -287,14 +288,16 @@ export class HttpCrawler extends BasicCrawler {
287
288
  }
288
289
  return $;
289
290
  };
290
- this._throwOnBlockedRequest(response.status);
291
- if (this.saveResponseCookies) {
291
+ this.throwOnBlockedRequest(response.status);
292
+ if (this.#saveResponseCookies) {
292
293
  try {
293
294
  for (const cookie of getCookiesFromResponse(response)) {
294
295
  if (!cookie)
295
296
  continue;
296
297
  try {
297
- crawlingContext.session.cookieJar.setCookieSync(cookie, response.url, { ignoreError: false });
298
+ await crawlingContext.session.cookieJar.setCookie(cookie, response.url, {
299
+ ignoreError: false,
300
+ });
298
301
  }
299
302
  catch (e) {
300
303
  this.log.debug(`Could not set cookie: ${e.message}`);
@@ -347,7 +350,7 @@ export class HttpCrawler extends BasicCrawler {
347
350
  async requestFunction({ request, session, proxyUrl }) {
348
351
  const opts = this.getRequestOptions(request, session, proxyUrl);
349
352
  try {
350
- return await this._requestAsBrowser(opts, session);
353
+ return await this.requestAsBrowser(opts, session);
351
354
  }
352
355
  catch (e) {
353
356
  if (e instanceof Error && e.constructor.name === 'TimeoutError') {
@@ -355,7 +358,7 @@ export class HttpCrawler extends BasicCrawler {
355
358
  return new Response(); // this will never happen, as handleRequestTimeout always throws
356
359
  }
357
360
  if (this.isProxyError(e)) {
358
- throw new SessionError(this._getMessageFromError(e));
361
+ throw new SessionError(this.getMessageFromError(e));
359
362
  }
360
363
  else {
361
364
  throw e;
@@ -391,10 +394,10 @@ export class HttpCrawler extends BasicCrawler {
391
394
  throw new Error(`${status} - Internal Server Error: ${body.slice(0, 100)}`);
392
395
  }
393
396
  else if (HTML_AND_XML_MIME_TYPES.includes(type)) {
394
- if (!charset && !this.forceResponseEncoding) {
397
+ if (!charset && !this.#forceResponseEncoding) {
395
398
  const rawBytes = Buffer.from(await response.arrayBuffer());
396
399
  const metaCharset = extractCharsetFromHtmlBytes(rawBytes);
397
- const charsetToUse = metaCharset ?? this.suggestResponseEncoding ?? 'utf-8';
400
+ const charsetToUse = metaCharset ?? this.#suggestResponseEncoding ?? 'utf-8';
398
401
  const body = iconv.encodingExists(charsetToUse)
399
402
  ? iconv.decode(rawBytes, charsetToUse)
400
403
  : rawBytes.toString('utf8');
@@ -419,7 +422,7 @@ export class HttpCrawler extends BasicCrawler {
419
422
  url: request.url,
420
423
  method: request.method,
421
424
  proxyUrl,
422
- timeout: this.navigationTimeoutMillis,
425
+ timeout: this.#navigationTimeoutMillis,
423
426
  sessionToken: session,
424
427
  headers: request.headers,
425
428
  https: {
@@ -428,7 +431,7 @@ export class HttpCrawler extends BasicCrawler {
428
431
  body: undefined,
429
432
  };
430
433
  if (requestOptions.headers?.cookie || requestOptions.headers?.Cookie) {
431
- requestOptions.headers.Cookie = this._getCookieHeaderFromRequest(request);
434
+ requestOptions.headers.Cookie = this.getCookieHeaderFromRequest(request);
432
435
  delete requestOptions.headers.cookie;
433
436
  }
434
437
  // Disable SSL verification for MITM proxies
@@ -443,11 +446,11 @@ export class HttpCrawler extends BasicCrawler {
443
446
  return requestOptions;
444
447
  }
445
448
  encodeResponse(request, response, encoding) {
446
- if (this.forceResponseEncoding) {
447
- encoding = this.forceResponseEncoding;
449
+ if (this.#forceResponseEncoding) {
450
+ encoding = this.#forceResponseEncoding;
448
451
  }
449
- else if (!encoding && this.suggestResponseEncoding) {
450
- encoding = this.suggestResponseEncoding;
452
+ else if (!encoding && this.#suggestResponseEncoding) {
453
+ encoding = this.#suggestResponseEncoding;
451
454
  }
452
455
  // Fall back to utf-8 if we still don't have encoding.
453
456
  const utf8 = 'utf8';
@@ -481,12 +484,12 @@ export class HttpCrawler extends BasicCrawler {
481
484
  extendSupportedMimeTypes(additionalMimeTypes) {
482
485
  for (const mimeType of additionalMimeTypes) {
483
486
  if (mimeType === '*/*') {
484
- this.supportedMimeTypes.add(mimeType);
487
+ this.#supportedMimeTypes.add(mimeType);
485
488
  continue;
486
489
  }
487
490
  try {
488
491
  const parsedType = contentTypeParser.parse(mimeType);
489
- this.supportedMimeTypes.add(parsedType.type);
492
+ this.#supportedMimeTypes.add(parsedType.type);
490
493
  }
491
494
  catch (err) {
492
495
  throw new Error(`Can not parse mime type ${mimeType} from "options.additionalMimeTypes".`);
@@ -498,22 +501,22 @@ export class HttpCrawler extends BasicCrawler {
498
501
  */
499
502
  handleRequestTimeout(session) {
500
503
  session.markBad();
501
- throw new Error(`Request timed out after ${this.navigationTimeoutMillis / 1000} seconds.`);
504
+ throw new Error(`Request timed out after ${this.#navigationTimeoutMillis / 1000} seconds.`);
502
505
  }
503
- _abortDownloadOfBody(request, response) {
506
+ abortDownloadOfBody(request, response) {
504
507
  const { status } = response;
505
508
  const { type } = parseContentTypeFromResponse(response);
506
509
  const isTransientContentType = status >= 500 || this.blockedStatusCodes.has(status);
507
- if (!this.supportedMimeTypes.has(type) && !this.supportedMimeTypes.has('*/*') && !isTransientContentType) {
510
+ if (!this.#supportedMimeTypes.has(type) && !this.#supportedMimeTypes.has('*/*') && !isTransientContentType) {
508
511
  request.noRetry = true;
509
512
  throw new Error(`Resource ${request.url} served Content-Type ${type}, ` +
510
- `but only ${Array.from(this.supportedMimeTypes).join(', ')} are allowed. Skipping resource.`);
513
+ `but only ${Array.from(this.#supportedMimeTypes).join(', ')} are allowed. Skipping resource.`);
511
514
  }
512
515
  }
513
516
  /**
514
517
  * @internal wraps public utility for mocking purposes
515
518
  */
516
- _requestAsBrowser = async (options, session) => {
519
+ requestAsBrowser = async (options, session) => {
517
520
  const opts = processHttpRequestOptions({
518
521
  ...options,
519
522
  responseType: 'text',
@@ -521,7 +524,7 @@ export class HttpCrawler extends BasicCrawler {
521
524
  // When saveResponseCookies is false, the response cookies must not mutate the
522
525
  // session jar. Reads still go through the session (so session.setCookie() in pre-nav
523
526
  // hooks keeps working) but a per-request clone is passed in so writes are discarded.
524
- const cookieJar = this.saveResponseCookies ? session.cookieJar : await session.cookieJar.clone();
527
+ const cookieJar = this.#saveResponseCookies ? session.cookieJar : await session.cookieJar.clone();
525
528
  // Bind the request to the shared navigation window instead of a fixed per-request timeout, so
526
529
  // `extendTimeout()` can push the deadline and a fixed `AbortSignal.timeout` won't fire on its own and
527
530
  // kill a lazily-read body mid-extension. This aborts the socket only during the header phase; the body
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/http",
3
- "version": "4.0.0-beta.105",
3
+ "version": "4.0.0-beta.107",
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.4.4",
51
51
  "@apify/utilities": "^2.15.5",
52
- "@crawlee/basic": "4.0.0-beta.105",
53
- "@crawlee/core": "4.0.0-beta.105",
54
- "@crawlee/http-client": "4.0.0-beta.105",
55
- "@crawlee/types": "4.0.0-beta.105",
56
- "@crawlee/utils": "4.0.0-beta.105",
52
+ "@crawlee/basic": "4.0.0-beta.107",
53
+ "@crawlee/core": "4.0.0-beta.107",
54
+ "@crawlee/http-client": "4.0.0-beta.107",
55
+ "@crawlee/types": "4.0.0-beta.107",
56
+ "@crawlee/utils": "4.0.0-beta.107",
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": "26073a822c7699ac487931383a39192bc3daae7a"
73
+ "gitHead": "5a3cb6242d0ae261df93c5e03cfebbb9b736e6b7"
74
74
  }