Skip to content

Commit

Permalink
feat: Add out of the box support for multipart and file upload
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolasdao committed Dec 27, 2017
1 parent 7ea29fa commit dd9e919
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 44 deletions.
46 changes: 16 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,40 +96,26 @@ node index.js
```js
const { listen, serve, app } = require('webfunc')

const server = serve('/users/:username', (req, res, params) => res.status(200).send(`Hello env ${params.username}`))
const server = serve(
[
// Create standard GET endpoints.
app.get('/users/:username', (req, res, params) => res.status(200).send(`Hello ${params.username}`)),
// Define multiple routes that trigger the same logic.
app.get(['/companies/:companyName', '/organizations/:orgName'], (req, res, params) => res.status(200).send(params.companyName ? `Hello company ${params.companyName}` : `Hello organization ${params.orgName}`)),
// Support for any standard http verbs (GET, POST, PUT, DELETE, HEAD, OPTIONS). In the example below, the payload is expected to be
// similar to { "username": "Nicolas", password: "1234" }. More details about body parsing below.
app.post('/login', (req, res, params) => res.status(200).send(`Welcome ${params.username}`)),
// Create endpoints that accept any http verbs.
app.any('/', (req, res) => res.status(200).send('Welcome to this awesome API!'))
])
eval(listen('server', 4000))
```

### CORS & Body Parsing Supported Out Of The Box
Those 2 features being so common, we decided to include them. That means no need for middleware such as [CORS](https://github.com/expressjs/cors) or [body-parser](https://github.com/expressjs/body-parser).

In its simplest form, a Google Cloud Functions project looks like this:
```js
exports.yourapp = (req, res) => {
res.status(200).send('Hello World')
}
```

Simply update it as follow:
```js
const { serveHttp } = require('webfunc')

exports.yourapp = serveHttp((req, res) => {
res.status(200).send('Hello World')
})
```

And if you want to add more routes:

```js
const { serveHttp, app } = require('webfunc')

exports.yourapp = serveHttp([
app.get('/', (req, res) => res.status(200).send('Hello World')),
app.get('/users/{userId}', (req, res, params) => res.status(200).send(`Hello user ${params.userId}`)),
app.get('/users/{userId}/document/{docName}', (req, res, params) => res.status(200).send(`Hello user ${params.userId}. I like your document ${params.docName}`)),
])
```

Easy isn't it!?
#### Body Parsing & Query Parameters
With webfunc, query parameters and the payload are treated equal. That means that they are all aggregated

To configure CORS, add a new _**appconfig.json**_ file and configure it as explained in the next section.

Expand Down
76 changes: 62 additions & 14 deletions src/webfunc.js
Original file line number Diff line number Diff line change
Expand Up @@ -364,24 +364,72 @@ const listen = (functionName, port) => {
}
}

/*eslint-disable */
const utf8ToHex = s => s ? Buffer.from(s).toString('hex') : ''
const hexToUtf8 = h => h ? Buffer.from(h, 'hex').toString() : ''
const hexToBuf = h => h ? Buffer.from(h, 'hex') : new Buffer(0)
/*eslint-enable */

const HEX_CAR_RTN_01 = utf8ToHex('--\r\n')
const HEX_CAR_RTN_02 = utf8ToHex('\r\n')
const HEX_BOUNDARY_TRAIL = utf8ToHex('--')
const HEX_DBL_CAR_RTN = utf8ToHex('\r\n\r\n')
const HEX_REGEX_TRAIL_CAR_RTN = new RegExp(HEX_CAR_RTN_02 + '$')

const getRequestParameters = req => {
const getBody = req.body ? Promise.resolve(req.body) : getRawBody(req, { encoding: true })
return getBody.then(body => {
console.log(typeof(body))
const headers = req.headers || []
const contentType = (headers['content-type'] || headers['Content-Type'] || headers['ContentType'] || '')
const isMultipart = contentType.match(/form-data|multipart/)
const isXwwwFormUrlencoded = contentType.indexOf('x-www-form-urlencoded') >= 0
const getBody = !isMultipart && req.body ? Promise.resolve({ encoded: false, body: req.body}) : getRawBody(req).then(b => ({ encoded: true, body: b}))
return getBody.then(bod => {
let bodyParameters = {}
if (body) {
const bodyType = typeof(body)
if (bodyType == 'object')
bodyParameters = body
else if (bodyType == 'string') {
try {
bodyParameters = JSON.parse(body)
}
catch(err) {
bodyParameters = {}
console.log(err)
if (bod.body) {
// Deal with standard encoding
if (!isMultipart) {
const body = bod.encoded ? bod.body.toString() : bod.body
const bodyType = typeof(body)
if (bodyType == 'object')
bodyParameters = Object.assign({ body: body }, body)
else if (bodyType == 'string') {
try {
if (isXwwwFormUrlencoded)
bodyParameters = body.split('&').filter(x => x).reduce((acc,x) => {
const [k,v] = x.split('=')
const key = k ? unescape(k) : null
const value = v ? unescape(v) : ''
if (key)
acc[key] = value
return acc
}, { body: body })
else
bodyParameters = Object.assign({ body: bod.body }, JSON.parse(body))
}
catch(err) {
bodyParameters = { body: bod.body }
}
}
}
// Deal with multipart encoding
else {
const bodyHex = bod.body.toString('hex')
const boundary = '--' + (contentType.split('boundary=') || [null, ''])[1]
const boundaryHex = utf8ToHex(boundary)

bodyParameters = (bodyHex.split(boundaryHex) || [])
.filter(x => x && x != HEX_CAR_RTN_01 && x != HEX_BOUNDARY_TRAIL)
.reduce((acc, x) => {
const [meta='', val=''] = (x.split(HEX_DBL_CAR_RTN).filter(y => y) || [null, null])
const metadata = hexToUtf8(meta) + ' '
const value = val.replace(HEX_REGEX_TRAIL_CAR_RTN, '')
const name = (metadata.match(/ name="(.*?)"/) || [null, null])[1]
const filename = (metadata.match(/ filename="(.*?)"/) || [null, null])[1]
const mimetype = (metadata.match(/Content-Type: (.*?) /) || [null, null])[1]
if (name)
acc[name] = { value: hexToBuf(value), filename, mimetype }
return acc
}, { body: bod.body })
}
}

const parameters = Object.assign((bodyParameters || {}), req.query || {})
Expand Down

0 comments on commit dd9e919

Please sign in to comment.