1# -*- coding: utf-8 -*- 2""" 3test_informational_responses 4~~~~~~~~~~~~~~~~~~~~~~~~~~ 5 6Tests that validate that hyper-h2 correctly handles informational (1XX) 7responses in its state machine. 8""" 9import pytest 10 11import h2.config 12import h2.connection 13import h2.events 14import h2.exceptions 15 16 17class TestReceivingInformationalResponses(object): 18 """ 19 Tests for receiving informational responses. 20 """ 21 example_request_headers = [ 22 (b':authority', b'example.com'), 23 (b':path', b'/'), 24 (b':scheme', b'https'), 25 (b':method', b'GET'), 26 (b'expect', b'100-continue'), 27 ] 28 example_informational_headers = [ 29 (b':status', b'100'), 30 (b'server', b'fake-serv/0.1.0') 31 ] 32 example_response_headers = [ 33 (b':status', b'200'), 34 (b'server', b'fake-serv/0.1.0') 35 ] 36 example_trailers = [ 37 (b'trailer', b'you-bet'), 38 ] 39 40 @pytest.mark.parametrize('end_stream', (True, False)) 41 def test_single_informational_response(self, frame_factory, end_stream): 42 """ 43 When receiving a informational response, the appropriate event is 44 signaled. 45 """ 46 c = h2.connection.H2Connection() 47 c.initiate_connection() 48 c.send_headers( 49 stream_id=1, 50 headers=self.example_request_headers, 51 end_stream=end_stream 52 ) 53 54 f = frame_factory.build_headers_frame( 55 headers=self.example_informational_headers, 56 stream_id=1, 57 ) 58 events = c.receive_data(f.serialize()) 59 60 assert len(events) == 1 61 event = events[0] 62 63 assert isinstance(event, h2.events.InformationalResponseReceived) 64 assert event.headers == self.example_informational_headers 65 assert event.stream_id == 1 66 67 @pytest.mark.parametrize('end_stream', (True, False)) 68 def test_receiving_multiple_header_blocks(self, frame_factory, end_stream): 69 """ 70 At least three header blocks can be received: informational, headers, 71 trailers. 72 """ 73 c = h2.connection.H2Connection() 74 c.initiate_connection() 75 c.send_headers( 76 stream_id=1, 77 headers=self.example_request_headers, 78 end_stream=end_stream 79 ) 80 81 f1 = frame_factory.build_headers_frame( 82 headers=self.example_informational_headers, 83 stream_id=1, 84 ) 85 f2 = frame_factory.build_headers_frame( 86 headers=self.example_response_headers, 87 stream_id=1, 88 ) 89 f3 = frame_factory.build_headers_frame( 90 headers=self.example_trailers, 91 stream_id=1, 92 flags=['END_STREAM'], 93 ) 94 events = c.receive_data( 95 f1.serialize() + f2.serialize() + f3.serialize() 96 ) 97 98 assert len(events) == 4 99 100 assert isinstance(events[0], h2.events.InformationalResponseReceived) 101 assert events[0].headers == self.example_informational_headers 102 assert events[0].stream_id == 1 103 104 assert isinstance(events[1], h2.events.ResponseReceived) 105 assert events[1].headers == self.example_response_headers 106 assert events[1].stream_id == 1 107 108 assert isinstance(events[2], h2.events.TrailersReceived) 109 assert events[2].headers == self.example_trailers 110 assert events[2].stream_id == 1 111 112 @pytest.mark.parametrize('end_stream', (True, False)) 113 def test_receiving_multiple_informational_responses(self, 114 frame_factory, 115 end_stream): 116 """ 117 More than one informational response is allowed. 118 """ 119 c = h2.connection.H2Connection() 120 c.initiate_connection() 121 c.send_headers( 122 stream_id=1, 123 headers=self.example_request_headers, 124 end_stream=end_stream 125 ) 126 127 f1 = frame_factory.build_headers_frame( 128 headers=self.example_informational_headers, 129 stream_id=1, 130 ) 131 f2 = frame_factory.build_headers_frame( 132 headers=[(':status', '101')], 133 stream_id=1, 134 ) 135 events = c.receive_data(f1.serialize() + f2.serialize()) 136 137 assert len(events) == 2 138 139 assert isinstance(events[0], h2.events.InformationalResponseReceived) 140 assert events[0].headers == self.example_informational_headers 141 assert events[0].stream_id == 1 142 143 assert isinstance(events[1], h2.events.InformationalResponseReceived) 144 assert events[1].headers == [(b':status', b'101')] 145 assert events[1].stream_id == 1 146 147 @pytest.mark.parametrize('end_stream', (True, False)) 148 def test_receive_provisional_response_with_end_stream(self, 149 frame_factory, 150 end_stream): 151 """ 152 Receiving provisional responses with END_STREAM set causes 153 ProtocolErrors. 154 """ 155 c = h2.connection.H2Connection() 156 c.initiate_connection() 157 c.send_headers( 158 stream_id=1, 159 headers=self.example_request_headers, 160 end_stream=end_stream 161 ) 162 c.clear_outbound_data_buffer() 163 164 f = frame_factory.build_headers_frame( 165 headers=self.example_informational_headers, 166 stream_id=1, 167 flags=['END_STREAM'] 168 ) 169 170 with pytest.raises(h2.exceptions.ProtocolError): 171 c.receive_data(f.serialize()) 172 173 expected = frame_factory.build_goaway_frame( 174 last_stream_id=0, 175 error_code=1, 176 ) 177 assert c.data_to_send() == expected.serialize() 178 179 @pytest.mark.parametrize('end_stream', (True, False)) 180 def test_receiving_out_of_order_headers(self, frame_factory, end_stream): 181 """ 182 When receiving a informational response after the actual response 183 headers we consider it a ProtocolError and raise it. 184 """ 185 c = h2.connection.H2Connection() 186 c.initiate_connection() 187 c.send_headers( 188 stream_id=1, 189 headers=self.example_request_headers, 190 end_stream=end_stream 191 ) 192 193 f1 = frame_factory.build_headers_frame( 194 headers=self.example_response_headers, 195 stream_id=1, 196 ) 197 f2 = frame_factory.build_headers_frame( 198 headers=self.example_informational_headers, 199 stream_id=1, 200 ) 201 c.receive_data(f1.serialize()) 202 c.clear_outbound_data_buffer() 203 204 with pytest.raises(h2.exceptions.ProtocolError): 205 c.receive_data(f2.serialize()) 206 207 expected = frame_factory.build_goaway_frame( 208 last_stream_id=0, 209 error_code=1, 210 ) 211 assert c.data_to_send() == expected.serialize() 212 213 214class TestSendingInformationalResponses(object): 215 """ 216 Tests for sending informational responses. 217 """ 218 example_request_headers = [ 219 (b':authority', b'example.com'), 220 (b':path', b'/'), 221 (b':scheme', b'https'), 222 (b':method', b'GET'), 223 (b'expect', b'100-continue'), 224 ] 225 unicode_informational_headers = [ 226 (u':status', u'100'), 227 (u'server', u'fake-serv/0.1.0') 228 ] 229 bytes_informational_headers = [ 230 (b':status', b'100'), 231 (b'server', b'fake-serv/0.1.0') 232 ] 233 example_response_headers = [ 234 (b':status', b'200'), 235 (b'server', b'fake-serv/0.1.0') 236 ] 237 example_trailers = [ 238 (b'trailer', b'you-bet'), 239 ] 240 server_config = h2.config.H2Configuration(client_side=False) 241 242 @pytest.mark.parametrize( 243 'hdrs', (unicode_informational_headers, bytes_informational_headers), 244 ) 245 @pytest.mark.parametrize('end_stream', (True, False)) 246 def test_single_informational_response(self, 247 frame_factory, 248 hdrs, 249 end_stream): 250 """ 251 When sending a informational response, the appropriate frames are 252 emitted. 253 """ 254 c = h2.connection.H2Connection(config=self.server_config) 255 c.initiate_connection() 256 c.receive_data(frame_factory.preamble()) 257 flags = ['END_STREAM'] if end_stream else [] 258 f = frame_factory.build_headers_frame( 259 headers=self.example_request_headers, 260 stream_id=1, 261 flags=flags, 262 ) 263 c.receive_data(f.serialize()) 264 c.clear_outbound_data_buffer() 265 frame_factory.refresh_encoder() 266 267 c.send_headers( 268 stream_id=1, 269 headers=hdrs 270 ) 271 272 f = frame_factory.build_headers_frame( 273 headers=hdrs, 274 stream_id=1, 275 ) 276 assert c.data_to_send() == f.serialize() 277 278 @pytest.mark.parametrize( 279 'hdrs', (unicode_informational_headers, bytes_informational_headers), 280 ) 281 @pytest.mark.parametrize('end_stream', (True, False)) 282 def test_sending_multiple_header_blocks(self, 283 frame_factory, 284 hdrs, 285 end_stream): 286 """ 287 At least three header blocks can be sent: informational, headers, 288 trailers. 289 """ 290 c = h2.connection.H2Connection(config=self.server_config) 291 c.initiate_connection() 292 c.receive_data(frame_factory.preamble()) 293 flags = ['END_STREAM'] if end_stream else [] 294 f = frame_factory.build_headers_frame( 295 headers=self.example_request_headers, 296 stream_id=1, 297 flags=flags, 298 ) 299 c.receive_data(f.serialize()) 300 c.clear_outbound_data_buffer() 301 frame_factory.refresh_encoder() 302 303 # Send the three header blocks. 304 c.send_headers( 305 stream_id=1, 306 headers=hdrs 307 ) 308 c.send_headers( 309 stream_id=1, 310 headers=self.example_response_headers 311 ) 312 c.send_headers( 313 stream_id=1, 314 headers=self.example_trailers, 315 end_stream=True 316 ) 317 318 # Check that we sent them properly. 319 f1 = frame_factory.build_headers_frame( 320 headers=hdrs, 321 stream_id=1, 322 ) 323 f2 = frame_factory.build_headers_frame( 324 headers=self.example_response_headers, 325 stream_id=1, 326 ) 327 f3 = frame_factory.build_headers_frame( 328 headers=self.example_trailers, 329 stream_id=1, 330 flags=['END_STREAM'] 331 ) 332 assert ( 333 c.data_to_send() == 334 f1.serialize() + f2.serialize() + f3.serialize() 335 ) 336 337 @pytest.mark.parametrize( 338 'hdrs', (unicode_informational_headers, bytes_informational_headers), 339 ) 340 @pytest.mark.parametrize('end_stream', (True, False)) 341 def test_sending_multiple_informational_responses(self, 342 frame_factory, 343 hdrs, 344 end_stream): 345 """ 346 More than one informational response is allowed. 347 """ 348 c = h2.connection.H2Connection(config=self.server_config) 349 c.initiate_connection() 350 c.receive_data(frame_factory.preamble()) 351 flags = ['END_STREAM'] if end_stream else [] 352 f = frame_factory.build_headers_frame( 353 headers=self.example_request_headers, 354 stream_id=1, 355 flags=flags, 356 ) 357 c.receive_data(f.serialize()) 358 c.clear_outbound_data_buffer() 359 frame_factory.refresh_encoder() 360 361 # Send two informational responses. 362 c.send_headers( 363 stream_id=1, 364 headers=hdrs, 365 ) 366 c.send_headers( 367 stream_id=1, 368 headers=[(':status', '101')] 369 ) 370 371 # Check we sent them both. 372 f1 = frame_factory.build_headers_frame( 373 headers=hdrs, 374 stream_id=1, 375 ) 376 f2 = frame_factory.build_headers_frame( 377 headers=[(':status', '101')], 378 stream_id=1, 379 ) 380 assert c.data_to_send() == f1.serialize() + f2.serialize() 381 382 @pytest.mark.parametrize( 383 'hdrs', (unicode_informational_headers, bytes_informational_headers), 384 ) 385 @pytest.mark.parametrize('end_stream', (True, False)) 386 def test_send_provisional_response_with_end_stream(self, 387 frame_factory, 388 hdrs, 389 end_stream): 390 """ 391 Sending provisional responses with END_STREAM set causes 392 ProtocolErrors. 393 """ 394 c = h2.connection.H2Connection(config=self.server_config) 395 c.initiate_connection() 396 c.receive_data(frame_factory.preamble()) 397 flags = ['END_STREAM'] if end_stream else [] 398 f = frame_factory.build_headers_frame( 399 headers=self.example_request_headers, 400 stream_id=1, 401 flags=flags, 402 ) 403 c.receive_data(f.serialize()) 404 405 with pytest.raises(h2.exceptions.ProtocolError): 406 c.send_headers( 407 stream_id=1, 408 headers=hdrs, 409 end_stream=True, 410 ) 411 412 @pytest.mark.parametrize( 413 'hdrs', (unicode_informational_headers, bytes_informational_headers), 414 ) 415 @pytest.mark.parametrize('end_stream', (True, False)) 416 def test_reject_sending_out_of_order_headers(self, 417 frame_factory, 418 hdrs, 419 end_stream): 420 """ 421 When sending an informational response after the actual response 422 headers we consider it a ProtocolError and raise it. 423 """ 424 c = h2.connection.H2Connection(config=self.server_config) 425 c.initiate_connection() 426 c.receive_data(frame_factory.preamble()) 427 flags = ['END_STREAM'] if end_stream else [] 428 f = frame_factory.build_headers_frame( 429 headers=self.example_request_headers, 430 stream_id=1, 431 flags=flags, 432 ) 433 c.receive_data(f.serialize()) 434 435 c.send_headers( 436 stream_id=1, 437 headers=self.example_response_headers 438 ) 439 440 with pytest.raises(h2.exceptions.ProtocolError): 441 c.send_headers( 442 stream_id=1, 443 headers=hdrs 444 ) 445