Python写一个简易的web服务器

3.找出它的要求;

4.获取数据(或动态生成它);

5.把数据格式化为HTML;

6.回发。

从一个应用程序到另一个,步骤1,2和6是相同的,所以Python标准库有一个名为BaseHTTPServer的模块为我们做这些步骤。我们只需要关注步骤3-5,这是我们在下面的小程序里做的:

库的BaseHTTPRequestHandler类负责解析传入的HTTP请求,并判断它含有什么方法。如果方法是GET,类调用一个名为do_GET的方法。我们的RequestHandler类覆盖此方法来动态生成一个简单的页面:文本被存储在类级别的变量Page中,在我们发送一个200响应码后被发回客户端,Content-Type头文件,告诉客户端把我们的数据和页面长度翻译为HTML格式。 (调用end_headers方法插入空白行,来区分头文件和页面本身。)

但RequestHandler并不是故事的全部:我们还需要最后三行来使服务器开始运行。其中第一行定义服务器的地址为一个元组:空字符串意味着“在当前计算机上运行”,8080是端口号。然后,我们创建一个BaseHTTPServer.HTTPServer的实例,实例中含有服务器地址和我们的请求处理类作为参数的名称,然后要求它永久运行(这实际上意味着,直到我们使用Control-C杀死它才停止运行)。

如果我们在命令行中运行这个程序,它不显示任何内容:

$ python server.py

然后如果我们用我们的浏览器访问http://localhost:8080,那么,在浏览器中我们得到这样的内容:

Hello, web!

在我们的shell中是这样的内容:

127.0.0.1 - - [24/Feb/2014 10:26:28] “GET / HTTP/1.1” 200 -

127.0.0.1 - - [24/Feb/2014 10:26:28] “GET /favicon.ico HTTP/1.1” 200 -

第一行是直观的:因为我们没有要求特定文件,我们的浏览器就要求"/"(服务器提供所有的根目录)。出现第二行是因为我们的浏览器会自动发送第二个请求来请求一个名为/favicon.ico的图像文件,如果图像文件存在,它会在地址栏显示为一个图标。

显示数值

让我们修改我们的Web服务器来显示一些包含在HTTP请求中的值。 (在调试的时候,我们会相当频繁的做这个,所以我们不妨做一些练习)。为了保持我们的代码简洁,我们将把创建页面和发送页面分开:

send_page我们之前几乎都有的:

因为我们要显示的页面模板仅仅是一串字符串,字符串包括带有一些格式占位符的HTML表:

填充方法是:

该程序的主体是不变的:和以前一样,它创建了一个HTTPServer类的实例,实例中含有一个地址和作为参数的请求处理器,然后永久服务请求。如果我们运行它,并从浏览器中发送一个请求:http://localhost:8080/something.html,我们得到:

Date and time Mon, 24 Feb 2014 17:17:12 GMT

Client host 127.0.0.1

Client port 54548

Command GET

Path /something.html

请注意,我们没有收到一个404错误,即使something.html页面文件在磁盘上不存在。这是因为Web服务器只是一个程序,当它获得一个请求时,它可以做任意想做的事:发回在先前的请求里被命名的文件、提供随机选择的一个维基百科页面,或我们编写的任意的东西。

提供静态页面服务

明显的,下一步是从磁盘启动服务页面,而不是动态生成。我们会通过改写do_GET来开始:

该方法假定提供Web服务器运行目录下的任何文件都是被允许的(使用os.getcwd函数来获得当前运行目录)。它把当前运行目录与URL提供的路径(库将路径自动放入self.path中,并始终以 “/”开头)相结合,来获得用户想要文件的路径。

如果路径不存在,或者如果它不是一个文件,该方法通过抛出和捕获异常报告错误。如果路径匹配一个文件,在另一方面,它调用一个帮助者方法命名为handle_file来读取和返回内容。这种方法只会读取文件,并使用我们现有的send_content将其发送回客户端:

请注意,我们在以二进制模式打开文件—the “b” in “rb”—,因此Python不会帮我们去通过改变字节序列而使其看起来像一个Windows结束行。还需要注意的是在现实生活中,当为它服务时,读取整个文件到内存是个坏主意,该文件可能是数千兆字节的视频数据。处理这种情况不在本章的范围之内。

为了完成这个类,我们需要编写错误处理方法和错误报告页面模板:

这个程序可用,但前提是我们别太细看。问题是,它总是返回状态码200,即使当被请求的页面不存在。在这种情况下,发回的页面包含一个错误信息,但由于我们的浏览器读不懂英文,它不知道该请求其实失败了。为了说清楚这点,我们需要按如下修改handle_error和send_content:

请注意,当找不到文件时我们不抛出ServerException,而是产生一个错误页面。ServerException是为了传递服务器代码中的内部错误信号,即,我们弄错了某东西。在另一方面,通过handle_error创建的错误页面,出现在用户犯了一些错误的时候,即,给我们发送了一个不存在的文件的URL。(1)

列出目录

作为我们的下一步,当URL中的路径是一个目录而不是文件时,我们可以教Web服务器显示目录列表。我们甚至可以更进一步,让它在那个目录中以index.html文件显示,并且文件不存在时,只显示目录内容的列表。

但是,把这些规则写进do_GET将是一个错误,因为方法最终将会成为长长一大团控制特定行为的if声明。正确的解决办法是退后一步,解决一般性问题,指出如何处理URL。这里是重写的do_GET方法:

第一步是一样的:指出被请求的东西的全路径。尽管在那之后,代码看起来相当不同。该版本遍历一组存储在列表中的cases类来代替一堆的内联测试。每个case是一个有两个方法的对象:方法test,它告诉我们它是否能够处理请求,方法act,它实际上采取了一些行动。我们一找到正确的case,我们马上让它处理请求并跳出循环。

这三个case类复制我们之前服务器的行为:

如下是我们如何在RequestHandler类的顶部构建case handlers的列表:

现在,表面上看这使得我们的服务器更加复杂,而不是更简单:该文件已经从74行增加至99行,并且有额外的间接寻址,没有增加任何新的功能。这样做的好处是,当我们回到那个本章开始的任务,如果有文件的话,就尝试教我们的服务器来提供index.html页面作为目录,如果没有的话,就提供目录的列表。前者的handler程序是:

在这里,帮助者方法index_path构建index.html文件的路径;把它放进case handler中防止主函数RequestHandler杂乱。 test检查路径是否是包含index.html的目录,act要求主函数请求处理器提供该页面。

唯一需要RequestHandler改变的是把一个case_directory_index_file对象添加到我们的Cases列表中:

如果目录中不包含index.html页怎么办呢?test方法和上面战略性插入not相同,但act方法该怎么样呢?他该怎么做?

我们似乎陷入了困境。从逻辑上讲,act方法应创建并返回目录列表,但是我们现有的代码不允许:RequestHandler.do_GET调用了act,却并不想要或处理返回值。现在,让我们在RequestHandler里添加一个方法来生成一个目录列表,并从case handler的act方法调用该方法:

CGI协议

当然,大多数人都不会想要编辑他们的网络服务器的源代码来增加新的功能。为了从这种不得不这么做的泥沼中解脱出来,服务器一直支持一个叫做通用网关接口(CGI)的机制,这为Web服务器为了满足请求去运行外部程序提供了一种标准方法。

例如,假设我们希望服务器能在HTML页面中显示本地时间。我们在只有几行代码的独立的程序中就可以做到:

为了使Web服务器运行该程序,我们添加如下的case handler:

test方法很简单:文件路径是以.py结尾吗?如果是,RequestHandler运行此程序。

这是非常不安全的:如果有人知道我们的服务器上的一个Python文件的路径,我们就只能任他们运行它,不管它访问了什么数据,它是否包含了一个无限循环,或任何其他什么(2)

先不管那个,核心的思想很简单:

1.在子进程中运行程序。

2.捕获任何子进程发送到标准输出的内容。

3.把内容发送回发出请求的客户端。

完整的CGI协议比这个要丰富的多-尤其是它允许url中服务器传递给程序的参数运行-但这些细节不影响系统的整体架构…这再次变得相当乱。 RequestHandler中最初有一个方法handle_file,用于处理内容。现在,我们已经在list_dir和run_cgi表中加入了两种特殊情况。这三个方法并不真正在所属类起作用,因为他们主要由其他方法使用。

修补程序很简单:为所有的case handler创建一个父类,当(且仅当)他们由两个或多个handler共享时,把其他方法移入这个类。当我们完成后,RequestHandler类看起来是这样的:

最后

🍅 硬核资料:关注即可领取PPT模板、简历模板、行业经典书籍PDF。
🍅 技术互助:技术群大佬指点迷津,你的问题可能不是问题,求资源在群里喊一声。
🍅 面试题库:由技术群里的小伙伴们共同投稿,热乎的大厂面试真题,持续更新中。
🍅 知识体系:含编程语言、算法、大数据生态圈组件(Mysql、Hive、Spark、Flink)、数据仓库、Python、前端等等。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里无偿获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
等等。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里无偿获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 30
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值