4
4
5
5
from .base_protocol import BaseProtocol
6
6
from .client_exceptions import (
7
+ ClientConnectionError ,
7
8
ClientOSError ,
8
9
ClientPayloadError ,
9
10
ServerDisconnectedError ,
10
11
SocketTimeoutError ,
11
12
)
12
13
from .helpers import (
14
+ _EXC_SENTINEL ,
13
15
BaseTimerContext ,
14
16
set_exception ,
15
17
set_result ,
16
18
status_code_must_be_empty_body ,
17
19
)
18
20
from .http import HttpResponseParser , RawResponseMessage , WebSocketReader
21
+ from .http_exceptions import HttpProcessingError
19
22
from .streams import EMPTY_PAYLOAD , DataQueue , StreamReader
20
23
21
24
@@ -80,41 +83,70 @@ def is_connected(self) -> bool:
80
83
def connection_lost (self , exc : Optional [BaseException ]) -> None :
81
84
self ._drop_timeout ()
82
85
83
- if exc is not None :
84
- set_exception (self .closed , exc )
85
- else :
86
+ original_connection_error = exc
87
+ reraised_exc = original_connection_error
88
+
89
+ connection_closed_cleanly = original_connection_error is None
90
+
91
+ if connection_closed_cleanly :
86
92
set_result (self .closed , None )
93
+ else :
94
+ assert original_connection_error is not None
95
+ set_exception (
96
+ self .closed ,
97
+ ClientConnectionError (
98
+ f"Connection lost: { original_connection_error !s} " ,
99
+ ),
100
+ original_connection_error ,
101
+ )
87
102
88
103
if self ._payload_parser is not None :
89
- with suppress (Exception ):
104
+ with suppress (Exception ): # FIXME: log this somehow?
90
105
self ._payload_parser .feed_eof ()
91
106
92
107
uncompleted = None
93
108
if self ._parser is not None :
94
109
try :
95
110
uncompleted = self ._parser .feed_eof ()
96
- except Exception as e :
111
+ except Exception as underlying_exc :
97
112
if self ._payload is not None :
98
- exc = ClientPayloadError ("Response payload is not completed" )
99
- exc .__cause__ = e
100
- self ._payload .set_exception (exc )
113
+ client_payload_exc_msg = (
114
+ f"Response payload is not completed: { underlying_exc !r} "
115
+ )
116
+ if not connection_closed_cleanly :
117
+ client_payload_exc_msg = (
118
+ f"{ client_payload_exc_msg !s} . "
119
+ f"{ original_connection_error !r} "
120
+ )
121
+ set_exception (
122
+ self ._payload ,
123
+ ClientPayloadError (client_payload_exc_msg ),
124
+ underlying_exc ,
125
+ )
101
126
102
127
if not self .is_eof ():
103
- if isinstance (exc , OSError ):
104
- exc = ClientOSError (* exc .args )
105
- if exc is None :
106
- exc = ServerDisconnectedError (uncompleted )
128
+ if isinstance (original_connection_error , OSError ):
129
+ reraised_exc = ClientOSError (* original_connection_error .args )
130
+ if connection_closed_cleanly :
131
+ reraised_exc = ServerDisconnectedError (uncompleted )
107
132
# assigns self._should_close to True as side effect,
108
133
# we do it anyway below
109
- self .set_exception (exc )
134
+ underlying_non_eof_exc = (
135
+ _EXC_SENTINEL
136
+ if connection_closed_cleanly
137
+ else original_connection_error
138
+ )
139
+ assert underlying_non_eof_exc is not None
140
+ assert reraised_exc is not None
141
+ self .set_exception (reraised_exc , underlying_non_eof_exc )
110
142
111
143
self ._should_close = True
112
144
self ._parser = None
113
145
self ._payload = None
114
146
self ._payload_parser = None
115
147
self ._reading_paused = False
116
148
117
- super ().connection_lost (exc )
149
+ super ().connection_lost (reraised_exc )
118
150
119
151
def eof_received (self ) -> None :
120
152
# should call parser.feed_eof() most likely
@@ -128,10 +160,14 @@ def resume_reading(self) -> None:
128
160
super ().resume_reading ()
129
161
self ._reschedule_timeout ()
130
162
131
- def set_exception (self , exc : BaseException ) -> None :
163
+ def set_exception (
164
+ self ,
165
+ exc : BaseException ,
166
+ exc_cause : BaseException = _EXC_SENTINEL ,
167
+ ) -> None :
132
168
self ._should_close = True
133
169
self ._drop_timeout ()
134
- super ().set_exception (exc )
170
+ super ().set_exception (exc , exc_cause )
135
171
136
172
def set_parser (self , parser : Any , payload : Any ) -> None :
137
173
# TODO: actual types are:
@@ -208,7 +244,7 @@ def _on_read_timeout(self) -> None:
208
244
exc = SocketTimeoutError ("Timeout on reading data from socket" )
209
245
self .set_exception (exc )
210
246
if self ._payload is not None :
211
- self ._payload . set_exception ( exc )
247
+ set_exception ( self ._payload , exc )
212
248
213
249
def data_received (self , data : bytes ) -> None :
214
250
self ._reschedule_timeout ()
@@ -234,14 +270,14 @@ def data_received(self, data: bytes) -> None:
234
270
# parse http messages
235
271
try :
236
272
messages , upgraded , tail = self ._parser .feed_data (data )
237
- except BaseException as exc :
273
+ except BaseException as underlying_exc :
238
274
if self .transport is not None :
239
275
# connection.release() could be called BEFORE
240
276
# data_received(), the transport is already
241
277
# closed in this case
242
278
self .transport .close ()
243
279
# should_close is True after the call
244
- self .set_exception (exc )
280
+ self .set_exception (HttpProcessingError (), underlying_exc )
245
281
return
246
282
247
283
self ._upgraded = upgraded
0 commit comments