File: persistent.rb

package info (click to toggle)
ruby-protocol-http1 0.35.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 376 kB
  • sloc: ruby: 2,367; makefile: 4
file content (133 lines) | stat: -rw-r--r-- 4,504 bytes parent folder | download
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