1
- import { once } from "node:events" ;
2
- import type { IncomingMessage , ServerResponse } from "node:http" ;
3
- import { TLSSocket } from "node:tls" ;
4
- import { Readable } from "node:stream" ;
5
- import { splitCookiesString } from "set-cookie-parser" ;
6
- import { createReadableStreamFromReadable } from "@react-router/node" ;
1
+ import type { ServerResponse } from "node:http" ;
2
+
3
+ import { createRequest } from "@mjackson/node-fetch-server" ;
7
4
import type * as Vite from "vite" ;
8
5
9
6
import invariant from "../invariant" ;
@@ -13,110 +10,16 @@ export type NodeRequestHandler = (
13
10
res : ServerResponse
14
11
) => Promise < void > ;
15
12
16
- function fromNodeHeaders ( nodeReq : IncomingMessage ) : Headers {
17
- let nodeHeaders = nodeReq . headers ;
18
-
19
- if ( nodeReq . httpVersionMajor >= 2 ) {
20
- nodeHeaders = { ...nodeHeaders } ;
21
- if ( nodeHeaders [ ":authority" ] ) {
22
- nodeHeaders . host = nodeHeaders [ ":authority" ] as string ;
23
- }
24
- delete nodeHeaders [ ":authority" ] ;
25
- delete nodeHeaders [ ":method" ] ;
26
- delete nodeHeaders [ ":path" ] ;
27
- delete nodeHeaders [ ":scheme" ] ;
28
- }
29
-
30
- let headers = new Headers ( ) ;
31
-
32
- for ( let [ key , values ] of Object . entries ( nodeHeaders ) ) {
33
- if ( values ) {
34
- if ( Array . isArray ( values ) ) {
35
- for ( let value of values ) {
36
- headers . append ( key , value ) ;
37
- }
38
- } else {
39
- headers . set ( key , values ) ;
40
- }
41
- }
42
- }
43
-
44
- return headers ;
45
- }
46
-
47
- // Based on `createRemixRequest` in packages/react-router-express/server.ts
48
13
export function fromNodeRequest (
49
14
nodeReq : Vite . Connect . IncomingMessage ,
50
15
nodeRes : ServerResponse < Vite . Connect . IncomingMessage >
51
16
) : Request {
52
- let protocol =
53
- nodeReq . socket instanceof TLSSocket && nodeReq . socket . encrypted
54
- ? "https"
55
- : "http" ;
56
- let origin =
57
- nodeReq . headers . origin && "null" !== nodeReq . headers . origin
58
- ? nodeReq . headers . origin
59
- : `${ protocol } ://${ nodeReq . headers . host } ` ;
60
17
// Use `req.originalUrl` so React Router is aware of the full path
61
18
invariant (
62
19
nodeReq . originalUrl ,
63
20
"Expected `nodeReq.originalUrl` to be defined"
64
21
) ;
65
- let url = new URL ( nodeReq . originalUrl , origin ) ;
66
-
67
- // Abort action/loaders once we can no longer write a response
68
- let controller : AbortController | null = new AbortController ( ) ;
69
- let init : RequestInit = {
70
- method : nodeReq . method ,
71
- headers : fromNodeHeaders ( nodeReq ) ,
72
- signal : controller . signal ,
73
- } ;
74
-
75
- // Abort action/loaders once we can no longer write a response iff we have
76
- // not yet sent a response (i.e., `close` without `finish`)
77
- // `finish` -> done rendering the response
78
- // `close` -> response can no longer be written to
79
- nodeRes . on ( "finish" , ( ) => ( controller = null ) ) ;
80
- nodeRes . on ( "close" , ( ) => controller ?. abort ( ) ) ;
81
-
82
- if ( nodeReq . method !== "GET" && nodeReq . method !== "HEAD" ) {
83
- init . body = createReadableStreamFromReadable ( nodeReq ) ;
84
- ( init as { duplex : "half" } ) . duplex = "half" ;
85
- }
86
-
87
- return new Request ( url . href , init ) ;
88
- }
89
-
90
- // Adapted from solid-start's `handleNodeResponse`:
91
- // https://github.com/solidjs/solid-start/blob/7398163869b489cce503c167e284891cf51a6613/packages/start/node/fetch.js#L162-L185
92
- export async function toNodeRequest ( res : Response , nodeRes : ServerResponse ) {
93
- nodeRes . statusCode = res . status ;
94
-
95
- // HTTP/2 doesn't support status messages
96
- // https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.4
97
- if ( ! nodeRes . req || nodeRes . req . httpVersionMajor < 2 ) {
98
- nodeRes . statusMessage = res . statusText ;
99
- }
100
-
101
- let cookiesStrings = [ ] ;
102
-
103
- for ( let [ name , value ] of res . headers ) {
104
- if ( name === "set-cookie" ) {
105
- cookiesStrings . push ( ...splitCookiesString ( value ) ) ;
106
- } else nodeRes . setHeader ( name , value ) ;
107
- }
108
-
109
- if ( cookiesStrings . length ) {
110
- nodeRes . setHeader ( "set-cookie" , cookiesStrings ) ;
111
- }
22
+ nodeReq . url = nodeReq . originalUrl ;
112
23
113
- if ( res . body ) {
114
- // https://github.com/microsoft/TypeScript/issues/29867
115
- let responseBody = res . body as unknown as AsyncIterable < Uint8Array > ;
116
- let readable = Readable . from ( responseBody ) ;
117
- readable . pipe ( nodeRes ) ;
118
- await once ( readable , "end" ) ;
119
- } else {
120
- nodeRes . end ( ) ;
121
- }
24
+ return createRequest ( nodeReq , nodeRes ) ;
122
25
}
0 commit comments