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

Commit d4d96a5

Browse files
committed
Merge pull request #131 from jdecuyper/error-code-registry
Add HTTP2 error code registry
2 parents 1b3c989 + 4069fd1 commit d4d96a5

File tree

3 files changed

+168
-6
lines changed

3 files changed

+168
-6
lines changed

hyper/http20/connection.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from .response import HTTP20Response, HTTP20Push
2020
from .window import FlowControlManager
2121
from .exceptions import ConnectionError
22+
from . import errors
2223

2324
import errno
2425
import logging
@@ -375,11 +376,20 @@ def receive_frame(self, frame):
375376
# shutdown and all is well. Otherwise, throw an exception.
376377
self.close()
377378

379+
# If an error occured, try to read the error description from
380+
# code registry otherwise use the frame's additional data.
378381
if frame.error_code != 0:
379-
raise ConnectionError(
380-
"Encountered error %d, extra data %s." %
381-
(frame.error_code, frame.additional_data)
382-
)
382+
try:
383+
name, number, description = errors.get_data(frame.error_code)
384+
except ValueError:
385+
error_string = ("Encountered error code %d, extra data %s" %
386+
(frame.error_code, frame.additional_data))
387+
else:
388+
error_string = ("Encountered error %s %s: %s" %
389+
(name, number, description))
390+
391+
raise ConnectionError(error_string)
392+
383393
elif frame.type == BlockedFrame.type:
384394
increment = self.window_manager._blocked()
385395
if increment:

hyper/http20/errors.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
hyper/http20/errors
4+
~~~~~~~~~~~~~~~~~~~
5+
6+
Global error code registry containing the established HTTP/2 error codes.
7+
The registry is based on a 32-bit space so we use the error code to index into
8+
the array.
9+
10+
The current registry is available at:
11+
https://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-11.4
12+
"""
13+
14+
NO_ERROR = {'Name': 'NO_ERROR',
15+
'Code': '0x0',
16+
'Description': 'Graceful shutdown'}
17+
PROTOCOL_ERROR = {'Name': 'PROTOCOL_ERROR',
18+
'Code': '0x1',
19+
'Description': 'Protocol error detected'}
20+
INTERNAL_ERROR = {'Name': 'INTERNAL_ERROR',
21+
'Code': '0x2',
22+
'Description': 'Implementation fault'}
23+
FLOW_CONTROL_ERROR = {'Name': 'FLOW_CONTROL_ERROR',
24+
'Code': '0x3',
25+
'Description': 'Flow control limits exceeded'}
26+
SETTINGS_TIMEOUT = {'Name': 'SETTINGS_TIMEOUT',
27+
'Code': '0x4',
28+
'Description': 'Settings not acknowledged'}
29+
STREAM_CLOSED = {'Name': 'STREAM_CLOSED',
30+
'Code': '0x5',
31+
'Description': 'Frame received for closed stream'}
32+
FRAME_SIZE_ERROR = {'Name': 'FRAME_SIZE_ERROR',
33+
'Code': '0x6',
34+
'Description': 'Frame size incorrect'}
35+
REFUSED_STREAM = {'Name': 'REFUSED_STREAM ',
36+
'Code': '0x7',
37+
'Description': 'Stream not processed'}
38+
CANCEL = {'Name': 'CANCEL',
39+
'Code': '0x8',
40+
'Description': 'Stream cancelled'}
41+
COMPRESSION_ERROR = {'Name': 'COMPRESSION_ERROR',
42+
'Code': '0x9',
43+
'Description': 'Compression state not updated'}
44+
CONNECT_ERROR = {'Name': 'CONNECT_ERROR',
45+
'Code': '0xa',
46+
'Description':
47+
'TCP connection error for CONNECT method'}
48+
ENHANCE_YOUR_CALM = {'Name': 'ENHANCE_YOUR_CALM',
49+
'Code': '0xb',
50+
'Description': 'Processing capacity exceeded'}
51+
INADEQUATE_SECURITY = {'Name': 'INADEQUATE_SECURITY',
52+
'Code': '0xc',
53+
'Description':
54+
'Negotiated TLS parameters not acceptable'}
55+
HTTP_1_1_REQUIRED = {'Name': 'HTTP_1_1_REQUIRED',
56+
'Code': '0xd',
57+
'Description': 'Use HTTP/1.1 for the request'}
58+
59+
H2_ERRORS = [NO_ERROR, PROTOCOL_ERROR, INTERNAL_ERROR, FLOW_CONTROL_ERROR,
60+
SETTINGS_TIMEOUT, STREAM_CLOSED, FRAME_SIZE_ERROR, REFUSED_STREAM,
61+
CANCEL, COMPRESSION_ERROR, CONNECT_ERROR, ENHANCE_YOUR_CALM,
62+
INADEQUATE_SECURITY, HTTP_1_1_REQUIRED]
63+
64+
def get_data(error_code):
65+
"""
66+
Lookup the error code description, if not available throw a value error
67+
"""
68+
if error_code < 0 or error_code >= len(H2_ERRORS):
69+
raise ValueError("Error code is invalid")
70+
71+
name = H2_ERRORS[error_code]['Name']
72+
number = H2_ERRORS[error_code]['Code']
73+
description = H2_ERRORS[error_code]['Description']
74+
75+
return name, number, description

test/test_hyper.py

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from hyper.packages.hyperframe.frame import (
33
Frame, DataFrame, RstStreamFrame, SettingsFrame,
44
PushPromiseFrame, PingFrame, WindowUpdateFrame, HeadersFrame,
5-
ContinuationFrame, BlockedFrame,
5+
ContinuationFrame, BlockedFrame, GoAwayFrame,
66
)
77
from hyper.packages.hpack.hpack_compat import Encoder, Decoder
88
from hyper.http20.connection import HTTP20Connection
@@ -11,7 +11,7 @@
1111
)
1212
from hyper.http20.response import HTTP20Response, HTTP20Push
1313
from hyper.http20.exceptions import (
14-
HPACKDecodingError, HPACKEncodingError, ProtocolError
14+
HPACKDecodingError, HPACKEncodingError, ProtocolError, ConnectionError,
1515
)
1616
from hyper.http20.window import FlowControlManager
1717
from hyper.http20.util import (
@@ -20,6 +20,7 @@
2020
from hyper.common.headers import HTTPHeaderMap
2121
from hyper.compat import zlib_compressobj
2222
from hyper.contrib import HTTP20Adapter
23+
import hyper.http20.errors as errors
2324
import errno
2425
import os
2526
import pytest
@@ -1183,6 +1184,82 @@ def test_stripping_multiple_connection_headers(self):
11831184

11841185
assert h2_safe_headers(headers) == stripped
11851186

1187+
def test_goaway_frame_PROTOCOL_ERROR(self):
1188+
f = GoAwayFrame(0)
1189+
# Set error code to PROTOCOL_ERROR
1190+
f.error_code = 1;
1191+
1192+
c = HTTP20Connection('www.google.com')
1193+
c._sock = DummySocket()
1194+
1195+
# 'Receive' the GOAWAY frame.
1196+
# Validate that the spec error name and description are used to throw
1197+
# the connection exception.
1198+
with pytest.raises(ConnectionError) as conn_err:
1199+
c.receive_frame(f)
1200+
1201+
err_msg = str(conn_err)
1202+
name, number, description = errors.get_data(1)
1203+
1204+
assert name in err_msg
1205+
assert number in err_msg
1206+
assert description in err_msg
1207+
1208+
def test_goaway_frame_HTTP_1_1_REQUIRED(self):
1209+
f = GoAwayFrame(0)
1210+
# Set error code to HTTP_1_1_REQUIRED
1211+
f.error_code = 13;
1212+
1213+
c = HTTP20Connection('www.google.com')
1214+
c._sock = DummySocket()
1215+
1216+
# 'Receive' the GOAWAY frame.
1217+
# Validate that the spec error name and description are used to throw
1218+
# the connection exception.
1219+
with pytest.raises(ConnectionError) as conn_err:
1220+
c.receive_frame(f)
1221+
1222+
err_msg = str(conn_err)
1223+
name, number, description = errors.get_data(13)
1224+
1225+
assert name in err_msg
1226+
assert number in err_msg
1227+
assert description in err_msg
1228+
1229+
def test_goaway_frame_NO_ERROR(self):
1230+
f = GoAwayFrame(0)
1231+
# Set error code to NO_ERROR
1232+
f.error_code = 0;
1233+
1234+
c = HTTP20Connection('www.google.com')
1235+
c._sock = DummySocket()
1236+
1237+
# 'Receive' the GOAWAY frame.
1238+
# Test makes sure no exception is raised; error code 0 means we are
1239+
# dealing with a standard and graceful shutdown.
1240+
c.receive_frame(f)
1241+
1242+
def test_goaway_frame_invalid_error_code(self):
1243+
f = GoAwayFrame(0)
1244+
# Set error code to non existing error
1245+
f.error_code = 100;
1246+
f.additional_data = 'data about non existing error code';
1247+
1248+
c = HTTP20Connection('www.google.com')
1249+
c._sock = DummySocket()
1250+
1251+
# 'Receive' the GOAWAY frame.
1252+
# If the error code does not exist in the spec then the additional
1253+
# data is used instead.
1254+
with pytest.raises(ConnectionError) as conn_err:
1255+
c.receive_frame(f)
1256+
1257+
err_msg = str(conn_err)
1258+
with pytest.raises(ValueError):
1259+
name, number, description = errors.get_data(100)
1260+
1261+
assert 'data about non existing error code' in err_msg
1262+
assert str(f.error_code) in err_msg
11861263

11871264
# Some utility classes for the tests.
11881265
class NullEncoder(object):

0 commit comments

Comments
 (0)