Skip to content

Commit 5841e46

Browse files
committed
WIP
1 parent dfcacca commit 5841e46

File tree

3 files changed

+119
-1
lines changed

3 files changed

+119
-1
lines changed

lib/protocol/http1/body/upgrade.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Upgrade
2+
3+
Connection upgrade in HTTP/1 is considered an intermediate response, yet it should be considered a final response as no further HTTP/1 messages are expected. The upgrade response is a signal to the client that the server is switching to a different protocol. The client should switch to the new protocol immediately.
4+
5+
```ruby
6+
connection = Protocol::HTTP1::Connection.new(peer)
7+
8+
while stream = connection.accept_stream
9+
stream.
10+
11+
12+
body = Protocol::HTTP::Body::Writable.new
13+
request = Protocol::HTTP::Request.new("wss", "websockets.local", "GET", "/chat", nil, Protocol::HTTP::Headers.new, body, ["websocket"])
14+
connection.write_request(request)
15+
16+
while response = connection.read_response
17+
break if response.final?
18+
19+
if response.status == 101
20+
21+
break
22+
end

lib/protocol/http1/body/upgrade.rb

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# frozen_string_literal: true
2+
3+
# Released under the MIT License.
4+
# Copyright, 2019-2024, by Samuel Williams.
5+
6+
require 'protocol/http/body/readable'
7+
8+
module Protocol
9+
module HTTP1
10+
module Body
11+
class Upgrade < HTTP::Body::Readable
12+
BLOCK_SIZE = 1024 * 64
13+
14+
class Trigger
15+
def initialize
16+
@queue = ::Thread::Queue.new
17+
end
18+
19+
def wait
20+
if queue = @queue
21+
queue.pop
22+
end
23+
24+
return true
25+
end
26+
27+
def signal
28+
if @queue
29+
@queue.close
30+
@queue = nil
31+
end
32+
end
33+
end
34+
35+
# block_size may be removed in the future. It is better managed by stream.
36+
def initialize(stream)
37+
@stream = stream
38+
@upgraded = false
39+
40+
@ready = Trigger.new
41+
@remainder = nil
42+
end
43+
44+
def upgrade!
45+
@upgraded = true
46+
@ready.signal
47+
end
48+
49+
def empty?
50+
if @remainder
51+
@remainder.empty?
52+
else
53+
false # Unknown.
54+
end
55+
end
56+
57+
def close(error = nil)
58+
if @remainder
59+
@remainder.close(error)
60+
else
61+
@stream = nil
62+
end
63+
end
64+
65+
# TODO this is a bit less efficient in order to maintain compatibility with `IO`.
66+
def read
67+
@ready.wait
68+
69+
return @remainder.read
70+
end
71+
72+
def call(stream)
73+
@ready.wait
74+
75+
@remainder.read
76+
end
77+
78+
def join
79+
@ready.wait
80+
81+
@remainder.join
82+
end
83+
84+
def inspect
85+
"\#<#{self.class} #{@ready ? }>"
86+
end
87+
end
88+
end
89+
end
90+
end

lib/protocol/http1/connection.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ def initialize(stream, persistent = true)
5555

5656
@persistent = persistent
5757

58+
@upgrade_body = nil
59+
5860
@count = 0
5961
end
6062

@@ -435,6 +437,10 @@ def read_tunnel_body
435437
read_remainder_body
436438
end
437439

440+
def read_upgrade_body
441+
@upgrade_body = Body::Upgrade.new(@stream)
442+
end
443+
438444
HEAD = "HEAD"
439445
CONNECT = "CONNECT"
440446

@@ -505,7 +511,7 @@ def read_request_body(method, headers)
505511

506512
# A successful upgrade response implies that the connection will become a tunnel immediately after the empty line that concludes the header fields.
507513
if headers[UPGRADE]
508-
return read_tunnel_body
514+
return read_upgrade_body
509515
end
510516

511517
# 6. If this is a request message and none of the above are true, then

0 commit comments

Comments
 (0)