@@ -11,22 +11,24 @@ import {
11
11
SchemasObject ,
12
12
} from '@loopback/openapi-v3-types' ;
13
13
import * as debugModule from 'debug' ;
14
- import { IncomingMessage } from 'http' ;
15
14
import * as HttpErrors from 'http-errors' ;
16
15
import * as parseUrl from 'parseurl' ;
17
16
import { parse as parseQuery } from 'qs' ;
18
- import { promisify } from 'util' ;
19
17
import { coerceParameter } from './coercion/coerce-parameter' ;
20
18
import { RestHttpErrors } from './index' ;
21
19
import { ResolvedRoute } from './router/routing-table' ;
22
20
import {
23
21
OperationArgs ,
24
22
PathParameterValues ,
25
23
Request ,
24
+ Response ,
26
25
RequestBodyParserOptions ,
27
26
} from './types' ;
28
27
import { validateRequestBody } from './validation/request-body.validator' ;
29
28
29
+ import { json , urlencoded , text } from 'body-parser' ;
30
+ import * as typeis from 'type-is' ;
31
+
30
32
type HttpError = HttpErrors . HttpError ;
31
33
32
34
const debug = debugModule ( 'loopback:rest:parser' ) ;
@@ -40,31 +42,6 @@ type RequestBody = {
40
42
coercionRequired ?: boolean ;
41
43
} ;
42
44
43
- const parseJsonBody : (
44
- req : IncomingMessage ,
45
- options : { } ,
46
- ) => Promise < any > = promisify ( require ( 'body/json' ) ) ;
47
-
48
- const parseFormBody : (
49
- req : IncomingMessage ,
50
- options : { } ,
51
- ) => Promise < any > = promisify ( require ( 'body/form' ) ) ;
52
-
53
- /**
54
- * Get the content-type header value from the request
55
- * @param req Http request
56
- */
57
- function getContentType ( req : Request ) : string | undefined {
58
- const val = req . headers [ 'content-type' ] ;
59
- if ( typeof val === 'string' ) {
60
- return val ;
61
- } else if ( Array . isArray ( val ) ) {
62
- // Assume only one value is present
63
- return val [ 0 ] ;
64
- }
65
- return undefined ;
66
- }
67
-
68
45
/**
69
46
* Parses the request to derive arguments to be passed in for the Application
70
47
* controller method
@@ -90,6 +67,41 @@ export async function parseOperationArgs(
90
67
) ;
91
68
}
92
69
70
+ /**
71
+ * Express body parser function type
72
+ */
73
+ type BodyParser = (
74
+ request : Request ,
75
+ response : Response ,
76
+ callback : ( err : HttpError ) => void ,
77
+ ) => void ;
78
+
79
+ /**
80
+ * Parse the body asynchronously
81
+ * @param handle The express middleware handler
82
+ * @param request Http request
83
+ */
84
+ function parse ( handle : BodyParser , request : Request ) : Promise < void > {
85
+ // A hack to fool TypeScript as we don't need `response`
86
+ const response = ( { } as any ) as Response ;
87
+ return new Promise < void > ( ( resolve , reject ) => {
88
+ handle ( request , response , err => {
89
+ if ( err ) {
90
+ debug ( 'Cannot parse request body %j' , err ) ;
91
+ if ( ! err . statusCode || err . statusCode >= 500 ) {
92
+ err . statusCode = 400 ;
93
+ }
94
+ reject ( err ) ;
95
+ return ;
96
+ }
97
+ resolve ( ) ;
98
+ } ) ;
99
+ } ) ;
100
+ }
101
+
102
+ // Default limit of the body length
103
+ const DEFAULT_LIMIT = '1mb' ;
104
+
93
105
async function loadRequestBodyIfNeeded (
94
106
operationSpec : OperationObject ,
95
107
request : Request ,
@@ -99,45 +111,65 @@ async function loadRequestBodyIfNeeded(
99
111
100
112
debug ( 'Request body parser options: %j' , options ) ;
101
113
102
- const contentType = getContentType ( request ) ;
103
- debug ( 'Loading request body with content type %j' , contentType ) ;
114
+ let body = await parseJsonBody ( request , options ) ;
115
+ if ( body ) return body ;
116
+ body = await parseUrlencodedBody ( request , options ) ;
117
+ if ( body ) return body ;
118
+ body = await parseTextBody ( request , options ) ;
119
+ if ( body ) return body ;
104
120
105
- if (
106
- contentType &&
107
- contentType . startsWith ( 'application/x-www-form-urlencoded' )
108
- ) {
109
- const body = await parseFormBody ( request , options ) . catch (
110
- ( err : HttpError ) => {
111
- debug ( 'Cannot parse request body %j' , err ) ;
112
- if ( ! err . statusCode || err . statusCode >= 500 ) {
113
- err . statusCode = 400 ;
114
- }
115
- throw err ;
116
- } ,
117
- ) ;
118
- // form parser returns an object with prototype
119
- return {
120
- value : Object . assign ( { } , body ) ,
121
- coercionRequired : true ,
122
- } ;
123
- }
121
+ throw new HttpErrors . UnsupportedMediaType (
122
+ `Content-type ${ request . get ( 'content-type' ) } is not supported.` ,
123
+ ) ;
124
+ }
124
125
125
- if ( contentType && ! / j s o n / . test ( contentType ) ) {
126
- throw new HttpErrors . UnsupportedMediaType (
127
- `Content-type ${ contentType } is not supported.` ,
128
- ) ;
126
+ async function parseJsonBody (
127
+ request : Request ,
128
+ options : RequestBodyParserOptions ,
129
+ ) {
130
+ const jsonOptions = Object . assign (
131
+ { type : 'json' , limit : DEFAULT_LIMIT } ,
132
+ options ,
133
+ ) ;
134
+ if ( typeis ( request , jsonOptions . type ) ) {
135
+ await parse ( json ( jsonOptions ) , request ) ;
136
+ return { value : request . body } ;
129
137
}
138
+ return undefined ;
139
+ }
130
140
131
- const jsonBody = await parseJsonBody ( request , options ) . catch (
132
- ( err : HttpError ) => {
133
- debug ( 'Cannot parse request body %j' , err ) ;
134
- if ( ! err . statusCode || err . statusCode >= 500 ) {
135
- err . statusCode = 400 ;
136
- }
137
- throw err ;
141
+ async function parseUrlencodedBody (
142
+ request : Request ,
143
+ options : RequestBodyParserOptions ,
144
+ ) {
145
+ const urlencodedOptions = Object . assign (
146
+ {
147
+ extended : true ,
148
+ type : 'urlencoded' ,
149
+ limit : DEFAULT_LIMIT ,
138
150
} ,
151
+ options ,
152
+ ) ;
153
+ if ( typeis ( request , urlencodedOptions . type ) ) {
154
+ await parse ( urlencoded ( urlencodedOptions ) , request ) ;
155
+ return { value : request . body , coercionRequired : true } ;
156
+ }
157
+ return undefined ;
158
+ }
159
+
160
+ async function parseTextBody (
161
+ request : Request ,
162
+ options : RequestBodyParserOptions ,
163
+ ) {
164
+ const textOptions = Object . assign (
165
+ { type : 'text/*' , limit : DEFAULT_LIMIT } ,
166
+ options ,
139
167
) ;
140
- return { value : jsonBody } ;
168
+ if ( typeis ( request , textOptions . type ) ) {
169
+ await parse ( text ( textOptions ) , request ) ;
170
+ return { value : request . body } ;
171
+ }
172
+ return undefined ;
141
173
}
142
174
143
175
function buildOperationArguments (
0 commit comments