elliottt commented on issue #7524:
Instead of checking the immutability flag in the callee, can we factor it into a
get_fields
that returns a&FieldMap
and use that in all the cases that don't require mutability, and then turnget_fields_mut
which returnsResult<&mut FieldMap, HeaderError>
that will only be used for set/append/delete and itself returnErr(HeaderError::Immutable)
?Great call! I've reworked this to have two accessors now, and I like it much more.
tschneidereit commented on issue #7524:
@elliottt I didn't catch this earlier, but I think it's somewhat unfortunate to take such drastic measures here. Specifically, I would predict that fields entries sometimes being mutable, sometimes not, while in many languages it's not really possible to express that in the type system—and it's not expressed in whether we have a borrowed or mutable handle—will lead to lots of confusion.
What if instead we made it so that headers are passed in as an owned handle when sending a request/response, ensuring that they can't be mutated at all anymore afterwards? For those use cases where the values still need to be accessible, the handle can be cloned. (I'd imagine that we could make that pretty cheap on the host side, by having
clone
return aCow
, such that if the clone is only ever read from, we don't incur overhead.
elliottt commented on issue #7524:
I think your suggestion is what's implemented here: the
outgoing-request
andoutgoing-response
constructors both take ownership of thefields
they're given, and it's only thefields
resources returned by theirheaders
methods that become immutable. Thefields.clone
method can be used on those handles for the case where it's necessary to mutate a new copy of thosefields
, but once afields
is associated with a request or response (incoming or outgoing) it becomes immutable.
elliottt commented on issue #7524:
More concretely: this doesn't treat individual headers in a special way, it makes it so that the
fields
resource returned by theheaders
method on request and response resources immutable as a whole. So if you tried to usefields.set
,fields.delete
, orfields.append
on thefields
resource returned byoutgoing-response.headers
, you would uniformly see theheader-error.immutable
error returned.
tschneidereit commented on issue #7524:
I think ensuring that headers values can't be changed after they become relevant for error handling makes a lot of sense. What I had in mind was to move that point further back, to the moment where the request/response is actually sent. What I mean is not having headers be associated with requests/responses at all until the latter are sent.
Concretely, that would entail four changes:
- In the
outgoing-handler
interface, changing the signature of the handle function tohandle: func( request: outgoing-request, headers: headers, options: option<request-options> ) -> result<future-incoming-response, error-code>;
- In the
response-outparam
resource type, changing the signature of the set method toset: static func( param: response-outparam, response: result<outgoing-response, headers, error-code>, );
In the
outgoing-request
resource type, removing theheaders
parameter from the constructor and removing theheaders
methodIn the
outgoing-response
resource type, removing theheaders
parameter from the constructor and removing theheaders
methodThat should make it so that whenever one can get a handle to a
headers
object, it's certain to be mutable, so that it's not required to carefully keep track of how that handle was acquired.All this said, I'm not 100% sure myself that this is the better approach, but it does seem to make things more self-explanatory at least.
elliottt commented on issue #7524:
That makes sense to me, thank you for the detailed explanation! I'm open to making this change, would it make sense to also make the incoming handler take a
fields
argument to keep symmetry between the two?
tschneidereit commented on issue #7524:
ah, good point yeah: I think that symmetry would make a lot of sense
pchickey commented on issue #7524:
Can't you achieve these with library code that just maintains the
headers
as an owned resource and just creates theoutgoing-request
at the point of callingoutgoing-handler.handle
?
tschneidereit commented on issue #7524:
Yes, that's possible, but I think there are a couple of downsides.
One is more of a conceptual one:
headers
as an owned resource has to completely different behaviors depending on whether it's retrieved from a parent resource or created standalone. I think it'd be nicer to have uniform behavior, but that's not that important.The other is that that means not being able to use the stream at all until after creating the final
outgoing-request
and sending it off.Given that the key property we want to get at is to ensure that the headers don't change between setting them and sending off the parent resource, it seems to make sense to me to create that association just in time, by passing them in when sending.
elliottt commented on issue #7524:
Thinking about this over the weekend, I think there's a problem with passing the headers to the handler: that means that the value of the
Content-Length
header could change after theoutgoing-body
is created, which would allow for the following situation that the current PR prohibits:
- Create an outgoing request
- Create a fields with a
Content-Length
of 20- Create an outgoing-body (this is where we would need to know the value of
Content-Length
, if it's present)- Update the
Content-Length
header to be 10- Send the request with those headers
- Write 20 bytes to the output stream associated with the outgoing-body
We should be able to report an error in step 6 for writing too much, but as the
outgoing-body
is derived from theoutgoing-request
, and the headers are no longer associated directly with the request, we miss the opportunity to fetch the most current value ofContent-Length
. Associating the headers with the request when it's constructed and considering the headers immutable at that point means that we can trust that theoutgoing-body
produced by theoutgoing-request.body
method sees the final value forContent-Length
when it's created.
tschneidereit commented on issue #7524:
What I was envisioning was a modified sequence along these lines:
- Create an outgoing request
- Create a fields with a
Content-Length
of 20- Create an
outgoing-body
(we don't yet need to know anything aboutContent-Length
here)- Update the
Content-Length
header to be 10- Send the request with those headers, and set the current value of
Content-Length
on theoutgoing-body
- Write 20 bytes to the output stream associated with the outgoing-body
- get an error
Now what I don't at all know is how hard it'd be to move setting the expected body length from step 3 into step 5. Certainly the request has a reference to it still, so ideally it shouldn't be too hard?
Last updated: Dec 23 2024 at 12:05 UTC