forked from sailshq/skipper
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
152 lines (125 loc) · 5.94 KB
/
index.js
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
/**
* Module dependencies
*/
var _ = require('lodash');
var toParseMultipartHTTPRequest = require('./lib/multipart');
var connect = require('connect');
var Upstream = require('./standalone/Upstream');
// Double-check that a valid Node version with support for streams2
// is being used
if (!require('semver').satisfies(process.version, '>=0.10.0')) {
console.error('`skipper` (bodyParser) requires node version >= 0.10.0.');
console.error('Please upgrade Node at http://nodejs.org/ or with `nvm`');
throw new Error('Invalid Node.js version');
}
//
// TODO: make this module lighter-weight by grabbing JSON and URLEncoded bodyparsers separately.
//
// (this allows us to drop the Connect dep-- which probably doesn't matter
// actually, because you almost certainly have Connect installed already w/
// Sails, but still would be cleaner....)
//
/**
* Skipper
*
* @param {Object} options [description]
* @return {Function}
*/
module.exports = function toParseHTTPBody(options) {
options = options || {};
// Configure body parser components
var URLEncodedBodyParser = connect.urlencoded(options);
var MultipartBodyParser = toParseMultipartHTTPRequest(options);
var JSONBodyParser = connect.json(options);
/**
* Connet/Express/Sails-compatible middleware.
*
* @param {Request} req [description]
* @param {Response} res [description]
* @param {Function} next [description]
*/
return function _parseHTTPBody(req, res, next) {
// Optimization: skip bodyParser for GET, OPTIONS, or body-less requests.
if (req.method.toLowerCase() === 'get' || req.method.toLowerCase() === 'options' || req.method.toLowerCase() === 'head') {
// But stub out a `req.file()` method with a usage error:
req.file = function() {
throw new Error('`req.file()` cannot be used with an HTTP GET, OPTIONS, or HEAD request.');
};
return next();
}
// TODO: Optimization: skip bodyParser for other HTTP requests w/o a body.
// TODO: Optimization: only run bodyParser if this is a known route
// log.verbose('Running request ('+req.method+' ' + req.url + ') through bodyParser...');
// Mock up a req.file handler that returns a noop upstream, so that user code
// can use `req.file` without having to check for it first. This is useful in cases
// where there may or may not be file params coming in. The Multipart parser will
// replace this with an actual upstream-acquiring function if the request isn't successfully
// handled by one of the other parsers first.
req.file = function(fieldName) {
var noopUpstream = new Upstream({
noop: true
});
noopUpstream.fieldName = 'NOOP_'+fieldName;
return noopUpstream;
};
// Try to parse a request that has application/json content type
JSONBodyParser(req, res, function(err) {
if (err) return next(err);
// If parsing was successful, exit
if (!_.isEqual(req.body, {})) {return next();}
// Otherwise try the URL-encoded parser (application/x-www-form-urlencoded type)
URLEncodedBodyParser(req, res, function(err) {
if (err) return next(err);
// If parsing was successful, exit
if (!_.isEqual(req.body, {})) {return next();}
// Otherwise try the multipart parser
MultipartBodyParser(req, res, function(err) {
if (err) return next(err);
/**
* OK, here's how the re-run of the JSON bodyparser works:
* ========================================================
* If the original pass of the bodyParser failed to parse anything, rerun it,
* but with an artificial `application/json` content-type header,
* forcing it to try and parse the request body as JSON. This is just in case
* the user sent a JSON request body, but forgot to set the appropriate header
* (which is pretty much every time, I think.)
*/
// If we were able to parse something at this point (req.body isn't empty)
// or files are/were being uploaded/ignored (req._fileparser.upstreams isn't empty)
// or the content-type is JSON,
var reqBodyNotEmpty = !_.isEqual(req.body, {});
var hasUpstreams = req._fileparser && req._fileparser.upstreams.length;
var contentTypeIsJSON = (backupContentType === 'application/json');
// ...then the original parsing must have worked.
// In that case, we'll skip the JSON retry stuff.
if (contentTypeIsJSON || reqBodyNotEmpty || hasUpstreams) {
return next();
}
// TODO: consider adding some basic support for this instead of relying on a peer dependency.
// Chances are, if `req.is()` doesn't exist, we really really don't want to rerun the JSON bodyparser.
if (!req.is) return next();
// If the content type is explicitly set to "multipart/form-data",
// we should not try to rerun the JSON bodyparser- it may hang forever.
if (req.is('multipart/form-data')) return next();
// Otherwise, set an explicit JSON content-type
// and try parsing the request body again.
var backupContentType = req.headers['content-type'];
req.headers['content-type'] = 'application/json';
JSONBodyParser(req, res, function(err) {
// Revert content-type to what it originally was.
// This is so we don't inadvertently corrupt `req.headers`--
// our apps' actions might be looking for 'em.
req.headers['content-type'] = backupContentType;
// If an error occurred in the retry, it's not actually an error
// (we can't assume EVERY requeset was intended to be JSON)
if (err) {
// log.verbose('Attempted to retry bodyParse as JSON. But no luck.', err);
}
// Proceed, whether or not the body was parsed.
next();
});
});
});
});
};
};