forked from vercel/next.js
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathweb.ts
More file actions
140 lines (112 loc) · 3.73 KB
/
web.ts
File metadata and controls
140 lines (112 loc) · 3.73 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
import type { IncomingHttpHeaders, OutgoingHttpHeaders } from 'http'
import type { FetchMetrics } from './index'
import { toNodeOutgoingHttpHeaders } from '../web/utils'
import { BaseNextRequest, BaseNextResponse } from './index'
import { DetachedPromise } from '../../lib/detached-promise'
import type { NextRequestHint } from '../web/adapter'
import { CloseController, trackBodyConsumed } from '../web/web-on-close'
import { InvariantError } from '../../shared/lib/invariant-error'
export class WebNextRequest extends BaseNextRequest<ReadableStream | null> {
public request: Request
public headers: IncomingHttpHeaders
public fetchMetrics: FetchMetrics | undefined
constructor(request: NextRequestHint) {
const url = new URL(request.url)
super(
request.method,
url.href.slice(url.origin.length),
request.clone().body
)
this.request = request
this.fetchMetrics = request.fetchMetrics
this.headers = {}
for (const [name, value] of request.headers.entries()) {
this.headers[name] = value
}
}
async parseBody(_limit: string | number): Promise<any> {
throw new Error('parseBody is not implemented in the web runtime')
}
}
export class WebNextResponse extends BaseNextResponse<WritableStream> {
private headers = new Headers()
private textBody: string | undefined = undefined
private closeController = new CloseController()
public statusCode: number | undefined
public statusMessage: string | undefined
constructor(public transformStream = new TransformStream()) {
super(transformStream.writable)
}
setHeader(name: string, value: string | string[]): this {
this.headers.delete(name)
for (const val of Array.isArray(value) ? value : [value]) {
this.headers.append(name, val)
}
return this
}
removeHeader(name: string): this {
this.headers.delete(name)
return this
}
getHeaderValues(name: string): string[] | undefined {
// https://developer.mozilla.org/docs/Web/API/Headers/get#example
return this.getHeader(name)
?.split(',')
.map((v) => v.trimStart())
}
getHeader(name: string): string | undefined {
return this.headers.get(name) ?? undefined
}
getHeaders(): OutgoingHttpHeaders {
return toNodeOutgoingHttpHeaders(this.headers)
}
hasHeader(name: string): boolean {
return this.headers.has(name)
}
appendHeader(name: string, value: string): this {
this.headers.append(name, value)
return this
}
body(value: string) {
this.textBody = value
return this
}
private readonly sendPromise = new DetachedPromise<void>()
private _sent = false
public send() {
this.sendPromise.resolve()
this._sent = true
}
get sent() {
return this._sent
}
public async toResponse() {
// If we haven't called `send` yet, wait for it to be called.
if (!this.sent) await this.sendPromise.promise
const body = this.textBody ?? this.transformStream.readable
let bodyInit: BodyInit = body
// if the response is streaming, onClose() can still be called after this point.
const canAddListenersLater = typeof bodyInit !== 'string'
const shouldTrackBody = canAddListenersLater
? true
: this.closeController.listeners > 0
if (shouldTrackBody) {
bodyInit = trackBodyConsumed(body, () => {
this.closeController.dispatchClose()
})
}
return new Response(bodyInit, {
headers: this.headers,
status: this.statusCode,
statusText: this.statusMessage,
})
}
public onClose(callback: () => void) {
if (this.closeController.isClosed) {
throw new InvariantError(
'Cannot call onClose on a WebNextResponse that is already closed'
)
}
return this.closeController.onClose(callback)
}
}