Skip to content
This repository was archived by the owner on Jan 13, 2021. It is now read-only.

Commit 2a0620c

Browse files
committed
Initial HTTP/1.1 integration tests.
1 parent f041329 commit 2a0620c

File tree

5 files changed

+186
-13
lines changed

5 files changed

+186
-13
lines changed

hyper/http11/response.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def __init__(self, code, reason, headers, sock):
4747

4848
# The expected length of the body.
4949
try:
50-
self._length = int(self.headers.get(b'content-length', [0])[0])
50+
self._length = int(self.headers[b'content-length'][0])
5151
except KeyError:
5252
self._length = None
5353

@@ -99,8 +99,8 @@ def read(self, amt=None, decode_content=True):
9999
if self._length is not None:
100100
amt = self._length
101101
elif self._expect_close:
102-
return self._read_expect_closed()
103-
else:
102+
return self._read_expect_closed(decode_content)
103+
else: # pragma: no cover
104104
raise RuntimeError("Unbounded read!")
105105

106106
# Otherwise, we've been asked to do a bounded read. We should read no
@@ -130,7 +130,7 @@ def read(self, amt=None, decode_content=True):
130130
# FIXME: Real exception, not RuntimeError
131131
if not chunk:
132132
self.close()
133-
raise RuntimeError("Remote end hung up!")
133+
raise ConnectionResetError("Remote end hung up!")
134134

135135
to_read -= len(chunk)
136136
chunks.append(chunk)
@@ -163,7 +163,7 @@ def close(self):
163163
self._sock.close()
164164
self._sock = None
165165

166-
def _read_expect_closed(self):
166+
def _read_expect_closed(self, decode_content):
167167
"""
168168
Implements the logic for an unbounded read on a socket that we expect
169169
to be closed by the remote end.
@@ -172,11 +172,14 @@ def _read_expect_closed(self):
172172
# socket, becuase we know we have to.
173173
chunks = []
174174
while True:
175-
chunk = self._sock.recv(65535).tobytes()
176-
if not chunk:
175+
try:
176+
chunk = self._sock.recv(65535).tobytes()
177+
if not chunk:
178+
break
179+
except ConnectionResetError:
177180
break
178-
179-
chunks.append(chunk)
181+
else:
182+
chunks.append(chunk)
180183

181184
self.close()
182185

test/server.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
from hyper import HTTP20Connection
2020
from hyper.compat import ssl
21+
from hyper.http11.connection import HTTP11Connection
2122
from hyper.http20.hpack import Encoder
2223
from hyper.http20.huffman import HuffmanEncoder
2324
from hyper.http20.huffman_constants import (
@@ -34,15 +35,19 @@ class SocketServerThread(threading.Thread):
3435
:param ready_event: Event which gets set when the socket handler is
3536
ready to receive requests.
3637
"""
37-
def __init__(self, socket_handler, host='localhost', ready_event=None):
38+
def __init__(self,
39+
socket_handler,
40+
host='localhost',
41+
ready_event=None,
42+
h2=True):
3843
threading.Thread.__init__(self)
3944

4045
self.socket_handler = socket_handler
4146
self.host = host
4247
self.ready_event = ready_event
4348

4449
self.cxt = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
45-
if ssl.HAS_NPN:
50+
if ssl.HAS_NPN and h2:
4651
self.cxt.set_npn_protocols([NPN_PROTOCOL])
4752
self.cxt.load_cert_chain(certfile='test/certs/server.crt',
4853
keyfile='test/certs/server.key')
@@ -89,14 +94,18 @@ def _start_server(self, socket_handler):
8994
self.server_thread = SocketServerThread(
9095
socket_handler=socket_handler,
9196
ready_event=ready_event,
97+
h2=self.h2,
9298
)
9399
self.server_thread.start()
94100
ready_event.wait()
95101
self.host = self.server_thread.host
96102
self.port = self.server_thread.port
97103

98104
def get_connection(self):
99-
return HTTP20Connection(self.host, self.port)
105+
if self.h2:
106+
return HTTP20Connection(self.host, self.port)
107+
else:
108+
return HTTP11Connection(self.host, self.port, secure=True)
100109

101110
def get_encoder(self):
102111
"""

test/test_http11.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ def test_initialization_separate_port(self):
5555
assert c.port == 8080
5656
assert not c.secure
5757

58+
def test_can_override_security(self):
59+
c = HTTP11Connection('localhost', 443, secure=False)
60+
61+
assert c.host == 'localhost'
62+
assert c.port == 443
63+
assert not c.secure
64+
5865
def test_basic_request(self):
5966
c = HTTP11Connection('http2bin.org')
6067
c._sock = sock = DummySocket()
@@ -309,7 +316,7 @@ def body():
309316

310317
class TestHTTP11Response(object):
311318
def test_short_circuit_read(self):
312-
r = HTTP11Response(200, 'OK', {}, None)
319+
r = HTTP11Response(200, 'OK', {b'content-length': [b'0']}, None)
313320

314321
assert r.read() == b''
315322

test/test_integration.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ def receive_preamble(sock):
5959

6060

6161
class TestHyperIntegration(SocketLevelTest):
62+
# These are HTTP/2 tests.
63+
h2 = True
64+
6265
def test_connection_string(self):
6366
self.set_up()
6467

test/test_integration_http11.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
test/integration_http11
4+
~~~~~~~~~~~~~~~~~~~~~~~
5+
6+
This file defines integration-type tests for hyper's HTTP/1.1 layer. These are
7+
still not fully hitting the network, so that's alright.
8+
"""
9+
import hyper
10+
import threading
11+
12+
from hyper.compat import ssl
13+
from hyper.http11.connection import HTTP11Connection
14+
from server import SocketLevelTest
15+
16+
# Turn off certificate verification for the tests.
17+
if ssl is not None:
18+
hyper.tls._context = hyper.tls._init_context()
19+
hyper.tls._context.check_hostname = False
20+
hyper.tls._context.verify_mode = ssl.CERT_NONE
21+
22+
23+
class TestHyperH11Integration(SocketLevelTest):
24+
# These are HTTP/1.1 tests.
25+
h2 = False
26+
27+
def test_basic_request_response(self):
28+
self.set_up()
29+
30+
data = []
31+
32+
def socket_handler(listener):
33+
sock = listener.accept()[0]
34+
35+
# We should get the initial request.
36+
data.append(sock.recv(65535))
37+
38+
# We need to send back a response.
39+
resp = (
40+
b'HTTP/1.1 201 No Content\r\n'
41+
b'Server: socket-level-server\r\n'
42+
b'Content-Length: 0\r\n'
43+
b'Connection: close\r\n'
44+
b'\r\n'
45+
)
46+
sock.send(resp)
47+
48+
sock.close()
49+
50+
self._start_server(socket_handler)
51+
c = self.get_connection()
52+
c.request('GET', '/')
53+
r = c.get_response()
54+
55+
assert r.status == 201
56+
assert r.reason == b'No Content'
57+
assert len(r.headers) == 3
58+
assert r.headers[b'server'] == [b'socket-level-server']
59+
assert r.headers[b'content-length'] == [b'0']
60+
assert r.headers[b'connection'] == [b'close']
61+
62+
assert r.read() == b''
63+
64+
def test_closing_response(self):
65+
self.set_up()
66+
67+
data = []
68+
69+
def socket_handler(listener):
70+
sock = listener.accept()[0]
71+
72+
# We should get the initial request.
73+
data.append(sock.recv(65535))
74+
75+
# We need to send back a response.
76+
resp = (
77+
b'HTTP/1.1 200 OK\r\n'
78+
b'Server: socket-level-server\r\n'
79+
b'Connection: close\r\n'
80+
b'\r\n'
81+
)
82+
sock.send(resp)
83+
84+
chunks = [
85+
b'hello',
86+
b'there',
87+
b'sir',
88+
b'finalfantasy',
89+
]
90+
91+
for chunk in chunks:
92+
sock.send(chunk)
93+
94+
sock.close()
95+
96+
self._start_server(socket_handler)
97+
c = self.get_connection()
98+
c.request('GET', '/')
99+
r = c.get_response()
100+
101+
assert r.status == 200
102+
assert r.reason == b'OK'
103+
assert len(r.headers) == 2
104+
assert r.headers[b'server'] == [b'socket-level-server']
105+
assert r.headers[b'connection'] == [b'close']
106+
107+
assert r.read() == b'hellotheresirfinalfantasy'
108+
109+
def test_response_with_body(self):
110+
self.set_up()
111+
112+
data = []
113+
114+
def socket_handler(listener):
115+
sock = listener.accept()[0]
116+
117+
# We should get the initial request.
118+
data.append(sock.recv(65535))
119+
120+
# We need to send back a response.
121+
resp = (
122+
b'HTTP/1.1 200 OK\r\n'
123+
b'Server: socket-level-server\r\n'
124+
b'Content-Length: 15\r\n'
125+
b'\r\n'
126+
)
127+
sock.send(resp)
128+
129+
chunks = [
130+
b'hello',
131+
b'there',
132+
b'hello',
133+
]
134+
135+
for chunk in chunks:
136+
sock.send(chunk)
137+
138+
sock.close()
139+
140+
self._start_server(socket_handler)
141+
c = self.get_connection()
142+
c.request('GET', '/')
143+
r = c.get_response()
144+
145+
assert r.status == 200
146+
assert r.reason == b'OK'
147+
assert len(r.headers) == 2
148+
assert r.headers[b'server'] == [b'socket-level-server']
149+
assert r.headers[b'content-length'] == [b'15']
150+
151+
assert r.read() == b'hellotherehello'

0 commit comments

Comments
 (0)