├── .gitignore ├── .travis.yml ├── test ├── index.js ├── unit.js └── integration.js ├── parse-arguments.js ├── json.js ├── form.js ├── package.json ├── LICENCE ├── any.js ├── index.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | *.err -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | require('./integration.js'); 2 | require('./unit.js'); 3 | -------------------------------------------------------------------------------- /parse-arguments.js: -------------------------------------------------------------------------------- 1 | module.exports = parseArguments 2 | 3 | function isWritable(stream) { 4 | return typeof stream.write === "function" && 5 | typeof stream.end === "function" 6 | } 7 | 8 | function parseArguments(req, res, opts, callback) { 9 | // (req, cb) 10 | if (typeof res === "function") { 11 | callback = res 12 | opts = {} 13 | res = null 14 | } 15 | 16 | // (req, res, cb) 17 | if (typeof opts === "function") { 18 | callback = opts 19 | opts = {} 20 | } 21 | 22 | // (req, opts, cb) 23 | if (res && !isWritable(res)) { 24 | opts = res 25 | res = null 26 | } 27 | 28 | // default (req, res, opts, cb) 29 | return { req: req, res: res, opts: opts, callback: callback } 30 | } 31 | -------------------------------------------------------------------------------- /json.js: -------------------------------------------------------------------------------- 1 | var jsonParse = require("safe-json-parse") 2 | 3 | var body = require("./index.js") 4 | var parseArguments = require("./parse-arguments.js") 5 | 6 | module.exports = jsonBody 7 | 8 | function jsonBody(req, res, opts, callback) { 9 | var args = parseArguments(req, res, opts, callback) 10 | req = args.req 11 | res = args.res 12 | opts = args.opts 13 | callback = args.callback 14 | 15 | if (!callback) { 16 | return jsonBody.bind(null, req, res, opts) 17 | } 18 | 19 | var parse = opts.JSON ? opts.JSON.parse : jsonParse 20 | var reviver = opts.reviver || null 21 | 22 | body(req, res, opts, function (err, body) { 23 | if (err) { 24 | return callback(err) 25 | } 26 | 27 | parse(body, reviver, callback) 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /form.js: -------------------------------------------------------------------------------- 1 | var querystringParse = require("querystring").parse 2 | 3 | var body = require("./index.js") 4 | var parseArguments = require("./parse-arguments.js") 5 | 6 | module.exports = formBody 7 | 8 | function formBody(req, res, opts, callback) { 9 | var args = parseArguments(req, res, opts, callback) 10 | req = args.req 11 | res = args.res 12 | opts = args.opts 13 | callback = args.callback 14 | 15 | if (!callback) { 16 | return formBody.bind(null, req, res, opts) 17 | } 18 | 19 | var parse = opts.querystring ? 20 | opts.querystring.parse : defaultQueryStringParse 21 | 22 | body(req, res, opts, function (err, body) { 23 | if (err) { 24 | return callback(err) 25 | } 26 | 27 | parse(body, callback) 28 | }) 29 | } 30 | 31 | function defaultQueryStringParse(str, callback) { 32 | callback(null, querystringParse(str)) 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "body", 3 | "version": "5.1.0", 4 | "description": "Body parsing", 5 | "keywords": [], 6 | "author": "Raynos ", 7 | "repository": "git://github.com/Raynos/body.git", 8 | "main": "index", 9 | "homepage": "https://github.com/Raynos/body", 10 | "contributors": [ 11 | { 12 | "name": "Jake Verbaten" 13 | } 14 | ], 15 | "bugs": { 16 | "url": "https://github.com/Raynos/body/issues", 17 | "email": "raynos2@gmail.com" 18 | }, 19 | "dependencies": { 20 | "continuable-cache": "^0.3.1", 21 | "error": "^7.0.0", 22 | "raw-body": "~1.1.0", 23 | "safe-json-parse": "~1.0.1" 24 | }, 25 | "devDependencies": { 26 | "after": "~0.7.0", 27 | "hammock": "^1.0.0", 28 | "test-server": "~0.1.3", 29 | "send-data": "~1.0.1", 30 | "tape": "~2.3.0", 31 | "process": "~0.5.1" 32 | }, 33 | "license": : "MIT", 34 | "scripts": { 35 | "test": "node ./test/index.js" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Raynos. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /any.js: -------------------------------------------------------------------------------- 1 | var TypedError = require("error/typed") 2 | 3 | var parseArguments = require("./parse-arguments.js") 4 | var jsonBody = require("./json.js") 5 | var formBody = require("./form.js") 6 | 7 | var jsonType = "application/json" 8 | var formType = "application/x-www-form-urlencoded" 9 | var INVALID_CONTENT_TYPE = TypedError({ 10 | message: "Could not parse content type header: {contentType}", 11 | type: "invalid.content.type", 12 | statusCode: 415, 13 | contentType: null 14 | }) 15 | 16 | module.exports = anyBody 17 | 18 | function anyBody(req, res, opts, callback) { 19 | var args = parseArguments(req, res, opts, callback) 20 | req = args.req 21 | res = args.res 22 | opts = args.opts 23 | callback = args.callback 24 | 25 | if (!callback) { 26 | return anyBody.bind(null, req, res, opts) 27 | } 28 | 29 | var contentType = req.headers["content-type"] || "" 30 | 31 | if (contentType.indexOf(jsonType) !== -1) { 32 | jsonBody(req, res, opts, callback) 33 | } else if (contentType.indexOf(formType) !== -1) { 34 | formBody(req, res, opts, callback) 35 | } else { 36 | callback(INVALID_CONTENT_TYPE({contentType: contentType})) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var rawBody = require("raw-body") 2 | var cache = require("continuable-cache") 3 | 4 | var parseArguments = require("./parse-arguments.js") 5 | 6 | var ONE_MB = 1024 * 1024 7 | var THUNK_KEY = '__npm_body_thunk_cache__'; 8 | 9 | module.exports = body 10 | 11 | function parseBodyThunk(req, res, opts) { 12 | return function thunk(callback) { 13 | var limit = "limit" in opts ? opts.limit : ONE_MB 14 | var contentLength = req.headers ? 15 | Number(req.headers["content-length"]) : null; 16 | 17 | rawBody(req, { 18 | limit: limit, 19 | length: contentLength, 20 | encoding: "encoding" in opts ? opts.encoding : true 21 | }, callback); 22 | }; 23 | } 24 | 25 | function body(req, res, opts, callback) { 26 | var args = parseArguments(req, res, opts, callback) 27 | req = args.req 28 | res = args.res 29 | opts = args.opts 30 | callback = args.callback 31 | 32 | var thunk; 33 | 34 | if (opts.cache) { 35 | var thunk = req[THUNK_KEY] || 36 | cache(parseBodyThunk(req, res, opts)); 37 | req[THUNK_KEY] = thunk; 38 | } else { 39 | thunk = parseBodyThunk(req, res, opts); 40 | } 41 | 42 | if (!callback) { 43 | return thunk; 44 | } 45 | 46 | thunk(callback); 47 | } 48 | -------------------------------------------------------------------------------- /test/unit.js: -------------------------------------------------------------------------------- 1 | var after = require('after'); 2 | var body = require('../index.js'); 3 | var hammock = require('hammock'); 4 | var test = require('tape'); 5 | 6 | test('caching works', function t(assert) { 7 | var request = hammock.Request({ 8 | method: 'GET', 9 | headers: { 10 | 'Content-Type': 'application/json' 11 | }, 12 | url: '/somewhere' 13 | }); 14 | var response = hammock.Response(); 15 | 16 | var done = after(2, assert.end.bind(assert)); 17 | 18 | body(request, response, { cache: true }, function onBody(err, body) { 19 | assert.equal(body, 'thisbody', 'raw body has been set'); 20 | assert.pass('body is parsed'); 21 | done(); 22 | }); 23 | 24 | request.on('end', function() { 25 | body(request, response, { cache: true }, function onBody(err, body) { 26 | assert.equal(body, 'thisbody', 'cached body is provided'); 27 | assert.pass('body is parsed'); 28 | done(); 29 | }); 30 | }); 31 | 32 | request.end('thisbody'); 33 | }); 34 | 35 | test('parallel caching works', function t(assert) { 36 | var request = hammock.Request({ 37 | method: 'GET', 38 | headers: { 39 | 'Content-Type': 'application/json' 40 | }, 41 | url: '/somewhere' 42 | }); 43 | request.end('thisbody'); 44 | var response = hammock.Response(); 45 | 46 | var done = after(5, function() { 47 | process.nextTick(function() { 48 | assert.equal(request.listeners('rawBody').length, 0, 'rawBody listeners cleared'); 49 | assert.end(); 50 | }); 51 | }); 52 | 53 | for (var i = 0; i < 5; ++i) { 54 | body(request, response, { cache: true }, function onBody(err, body) { 55 | assert.equal(body, 'thisbody', 'raw body has been set'); 56 | assert.pass('body is parsed'); 57 | done(); 58 | }); 59 | } 60 | }); 61 | -------------------------------------------------------------------------------- /test/integration.js: -------------------------------------------------------------------------------- 1 | var testServer = require("test-server") 2 | var test = require("tape") 3 | var sendJson = require("send-data/json") 4 | var after = require("after") 5 | 6 | var body = require("../index") 7 | var jsonBody = require("../json") 8 | var formBody = require("../form") 9 | var anyBody = require("../any") 10 | 11 | testServer(handleRequest, runTests) 12 | 13 | function handleRequest(req, res) { 14 | function send(err, body) { 15 | if (err) { 16 | return sendJson(req, res, err.message) 17 | } 18 | 19 | sendJson(req, res, body) 20 | } 21 | 22 | if (req.url === "/body") { 23 | body(req, res, {}, send) 24 | } else if (req.url === "/form") { 25 | formBody(req, res, send) 26 | } else if (req.url === "/json") { 27 | jsonBody(req, {}, send) 28 | } else if (req.url === "/any") { 29 | anyBody(req, send) 30 | } 31 | } 32 | 33 | function runTests(request, done) { 34 | test("body works", function (t) { 35 | t.end = after(2, t.end.bind(t)) 36 | testBody("/body", request, t) 37 | 38 | request({ 39 | uri: "/any", 40 | body: "foo" 41 | }, function (err, res, body) { 42 | t.equal(err, null) 43 | t.equal(JSON.parse(body), "Could not parse content type header: ") 44 | t.end() 45 | }) 46 | }) 47 | 48 | test("form works", function (t) { 49 | t.end = after(2, t.end.bind(t)) 50 | testFormBody("/form", request, t) 51 | testFormBody("/any", request, t) 52 | }) 53 | 54 | test("json works", function (t) { 55 | t.end = after(2, t.end.bind(t)) 56 | testJsonBody("/json", request, t) 57 | testJsonBody("/any", request, t) 58 | }) 59 | 60 | .on("end", done) 61 | } 62 | 63 | function testBody(uri, request, t) { 64 | request({ 65 | uri: uri, 66 | body: "foo" 67 | }, function (err, res, body) { 68 | t.equal(err, null, "error is not null") 69 | 70 | console.log("body", body, JSON.parse(body)) 71 | t.equal(JSON.parse(body), "foo", "body is incorrect") 72 | 73 | t.end() 74 | }) 75 | } 76 | 77 | function testFormBody(uri, request, t) { 78 | request({ 79 | uri: uri, 80 | form: { 81 | foo: "bar" 82 | } 83 | }, function (err, res, body) { 84 | t.equal(err, null, "error is not null") 85 | 86 | t.equal(JSON.parse(body).foo, "bar", "body is incorrect") 87 | 88 | t.end() 89 | }) 90 | } 91 | 92 | function testJsonBody(uri, request, t) { 93 | request({ 94 | uri: uri, 95 | json: { 96 | foo: "bar" 97 | } 98 | }, function (err, res, body) { 99 | t.equal(err, null, "error is not null") 100 | 101 | t.equal(body.foo, "bar", "body is incorrect") 102 | 103 | t.end() 104 | }) 105 | } 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # body [![build status][1]][2] 2 | 3 | Body parsing 4 | 5 | Originally taken from [npm-www](https://github.com/isaacs/npm-www) 6 | 7 | ## Example 8 | 9 | ```js 10 | var textBody = require("body") 11 | var jsonBody = require("body/json") 12 | var formBody = require("body/form") 13 | var anyBody = require("body/any") 14 | var http = require("http") 15 | var sendJson = require("send-data/json") 16 | 17 | http.createServer(function handleRequest(req, res) { 18 | function send(err, body) { 19 | sendJson(req, res, body) 20 | } 21 | 22 | if (req.url === "/body") { 23 | // all functions can be called with (req, cb) 24 | textBody(req, send) 25 | } else if (req.url === "/form") { 26 | // all functions can be called with (req, opts, cb) 27 | formBody(req, {}, send) 28 | } else if (req.url === "/json") { 29 | // all functions can be called with (req, res, cb) 30 | jsonBody(req, res, send) 31 | } else if (req.url === "/any") { 32 | // all functions can be called with (req, res, opts, cb) 33 | anyBody(req, res, {}, send) 34 | } 35 | }) 36 | ``` 37 | 38 | `body` simply parses the request body and returns it in the callback. `jsonBody` and `formBody` call JSON.parse and querystring.parse respectively on the body. 39 | 40 | anyBody will detect the content-type of the request and use the appropiate body method. 41 | 42 | ## Example generators 43 | 44 | You can use `body` with generators as the body functions will 45 | return a continuable if you don't pass a callback. 46 | 47 | ```js 48 | var http = require("http") 49 | var Router = require("routes-router") 50 | var jsonBody = require("body/json") 51 | var formBody = require("body/form") 52 | // async turns a generator into an async function taking a cb 53 | var async = require("gens") 54 | 55 | // the router works with normal async functions. 56 | // router automatically handles errors as 500 responses 57 | var app = Router({ 58 | // do whatever you want. the jsonBody error would go here 59 | errorHandler: function (req, res, err) { 60 | res.statusCode = 500 61 | res.end(err.message) 62 | } 63 | }) 64 | 65 | app.addRoute("/json", async(function* (req, res) { 66 | // if jsonBody has an error it just goes to the cb 67 | // in the called in the router. and it does the correct thing 68 | // it shows your 500 page. 69 | var body = yield jsonBody(req, res) 70 | 71 | res.setHeader("content-type", "application/json") 72 | res.end(JSON.stringify(body)) 73 | })) 74 | 75 | app.addRoute("/form", async(function* (req, res) { 76 | var body = yield formBody(req, res) 77 | 78 | res.setHeader("content-type", "application/json") 79 | res.end(JSON.stringify(body)) 80 | })) 81 | 82 | // app returned from the router is just a function(req, res) {} 83 | // that dispatches the req/res to the correct route based on 84 | // the routers routing table & req.url 85 | http.createServer(app).listen(8080) 86 | ``` 87 | 88 | ## Documentation 89 | 90 | ### `textBody(req, res?, opts?, cb)` 91 | 92 | ```ocaml 93 | textBody := ( 94 | req: HttpRequest, 95 | res?: HttpResponse, 96 | opts?: { 97 | limit?: Number, 98 | cache?: Boolean, 99 | encoding?: String 100 | }, 101 | cb: Callback 102 | ) => void 103 | ``` 104 | 105 | `textBody` allows you to get the body from any readable stream. 106 | It will read the entire content of the stream into memory and 107 | give it back to you in the callback. 108 | 109 | - `limit`: You can set `opts.limit` to a custom number to change the 110 | limit at which `textBody` gives up. By default it will only 111 | read a 1MB body, if a stream contains more then 1MB it returns 112 | an error. This prevents someone attacking your HTTP server 113 | with an infinite body causing an out of memory attack. 114 | - `encoding`: You can set `encoding`. All encodings that are valid on a 115 | [`Buffer`](http://nodejs.org/api/buffer.html#buffer_buffer) are 116 | valid options. It defaults to `'utf8'` 117 | 118 | ```js 119 | var textBody = require("body") 120 | var http = require("http") 121 | 122 | http.createServer(function (req, res) { 123 | textBody(req, res, function (err, body) { 124 | // err probably means invalid HTTP protocol or some shiz. 125 | if (err) { 126 | res.statusCode = 500 127 | return res.end("NO U") 128 | } 129 | 130 | // I am an echo server 131 | res.end(body) 132 | }) 133 | }).listen(8080) 134 | ``` 135 | 136 | ### `formBody(req, res?, opts?, cb)` 137 | 138 | ```ocaml 139 | formBody := ( 140 | req: HttpRequest, 141 | res?: HttpResponse, 142 | opts?: { 143 | limit?: Number, 144 | encoding?: String, 145 | querystring: { 146 | parse: (String, Callback) => void 147 | } 148 | }, 149 | cb: Callback 150 | ) => void 151 | ``` 152 | 153 | `formBody` allows you to get the body of a readable stream. It 154 | does the same as `textBody` but assumes the content is querystring 155 | encoded and parses just like it was a <form> submit. 156 | 157 | - `limit`: same as `textBody` 158 | - `encoding`: same as `textBody` 159 | - `querystring`: You can pass a custom querystring parser if 160 | you want. It should have a `parse` method that takes a 161 | string and a callback. It should return the value in the 162 | callback or a parsing error 163 | 164 | ```js 165 | var formBody = require("body/form") 166 | var http = require("http") 167 | 168 | http.createServer(function (req, res) { 169 | formBody(req, res, function (err, body) { 170 | // err probably means invalid HTTP protocol or some shiz. 171 | if (err) { 172 | res.statusCode = 500 173 | return res.end("NO U") 174 | } 175 | 176 | // I am an echo server 177 | res.setHeader("content-type", "application/json") 178 | res.end(JSON.stringify(body)) 179 | }) 180 | }).listen(8080) 181 | ``` 182 | 183 | ### `jsonBody(req, res?, opts?, cb)` 184 | 185 | ```ocaml 186 | jsonBody := ( 187 | req: HttpRequest, 188 | res?: HttpResponse, 189 | opts?: { 190 | limit?: Number, 191 | encoding?: String, 192 | reviver?: (Any) => Any 193 | JSON?: { 194 | parse: (String, reviver?: Function, Callback) => void 195 | } 196 | }, 197 | cb: Callback 198 | ) => void 199 | ``` 200 | 201 | `jsonBody` allows you to get the body of a readable stream. It 202 | does the same as `textbody` but assumes the content it a JSON 203 | value and parses it using `JSON.parse`. If `JSON.parse` throws 204 | an exception then it calls the callback with the exception. 205 | 206 | - `limit`: same as `textBody` 207 | - `encoding`: same as `textBody` 208 | - `reviver`: A reviver function that will be passed to `JSON.parse` 209 | as the second argument 210 | - `JSON`: You can pass a custom JSON parser if you want. 211 | It should have a `parse` method that takes a string, an 212 | optional reviver and a callback. It should return the value 213 | in the callback or a parsing error. 214 | 215 | ```js 216 | var jsonBody = require("body/json") 217 | var http = require("http") 218 | 219 | http.createServer(function (req, res) { 220 | jsonBody(req, res, function (err, body) { 221 | // err is probably an invalid json error 222 | if (err) { 223 | res.statusCode = 500 224 | return res.end("NO U") 225 | } 226 | 227 | // I am an echo server 228 | res.setHeader("content-type", "application/json") 229 | res.end(JSON.stringify(body)) 230 | }) 231 | }).listen(8080) 232 | ``` 233 | 234 | ### `anyBody(req, res?, opts?, cb)` 235 | 236 | ```ocaml 237 | anyBody := ( 238 | req: HttpRequest, 239 | res?: HttpResponse, 240 | opts?: { 241 | limit?: Number, 242 | encoding?: String, 243 | reviver?: (Any) => Any 244 | JSON?: { 245 | parse: (String, reviver?: Function, Callback) => void 246 | }, 247 | querystring: { 248 | parse: (String, Callback) => void 249 | } 250 | }, 251 | cb: Callback 252 | ) => void 253 | ``` 254 | 255 | `anyBody` allows you to get the body of a HTTPRequest. It 256 | does the same as `textBody` except it parses the `content-type` 257 | header and uses either the jsonBody or the formBody function. 258 | 259 | This allows you to write POST route handlers that work with 260 | both ajax and html form submits. 261 | 262 | - `limit`: same as `textBody` 263 | - `encoding`: same as `textBody` 264 | - `reviver`: same as `jsonBody` 265 | - `JSON`: same as `jsonBody` 266 | - `querystring`: same as `formBody` 267 | 268 | ```js 269 | var anyBody = require("body/any") 270 | var http = require("http") 271 | 272 | http.createServer(function (req, res) { 273 | anyBody(req, res, function (err, body) { 274 | // err is probably an invalid json error 275 | if (err) { 276 | res.statusCode = 500 277 | return res.end("NO U") 278 | } 279 | 280 | // I am an echo server 281 | res.setHeader("content-type", "application/json") 282 | res.end(JSON.stringify(body)) 283 | }) 284 | }).listen(8080) 285 | ``` 286 | 287 | 288 | ## Installation 289 | 290 | `npm install body` 291 | 292 | ## Tests 293 | 294 | `npm test` 295 | 296 | ## Contributors 297 | 298 | - Raynos 299 | 300 | ## MIT Licenced 301 | 302 | [1]: https://secure.travis-ci.org/Raynos/body.png 303 | [2]: http://travis-ci.org/Raynos/body 304 | --------------------------------------------------------------------------------