-
Notifications
You must be signed in to change notification settings - Fork 984
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add support for non-strict formatters #1721
Conversation
For those who are not able to access the internal Netflix repo linked above, the no-op formatter looks like this: const formatter = (req, res, body) => body;
...
restify.createServer({
formatters: {
'text/html': formatter,
'text/css': formatter,
...
}
}); It's not a great ergonomic, because if a developer forgets to add a line for each content-type they want to send, and don't remember to use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a 7to8 guide to here:
https://github.com/restify/node-restify/tree/master/docs/guides
I'm a 👍 for this change. Definitely don't want to magically pick a content-type without developer input. An additional option would be to allow a developer to specify a fallback or default content-type at initialization time that would then be used if there wasn't anything else specified on the request. |
docs/index.md
Outdated
If you're using `res.send()` restify will determine the content-type to respond | ||
with by: | ||
|
||
1. negotiating the content-type by matching available formatters with the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we reorder this list to reflect the order of lookup in the code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way I ordered it is by order of priority, which is the reverse of the order in which the code runs those checks. Does that make sense?
If so would you still prefer to order it the same way the code performs those checks or should I keep it the way it is and mention that it's ordered by priority?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see now, I was expecting it the other way around. Maybe a note to say "ascending priority" for clarity, then?
lib/response.js
Outdated
self.setHeader('Content-Type', type); | ||
|
||
if (!formatter) { | ||
return flush(self, body); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was wondering if we should assert on this unformatted body to ensure it is either string/buffer, but I realized we call flush()
in many places so it may be outside of the scope of this PR. I would expect res.write to throw in that scenario, but it may be better if we can catch it earlier and provide a better error message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good point. We're already asserting on that in the sendRaw
case, so we might as well do the same as you suggest in this case.
Apologies, one more q - does this effectively remove the need for |
I don't think it does. With this change
|
@DonutEspresso I just pushed a second commit that changes the logic in
Let me know if that looks better to you! |
@cprussin Would it be worth it for you to test in a branch of your application that this PR addresses your use cases before we merge it? |
lib/response.js
Outdated
return flush(self, formatter(self.req, self, body)); | ||
// if no formatting, assert that the value to be written is a string or | ||
// a buffer, then send it. | ||
assert.ok( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized that the formatters don't necessarily enforce a proper return value, so could we benefit from moving this assertion into flush()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DonutEspresso Done in the commit I just pushed (force-pushed unintentionally, sorry for that).
c282890
to
7af4e37
Compare
Another approach that I haven't mentioned so far would be to "rename" @restify/core Any preference between the approach currently implemented in this PR (making formatters optional and their usage implicit) and the one I just mentioned (making using formatters more explicit)? I don't have a strong preference either way, but I thought I'd make sure we present various approaches before committing, as those changes are all breaking. |
Update on current status: had a discussion with @DonutEspresso and @mridgway and we decided to make this change non-breaking and opt-in via a flag passed at server creation time. This will:
If/when we're confident this new behavior is suitable for all consumers, we can propose to enable it by default as a breaking change. |
7af4e37
to
05843fa
Compare
05843fa
to
20566cf
Compare
@restify/core This is ready for another (hopefully final) round of review. Thanks for your patience and my apologies for the unnecessary churn on that one 🙏 @hekike Since this is no longer a breaking change, we don't need a migration guide anymore. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this looks good! One thought - because it is possible to have both formatters
and optionalFormatters
defined, I found it confusing at first to think about what the end behavior would be. At first I thought it might mean that formatters are skipped entirely, but that's not the case.
Would it be less confusing if the option name was strictFormatters
, which defaults to true and enforces the current RFC behavior?
@@ -346,6 +348,11 @@ function patch(Response) { | |||
}; | |||
} | |||
|
|||
assert.ok( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👌
test/formatter-optional.test.js
Outdated
// res.send still sends a successful response even when a formatter is not | ||
// set up for a specific content-type. | ||
SERVER.get('/11', function handle(req, res, next) { | ||
console.log('got request'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be removed? One in the subsequent test too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes good catch :)
I went for |
@DonutEspresso I believe I addressed your latest comments, do you mind taking another look? Thanks! |
🎉 |
Pre-Submission Checklist
make prepush
Issues
I did not create an issue prior to submitting this PR, as I thought looking at the rationale for this change without looking at the actual code change could be confusing and less productive. Please let me know if you'd still like me to open an issue.
Changes
This PR makes
res.send
not override any content-type that was previously set on the response even when no formatter can be found for that content-type.The goal of this PR is to improve developers' experience. Silently overriding the content-type of the response when no formatter is present seems implicit and surprising. I think we should aim for behavior that is less surprising.
One consequence of this PR is that it decouples the content-type from formatters as long as the content-type is set explicitly (e.g. using
res.setHeader('content-type', ...)
orres.contentType = ...
). In those cases, it effectively makes formatters optional.If no content-type is set explicitly on the response, the request would still result in an error if content-type negotiation failed to match the set of accepted content types from the client's request's
accept
header and the set of formatters available to the server.Another approach I considered was to make
res.send
return or throw an error in this case and having users who run into that issue useres.sendRaw
. However it seems that while that behavior would be even more explicit, the current approach seems more flexible and natural for at least some of our partners.For instance, the approach implemented in this PR is what some developers at Netflix seem to expect from a HTTP framework. Using
res.sendRaw
feels very unnatural to them and would require that they write linting rules to catch when their developers useres.send
instead ofres.sendRaw
. This change would allow them to get rid of the no-op formatter they wrote to work around this issue while still usingres.send
.@restify/core I'd like to read your thoughts on this.