1
1
# -*- coding: utf-8 -*-
2
2
3
+ from typing import Optional , Any
4
+
3
5
from .asynctcp import TcpServer , TcpConnection , TcpClient
4
6
5
7
import logging
6
8
log = logging .getLogger (__name__ )
7
9
10
+ class HttpResponseProcessing (Exception ):
11
+ # Exception class that user logic can throw during HandleHttpRequest to indicate that they have received this
12
+ # request, but are not yet ready to return the response (e.g, calling a slow external resource), and the
13
+ # connection should not block for a response.
14
+ # If this is thrown, then the active request will be stashed on the connection and the http server will
15
+ # continue processing any other active requests
16
+ pass
17
+
8
18
9
19
class HttpConnection (TcpConnection ):
10
- pass
20
+ # If this connection has already parsed a request, and that request has been pushed to the caller but backpressured,
21
+ # then it will be stashed on the connection so that it can be re-submitted each spin until the user logic is ready
22
+ # to return a response for it.
23
+ pendingRequest : Optional ["HttpRequest" ] = None
11
24
12
25
class HttpRequest (object ):
13
26
method = 'GET'
@@ -19,6 +32,10 @@ class HttpRequest(object):
19
32
20
33
response = None
21
34
35
+ # For user programs that make use of non-blocking handlers
36
+ # Any information necessary to track the progress of the request can be stored here
37
+ userState : Optional [Any ] = None
38
+
22
39
def __repr__ (self ):
23
40
return '<%s(%s)>' % (self .__class__ .__name__ , ', ' .join ([
24
41
('%s=%r' % (key , getattr (self , key )))
@@ -73,19 +90,34 @@ def _HandleHttpReceiveOnce(self, connection):
73
90
response = None
74
91
try :
75
92
response = self ._HandleHttpRequest (connection , request )
93
+ except HttpResponseProcessing :
94
+ # If the current request would block, then cache it and re-submit it next loop to check if it is complete.
95
+ # Send no response on the connection here - the request is still processing
96
+ connection .pendingRequest = request
97
+ connection .hasPendingWork = True
98
+ return
76
99
except Exception as e :
77
100
log .exception ('caught exception while handling http request %r: %s' , request , e )
78
- finally :
79
- if response is None :
80
- response = HttpResponse (request , statusCode = 500 , statusText = 'Internal Server Error' )
81
- log .verbose ('sending http response: %r' , response )
82
- self ._SendHttpResponse (connection , request , response )
101
+
102
+ if response is None :
103
+ response = HttpResponse (request , statusCode = 500 , statusText = 'Internal Server Error' )
104
+
105
+ log .verbose ('sending http response: %r' , response )
106
+ self ._SendHttpResponse (connection , request , response )
107
+
83
108
return True # handled one request, try next one
84
109
85
110
def _HandleHttpRequest (self , connection , request ):
86
111
return self ._CallApi ('HandleHttpRequest' , request = request , connection = connection , server = self )
87
112
88
113
def _TryReceiveHttpRequest (self , connection ):
114
+ # If this connection already has a pending request, pop it off and return it
115
+ if connection .pendingRequest :
116
+ request = connection .pendingRequest
117
+ connection .pendingRequest = None
118
+ connection .hasPendingWork = False
119
+ return request
120
+
89
121
bufferData = connection .receiveBuffer .readView .tobytes ()
90
122
if b'\r \n \r \n ' not in bufferData :
91
123
if len (bufferData ) > 10240 :
0 commit comments