-
Notifications
You must be signed in to change notification settings - Fork 191
Conversation
/// </summary> | ||
/// <param name="callback">The callback to invoke before writing the trailers.</param> | ||
/// <param name="state">The state to pass into the callback.</param> | ||
void OnStarting(Func<object, Task> callback, object state); |
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.
Nit: Maybe call this OnWritingResponseTrailers.
I get that my suggested name is a little redundant with the name of the interface, but I think the current naming is too similar to IHttpResponseFeature.OnStarting and could cause confusion.
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's already an .OnSendingHeaders
, maybe an .OnSendingTrailerHeaders
would work?
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's an .OnSendingHeaders
? I think that's the OWIN key. It's called OnStarting
in the ASP.NET Core abstractions. That's why I chose this name, for consistency. I'm fine with whatever.
HttpAbstractions/src/Microsoft.AspNetCore.Http.Features/IHttpResponseFeature.cs
Lines 42 to 49 in b85ed9d
/// <summary> | |
/// Registers a callback to be invoked just before the response starts. This is the | |
/// last chance to modify the <see cref="Headers"/>, <see cref="StatusCode"/>, or | |
/// <see cref="ReasonPhrase"/>. | |
/// </summary> | |
/// <param name="callback">The callback to invoke when starting the response.</param> | |
/// <param name="state">The state to pass into the callback.</param> | |
void OnStarting(Func<object, Task> callback, object state); |
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 I see what you mean. Shame it wasn't called something more like the owin feature then. Consistency would probably win here.
/// Indicates if the server has started writing the response trailers. | ||
/// If true, the <see cref="Trailers"/> are now immutable, and <see cref="OnStarting"/> should no longer be called. | ||
/// </summary> | ||
bool HasStarted { get; } |
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.
WasWritten?
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 would indicate that it's set after the trailers has been written? This would be set just before the server starts writing.
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'm not too convinced WasWritten is a better name either.
I proposed WasWritten since the Trailers dictionary is closed for modification and the server has committed to writing what's in the dictionary as trailers at the point this property is set. From the perspective of the app developer, I don't see how committing to writing the trailers is observably different from actually writing the trailers especially given that there's write-behind response buffer.
HasStarted isn't bad though. I was thinking HasStartedWriting to make it less ambiguous, but I suggested WasWritten since that's shorter.
If we decided to change OnStarting to OnStartingTrailers or something similar, we might want to change this to HasStartedTrailers instead.
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.
Agan, I chose HasStarted
because it's consistent with IHttpResponseFeature.HasStarted
. If we can find another name, or get rid of the property entirely, that's cool with me.
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.
IsReadOnly? IHeaderDictionary already has that property, we can probably remove this one.
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.
Why would the trailer dictionary not be mutable? I think I'm missing something in the IsReadOnly comment
Ok, got it. On the trailer header dictionary. Makes sense.
I'm not too familiar with the Trailer spec, so I don't know what should be done as far as validation goes. I do like have some sort of callback that gets invoked before writing trailers though. |
Of interest is the current conversations in http bis and fetch. For example httpwg/http-core#16 From this, I'd think that if you say "i'll send trailers", the fact that you do in the end or not is not problematic for the client, so I'd assume it's ok to not send them. Plus, if it wasn't, you're way way way down in the response sending, so there's little you can do about it.
From a dev perspective, magic = bad, so i'd throw immediately if you try and write a trailer you didn't say you would write before the non-trailered headers are sent. Silently erroring out doesn't help anyone in this context.
Headers that are forbidden to be sent in trailers from the 1.1 spec could throw, as there is no way anyone could find any use whatsoever for breaking the spec. Knowning that, it means that you'll ever only be able to validate those headers you decided on at that point, as adding more forbidden headers in the future would break compat. So i'd thnk throw on 1.1 forbidden headers, and if that list gets extended in the future, then add an opt out for people upgrading. It's easier to stop an exclusion than to start an exclusion |
Of interest, in go: in nodejs: https://nodejs.org/api/http.html#http_response_addtrailers_headers May be interesting to have a look at other platforms |
Yeah, looks like this is a good list; |
Hmm. What do you think about just a single method like
You'd have to call the method to declare all trailers before It also answers the other questions, as there would be no way to add more (or less) trailers than those declared in the header, plus you can validate them all up-front, before the response has started. |
That looks good. I'm gonna have a think and see if there are scenarios this wouldn't allow for. |
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.
@khellang not a fan of DeclareTrailer, that looks like a higher layer wrapper over the current minimal mechanics. Consider if you have one component declaring the trailers and another providing them.
/// Indicates if the server has started writing the response trailers. | ||
/// If true, the <see cref="Trailers"/> are now immutable, and <see cref="OnStarting"/> should no longer be called. | ||
/// </summary> | ||
bool HasStarted { get; } |
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.
IsReadOnly? IHeaderDictionary already has that property, we can probably remove this one.
|
||
namespace Microsoft.AspNetCore.Http.Features | ||
{ | ||
public class HttpResponseTrailersFeature : IHttpResponseTrailersFeature |
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.
Where do you expect to need this implementation? I expect the servers to provide their own.
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.
Where do you expect to need this implementation?
I don't. I just saw the other features had "dummy" implementations, so just followed along.
Maybe. The problem is just that I think these mechanics are too minimal. Hence the questions in the initial post. The
Yeah, that's problematic for many reasons. How do you coordinate between those components? Do you have an example in mind? |
The feature interfaces are supposed to be as low level and as flexible as possible. It's only at the HttpContext layer that we try to provide more explicit usage patterns. A basic example would be that I know all responses will included some set of trailers. It's quite easy and most efficient to declare a flat list up front. However, the code responsible for generating each those values is distributed throughout the response processing. Consider one that gives you database time, one for serialization time, etc.. With DeclareTrailer you'd have to stash that information as it's generated and then retrieve it later when the callback is invoked. I'd much rather be able to add it to the response when it's generated. |
Separate question: is symmetry important? Are there scenarios we're interested in that require both request and response trailers? Or are response trailers the majority case? I ask because Http.Sys can't support request trailers, but it can support response trailers. Not sure if you can do response trailers on IIS. Kestrel can do both. |
With DeclareTrailer you'd have to stash that information as it's generated and then retrieve it later when the callback is invoked. I'd much rather be able to add it to the response when it's generated. Ok, I delved a bit more into this. For example, in OpenRasta we have a single tap pipeline that gets auto wired-up at various stages in http processing, one being response coding, which a component can declare to want to execute before of. We don't currently have a separate part of the pipeline for ResponseSending, they're assumed to be one and the same. Trailers would allow me to add an extra I attach some code that would be what I expect to write: public class ResponseWriteTimings : IPipelienContributor
{
StopWatch timer;
public void Initialize(IPipeline builder) {
builder.Notify(RegisterTrailer).Before<KnownStage.IResponseCoding();
builder.NotifyAsync(WriteTrailer).Before<KnownStages.IResponseTrailerSending();
}
void RegisterTrailer(ICommunicationContext context) {
context.Response.Trailer += "Server-Timing";
// initialise StopWatch
}
void WriteTrailer(ICommunicationContext context) {
context.Response.Headers.ServerTiming += new Timing(stopWatch.ElapsedMilliseconds);
}
} Any model that wouldn't break something along those lines would allow us to implement that feature, when the server supports it. Request trailers would work the same way, so I would need a rather symetric appraoch for the request pipeline. On a side note, as we don't use the asp.net object model for http headers, i'd rather we had as low level as possible access to headers, so any object model deviating from that would make me less happy. |
@serialseb I'm curious how you're planning to tackle the questions mentioned in the top post. Looking at the example you posted, there's still some unanswered questions:
Are you implementing OpenRasta on top of the ASP.NET Core abstractions? I'm assuming you have to re-implement a lot of these checks to get a consistent experience across different hosts? |
@Tratcher How do you propose we answer the questions above when you can have some components that declare and some components that provide trailers? How do we make sure they're consistent? Do we need to? |
The server's job is to make sure the client gets an intelligible response. Most of these mistakes wouldn't corrupt the response, the client would still be able to read it but it may end up with more or less information than it was expecting. Do we have any clients that actually surface trailers to do some interop testing with? |
Chrome 65 supports |
That shouldn't be a problem, a trailer is just a lose promise, from what i understand of the spec. I may send a Server-Timing but I may not
Well that's a server error but the response, I'd flag / warn about it but I can't really do much about it.
Well, it's pretty strict as to which headers cannot be sent :) So i'd probably make sure i implement the spec as correctly as possible. That said it's entirely possible that i'd build an api on top of the low level pipeline one above, but at that point I can implement the delegate on top of it, the other way around is not possible. |
This was superseded by #1043 |
Opening this up for discussion, starting out with response trailers. We can attack request trailers later. This addresses the HttpAbstractions part of https://github.com/aspnet/KestrelHttpServer/issues/622...
My current thinking is that you add the trailing header names using the regular
Headers
dictionary:This would automatically chunk the response, if possible and allowed. You can, of course, also add headers with the same name:
These are, as expected, flushed together with the rest of the headers. You can then explicitly add trailers:
The entire response would look something like this:
I'm thinking it's best to keep the feature by itself, as it doesn't make sense to expose such a niche feature on the
HttpResponse
itself.There are a couple of open questions, mostly related to implementation behavior:
Trailers
dictionary (afterOnStarting
has been called)?Trailers
dictionary?Trailer
entries? The RFC is pretty strict on which headers are allowed as trailers:// @Tratcher @benaadams @Drawaes @NickCraver @serialseb @halter73