-
-
Notifications
You must be signed in to change notification settings - Fork 365
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
Support Rack 3.x #399
base: master
Are you sure you want to change the base?
Support Rack 3.x #399
Conversation
Casue: Response header keys can no longer include uppercase characters.
This spec failed: Thin::Request parser should not fuck up on stupid fucked IE6 headers with: should validate with Rack Lint: env[HTTP_VERSION] does not equal env[SERVER_PROTOCOL] where: "HTTP_VERSION"=>"HTTP/1.0", "SERVER_PROTOCOL"=>"HTTP/1.1", Indeed: > Rack::HTTP_VERSION has been removed and the HTTP_VERSION env setting is no longer set in the CGI and Webrick handlers See: - rack/rack#970 - rack/rack#969 - rack/rack#1691 The version is necessary to negotiate `persistent?`, so here we punt on a further refactoring to find a good way to get the request HTTP version from the first line, instead shoving it in a Thin-specific `thin.request_http_header`.
Here's a quick run of https://github.com/socketry/rack-conform as was suggested by @ioquatix:
The internal error is:
Pointing to
|
Follows the Rack 3 spec: - https://github.com/rack/rack/blob/64ad26e3381da2ce1853638a2c4ea241c2ad3729/SPEC.rdoc#label-The+Body - https://github.com/rack/rack/blob/64ad26e3381da2ce1853638a2c4ea241c2ad3729/SPEC.rdoc#label-Streaming+Body Implemented using a wrapper object that pretends to be IO and makes it behave like it used to for enumerable. Does not support reading, hence no WebSockets. This needs some bidirectional access to receiving data and it's a bit complex to address with EventMachine + Thin's Connection#receive_data.
This is fixed:
The last error is more about implementing WebSocket support in Thin than any Rack 3 compliance, which I feel is an entirely separate task:
I tried some hacky things, but eventually with the test design it's about getting access to the socket behind EventMachine and passing it down for direct handling. Tangent: it seems for that part the conformance test implementation is heavily biased towards the Falcon implementation of WebSockets (like, it excludes everyone but Falcon to test against Rack 3) |
I don't recall any such limitation, can you clarify what limitation you have found? |
Huh I can't find it now. Maybe I got confused. |
That's okay. It's been one of the more tricky parts of the spec to get consistency on, and all Rack 3 conforming servers should be capable of supporting live WebSockets per request/response using streaming response body. Feel free to make a PR to |
Heh I already have a branch locally, I just need to push and create the PR there :) I'm a bit surprised about Rack 3 mandating WS support, but whatever. I'll try to figure out something once I get a moment to spare for that. |
There is nothing specific in Rack 3 about WebSockets, it's just a good test of streaming responses. There is very little difference between that and partial |
Note: thin is under developing Rack 3 support: macournoyer/thin#399
Thanks for your hard work. any update on this? |
I think the requirements to merge this would be:
|
If supporting old releases of Ruby is too much work, we could drop them. I'd be okay with dropping all EOL Rubies for the next release. |
which means websocket support
I've been:
Not much time/energy to do this lately. Not giving up though.
Could make sense, seems like a sensible option but first things first, let's make websockets pass. |
You only need to support streaming responses, which is not the same as WebSockets. It's the same as partial hijack. Even puma can support it with threads. |
@@ -12,7 +12,7 @@ | |||
request = R("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n") | |||
expect(request.env['SERVER_PROTOCOL']).to eq('HTTP/1.1') | |||
expect(request.env['REQUEST_PATH']).to eq('/') | |||
expect(request.env['HTTP_VERSION']).to eq('HTTP/1.1') | |||
expect(request.env['thin.request_http_version']).to eq('HTTP/1.1') |
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.
IIRC, you should use SERVER_PROTOCOL
for this. See https://github.com/rack/rack/blob/main/SPEC.rdoc for more details.
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.
Thin internals relied on HTTP_VERSION
. Removing it would have broken things so I added this so as not to mess with things too much while I got clarification.
IIUC you're saying:
- this logic here should rely on
SERVER_PROTOCOL
instead ofHTTP_VERSION
thin.request_http_version
can then disappear entirely
Correct?
I'm not that good with websockets, but AIUI this rack-conform test (the last one failing) does bidirectional access to the socket:
To pass this test, the underlying EventMachine connection must be somehow made available for |
Yes, that's correct. It's almost identical to partial hijack. Does |
See: It looks like thin never supported partial hijack. It became a requirement for Rack 3. I understand that EM does not support bare non-blocking IO. I would advise that EM can be updated to use the fiber scheduler to solve this problem, and thin can adopt that model to provide proper hijack support (event driven IO). I believe this already exists, but I don't have much experience with it: https://git.singpolyma.net/em_fiberscheduler |
Interesting. Thanks for these pointers. Overall the plan makes sense to me and although I did not anticipate things to such detail it matches what I broadly expected. Coincidentally I've recently toyed with https://github.com/bruno-/fiber_scheduler a little so I have little practical experience with hijack and none about partial hijack but I'll dig into it. So, setting expectations for people following this: it means I now have a bunch of solid known unknowns to explore, which I will tackle eventually but will take me a bit of time. |
where is partial hijack documented as a requirement for Rack 3? According to the Rack upgrade doc, Websockets can be supported using streaming bodies as described here https://github.com/rack/rack/blob/main/UPGRADE-GUIDE.md#response-bodies-can-be-used-for-bi-directional-streaming @lloeki I would be inclined to push the change without the websocket test passing (which selfishly suits my use-case :-) ) and look at Websockets as a separate item |
Partial hijack and streaming response bodies are semantically identical. See https://github.com/rack/rack/blob/main/SPEC.rdoc#streaming-body- for a clarification. |
Dropping some notes. Rack reaches down to
Then performs Which validates a bunch of things and ultimately calls
The The response The And a readable that lazily sets up a The Thin sees the Before Thin sees the response, it gets transformed via https://github.com/socketry/async-websocket/blob/main/lib/async/websocket/adapters/rack.rb#L26 Which makes it the usual Rack response array: But not before transforming the Because it declares itself as being a streaming response: So Thin sees:
According to Rack a body responding to But this goes through:
Which comes into play in But currently this code path is unused ( |
So AIUI we'd leverage partial hijack to have Thin itself return Instead partial hijack support would have the "body" (which is really a wrapper for WS stream handling) be called with a stream (really the connection stream be handed over) in a separate specific case before we reach this: That, or In both cases Thin handles the status + headers for switching protocols then hands things over one way or another. |
Oh I realise that my comment was ambiguous: indeed what I meant by "WS support" is "do the thing in Thin that enables use of WS for the test to pass" which indeed means partial hijack if the non-Thin WS thing delegates 101 to Rack (which AIUI is the case for this rack-conform test) or theoretically full hijack if the non-Thin WS thing handles that itself by writing it directly to the connection (which is not the case for this test). Sorry for the confusion. |
I think that is a better, more generic approach. And if you set a streaming flag in Thin::Response for the case where you are dealing with a streaming body, then you can have a check in Thin:Connection->receive_data where you check the streaming flag and if set buffer the data in the stream, bypassing the pre/post_process logic. That handles the 2 way streaming support. You would probably have to skip the terminate_request in that case too, and call that somehow once you get a close from the stream Line 134 in 3f37538
|
Thanks for all the effort here, appreciated! Is there any progress since April? |
@ioquatix if the only thing stopping this is one test in the rack conformity tests when Thin never supported the functionality in the first place, can this be merged with that as a limitation and a new ticket opened? |
Okay, let me review where we are at with this. |
From #389 here's a first draft that makes Thin specs pass.
There's a warning that should probably be addressed: