【翻译】一步步开发一个Web服务器.Part 2.

2024-08-28 13:32

本文主要是介绍【翻译】一步步开发一个Web服务器.Part 2.,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

原文地址:Let’s Build A Web Server. Part 2.
译文地址:【翻译】一步步开发一个Web服务器.Part 2.
原文代码基于python2.x

本系列其他文章:
【翻译】一步步开发一个Web服务器.Part 1.
【翻译】一步步开发一个Web服务器.Part 2.
【翻译】一步步开发一个Web服务器.Part 3.

记得,我在Part 1 中留了个作业题:不改变服务器代码的情况下,怎样在你刚完成的服务器上运行一个Django、Flask、Pyramid应用,适应不同的web框架? 阅读完这篇文章你就能得到答案。

在过去,你选择一个python web框架往往受限于可用的web服务器的选择,反之亦然。如果你选择的框架和你的服务器恰好能在一起工作,这时候你就乐了:
这里写图片描述
你或许要面对(或者已经遇到了)这样一个问题:当你想要把一个框架和一个服务器结合在一起时,你发现它们并不能很好的结合在一起。
这里写图片描述
从根本上来讲,你不得不用那些能一起工作的却不是你想用的框架和服务器。

所以,在不改变web服务器和web框架的情况下,你要怎样才能确保你的web服务器能够适应不同的web框架?答案就是Python 服务器网关接口(Web Server Gateway Interface, 缩写为WSGI,读作wizgy)
这里写图片描述
WSGI 允许开发者在web框架和web服务器上做出不同选择。你可以根据你的需要搭配不同的web框架和web服务器了。可以使用Django、Flask或者Pyramid,举个栗子,Gunicorn或者Nginx/uWSGI又或者Waitress。它做到了真正的混合和搭配,同时支持服务器和框架,WSGI谢天谢地你来了。
这里写图片描述
所以,WSGI就是我一遍又一遍问你的那个问题的答案。你的web服务器必须实现一个WSGI的服务器接口,当下的Python Web框架都已经实现了WSGI的框架接口。如此一来,不用修改任何代码,你的web服务器可以使用接口适用于一个特定的web框架。

现在你已经知道了WSGI同时支持服务器和框架来满足你不同选择的需求,这也使得服务器和框架的开发者们收益,它们能专注于他们擅长的领域而不必涉足对方的领域。其他语言也有类似的接口,又举个栗子:Java有Servlet API、Ruby有Rack。

这些都挺不错的(你心里或许正嘀咕:别逼逼了,上代码!)。OK,这下面是一个最简单的WSGI服务器的实现:

# Tested with Python 2.7.9, Linux & Mac OS X
import socket
import StringIO
import sysclass WSGIServer(object):address_family = socket.AF_INETsocket_type = socket.SOCK_STREAMrequest_queue_size = 1def __init__(self, server_address):# Create a listening socketself.listen_socket = listen_socket = socket.socket(self.address_family,self.socket_type)# Allow to reuse the same addresslisten_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)# Bindlisten_socket.bind(server_address)# Activatelisten_socket.listen(self.request_queue_size)# Get server host name and porthost, port = self.listen_socket.getsockname()[:2]self.server_name = socket.getfqdn(host)self.server_port = port# Return headers set by Web framework/Web applicationself.headers_set = []def set_app(self, application):self.application = applicationdef serve_forever(self):listen_socket = self.listen_socketwhile True:# New client connectionself.client_connection, client_address = listen_socket.accept()# Handle one request and close the client connection. Then# loop over to wait for another client connectionself.handle_one_request()def handle_one_request(self):self.request_data = request_data = self.client_connection.recv(1024)# Print formatted request data a la 'curl -v'print(''.join('< {line}\n'.format(line=line)for line in request_data.splitlines()))self.parse_request(request_data)# Construct environment dictionary using request dataenv = self.get_environ()# It's time to call our application callable and get# back a result that will become HTTP response bodyresult = self.application(env, self.start_response)# Construct a response and send it back to the clientself.finish_response(result)def parse_request(self, text):request_line = text.splitlines()[0]request_line = request_line.rstrip('\r\n')# Break down the request line into components(self.request_method,  # GETself.path,            # /helloself.request_version  # HTTP/1.1) = request_line.split()def get_environ(self):env = {}# The following code snippet does not follow PEP8 conventions# but it's formatted the way it is for demonstration purposes# to emphasize the required variables and their values## Required WSGI variablesenv['wsgi.version']      = (1, 0)env['wsgi.url_scheme']   = 'http'env['wsgi.input']        = StringIO.StringIO(self.request_data)env['wsgi.errors']       = sys.stderrenv['wsgi.multithread']  = Falseenv['wsgi.multiprocess'] = Falseenv['wsgi.run_once']     = False# Required CGI variablesenv['REQUEST_METHOD']    = self.request_method    # GETenv['PATH_INFO']         = self.path              # /helloenv['SERVER_NAME']       = self.server_name       # localhostenv['SERVER_PORT']       = str(self.server_port)  # 8888return envdef start_response(self, status, response_headers, exc_info=None):# Add necessary server headersserver_headers = [('Date', 'Tue, 31 Mar 2015 12:54:48 GMT'),('Server', 'WSGIServer 0.2'),]self.headers_set = [status, response_headers + server_headers]# To adhere to WSGI specification the start_response must return# a 'write' callable. We simplicity's sake we'll ignore that detail# for now.# return self.finish_responsedef finish_response(self, result):try:status, response_headers = self.headers_setresponse = 'HTTP/1.1 {status}\r\n'.format(status=status)for header in response_headers:response += '{0}: {1}\r\n'.format(*header)response += '\r\n'for data in result:response += data# Print formatted response data a la 'curl -v'print(''.join('> {line}\n'.format(line=line)for line in response.splitlines()))self.client_connection.sendall(response)finally:self.client_connection.close()SERVER_ADDRESS = (HOST, PORT) = '', 8888def make_server(server_address, application):server = WSGIServer(server_address)server.set_app(application)return serverif __name__ == '__main__':if len(sys.argv) < 2:sys.exit('Provide a WSGI application object as module:callable')app_path = sys.argv[1]module, application = app_path.split(':')module = __import__(module)application = getattr(module, application)httpd = make_server(SERVER_ADDRESS, application)print('WSGIServer: Serving HTTP on port {port} ...\n'.format(port=PORT))httpd.serve_forever()

你肯定在想,什么鬼?说好的简单实现呢?(译者:我也在想什么鬼!这么多!)但是它足够小到能让你理解却不会陷入到细节上。上面最简单的服务器也能做到很多,他可以运行最基本的Pyramid、Flask、Django或者其他的PythonWSGI框架。

还别不信,咱们来试试。看见上面的代码了吗?直接拷下来或者从Github上down下来,保存为webserver2.py文件。如果不加参数直接运行会报错并退出。

$ python webserver2.py
Provide a WSGI application object as module:callable

它真想为你的web应用服务,却无从下手。想让这个服务器运行起来只需要安装python就行了,但是想要运行Pyramid、Flask、Django的应用,还得先安装这些框架。我推荐的方法是使用虚拟环境(virtualenv)。你只要跟着下面的步骤一步步来就OK,首先安装虚拟环境,然后安装这三个框架。(译者认为可以不用虚拟环境,直接安装三个框架)

$ [sudo] pip install virtualenv
$ mkdir ~/envs
$ virtualenv ~/envs/lsbaws/
$ cd ~/envs/lsbaws/
$ ls
bin  include  lib
$ source bin/activate
(lsbaws) $ pip install pyramid
(lsbaws) $ pip install flask
(lsbaws) $ pip install django

这时候要创建一个web应用。这里以Pyramid为例。同样的,保存下面的代码或者从Github直接down下来,命名为pyramidapp.py。跟你的webserver2.py保存到同一目录下:

from pyramid.config import Configurator
from pyramid.response import Responsedef hello_world(request):return Response('Hello world from Pyramid!\n',content_type='text/plain',)config = Configurator()
config.add_route('hello', '/hello')
config.add_view(hello_world, route_name='hello')
app = config.make_wsgi_app()

现在你可以写一个Pyramid程序并且用你刚才的web服务器运行了。

(lsbaws) $ python webserver2.py pyramidapp:app
WSGIServer: Serving HTTP on port 8888 ...

你只是告诉你的服务器从python的pyramidapp模块中加载这个‘app’。你的服务器已经准备好接收请求并且转发给你的Pyramid程序。现在你的web应用只处理/hello这一个路由路径。在你的浏览器中输入http://localhost:8888/hello 按下回车,你会发现,神奇的操作又一次发生了。
这里写图片描述
同样你也可以使用curl命令在命令行中测试你的服务器。

$ curl -v http://localhost:8888/hello
...

查看一下使用curl命令,你的服务器输出了什么。

现在我们使用Flask创建一个同样web应用。创建、启动、运行、测试都与上面相同。

from flask import Flask
from flask import Response
flask_app = Flask('flaskapp')@flask_app.route('/hello')
def hello_world():return Response('Hello world from Flask!\n',mimetype='text/plain')app = flask_app.wsgi_app

保存上面的代码或者直接从Github上下载,保存为flaskapp.py 也是放在同一文件夹。在命令行中输入:

(lsbaws) $ python webserver2.py flaskapp:app
WSGIServer: Serving HTTP on port 8888 ...

再次在浏览器中输入 http://localhost:8888/hello 回车,神奇的操作又来了!(大家别嫌烦,后面Django还要来一遍…)
这里写图片描述
同样使用curl命令在命令行中测试web服务器,Flask应用回产生。。。

$ curl -v http://localhost:8888/hello
...

这个服务器能处理Django应用吗?(真的又要来一遍。。。)来试试!但是这次有点儿复杂,我建议从Github直接把整个项目down下来,只使用djangoapp.py这部分。这是工程的源代码,只需要将‘helloworld’这个Django工程添加到当前路径,并且将工程的WSGI应用导入。

import sys
sys.path.insert(0, './helloworld')
from helloworld import wsgiapp = wsgi.application

保存上面的代码,命名为djangoapp.py然后用你的web服务器运行这个程序。

(lsbaws) $ python webserver2.py djangoapp:app
WSGIServer: Serving HTTP on port 8888 ...

然后再浏览器中输入http://localhost:8888/hello
这里写图片描述
前面你已经做过两次了,这次你也可以在命令行中测试,以进一步确认你的Django程序能正确处理的请求:

$ curl -v http://localhost:8888/hello
...

你确定你试过了吗?你确定你的服务器支持这几种框架吗?如果没有,请认真做一遍。读过文章和代码固然重要,但这一系列文章是动手开发自己的web服务器,这意味着你必须自己亲自动手写一遍。撸起袖子加油干~你必须自己亲自动手把代码敲一遍以确保能按照预期运行。

相信你已经见识到WSGI的强大了。它能让你随意搭配服务器和web框架。WSGI在Python Web服务器和Python Web框架之间提供了一个极简的接口。不管是对于框架还是服务器,WSGI都非常容易实现。下面的代码片段展示了服务器和框架接口的代码实现。

def run_application(application):"""Server code."""# This is where an application/framework stores# an HTTP status and HTTP response headers for the server# to transmit to the clientheaders_set = []# Environment dictionary with WSGI/CGI variablesenviron = {}def start_response(status, response_headers, exc_info=None):headers_set[:] = [status, response_headers]# Server invokes the ‘application' callable and gets back the# response bodyresult = application(environ, start_response)# Server builds an HTTP response and transmits it to the clientdef app(environ, start_response):"""A barebones WSGI app."""start_response('200 OK', [('Content-Type', 'text/plain')])return ['Hello world!']run_application(app)

接下来讲讲它的工作原理:

  1. 框架提供一个可供调用的‘application’对象。(WSGI规范里并没有规定这个application要怎么实现)

2.对于每个来自HTTP客户端的请求,服务器都会调用这个可调用的‘application’对象。服务器会向可调用对象‘application’传递一个名为‘environ’字典类型的参数,字典里包含WSGI/SGI的一些变量和一个‘start_response’可调用对象。

3.框架/应用程序会产生一个HTPP状态和HTTP响应报头,传递给可调用对象‘start_response’以供服务器储存。框架/应用程序也会返回一个响应的主体。

4.服务器将状态码、响应头部、响应主体组合成HTTP响应,将它传递给客户端(这一部没有在规范里写出来,但为了下一步的逻辑能清晰点儿,我才把它加上)。

下面是接口的一个可视化表示:
这里写图片描述
目前为止,你已经见识了Pyramid、Flask、Django的Web应用,服务器的WSGI接口实现,没用任何框架的WSGI应用的代码片段。

还有一件事,当你使用任何一种框架写一个web应用时,你的思想在一个更高的逻辑,不会与WSGI接口直接接触,我知道你会好奇框架的WSGI接口是什么样的,因为你在读这篇文章啊。所以我早就准备好了一个不用各种框架就能在服务器上运行的WSGI Web应用/Web框架。

def app(environ, start_response):"""A barebones WSGI application.This is a starting point for your own Web framework :)"""status = '200 OK'response_headers = [('Content-Type', 'text/plain')]start_response(status, response_headers)return ['Hello world from a simple WSGI application!\n']

保存上面的代码或者直接从Github上down下来,保存为wsgiapp.py文件,与你的服务器保存在同一路径。这样就可以运行了:

(lsbaws) $ python webserver2.py wsgiapp:app
WSGIServer: Serving HTTP on port 8888 ...

在浏览器地址栏中输入http://localhost:8888/hello,你就能看见如下结果:
这里写图片描述
学习开发web服务器的时候,你还写了个最简单的WSGI Web框架,惊不惊喜?意不意外?

接下来,我们转向服务器传递给客户端的HTPP报文。下面是你使用HTTP客户端调用Pyramid应用时,服务器产生的HTTP响应报文:
这里写图片描述
这个响应与你再Part1中见到的有相似之处,也有一些新东西。再举个栗子,它有四个你以前没见过的HTTP头部:Content-Type、Content-Length、Date和Server。这些头部是一个web服务器产生的响应应该有的,尽管并没有严格要求。这些头部的目的是传递一些关于HTTP请求/响应的附加信息。

既然你已经对WSGI接口有了进一步的了解,下图还是那个HTTP响应,不过多了些内容,告诉你响应的每条信息是从哪里产生的。
这里写图片描述
目前为止我还没有提及‘environ’这个字典,简单来说‘environ’是个标准的Python字典类型,它必须包含WSGI规范里规定的一些特定的WSGI和SGI变量。服务器解析请求之后从HTTP请求中获取字典所需要的值。下图是字典内容:
这里写图片描述
web框架从字典中获取信息,根据特定的路由、请求方法等来决定使用哪个页面、从哪里读取请求主体,或者写入错误信息。

目前为止,写过了WSGI Web服务器,使用各种框架写过Web应用,写过最基本的web应用/web框架,真真是可怕的噩梦。一起来概括下你的WSGIweb服务器针对一个WSGI应用都要处理哪些请求。

1.服务器启动并且加载一个web应用/web框架提供的‘application’可调用对象。

2.服务器读取一个请求

3.服务器解析这个请求

4.服务器创建使用请求的数据创建一个‘environ’的字典

5.调用可调用对象‘application’,使用‘environ’字典和可调用对象‘start_response’作为参数,得到一个响应主体。

6.使用可调用对象‘application’返回的数据、状态码、可调用对象‘start_response’设置的响应头部。

7.最后服务器把这个HTTP响应发送回客户端。
这里写图片描述
上面就是所有的内容了,你已经有一个可以工作的WSGI服务器了,他可以运行与WSGI兼容的web框架例如Flask、Django、Pyramid或者你自己的WSGI框架。最棒的是不用修改任何代码就可以使用不同的web框架。哎哟,不错哟!

留一个课后思考题,“怎样保证你的服务器能在同一时间处理多个请求?”

敬请关注,我会在Part 3中讲解。

译者注:
文章中的图大家不要只看看热闹就过去了,作者在图中放了很多干货,大家看仔细点儿

这篇关于【翻译】一步步开发一个Web服务器.Part 2.的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1114900

相关文章

服务器集群同步时间手记

1.时间服务器配置(必须root用户) (1)检查ntp是否安装 [root@node1 桌面]# rpm -qa|grep ntpntp-4.2.6p5-10.el6.centos.x86_64fontpackages-filesystem-1.41-1.1.el6.noarchntpdate-4.2.6p5-10.el6.centos.x86_64 (2)修改ntp配置文件 [r

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

Java Web指的是什么

Java Web指的是使用Java技术进行Web开发的一种方式。Java在Web开发领域有着广泛的应用,主要通过Java EE(Enterprise Edition)平台来实现。  主要特点和技术包括: 1. Servlets和JSP:     Servlets 是Java编写的服务器端程序,用于处理客户端请求和生成动态网页内容。     JSP(JavaServer Pages)

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

BUUCTF靶场[web][极客大挑战 2019]Http、[HCTF 2018]admin

目录   [web][极客大挑战 2019]Http 考点:Referer协议、UA协议、X-Forwarded-For协议 [web][HCTF 2018]admin 考点:弱密码字典爆破 四种方法:   [web][极客大挑战 2019]Http 考点:Referer协议、UA协议、X-Forwarded-For协议 访问环境 老规矩,我们先查看源代码

【区块链 + 人才服务】区块链集成开发平台 | FISCO BCOS应用案例

随着区块链技术的快速发展,越来越多的企业开始将其应用于实际业务中。然而,区块链技术的专业性使得其集成开发成为一项挑战。针对此,广东中创智慧科技有限公司基于国产开源联盟链 FISCO BCOS 推出了区块链集成开发平台。该平台基于区块链技术,提供一套全面的区块链开发工具和开发环境,支持开发者快速开发和部署区块链应用。此外,该平台还可以提供一套全面的区块链开发教程和文档,帮助开发者快速上手区块链开发。