Skip to content

feat: reply trailers support#3794

Merged
mcollina merged 15 commits intofastify:mainfrom
climba03003:feat-trailers
Mar 27, 2022
Merged

feat: reply trailers support#3794
mcollina merged 15 commits intofastify:mainfrom
climba03003:feat-trailers

Conversation

@climba03003
Copy link
Copy Markdown
Member

@climba03003 climba03003 commented Mar 25, 2022

Closes #630

Some question

  1. Is it fine for the payload as null when stream is sent?
  2. Is payload.on('end') correct for stream handling and add trailers?
  3. Is it fine for trailer only support sync function?

Checklist

@s9tpepper
Copy link
Copy Markdown

Is it fine for trailer only support sync function?

I feel like it's already supporting an asynchronous operation that by the time this callback is invoked all the inputs should be available to calculate the trailing values you need.

At the same time, I can see it useful to support async callbacks for cases where someone might want to use an api call in order to calculate a value you want to send in a trailing header. I think this would make it most flexible, but would also give more rope to the framework user to do lots of extraneous network calls before resolving a header value. I suppose its not necessarily terrible since the response to the client has already been sent.

@climba03003
Copy link
Copy Markdown
Member Author

climba03003 commented Mar 25, 2022

I feel like it's already supporting an asynchronous operation that by the time this callback is invoked all the inputs should be available to calculate the trailing values you need.

At the same time, I can see it useful to support async callbacks for cases where someone might want to use an api call in order to calculate a value you want to send in a trailing header. I think this would make it most flexible, but would also give more rope to the framework user to do lots of extraneous network calls before resolving a header value. I suppose its not necessarily terrible since the response to the client has already been sent.

I can think of a solution for async trailer if the payload is string or buffer.
But when it comes to stream, it is a tough one as the response will end much sooner than I think.

At least, I need somebody help on the area of stream async trailer.

@mcollina
Copy link
Copy Markdown
Member

I'm not sure this is needed to solve the etag case. In the vast majority of cases we would want to return a 304 - the content can be cached. In that case you'd need to compute the etag before sending it. This is not very efficient.

As you can read at https://serverfault.com/questions/690341/algorithm-behind-nginx-etag-generation, NGINX does not read the full file to compute the etag.

I have never found a good use case for trailers and in practices they are not very commonly used.

@climba03003
Copy link
Copy Markdown
Member Author

I'm not sure this is needed to solve the etag case. In the vast majority of cases we would want to return a 304 - the content can be cached. In that case you'd need to compute the etag before sending it. This is not very efficient.

As you can read at https://serverfault.com/questions/690341/algorithm-behind-nginx-etag-generation, NGINX does not read the full file to compute the etag.

I have never found a good use case for trailers and in practices they are not very commonly used.

I never use the trailers before, but if somebody need it. The current code do not support it in a good way.
It must compute the trailers before sending the content, that means the trailers isn't take advantage of trailers anymore.

I am good with the current proposal for the minimal support of trailers.
It can return the Server-Timing like the discussed issue.

@zekth
Copy link
Copy Markdown
Member

zekth commented Mar 25, 2022

That's cool!
I feel like Server-Timing might be worth to be a plugin on its own. But exposing core metrics of Fastify might be a good thing to be consumed by the Server-timing plugin. Don't you think so?
I'd see some kind of api like:

fastify.get('/', function (request, reply) {
  reply.addTiming('reqLifecycle', rrequest.RequestLifecyleTime)
  reply.addTiming('routingTime', request.RoutingTime)
  reply.send(data)
})

Edit: ok there already is a Server-Timing plugin my bad

@s9tpepper
Copy link
Copy Markdown

I'm not sure this is needed to solve the etag case. In the vast majority of cases we would want to return a 304 - the content can be cached. In that case you'd need to compute the etag before sending it. This is not very efficient.

As you can read at https://serverfault.com/questions/690341/algorithm-behind-nginx-etag-generation, NGINX does not read the full file to compute the etag.

I have never found a good use case for trailers and in practices they are not very commonly used.

This is from MDN:

The method by which ETag values are generated is not specified. Typically, the ETag value is a hash of the content, a hash of the last modification timestamp, or just a revision number. 

There are two types of etags, weak and strong. Date and content length hashes are a good approach at generated weak etags. But if some content has a one character typo that is correct then, for example, a base64 hash of date+contentLength would not be great.

In regards to having to read the full file contents up front, that scenario is only necessary if the client has sent a If-Match or an If-None-Match header, in which case you'd need to read the file contents and calculate the strong etag to check for the match. Otherwise, if no headers then the client can receive the contents asap and the etag in a trailer.

@mcollina
Copy link
Copy Markdown
Member

In regards to having to read the full file contents up front, that scenario is only necessary if the client has sent a If-Match or an If-None-Match header, in which case you'd need to read the file contents and calculate the strong etag to check for the match. Otherwise, if no headers then the client can receive the contents asap and the etag in a trailer.

Considering how CDNs works, the if-match and if-none-match cases are the most common ones. You shouldn't be serving files from Node.js anyway.

@s9tpepper
Copy link
Copy Markdown

In regards to having to read the full file contents up front, that scenario is only necessary if the client has sent a If-Match or an If-None-Match header, in which case you'd need to read the file contents and calculate the strong etag to check for the match. Otherwise, if no headers then the client can receive the contents asap and the etag in a trailer.

Considering how CDNs works, the if-match and if-none-match cases are the most common ones. You shouldn't be serving files from Node.js anyway.

I'm not sure what you mean here, I shouldn't be serving files from node.js? This is confusing, isn't that what fastify.js is basically facilitating here? And yes, if-match/if-none-match are the most common cases, how do you suppose the CDN validates the etags it has cached with the etags its receiving from clients like browsers if not to ask an origin server that may or may not be running fastify.js? I'm confused as to what you're implying.

@mcollina
Copy link
Copy Markdown
Member

I'm not sure what you mean here, I shouldn't be serving files from node.js?

NGINX and others provide better performance for serving files from disk.

And yes, if-match/if-none-match are the most common cases, how do you suppose the CDN validates the etags it has cached with the etags its receiving from clients like browsers if not to ask an origin server that may or may not be running fastify.js?

Browser (not cached) -> CDN (cached) -> If-None-Match header is automatically added by the CDN -> Fastify.

Therefore you are almost always hitting the case of having to compute the etag before starting to send the content. Doing it as a trailer only adds complexity with extremely limited gain.

Finally quite a few CDNs do not support trailers.

@s9tpepper
Copy link
Copy Markdown

s9tpepper commented Mar 25, 2022

I'm trying for that gain as this does not apply to a simple single website. Our cluster runs lots of websites and we incur millions of requests per half hour, so small incremental gains all add up quickly, which is why I was trying to do it with this approach, and our CDN supports them just fine.

We also use fastify.js because our entire application and build pipeline is entirely javascript, and our web server doesn't only serve static files, that is just a portion of the application.

Copy link
Copy Markdown
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs are missing too.

@climba03003 climba03003 requested a review from mcollina March 26, 2022 01:08
### .trailer(key, function)
<a id="trailer"></a>

Sets a response trailer.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please
document in which moments we call the functions as well as that the functions must be synchronous.

@climba03003 climba03003 requested a review from mcollina March 26, 2022 09:17
Co-authored-by: Tomas Della Vedova <delvedor@users.noreply.github.com>
@climba03003 climba03003 requested a review from delvedor March 26, 2022 15:16
Copy link
Copy Markdown
Member

@delvedor delvedor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Copy Markdown
Member

@zekth zekth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job!

@mcollina mcollina added semver-minor Issue or PR that should land as semver minor backport 3.x Issue or pr that should be backported to Fastify v3 labels Mar 27, 2022
Copy link
Copy Markdown
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@mcollina mcollina merged commit 8a1234e into fastify:main Mar 27, 2022
mcollina pushed a commit that referenced this pull request Apr 4, 2022
* feat: reply trailers support (#3794)

* chore: remove content-length when Transfer-Encoding is added (#3814)

Co-authored-by: 小菜 <xtx1130@gmail.com>
@github-actions
Copy link
Copy Markdown

This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 28, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

backport 3.x Issue or pr that should be backported to Fastify v3 semver-minor Issue or PR that should land as semver minor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support HTTP/1.1 trailers

7 participants