Skip to content

Commit

Permalink
More fixes/coverage.
Browse files Browse the repository at this point in the history
  • Loading branch information
ioquatix committed Sep 18, 2024
1 parent dbbf147 commit e54fe82
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 38 deletions.
30 changes: 13 additions & 17 deletions lib/protocol/http1/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ def read_request

body = read_request_body(method, headers)

if body.nil?
unless body
self.receive_end_stream!
end

Expand All @@ -306,7 +306,9 @@ def read_response_line
end

def read_response(method)
raise ProtocolError, "Cannot read response in #{@state}!" unless @state == :open
unless @state == :open or @state == :half_closed_local
raise ProtocolError, "Cannot read response in #{@state}!"
end

version, status, reason = read_response_line

Expand All @@ -316,6 +318,10 @@ def read_response(method)

body = read_response_body(method, status, headers)

unless body
self.receive_end_stream!
end

@count += 1

return version, status, reason, headers, body
Expand Down Expand Up @@ -474,24 +480,14 @@ def write_body_and_close(body, head)
@stream.close_write
end

def half_closed_local!
raise ProtocolError, "Cannot close local in #{@state}!" unless @state == :open

@state = :half_closed_local
end

def half_closed_remote!
raise ProtocolError, "Cannot close remote in #{@state}!" unless @state == :open

@state = :half_closed_remote
end

def idle!
@state = :idle
end

def closed!
raise ProtocolError, "Cannot close in #{@state}!" unless @state == :half_closed_local or @state == :half_closed_remote
unless @state == :half_closed_local or @state == :half_closed_remote
raise ProtocolError, "Cannot close in #{@state}!"
end

if @persistent
self.idle!
Expand All @@ -502,7 +498,7 @@ def closed!

def send_end_stream!
if @state == :open
self.half_closed_local!
@state = :half_closed_local
elsif @state == :half_closed_remote
self.closed!
else
Expand Down Expand Up @@ -545,7 +541,7 @@ def write_body(version, body, head = false, trailer = nil)

def receive_end_stream!
if @state == :open
self.half_closed_remote!
@state = :half_closed_remote
elsif @state == :half_closed_local
self.closed!
else
Expand Down
147 changes: 126 additions & 21 deletions test/protocol/http1/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

require "protocol/http1/connection"
require "protocol/http/body/buffered"
require "protocol/http/body/writable"

require "connection_context"

Expand Down Expand Up @@ -548,26 +549,130 @@
end
end

with "persistent connection" do
it "returns back to idle state" do
expect(client).to be(:idle?)

client.write_request("localhost", "GET", "/", "HTTP/1.1", {})
expect(client).to be(:open?)
client.write_body("HTTP/1.1", nil)

expect(client).to be(:half_closed_local?)

expect(server).to be(:idle?)

request = server.read_request
expect(request).to be == ["localhost", "GET", "/", "HTTP/1.1", {}, nil]
expect(server).to be(:half_closed_remote?)

server.write_response("HTTP/1.1", 200, {}, [])
server.write_body("HTTP/1.1", nil)

expect(server).to be(:idle?)
end
it "enters half-closed (local) state after writing response body" do
expect(client).to be(:idle?)
client.write_request("localhost", "GET", "/", "HTTP/1.1", {})
expect(client).to be(:open?)
body = Protocol::HTTP::Body::Buffered.new(["Hello World"])
client.write_body("HTTP/1.1", body)
expect(client).to be(:half_closed_local?)

expect(server).to be(:idle?)
request = server.read_request
server.write_response("HTTP/1.1", 200, {}, nil)
server.write_body("HTTP/1.1", nil)
expect(server).to be(:half_closed_local?)
end

it "returns back to idle state" do
expect(client).to be(:idle?)
client.write_request("localhost", "GET", "/", "HTTP/1.1", {})
expect(client).to be(:open?)
client.write_body("HTTP/1.1", nil)
expect(client).to be(:half_closed_local?)

expect(server).to be(:idle?)
request = server.read_request
expect(request).to be == ["localhost", "GET", "/", "HTTP/1.1", {}, nil]
expect(server).to be(:half_closed_remote?)

server.write_response("HTTP/1.1", 200, {}, [])
server.write_body("HTTP/1.1", nil)
expect(server).to be(:idle?)

response = client.read_response("GET")
expect(client).to be(:idle?)
end

it "transitions to the closed state when using connection: close response body" do
expect(client).to be(:idle?)
client.write_request("localhost", "GET", "/", "HTTP/1.0", {})
expect(client).to be(:open?)

client.write_body("HTTP/1.0", nil)
expect(client).to be(:half_closed_local?)

expect(server).to be(:idle?)
request = server.read_request
expect(server).to be(:half_closed_remote?)

server.write_response("HTTP/1.0", 200, {}, [])

# Length is unknown, and HTTP/1.0 does not support chunked encoding, so this will close the connection:
body = Protocol::HTTP::Body::Writable.new
body.write "Hello World"
body.close_write

server.write_body("HTTP/1.0", body)
expect(server).not.to be(:persistent)
expect(server).to be(:closed?)

response = client.read_response("GET")
body = response.last
expect(body.join).to be == "Hello World"
expect(client).to be(:closed?)
end

it "can't write a request in the closed state" do
client.state = :closed

expect do
client.write_request("localhost", "GET", "/", "HTTP/1.0", {})
end.to raise_exception(Protocol::HTTP1::ProtocolError)
end

it "can't read a response in the closed state" do
client.state = :closed

expect do
client.read_response("GET")
end.to raise_exception(Protocol::HTTP1::ProtocolError)
end

it "can't write a response in the closed state" do
server.state = :closed

expect do
server.write_response("HTTP/1.0", 200, {}, nil)
end.to raise_exception(Protocol::HTTP1::ProtocolError)
end

it "can't read a request in the closed state" do
server.state = :closed

expect do
server.read_request
end.to raise_exception(Protocol::HTTP1::ProtocolError)
end

it "can't enter the closed state from the idle state" do
expect do
client.closed!
end.to raise_exception(Protocol::HTTP1::ProtocolError)
end

it "can't write response body without writing response" do
expect do
server.write_body("HTTP/1.0", nil)
end.to raise_exception(Protocol::HTTP1::ProtocolError)
end

it "can't write request body without writing request" do
expect do
client.write_body("HTTP/1.0", nil)
end.to raise_exception(Protocol::HTTP1::ProtocolError)
end

it "can't read request body without reading request" do
# Fake empty chunked encoded body:
client.stream.write("0\r\n\r\n")

body = server.read_request_body("POST", {"transfer-encoding" => ["chunked"]})

expect(body).to be_a(Protocol::HTTP1::Body::Chunked)

expect do
body.join
end.to raise_exception(Protocol::HTTP1::ProtocolError)
end
end

0 comments on commit e54fe82

Please sign in to comment.