1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
|
# frozen_string_literal: true
# Released under the MIT License.
# Copyright, 2025, by Samuel Williams.
require "protocol/http1/connection"
require "protocol/http/body/buffered"
require "protocol/http/body/writable"
require "connection_context"
describe Protocol::HTTP1::Connection do
include_context ConnectionContext
with "multiple requests in a single connection" do
it "handles two back-to-back GET requests (HTTP/1.1 keep-alive)" do
client.write_request("localhost", "GET", "/first", "HTTP/1.1", {"Header-A" => "Value-A"})
client.write_body("HTTP/1.1", nil)
expect(client).to be(:half_closed_local?)
# Server reads it:
authority, method, path, version, headers, body = server.read_request
expect(authority).to be == "localhost"
expect(method).to be == "GET"
expect(path).to be == "/first"
expect(version).to be == "HTTP/1.1"
expect(headers["header-a"]).to be == ["Value-A"]
expect(body).to be_nil
# Server writes a response:
expect(server).to be(:half_closed_remote?)
server.write_response("HTTP/1.1", 200, {"Res-A" => "ValA"}, "OK")
server.write_body("HTTP/1.1", nil)
expect(server).to be(:idle?)
# Client reads first response:
version, status, reason, headers, body = client.read_response("GET")
expect(version).to be == "HTTP/1.1"
expect(status).to be == 200
expect(reason).to be == "OK"
expect(headers["res-a"]).to be == ["ValA"]
expect(body).to be_nil
# Now both sides should be back to :idle (persistent re-use):
expect(client).to be(:idle?)
expect(server).to be(:idle?)
# Second request:
client.write_request("localhost", "GET", "/second", "HTTP/1.1", {"Header-B" => "Value-B"})
client.write_body("HTTP/1.1", nil)
expect(client).to be(:half_closed_local?)
# Server reads it:
authority, method, path, version, headers, body = server.read_request
expect(authority).to be == "localhost"
expect(method).to be == "GET"
expect(path).to be == "/second"
expect(version).to be == "HTTP/1.1"
expect(headers["header-b"]).to be == ["Value-B"]
expect(body).to be_nil
# Server writes a response:
expect(server).to be(:half_closed_remote?)
server.write_response("HTTP/1.1", 200, {"Res-B" => "ValB"}, "OK")
server.write_body("HTTP/1.1", nil)
# Client reads second response:
version, status, reason, headers, body = client.read_response("GET")
expect(version).to be == "HTTP/1.1"
expect(status).to be == 200
expect(reason).to be == "OK"
expect(headers["res-b"]).to be == ["ValB"]
expect(body).to be_nil
# Confirm final states:
expect(client).to be(:idle?)
expect(server).to be(:idle?)
end
end
with "partial body read" do
it "closes correctly if server does not consume entire fixed-length body" do
# Indicate Content-Length = 11 but only read part of it on server side:
client.stream.write "POST / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 11\r\n\r\nHello"
client.stream.close
# Server reads request line/headers:
authority, method, path, version, headers, body = server.read_request
expect(method).to be == "POST"
expect(body).to be_a(Protocol::HTTP1::Body::Fixed)
# Partially read 5 bytes only:
partial = body.read
expect(partial).to be == "Hello"
expect do
body.read
end.to raise_exception(EOFError)
# Then server forcibly closes read (simulating a deliberate stop):
server.close_read
# Because of partial consumption, that should move the state to half-closed remote or closed, etc.
expect(server).to be(:half_closed_remote?)
expect(server).not.to be(:persistent)
end
end
with "no persistence" do
it "closes connection after request" do
server.persistent = false
client.write_request("localhost", "GET", "/first", "HTTP/1.1", {"Header-A" => "Value-A"})
client.write_body("HTTP/1.1", nil)
expect(client).to be(:half_closed_local?)
# Server reads it:
authority, method, path, version, headers, body = server.read_request
expect(authority).to be == "localhost"
expect(method).to be == "GET"
expect(path).to be == "/first"
expect(version).to be == "HTTP/1.1"
expect(headers["header-a"]).to be == ["Value-A"]
expect(body).to be_nil
# Server writes a response:
expect(server).to be(:half_closed_remote?)
server.write_response("HTTP/1.1", 200, {"Res-A" => "ValA"}, "OK")
server.write_body("HTTP/1.1", nil)
expect(server).to be(:closed?)
end
end
end
|