1818"""
1919
2020from argparse import ArgumentParser , ArgumentDefaultsHelpFormatter
21- from http .server import HTTPServer , BaseHTTPRequestHandler
22- from os import path
23- from urllib .parse import urlparse
24-
2521import errno
2622import pkg_resources
23+ from http .server import HTTPServer , BaseHTTPRequestHandler
24+ from os import path
2725import socket
2826import sys
2927import time
3028import threading
29+ from typing import Any , Optional # noqa: F401
30+ from urllib .parse import urlparse
31+
3132
3233if sys .platform == 'win32' :
3334 import win32file
3435 import pywintypes
36+ else :
37+ win32file : Any = None
38+ pywintypes : Any = None
3539
3640try :
3741 VERSION = pkg_resources .get_distribution ("dopple" ).version
4044
4145BUFSIZE = 32
4246DELIMITER = ord ('\n ' )
43- BACKEND_CONNECTION_TIMEOUT = 30.0
47+ BACKEND_CONNECTION_TIMEOUT = 30.0
4448INFO = """Dopple JSON-RPC Proxy
4549
4650Version: {version}
4751Proxy: {proxy_url}
4852Backend: {backend_url} (connected: {connected})
4953"""
5054
55+ SocketAlias = socket .socket
56+
5157
5258class BackendError (Exception ):
5359 pass
@@ -56,19 +62,19 @@ class BackendError(Exception):
5662class UnixSocketConnector (object ):
5763 """Unix Domain Socket connector. Connects to socket lazily."""
5864
59- def __init__ (self , socket_path ) :
65+ def __init__ (self , socket_path : str ) -> None :
6066 self ._socket_path = socket_path
61- self ._socket = None
67+ self ._socket : Optional [ SocketAlias ] = None
6268
6369 @staticmethod
64- def _get_error_message (os_error_number ) :
70+ def _get_error_message (os_error_number : int ) -> str :
6571 if os_error_number == errno .ENOENT :
6672 return "Unix Domain Socket '{}' does not exist"
6773 if os_error_number == errno .ECONNREFUSED :
6874 return "Connection to '{}' refused"
6975 return "Unknown error when connecting to '{}'"
7076
71- def socket (self ):
77+ def socket (self ) -> SocketAlias :
7278 """Returns connected socket."""
7379 if self ._socket is None :
7480 try :
@@ -83,16 +89,16 @@ def socket(self):
8389 raise err from ex
8490 return self ._socket
8591
86- def close (self ):
92+ def close (self ) -> None :
8793 if self ._socket is not None :
8894 self ._socket .shutdown (socket .SHUT_RDWR )
8995 self ._socket .close ()
9096 self ._socket = None
9197
92- def is_connected (self ):
98+ def is_connected (self ) -> bool :
9399 return self ._socket is not None
94100
95- def check_connection (self , timeout ) :
101+ def check_connection (self , timeout : float ) -> None :
96102 SLEEPTIME = 0.1
97103 wait_time = 0.0
98104 last_exception = None
@@ -106,12 +112,15 @@ def check_connection(self, timeout):
106112 time .sleep (SLEEPTIME )
107113 wait_time += SLEEPTIME
108114 if wait_time > timeout :
109- raise last_exception if last_exception else TimeoutError
115+ if last_exception is not None :
116+ raise last_exception
117+ else :
118+ raise TimeoutError ()
110119
111- def recv (self , max_length ) :
120+ def recv (self , max_length : int ) -> bytes :
112121 return self .socket ().recv (max_length )
113122
114- def sendall (self , data ) :
123+ def sendall (self , data : bytes ) -> None :
115124 try :
116125 return self .socket ().sendall (data )
117126 except OSError as ex :
@@ -126,42 +135,44 @@ def sendall(self, data):
126135class NamedPipeConnector (object ):
127136 """Windows named pipe simulating socket."""
128137
129- def __init__ (self , ipc_path ) :
138+ def __init__ (self , ipc_path : str ) -> None :
130139 try :
131140 self .handle = win32file .CreateFile (
132141 ipc_path , win32file .GENERIC_READ | win32file .GENERIC_WRITE ,
133142 0 , None , win32file .OPEN_EXISTING , 0 , None )
134143 except pywintypes .error as err :
135144 raise IOError (err )
136145
137- def is_connected (self ):
146+ def is_connected (self ) -> bool :
138147 return True
139148
140- def check_connection (self , timeout ) :
149+ def check_connection (self , timeout : float ) -> None :
141150 pass
142151
143- def recv (self , max_length ) :
152+ def recv (self , max_length : int ) -> Any :
144153 (err , data ) = win32file .ReadFile (self .handle , max_length )
145154 if err :
146155 raise IOError (err )
147156 return data
148157
149- def sendall (self , data ) :
158+ def sendall (self , data : bytes ) -> 'win32file.WriteFile' :
150159 return win32file .WriteFile (self .handle , data )
151160
152- def close (self ):
161+ def close (self ) -> None :
153162 self .handle .close ()
154163
155164
156- def get_ipc_connector (ipc_path ) :
165+ def get_ipc_connector (ipc_path : str ) -> Any :
157166 if sys .platform == 'win32' :
158167 return NamedPipeConnector (ipc_path )
159168 return UnixSocketConnector (ipc_path )
160169
161170
162171class HTTPRequestHandler (BaseHTTPRequestHandler ):
163172
164- def do_GET (self ):
173+ server : 'Proxy'
174+
175+ def do_GET (self ) -> None :
165176 if self .path != '/' :
166177 self .send_response (404 )
167178 self .end_headers ()
@@ -179,14 +190,14 @@ def do_GET(self):
179190 connected = self .server .conn .is_connected ())
180191 self .wfile .write (info .encode ('utf-8' ))
181192
182- def do_OPTIONS (self ):
193+ def do_OPTIONS (self ) -> None :
183194 self .send_response (200 )
184195 self .send_header ("Content-type" , "text/plain" )
185196 self .addCORS ()
186197 self .end_headers ()
187198
188- def do_POST (self ):
189- request_length = int (self .headers ['Content-Length' ])
199+ def do_POST (self ) -> None :
200+ request_length = int (self .headers ['Content-Length' ]) # type: ignore
190201 request_content = self .rfile .read (request_length )
191202 # self.log_message("Headers: {}".format(self.headers))
192203 # self.log_message("Request: {}".format(request_content))
@@ -197,7 +208,7 @@ def do_POST(self):
197208
198209 self .send_response (200 )
199210 self .send_header ("Content-type" , "application/json" )
200- self .send_header ("Content-length" , len (response_content ))
211+ self .send_header ("Content-length" , str ( len (response_content ) ))
201212 self .addCORS ()
202213 self .end_headers ()
203214 self .wfile .write (response_content )
@@ -206,22 +217,22 @@ def do_POST(self):
206217 error_msg = str (err ).encode ('utf-8' )
207218 # TODO: Send as JSON-RPC response
208219 self .send_header ("Content-type" , "text/plain" )
209- self .send_header ("Content-length" , len (error_msg ))
220+ self .send_header ("Content-length" , str ( len (error_msg ) ))
210221 self .end_headers ()
211222 self .wfile .write (error_msg )
212223 self .log_message ("Backend Error: {}" .format (err ))
213224
214225 # TODO: Handle other exceptions as error 500.
215226
216- def addCORS (self ):
227+ def addCORS (self ) -> None :
217228 self .send_header ("Access-Control-Allow-Origin" , "*" )
218229 self .send_header ("Access-Control-Allow-Methods" , "POST, GET, OPTIONS" )
219230 self .send_header ("Access-Control-Allow-Headers" , "content-type" )
220231
221232
222233class Proxy (HTTPServer ):
223234
224- def __init__ (self , proxy_url , backend_path ) :
235+ def __init__ (self , proxy_url : str , backend_path : str ) -> None :
225236 self .proxy_url = proxy_url
226237 url = urlparse (proxy_url )
227238 assert url .scheme == 'http'
@@ -231,7 +242,7 @@ def __init__(self, proxy_url, backend_path):
231242
232243 self .backend_address = path .expanduser (backend_path )
233244
234- def process (self , request ) :
245+ def process (self , request : Any ) -> bytes :
235246 self .conn .sendall (request )
236247
237248 response = b''
@@ -246,7 +257,7 @@ def process(self, request):
246257
247258 return response
248259
249- def run (self ):
260+ def run (self ) -> None :
250261 self .conn = get_ipc_connector (self .backend_address )
251262 self .conn .check_connection (timeout = BACKEND_CONNECTION_TIMEOUT )
252263
@@ -266,7 +277,7 @@ def run(self):
266277PROXY_URL_HELP = "URL for this proxy server"
267278
268279
269- def parse_args ():
280+ def parse_args () -> Any :
270281 parser = ArgumentParser (
271282 description = 'Dopple HTTP Proxy for JSON-RPC servers' ,
272283 formatter_class = ArgumentDefaultsHelpFormatter
@@ -281,25 +292,26 @@ def parse_args():
281292 return parser .parse_args ()
282293
283294
284- def run (proxy_url = DEFAULT_PROXY_URL , backend_path = DEFAULT_BACKEND_PATH ):
295+ def run (proxy_url : str = DEFAULT_PROXY_URL , backend_path : str = DEFAULT_BACKEND_PATH ) -> None :
285296 proxy = Proxy (proxy_url , backend_path )
286297 try :
287298 proxy .run ()
288299 except KeyboardInterrupt :
289300 proxy .shutdown ()
290301
291302
292- def run_daemon (proxy_url = DEFAULT_PROXY_URL , backend_path = DEFAULT_BACKEND_PATH ):
303+ def run_daemon (proxy_url : str = DEFAULT_PROXY_URL , backend_path : str = DEFAULT_BACKEND_PATH ) -> Proxy :
293304 proxy = Proxy (proxy_url , backend_path )
294305 th = threading .Thread (name = 'dopple' , target = proxy .run )
295306 th .daemon = True
296307 th .start ()
297308 return proxy
298309
299- def main ():
310+
311+ def main () -> None :
300312 args = parse_args ()
301313 run (args .proxy_url , args .backend_path )
302314
315+
303316if __name__ == '__main__' :
304317 main ()
305-
0 commit comments