File size: 2,347 Bytes
ff7a361
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import type { ServerResponse } from "node:http";

/**
 * Reads at most `maxBytes` of a fetch response body. A hostile or merely huge
 * upstream must not be able to pin the server's memory, and callers that only
 * need the head of a document (page text, thumbnails) gain nothing from the
 * tail anyway.
 */
export async function readCappedBytes(
  response: Response,
  maxBytes: number,
): Promise<{ bytes: Uint8Array; truncated: boolean }> {
  const reader = response.body?.getReader();
  if (!reader) return { bytes: new Uint8Array(), truncated: false };

  const chunks: Uint8Array[] = [];
  let bytesRead = 0;
  let reachedEnd = false;

  while (bytesRead < maxBytes) {
    const { done, value } = await reader.read();
    if (done) {
      reachedEnd = true;
      break;
    }

    const remaining = maxBytes - bytesRead;
    const chunk =
      value.byteLength > remaining ? value.subarray(0, remaining) : value;
    bytesRead += chunk.byteLength;
    chunks.push(chunk);

    if (chunk.byteLength < value.byteLength) break;
  }

  await reader.cancel().catch(() => {});

  const bytes = new Uint8Array(bytesRead);
  let offset = 0;
  for (const chunk of chunks) {
    bytes.set(chunk, offset);
    offset += chunk.byteLength;
  }
  return { bytes, truncated: !reachedEnd };
}

/** Calculates the backoff delay for a retry attempt, with jitter. */
export function calculateBackoffTime(
  attempt: number,
  baseDelayMs = 100,
  maxDelayMs = 5000,
): number {
  const delay = Math.min(baseDelayMs * 2 ** (attempt - 1), maxDelayMs);
  return delay * (0.7 + Math.random() * 0.3);
}

export function isResponseWritable(response: ServerResponse): boolean {
  return !response.writableEnded && !response.destroyed;
}

export function safeWriteResponse(
  response: ServerResponse,
  data: string,
): boolean {
  if (!isResponseWritable(response)) return false;

  try {
    return response.write(data);
  } catch (error) {
    console.error("Failed to write to response:", error);
    return false;
  }
}

export function safeEndResponse(response: ServerResponse, data?: string): void {
  if (response.writableEnded || response.destroyed) return;

  try {
    if (data) {
      response.end(data);
    } else {
      response.end();
    }
  } catch (error) {
    console.error("Failed to end response:", error);
    response.destroy();
  }
}