Skip to content

Latest commit

 

History

History
255 lines (206 loc) · 8.29 KB

ContentTypeParser.md

File metadata and controls

255 lines (206 loc) · 8.29 KB

Fastify

Content-Type Parser

Fastify natively supports 'application/json' and 'text/plain' content types with a default charset of utf-8. These default parsers can be changed or removed.

Unsupported content types will throw an FST_ERR_CTP_INVALID_MEDIA_TYPE error.

To support other content types, use the addContentTypeParser API or an existing plugin.

As with other APIs, addContentTypeParser is encapsulated in the scope in which it is declared. If declared in the root scope, it is available everywhere; if declared in a plugin, it is available only in that scope and its children.

Fastify automatically adds the parsed request payload to the Fastify request object, accessible via request.body.

Note that for GET and HEAD requests, the payload is never parsed. For OPTIONS and DELETE requests, the payload is parsed only if a valid content-type header is provided. Unlike POST, PUT, and PATCH, the catch-all parser is not executed, and the payload is simply not parsed.

⚠ Security Notice

When using regular expressions to detect Content-Type, it is important to ensure proper detection. For example, to match application/*, use /^application\/([\w-]+);?/ to match the essence MIME type only.

Usage

fastify.addContentTypeParser('application/jsoff', function (request, payload, done) {
  jsoffParser(payload, function (err, body) {
    done(err, body)
  })
})

// Handle multiple content types with the same function
fastify.addContentTypeParser(['text/xml', 'application/xml'], function (request, payload, done) {
  xmlParser(payload, function (err, body) {
    done(err, body)
  })
})

// Async is also supported in Node versions >= 8.0.0
fastify.addContentTypeParser('application/jsoff', async function (request, payload) {
  const res = await jsoffParserAsync(payload)

  return res
})

// Handle all content types that matches RegExp
fastify.addContentTypeParser(/^image\/([\w-]+);?/, function (request, payload, done) {
  imageParser(payload, function (err, body) {
    done(err, body)
  })
})

// Can use default JSON/Text parser for different content Types
fastify.addContentTypeParser('text/json', { parseAs: 'string' }, fastify.getDefaultJsonParser('ignore', 'ignore'))

Fastify first tries to match a content-type parser with a string value before trying to find a matching RegExp. For overlapping content types, it starts with the last one configured and ends with the first (last in, first out). To specify a general content type more precisely, first specify the general type, then the specific one, as shown below.

// Here only the second content type parser is called because its value also matches the first one
fastify.addContentTypeParser('application/vnd.custom+xml', (request, body, done) => {} )
fastify.addContentTypeParser('application/vnd.custom', (request, body, done) => {} )

// Here the desired behavior is achieved because fastify first tries to match the
// `application/vnd.custom+xml` content type parser
fastify.addContentTypeParser('application/vnd.custom', (request, body, done) => {} )
fastify.addContentTypeParser('application/vnd.custom+xml', (request, body, done) => {} )

Using addContentTypeParser with fastify.register

When using addContentTypeParser with fastify.register, avoid await when registering routes. Using await makes route registration asynchronous, potentially registering routes before addContentTypeParser is set.

Correct Usage

const fastify = require('fastify')();


fastify.register((fastify, opts) => {
  fastify.addContentTypeParser('application/json', function (request, payload, done) {
    jsonParser(payload, function (err, body) {
      done(err, body)
    })
  })

  fastify.get('/hello', async (req, res) => {});
});

In addition to addContentTypeParser, the hasContentTypeParser, removeContentTypeParser, and removeAllContentTypeParsers APIs are available.

hasContentTypeParser

Use the hasContentTypeParser API to check if a specific content type parser exists.

if (!fastify.hasContentTypeParser('application/jsoff')){
  fastify.addContentTypeParser('application/jsoff', function (request, payload, done) {
    jsoffParser(payload, function (err, body) {
      done(err, body)
    })
  })
}

removeContentTypeParser

removeContentTypeParser can remove a single content type or an array of content types, supporting both string and RegExp.

fastify.addContentTypeParser('text/xml', function (request, payload, done) {
  xmlParser(payload, function (err, body) {
    done(err, body)
  })
})

// Removes the both built-in content type parsers so that only the content type parser for text/html is available
fastify.removeContentTypeParser(['application/json', 'text/plain'])

removeAllContentTypeParsers

The removeAllContentTypeParsers API removes all existing content type parsers eliminating the need to specify each one individually. This API supports encapsulation and is useful for registering a catch-all content type parser that should be executed for every content type, ignoring built-in parsers.

fastify.removeAllContentTypeParsers()

fastify.addContentTypeParser('text/xml', function (request, payload, done) {
  xmlParser(payload, function (err, body) {
    done(err, body)
  })
})

Notice: function(req, done) and async function(req) are still supported but deprecated.

Body Parser

The request body can be parsed in two ways. First, add a custom content type parser and handle the request stream. Or second, use the parseAs option in the addContentTypeParser API, specifying 'string' or 'buffer'. Fastify will handle the stream, check the maximum size of the body, and the content length. If the limit is exceeded, the custom parser will not be invoked.

fastify.addContentTypeParser('application/json', { parseAs: 'string' }, function (req, body, done) {
  try {
    const json = JSON.parse(body)
    done(null, json)
  } catch (err) {
    err.statusCode = 400
    done(err, undefined)
  }
})

See example/parser.js for an example.

Custom Parser Options
  • parseAs (string): 'string' or 'buffer' to designate how the incoming data should be collected. Default: 'buffer'.
  • bodyLimit (number): The maximum payload size, in bytes, that the custom parser will accept. Defaults to the global body limit passed to the Fastify factory function.

Catch-All

To catch all requests regardless of content type, use the '*' content type:

fastify.addContentTypeParser('*', function (request, payload, done) {
  let data = ''
  payload.on('data', chunk => { data += chunk })
  payload.on('end', () => {
    done(null, data)
  })
})

All requests without a corresponding content type parser will be handled by this function.

This is also useful for piping the request stream. Define a content parser like:

fastify.addContentTypeParser('*', function (request, payload, done) {
  done()
})

And then access the core HTTP request directly for piping:

app.post('/hello', (request, reply) => {
  reply.send(request.raw)
})

Here is a complete example that logs incoming json line objects:

const split2 = require('split2')
const pump = require('pump')

fastify.addContentTypeParser('*', (request, payload, done) => {
  done(null, pump(payload, split2(JSON.parse)))
})

fastify.route({
  method: 'POST',
  url: '/api/log/jsons',
  handler: (req, res) => {
    req.body.on('data', d => console.log(d)) // log every incoming object
  }
})

For piping file uploads, check out @fastify/multipart.

To execute the content type parser on all content types, call removeAllContentTypeParsers first.

// Without this call, the request body with the content type application/json would be processed by the built-in JSON parser
fastify.removeAllContentTypeParsers()

fastify.addContentTypeParser('*', function (request, payload, done) {
  const data = ''
  payload.on('data', chunk => { data += chunk })
  payload.on('end', () => {
    done(null, data)
  })
})