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 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
|
# frozen_string_literal: true
require_relative "helper"
require_relative "helpers/test_puma/puma_socket"
class TestPersistent < TimeoutTestCase
parallelize_me!
include ::TestPuma::PumaSocket
HOST = "127.0.0.1"
def setup
@body = ["Hello"]
@valid_request = "GET / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
@valid_response = <<~RESP.gsub("\n", "\r\n").rstrip
HTTP/1.1 200 OK
X-Header: Works
Content-Length: 5
Hello
RESP
@close_request = "GET / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n"
@http10_request = "GET / HTTP/1.0\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
@keep_request = "GET / HTTP/1.0\r\nHost: test.com\r\nContent-Type: text/plain\r\nConnection: Keep-Alive\r\n\r\n"
@valid_post = "POST / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\nContent-Length: 5\r\n\r\nhello"
@valid_no_body = "GET / HTTP/1.1\r\nHost: test.com\r\nX-Status: 204\r\nContent-Type: text/plain\r\n\r\n"
@headers = { "X-Header" => "Works" }
@inputs = []
@simple = lambda do |env|
@inputs << env['rack.input']
status = Integer(env['HTTP_X_STATUS'] || 200)
[status, @headers, @body]
end
opts = {min_thread: 1, max_threads: 1}
@server = Puma::Server.new @simple, nil, opts
@bind_port = (@server.add_tcp_listener HOST, 0).addr[1]
@server.run
sleep 0.15 if Puma.jruby?
end
def teardown
@server.stop(true)
end
def test_one_with_content_length
response = send_http_read_response @valid_request
assert_equal @valid_response, response
end
def test_two_back_to_back
socket = send_http @valid_request
response = socket.read_response
assert_equal @valid_response, response
response = socket.req_write(@valid_request).read_response
assert_equal @valid_response, response
end
def test_post_then_get
socket = send_http @valid_post
response = socket.read_response
expected = <<~RESP.gsub("\n", "\r\n").rstrip
HTTP/1.1 200 OK
X-Header: Works
Content-Length: 5
Hello
RESP
assert_equal expected, response
response = socket.req_write(@valid_request).read_response
assert_equal @valid_response, response
end
def test_no_body_then_get
socket = send_http @valid_no_body
response = socket.read_response
assert_equal "HTTP/1.1 204 No Content\r\nX-Header: Works\r\n\r\n", response
response = socket.req_write(@valid_request).read_response
assert_equal @valid_response, response
end
def test_chunked
@body << "Chunked"
@body = @body.to_enum
response = send_http_read_response @valid_request
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nTransfer-Encoding: chunked\r\n\r\n" \
"5\r\nHello\r\n7\r\nChunked\r\n0\r\n\r\n", response
end
def test_chunked_with_empty_part
@body << ""
@body << "Chunked"
@body = @body.to_enum
response = send_http_read_response @valid_request
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nTransfer-Encoding: chunked\r\n\r\n" \
"5\r\nHello\r\n7\r\nChunked\r\n0\r\n\r\n", response
end
def test_no_chunked_in_http10
@body << "Chunked"
@body = @body.to_enum
socket = send_http GET_10
sleep 0.01 if ::Puma::IS_JRUBY
response = socket.read_response
assert_equal "HTTP/1.0 200 OK\r\nX-Header: Works\r\n\r\n" \
"HelloChunked", response
end
def test_hex
str = "This is longer and will be in hex"
@body << str
@body = @body.to_enum
response = send_http_read_response @valid_request
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nTransfer-Encoding: chunked\r\n\r\n" \
"5\r\nHello\r\n#{str.size.to_s(16)}\r\n#{str}\r\n0\r\n\r\n", response
end
def test_client11_close
response = send_http_read_response @close_request
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nConnection: close\r\nContent-Length: 5\r\n\r\n" \
"Hello", response
end
def test_client10_close
response = send_http_read_response GET_10
assert_equal "HTTP/1.0 200 OK\r\nX-Header: Works\r\nContent-Length: 5\r\n\r\n" \
"Hello", response
end
def test_one_with_keep_alive_header
response = send_http_read_response @keep_request
assert_equal "HTTP/1.0 200 OK\r\nX-Header: Works\r\nConnection: Keep-Alive\r\nContent-Length: 5\r\n\r\n" \
"Hello", response
end
def test_persistent_timeout
@server.instance_variable_set(:@persistent_timeout, 1)
socket = send_http @valid_request
response = socket.read_response
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: 5\r\n\r\n" \
"Hello", response
sleep 2
assert_raises EOFError do
socket.read_nonblock(1)
end
end
def test_app_sets_content_length
@body = ["hello", " world"]
@headers['Content-Length'] = "11"
response = send_http_read_response @valid_request
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: 11\r\n\r\n" \
"hello world", response
end
def test_allow_app_to_chunk_itself
@headers = {'Transfer-Encoding' => "chunked" }
@body = ["5\r\nhello\r\n0\r\n\r\n"]
response = send_http_read_response @valid_request
assert_equal "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n" \
"5\r\nhello\r\n0\r\n\r\n", response
end
def test_two_requests_in_one_chunk
@server.instance_variable_set(:@persistent_timeout, 3)
req = @valid_request.to_s
req += "GET /second HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
response = send_http_read_all req
assert_equal @valid_response * 2, response
end
def test_second_request_not_in_first_req_body
@server.instance_variable_set(:@persistent_timeout, 3)
req = @valid_request.to_s
req += "GET /second HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
response = send_http_read_all req
assert_equal @valid_response * 2, response
assert_kind_of Puma::NullIO, @inputs[0]
assert_kind_of Puma::NullIO, @inputs[1]
end
def test_keepalive_doesnt_starve_clients
send_http @valid_request
c2 = send_http @valid_request
assert c2.wait_readable(1), "2nd request starved"
response = c2.read_response
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: 5\r\n\r\n" \
"Hello", response
end
end
|