mjackson/mach

"Can parse content" answer for `undefined` mediaType

Schoonology opened this issue · 4 comments

I'm happy to submit a pull request solving this problem, but I want to talk through the design first.

The server I'm currently working on is getting some bad JSON requests. I cannot change the client, so I have to work with what I've got. That said, it's not providing a Content-Type header, so the Request's mediaType property is undefined. That, in turn, makes its canParseContent property true, and parseContent tries to handle the content as if it's application/x-www-form-urlencoded.

Questions:

  • What should canParseContent be in this situation?
  • If we continue to try to parse content without a Content-Type, should we try { JSON.parse(...) } as a part of our guessing game?
  • Should Request have a configurable "defaultContentType" to attempt if it's undefined?

When the Content-Type header is missing a mach.Request should only assume the request is application/x-www-form-urlencoded (and therefore parseable) if it's a POST, which I assume you're getting. If this is the case then I'd like to leave the behavior of canParseContent as-is since this is the default behavior of web browsers submitting forms.

If your only problem is that the request is missing the Content-Type header, my recommended would be to force mach to use its JSON parser on the request content. You can do this by manually set the Content-Type in your request handler, e.g.:

function (request) {
  // Force mach to handle the request as JSON.
  request.headers['Content-Type'] = 'application/json';

  return request.parseContent().then(function (params) {
    // The client gave us valid JSON.
    return 200;
  }, function (error) {
    // The request body failed to parse as JSON. Maybe it's not valid?
    return 403;
  });
}

It might be a good idea to put this in a middleware.

function enforceJson(app) {
  return function (request) {
    var headers = request.headers;
    if (!headers['Content-Type']) headers['Content-Type'] = 'application/json';
    return request.call(app);
  }
}

Does that help?

@Schoonology Just following up on this. Did I answer your question?

Thanks for following up on this. You definitely answered the question, and I think I wound up doing something similar. It's on a personal project I haven't been able to visit for a while (hence the radio silence), but I'll take a look tonight.

I'll confirm that your expectations match what I was experiencing, and close or comment appropriately.

For posterity, the solution above solves the problem nicely. An alternative approach is to build middleware that always parses the content as JSON within said middleware:

stack.use(function (app) {
  return function (request) {
    return mach.utils.bufferStream(request.content)
      .then(function (body) {
        try {
          request.body = JSON.parse(String(body))
        } catch(e) {
          request.body = {}
        }

        return request.call(app)
      })
  }
})