@@ -6,11 +6,9 @@ import {
66 NowResponse ,
77} from '@vercel/node' ;
88import { IncomingMessage , ServerResponse , Server , RequestListener } from 'http' ;
9- import { parse } from 'cookie' ;
10- import { parse as parseContentType } from 'content-type' ;
11- import { parse as parseQS } from 'querystring' ;
12- import { URL } from 'url' ;
139import micro , { buffer , send } from 'micro' ;
10+ // @ts -expect-error
11+ import cloneResponse from 'clone-response' ;
1412
1513export class ApiError extends Error {
1614 readonly statusCode : number ;
@@ -21,69 +19,98 @@ export class ApiError extends Error {
2119 }
2220}
2321
24- function parseBody ( req : IncomingMessage , body : Buffer ) : NowRequestBody {
25- if ( ! req . headers [ 'content-type' ] ) {
26- return undefined ;
27- }
28-
29- const { type } = parseContentType ( req . headers [ 'content-type' ] ) ;
22+ function getBodyParser ( req : NowRequest , body : Buffer | string ) {
23+ return function parseBody ( ) : NowRequestBody {
24+ if ( ! req . headers [ 'content-type' ] ) {
25+ return undefined ;
26+ }
27+ // eslint-disable-next-line @typescript-eslint/no-var-requires
28+ const { parse : parseContentType } = require ( 'content-type' ) ;
29+ const { type } = parseContentType ( req . headers [ 'content-type' ] ) ;
30+
31+ if ( type === 'application/json' ) {
32+ try {
33+ const str = body . toString ( ) ;
34+ return str ? JSON . parse ( str ) : { } ;
35+ } catch ( error ) {
36+ throw new ApiError ( 400 , 'Invalid JSON' ) ;
37+ }
38+ }
3039
31- if ( type === 'application/json' ) {
32- try {
33- return JSON . parse ( body . toString ( ) ) ;
34- } catch ( error ) {
35- throw new ApiError ( 400 , 'Invalid JSON' ) ;
40+ if ( type === 'application/octet-stream' ) {
41+ return body ;
3642 }
37- }
3843
39- if ( type === 'application/octet-stream' ) {
40- return body ;
41- }
44+ if ( type === 'application/x-www-form-urlencoded' ) {
45+ // eslint-disable-next-line @typescript-eslint/no-var-requires
46+ const { parse : parseQS } = require ( 'querystring' ) ;
47+ // note: querystring.parse does not produce an iterable object
48+ // https://nodejs.org/api/querystring.html#querystring_querystring_parse_str_sep_eq_options
49+ return parseQS ( body . toString ( ) ) ;
50+ }
4251
43- if ( type === 'application/x-www-form-urlencoded' ) {
44- // note: querystring.parse does not produce an iterable object
45- // https://nodejs.org/api/querystring.html#querystring_querystring_parse_str_sep_eq_options
46- return parseQS ( body . toString ( ) ) ;
47- }
52+ if ( type === 'text/plain' ) {
53+ return body . toString ( ) ;
54+ }
4855
49- if ( type === 'text/plain' ) {
50- return body . toString ( ) ;
51- }
56+ return undefined ;
57+ } ;
58+ }
5259
53- return undefined ;
60+ function getQueryParser ( { url = '/' } : NowRequest ) {
61+ return function parseQuery ( ) : NowRequestQuery {
62+ // eslint-disable-next-line @typescript-eslint/no-var-requires
63+ const { parse : parseURL } = require ( 'url' ) ;
64+ return parseURL ( url , true ) . query ;
65+ } ;
5466}
5567
56- function parseQuery ( { url = '/' } : IncomingMessage ) : NowRequestQuery {
57- // we provide a placeholder base url because we only want searchParams
58- const params = new URL ( url , 'https://n' ) . searchParams ;
68+ function getCookieParser ( req : NowRequest ) {
69+ return function parseCookie ( ) : NowRequestCookies {
70+ const header : undefined | string | string [ ] = req . headers . cookie ;
71+
72+ if ( ! header ) {
73+ return { } ;
74+ }
75+
76+ // eslint-disable-next-line @typescript-eslint/no-var-requires
77+ const { parse } = require ( 'cookie' ) ;
78+ return parse ( Array . isArray ( header ) ? header . join ( ';' ) : header ) ;
79+ } ;
80+ }
5981
60- const query : { [ key : string ] : string | string [ ] } = { } ;
61- params . forEach ( ( value , name ) => {
62- query [ name ] = value ;
82+ function setLazyProp < T > ( req : NowRequest , prop : string , getter : ( ) => T ) {
83+ const opts = { configurable : true , enumerable : true } ;
84+ const optsReset = { ...opts , writable : true } ;
85+
86+ Object . defineProperty ( req , prop , {
87+ ...opts ,
88+ get : ( ) => {
89+ const value = getter ( ) ;
90+ // we set the property on the object to avoid recalculating it
91+ Object . defineProperty ( req , prop , { ...optsReset , value } ) ;
92+ return value ;
93+ } ,
94+ set : value => {
95+ Object . defineProperty ( req , prop , { ...optsReset , value } ) ;
96+ } ,
6397 } ) ;
64- return query ;
6598}
6699
67- function parseCookie ( req : IncomingMessage ) : NowRequestCookies {
68- const header : undefined | string | string [ ] = req . headers . cookie ;
69- if ( ! header ) {
70- return { } ;
100+ export const enhanceRequest = async ( req : NowRequest ) : Promise < NowRequest > => {
101+ // We clone the request, so that we can read the incoming stream but then
102+ // still allow subsequent consumers to do the same
103+ const reqClone = cloneResponse ( req ) ;
104+ const newReq = cloneResponse ( req ) ;
105+ const body = await buffer ( reqClone ) ;
106+
107+ setLazyProp < NowRequestCookies > ( newReq , 'cookies' , getCookieParser ( newReq ) ) ;
108+ setLazyProp < NowRequestQuery > ( newReq , 'query' , getQueryParser ( newReq ) ) ;
109+ if ( body != null ) {
110+ setLazyProp < NowRequestBody > ( newReq , 'body' , getBodyParser ( newReq , body ) ) ;
71111 }
72- return parse ( Array . isArray ( header ) ? header . join ( ';' ) : header ) ;
73- }
74112
75- export const enhanceRequest = async (
76- req : IncomingMessage
77- ) : Promise < NowRequest > => {
78- const bufferOrString = await buffer ( req ) ;
79- return Object . assign ( req , {
80- body :
81- typeof bufferOrString === 'string'
82- ? bufferOrString
83- : parseBody ( req , bufferOrString ) ,
84- cookies : parseCookie ( req ) ,
85- query : parseQuery ( req ) ,
86- } ) ;
113+ return newReq ;
87114} ;
88115
89116export const enhanceResponse = ( res : ServerResponse ) : NowResponse => {
@@ -132,6 +159,7 @@ export const createServer = <C extends Config = DefaultConfig>(
132159 return new Server ( route ) ;
133160 } else {
134161 return micro ( async ( req : IncomingMessage , res : ServerResponse ) => {
162+ // @ts -expect-error
135163 const nowReq = await enhanceRequest ( req ) ;
136164 const nowRes = enhanceResponse ( res ) ;
137165 return await route ( nowReq , nowRes ) ;
0 commit comments