**
**
由于项目最终要在一个封闭式服务器(无法连接外网)上部署一个小型API,由于其规模较小,一开始选择了WSGI作为项目框架,但是由于WSGI本身为单线程串行的服务器,导致一旦API出现了网络错误,或者较多人请求的时候,其会出现堵塞的现象,为了解决这个问题,我需要将API改为多线程并行模式来适应项目需求。
一开始,我当然是考虑到利用uWSGI来直接部署WSGI应用为多线程模式,然而我发现在封闭式服务器上安装uWSGI时缺少Python.h依赖文件,这意味着我需要安装了python-devel在服务器上,但是由于服务器无法上网,我也无法确定其需要的python-devel的版本信息,而且如果后续还缺少相关的文件,还需要继续踩坑。
Dejongo也是一个很不错的web服务框架,但是用这个总有一种杀鸡焉用牛刀的感觉,此外,由于我内部API为Inception,其调用了MySQLdb的库,封闭服务器上依旧缺少该库,并且安装时,同样提示缺少python-devel,这意味着我无法在服务器上用过python来启动服务,那么Dejongo和uWSGI的命令行式启动同样并不适合我
最后自己想想觉得,其实只需要将WSGI的运行模式由单线程串行模式改成监听-启动线程执行的模式即可,这不就是上大学时学的基础内容吗?其实难度也没有自己想的那么难,既然轮子这么难找,那何不尝试一下自己造轮子呢?于是我开始阅读WSGI的源码
make_server是一个装配的过程,它的作用是制定了我们要监听的域名和端口,并且将API的功能方法通过set_app装配进来。
def make_server(
host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
):
"""Create a new WSGI server listening on `host` and `port` for `app`"""
server = server_class((host, port), handler_class)
server.set_app(app)
return server
server_forever是一个等待请求的过程,_eintr_retry会不断地监视请求就像是我们的while(1){socket.accept()}一样,看到这里,相信大家已经对如何造轮子应该已经有了大致思路。
def serve_forever(self, poll_interval=0.5):
"""Handle one request at a time until shutdown.
Polls for shutdown every poll_interval seconds. Ignores
self.timeout. If you need to do periodic tasks, do them in
another thread.
"""
self.__is_shut_down.clear()
try:
while not self.__shutdown_request:
# XXX: Consider using another file descriptor or
# connecting to the socket to wake this up instead of
# polling. Polling reduces our responsiveness to a
# shutdown request and wastes cpu at all other times.
r, w, e = _eintr_retry(select.select, [self], [], [],
poll_interval)
if self in r:
self._handle_request_noblock()
finally:
self.__shutdown_request = False
self.__is_shut_down.set()
当_eintr_retry捕捉到用户的request后,就会调用_handle_request_noblock()
,可以看到get_request里面就是accept()函数,由此来获得请求者的IP和Port。
def _handle_request_noblock(self):
"""Handle one request, without blocking.
I assume that select.select has returned that the socket is
readable before this function was called, so there should be
no risk of blocking in get_request().
"""
try:
request, client_address = self.get_request()
except socket.error:
return
if self.verify_request(request, client_address):
try:
self.process_request(request, client_address)
except:
self.handle_error(request, client_address)
self.shutdown_request(request)
def get_request(self):
"""Get the request and client address from the socket.
May be overridden.
"""
return self.socket.accept()
之后就是执行application会完成API的相关功能并对其回应,可以看到process_request中就是完成请求,结束请求,这就是为什么WSGI是单线程的原因,因为它只是串行地调用函数来完成一系列API操作,那么我们要做成多线程自然要从这一块下手,如果我们在每收到一个请求时,选择创建一个新的线程,并去执行finish_request+shutdown_request,两步操作,那么他不就变成了一个多线程并行API了吗?
def process_request(self, request, client_address):
self.finish_request(request, client_address)
self.shutdown_request(request)
每次收到一个请求,我们都会创建一个线程,并在其中执行self.finish_request(request, client_address),self.shutdown_request(request)完成API的相关操作,这样就将WSGI变成了一个便携式的多线程API框架,最后通过pyinstaller打包成二进制文件,拿去封闭式服务器中,直接后台运行即可nohup ./testAPI &。
def process_request(self, request, client_address):
"""Call finish_request.
"""Start a new thread to process the request."""
t = threading.Thread(target=self.process_request_thread,
args=(request, client_address))
t.daemon = self.daemon_threads
t.start()
def process_request_thread(self, request, client_address):
try:
self.finish_request(request, client_address)
self.shutdown_request(request)
except:
self.handle_error(request, client_address)
self.shutdown_request(request)