Skip to content
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

IPIP-332: Streaming Error Handling on Web Gateways #332

Closed
wants to merge 11 commits into from

Conversation

hacdias
Copy link
Member

@hacdias hacdias commented Oct 12, 2022

See ipfs/kubo#9333 (comment) for more context.

@hacdias hacdias requested review from lidel and a team as code owners October 12, 2022 11:59
@hacdias hacdias self-assigned this Oct 12, 2022
@hacdias hacdias changed the title IPIP: Error Handling on Web Gateways IPIP: Streaming Error Handling on Web Gateways Oct 12, 2022
@Jorropo
Copy link
Contributor

Jorropo commented Oct 12, 2022

I would like to be an options for clients to optout of this behaviour (with a header probably).

Killing the connection can have real performance costs and will require clients to reopen a new connection if they are using HTTP1.1.
If your client as able to detect errors in car files or support trailing headers, you would probably like thoses methods instead of reopenning the connection.

Co-authored-by: Jorropo <jorropo.pgm@gmail.com>
@lidel lidel changed the title IPIP: Streaming Error Handling on Web Gateways IPIP-332: Streaming Error Handling on Web Gateways Oct 26, 2022
IPIP/0000-gateway-error-handling.md Outdated Show resolved Hide resolved
This can be done via the following mechanisms:

- Sending a `RST` (reset) frame for HTTP/1.1
- Sending a `RST_STREAM` (reset stream) frame for HTTP/2
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
- Sending a `RST_STREAM` (reset stream) frame for HTTP/2
- Sending a `RST_STREAM` (reset stream) frame for HTTP/2
- Sending a `CANCEL_PUSH` frame for HTTP/3, see RFC9114 section A.2.5.3.

Copy link
Member

Choose a reason for hiding this comment

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

I haven't read the entire spec, but according to BCP 56, HTTP APIs shouldn't go into this level of detail. HTTP is designed in a way that allows building applications without referring to the specific HTTP version.

Copy link
Member

Choose a reason for hiding this comment

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

@Jorropo Again, haven't read the entire spec, but I are we actually doing server push (which is the only context in which CANCEL_PUSH would be valid)? That feature of HTTP is going to be removed from major browser implementations very soon.

Copy link
Contributor

@Jorropo Jorropo Nov 8, 2022

Choose a reason for hiding this comment

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

I didn't actually red the RFC, I know RST_STREAM on HTTP/2 is what we want, I just CTRL + F RST_STREAM in the HTTP/3 RFC and found this text:

RST_STREAM (0x03): RST_STREAM frames do not exist in HTTP/3, since
QUIC provides stream lifecycle management. The same code point is
used for the CANCEL_PUSH frame (Section 7.2.3).

I guess it should be whatever QUIC's frame is to close a stream unexpectedly.


HTTP is designed in a way that allows building applications without referring to the specific HTTP version.

Streaming errors is not a thing HTTP supports, you can setup trailler headers but they aren't supported by browsers.
So we are stealing the rug from under HTTP and use the underlying protocol to generate an unexpected error (which is actually supported and will return errors in all client implementations I've tested), that why we need to use TCP FIN.

I don't know if we should add this to the gateway spec, because most HTTP server implementations give you as much control as the go std does.
How will you implement that on reverse proxies ?

Copy link
Member Author

Choose a reason for hiding this comment

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

@marten-seemann as @Jorropo mentioned, the big issue here is that HTTP doesn't have any error handling for streaming. One of my motivations behind this IPIP regards the new TAR format for the gateway (ipfs/kubo#9029).

It is possible that the TAR creation fails while streaming the file to the client due to many reasons. However, if you just stop streaming the TAR, you still get a valid TAR file, but it is incomplete. There's no feedback. Printing a trailer header is useless since browsers are not able to parse them. The only way we found so far to tell the user that something is wrong was by force-closing the HTTP stream.

I also have mixed feelings about having this on the spec since it is so specific. In addition, as @Jorropo mentioned it may be worth it having an opt-out of the behaviour through some header.

Copy link
Contributor

Choose a reason for hiding this comment

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

@hacdias

How will you implement that on reverse proxies ?

I think this is an important question.
I would like to see something like: X-On-Error: reset header sent by the server to indicate this will be done, then a reverse proxy could just not send thoses headers, and clients would be able to tell it is not gonna do this.

Copy link
Member Author

Choose a reason for hiding this comment

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

@Jorropo please see the updates I've made to the IPIP.

This can be done via the following mechanisms:

- Sending a `RST` (reset) frame for HTTP/1.1
- Sending a `RST_STREAM` (reset stream) frame for HTTP/2
Copy link
Member

Choose a reason for hiding this comment

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

I haven't read the entire spec, but according to BCP 56, HTTP APIs shouldn't go into this level of detail. HTTP is designed in a way that allows building applications without referring to the specific HTTP version.

Copy link
Member

@lidel lidel left a comment

Choose a reason for hiding this comment

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

Dropping some quick notes:

  • Agree with Marten, HTTP API should not go into low level connection detail. We want gateway interface to be future-proof.
  • The more gateway implementations exist, the less control we have over these low level details. Need to solve this on higher client application layer.

👉 due to this I am closing this IPIP

Instead, suggest opening a new IPIP with simpler proposal (but looking for better ones! – comment below):

  • The problem is that there is no way to signal that mid-way transfer error occurred. Instead of trying to bolt-on something on the server side, instead, spec should specify inexpensive way how clients should detect that the entire CAR got transferred:

  • Introduce optimization where we always add a zero-length raw block at the end of a CAR as a tombstone indicator – if this well-known CID is present, the client can skip hashing the last block, because we know the last block arrived safely.

  • If tombstone is not present, then the client MUST verify hash of the last block. If hash does match, means the connection was not interrupted mid-transfer (downside is additional CPU cost due to hashing that one block, that is why we prefer to have tombstone)

The tombstone is backward-compatible, and allows us to have an escape hatch to skip hashing and save CPU.

@lidel lidel closed this Jan 30, 2023
Comment on lines +35 to +37
The client can opt-out of this behavior by sending a `X-Error-Behavior: trailer`.
In that case, the server will not force-close the HTTP stream. Instead, the server
will send the trailing header `X-Stream-Error` header with the error message.
Copy link
Member

@lidel lidel Jan 30, 2023

Choose a reason for hiding this comment

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

I know this was opt-in, but as usual, reminding that Trailers can't be read by JS in web browsers: mdn/browser-compat-data#14703 (lesson learned from Kubo RPC: ipfs/js-ipfs#2519)
Gateway specs should never require things that are not supported in browsers, as this leads to the very problem types this IPIP aims to solve.

@jbenet
Copy link
Member

jbenet commented Feb 13, 2023

#332 (review)

  • null objects are a very nasty way to go.
  • having a tombstone (ie some car files have it and some dont) is super messy and likely to lead to mysterious errors.
  • it also opens a surface area of attack: now everyone has to check that we arent linking to the null object from some object, because if we are, then it will terminate a car stream early, and we have to do that in a ton of places (think the lovely wonders of \0 in string operations). this is really nasty and you can guarantee that it will not be running _ correctly everywhere_, so you can guarantee that it will be run incorrectly somewhere, and also have to deal with that.
  • null termination is just a world of pain, and why ipfs went the way of self-describing objects. usually, most problems can indeed be turned into self-describing structures, even streams of unknown length. -- this is usually done by introducing a wrapper object that contains the information you want.
  • for example, define a format like a car-stream that includes a header in between every object (similar to tar) and that header can signal the end of a car-stream. you would have a 1-1 from any car to any car-stream, and make very explicit what is data and what is control plane.

@jbenet
Copy link
Member

jbenet commented Feb 13, 2023

  • more succinctly: i think you want a "content addressed stream" (CAS), not a "content addressed archive" (CAR).
  • separately, this thread reminded me that we're likely due for a CARv3 with (a) support for explicit finite car files (ie the object count in the header), and (b) verifiable header (explicit roots in the header (as in v1) + a hash of the v2 index objects) -- such that a single hash (of a single small object) can be used to verify the entire thing (the original property CARs were invented for)

@rvagg
Copy link
Member

rvagg commented Feb 14, 2023

we're likely due for a CARv3 with (a) support for explicit finite car files (ie the object count in the header), and (b) verifiable header (explicit roots in the header (as in v1) + a hash of the v2 index objects) -- such that a single hash (of a single small object) can be used to verify the entire thing (the original property CARs were invented for)

Happily I think we can mush all of that into CARv2 without breaking. We'd just need that as a wishlist and some consumers of that functionality to help drive a spec to lock it in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Deferred
Archived in project
Development

Successfully merging this pull request may close these issues.

6 participants