-
Notifications
You must be signed in to change notification settings - Fork 309
Expand file tree
/
Copy pathresponse.ts
More file actions
168 lines (162 loc) · 4.78 KB
/
response.ts
File metadata and controls
168 lines (162 loc) · 4.78 KB
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
import type { H3Event } from "../event.ts";
import { HTTPResponse } from "../response.ts";
import {
serializeIterableValue,
coerceIterable,
type IterationSource,
type IteratorSerializer,
} from "./internal/iterable.ts";
/**
* Respond with an empty payload.<br>
*
* @example
* app.get("/", () => noContent());
*
* @param status status code to be send. By default, it is `204 No Content`.
*/
export function noContent(status: number = 204): HTTPResponse {
return new HTTPResponse(null, {
status,
statusText: "No Content",
});
}
/**
* Send a redirect response to the client.
*
* It adds the `location` header to the response and sets the status code to 302 by default.
*
* In the body, it sends a simple HTML page with a meta refresh tag to redirect the client in case the headers are ignored.
*
* @example
* app.get("/", () => {
* return redirect("https://example.com");
* });
*
* @example
* app.get("/", () => {
* return redirect("https://example.com", 301); // Permanent redirect
* });
*/
export function redirect(
location: string,
status: number = 302,
statusText?: string,
): HTTPResponse {
const encodedLoc = location.replace(/"/g, "%22");
const body = /* html */ `<html><head><meta http-equiv="refresh" content="0; url=${encodedLoc}" /></head></html>`;
return new HTTPResponse(body, {
status,
statusText: statusText || (status === 301 ? "Moved Permanently" : "Found"),
headers: {
"content-type": "text/html; charset=utf-8",
location,
},
});
}
/**
* Write `HTTP/1.1 103 Early Hints` to the client.
*
* In runtimes that don't support early hints natively, this function
* falls back to setting response headers which can be used by CDN.
*/
export function writeEarlyHints(
event: H3Event,
hints: Record<string, string | string[]>,
): void | Promise<void> {
// Use native early hints if available (Node.js)
if (event.runtime?.node?.res?.writeEarlyHints) {
return new Promise((resolve) => {
event.runtime?.node?.res?.writeEarlyHints(hints, () => resolve());
});
}
// Fallback: Set Link headers for CDN support (only Link headers to avoid leaking sensitive headers)
for (const [name, value] of Object.entries(hints)) {
if (name.toLowerCase() !== "link") {
continue;
}
if (Array.isArray(value)) {
for (const v of value) {
event.res.headers.append("link", v);
}
} else {
event.res.headers.append("link", value);
}
}
}
/**
* Iterate a source of chunks and send back each chunk in order.
* Supports mixing async work together with emitting chunks.
*
* Each chunk must be a string or a buffer.
*
* For generator (yielding) functions, the returned value is treated the same as yielded values.
*
* @param iterable - Iterator that produces chunks of the response.
* @param serializer - Function that converts values from the iterable into stream-compatible values.
* @template Value - Test
*
* @example
* return iterable(async function* work() {
* // Open document body
* yield "<!DOCTYPE html>\n<html><body><h1>Executing...</h1><ol>\n";
* // Do work ...
* for (let i = 0; i < 1000; i++) {
* await delay(1000);
* // Report progress
* yield `<li>Completed job #`;
* yield i;
* yield `</li>\n`;
* }
* // Close out the report
* return `</ol></body></html>`;
* });
* async function delay(ms) {
* return new Promise((resolve) => setTimeout(resolve, ms));
* }
*/
export function iterable<Value = unknown, Return = unknown>(
iterable: IterationSource<Value, Return>,
options?: {
serializer: IteratorSerializer<Value | Return>;
},
): HTTPResponse {
const serializer = options?.serializer ?? serializeIterableValue;
const iterator = coerceIterable(iterable);
return new HTTPResponse(
new ReadableStream({
async pull(controller) {
const { value, done } = await iterator.next();
if (value !== undefined) {
const chunk = serializer(value);
if (chunk !== undefined) {
controller.enqueue(chunk);
}
}
if (done) {
controller.close();
}
},
cancel() {
iterator.return?.();
},
}),
);
}
/**
* Respond with HTML content.
*
* @example
* app.get("/", () => html("<h1>Hello, World!</h1>"));
* app.get("/", () => html`<h1>Hello, ${name}!</h1>`);
*/
export function html(strings: TemplateStringsArray, ...values: unknown[]): HTTPResponse;
export function html(markup: string): HTTPResponse;
export function html(first: TemplateStringsArray | string, ...values: unknown[]): HTTPResponse {
const body =
typeof first === "string"
? first
: first.reduce((out, str, i) => out + str + (values[i] ?? ""), "");
return new HTTPResponse(body, {
headers: { "content-type": "text/html; charset=utf-8" },
});
}