Python学习笔记20:服务器进阶

上一篇我们在不依赖框架和CGI的情况下,仅使用socket接口,完成了一个可以处理HTTP请求的Python服务器。

基于此,任何一台装有操作系统(推荐Linux)和Python的计算机都可以作为HTTP服务器使用,来架设你的网站。

我们将在这里不断改写上一篇文章中的程序,并引入更高级的Python包,以写出更成熟的Python服务器。

一 支持POST

我们首先改写原文中的HTTP服务器,从而让该服务器支持更加丰富的HTTP请求。

相对于原程序,这里增添了表格以及对应”POST”方法的操作。如果你已经读过用socket写一个Python服务器,会发现这里只是增加很少的一点内容。

原始程序:

# A messy HTTP server based on TCP socket 

import socket

# Address
HOST = ''
PORT = 8000

text_content = '''
HTTP/1.x 200 OK
Content-Type: text/html

<head>
<title>WOW</title>
</head>
<html>
<p>Wow, Python Server</p>
<IMG src="test.jpg"/>
<form name="input" action="/" method="post">
First name:<input type="text" name="firstname"><br>
<input type="submit" value="Submit">
</form>
</html>
'''

f = open('test.jpg','rb')
pic_content = '''
HTTP/1.x 200 OK
Content-Type: image/jpg

'''
pic_content = pic_content + f.read()

# Configure socket
s    = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))

# Serve forever
while True:
    s.listen(3)
    conn, addr = s.accept()
    request    = conn.recv(1024)         # 1024 is the receiving buffer size
    method     = request.split(' ')[0]
    src        = request.split(' ')[1]

    print 'Connected by', addr
    print 'Request is:', request

    # if GET method request
    if method == 'GET':
        # if ULR is /test.jpg
        if src == '/test.jpg':
            content = pic_content
        else: content = text_content
        # send message
        conn.sendall(content)
    # if POST method request
    if method == 'POST':
        form = request.split('rn')
        idx = form.index('')             # Find the empty line
        entry = form[idx:]               # Main content of the request

        value = entry[-1].split('=')[-1]
        conn.sendall(text_content + 'n <p>' + value + '</p>')
        ######
        # More operations, such as put the form into database
        # ...
        ######
    # close connection
    conn.close()

运行上面Python服务器,向上一篇文章那样,使用一个浏览器作为客户端。

我们看到了新增的表格以及提交(submit)按钮。在表格中输入aa并提交表格,我们的Python服务器给出上面的结果。

二 使用SocketServer

我们首先使用SocketServer包来简化我们架设服务器的过程。

在上面使用socket的过程中,我们先设置了socket的类型,然后依次调用bind(),listen(),accept(),并使用while循环来让服务器不断的接受请求。

上面的这些步骤可以通过SocketServer包来简化。

SocketServer:

# use TCPServer

import SocketServer

HOST = ''
PORT = 8000

text_content = '''
HTTP/1.x 200 OK
Content-Type: text/html

<head>
<title>WOW</title>
</head>
<html>
<p>Wow, Python Server</p>
<IMG src="test.jpg"/>
<form name="input" action="/" method="post">
First name:<input type="text" name="firstname"><br>
<input type="submit" value="Submit">
</form>
</html>
'''

f = open('test.jpg','rb')
pic_content = '''
HTTP/1.x 200 OK
Content-Type: image/jpg

'''
pic_content = pic_content + f.read()

# This class defines response to each request
class MyTCPHandler(SocketServer.BaseRequestHandler):
    def handle(self):
        # self.request is the TCP socket connected to the client
        request = self.request.recv(1024)

        print 'Connected by',self.client_address[0]
        print 'Request is', request

        method = request.split(' ')[0]
        src = request.split(' ')[1]

        if method == 'GET':
            if src == '/test.jpg':
                content = pic_content
            else: content = text_content
            self.request.sendall(content)

        if method == 'POST':
            form = request.split('rn')
            idx = form.index('')             # Find the empty line
            entry = form[idx:]               # Main content of the request

            value = entry[-1].split('=')[-1]
            self.request.sendall(text_content + 'n <p>' + value + '</p>')
            ######
            # More operations, such as put the form into database
            # ...
            ######

# Create the server
server = SocketServer.TCPServer((HOST, PORT), MyTCPHandler)
# Start the server, and work forever
server.serve_forever()

我们建立了一个TCPServer对象来创建一个TCP socket服务器,并同时设置IP地址和端口。

然后使用server_forever()方法来让服务器不断工作(就像原始程序中的while循环一样)。

我们传递给TCPServer一个MyTCPHandler类,用对socket作出操作。

注意,MyTCPHandler继承自BaseRequestHandler,我们通过改写handler()方法来个性化我们的操作。

在handler()中,可以通过self.request来引用socket (正如我们在handler()中对socket进行recv()和sendall()操作),

还可以使用self.address来引用socket的客户端地址。

三 SimpleHTTPServer: 使用静态文件来回应请求

在经过了SocketServer的改造之后,我们的handler(),也就是对请求进行处理的部分,依然是乱糟糟的一团。

这对于大型服务器来说可能是个问题。

为什么呢? 对于一个HTTP请求(request)来说,它的起始行包含两个重要信息:请求方法和URL。

之前,我们都用if结构来区分不同的请求方法和URL,并针对不同的情况来进行不同的操作:

请求方法(request method)       URL                操作

GET                           /                  发送text_content

GET                           /text.jpg          发送pic_content

POST                          /                  分析request主体中包含的value(实际上是我们填入表格的内容); 发送text_content和value

根据请求方法和URL的不同,一个大型的HTTP服务器可能需要应付成千上万种不同的请求。

如果针对每个请求都在程序中写出不同的操作的话,需要大量的时间和精力,同时为运营和维护带来很大的困难。

我们需要有更标准化,也更简便的方式来处理这些请求。

在Python中,我们可以使用SimpleHTTPServer包和CGIHTTPServer包来减小以上的负担。

其中,SimpleHTTPServer可以用于处理GET方法和HEAD方法的请求。

它读取request中的URL地址,并在当前目录中找到对应的静态文件,并将文件的内容发送给客户端。

对应于我们的情况,就是将text_content放置在index.html中,而不用读取text.jpg文件。

当一个HTTP请求到来时,其URL指向某个文件,SimpleHTTPServer会读取这个文件,并分析文件类型,自动生成response,回复客户端。

如果URL指向某个文件夹,SimpleHTTPServer会读取该文件夹下的index.html或者index.hml文件。

首先,我们在当前目录下生成如下index.html文件:

<head>
<title>WOW</title>
</head>
<html>
<p>Wow, Python Server</p>
<IMG src="test.jpg"/>
<form name="input" action="/" method="post">
First name:<input type="text" name="firstname"><br>
<input type="submit" value="Submit">
</form>
</html>

然后,改写我们的Python服务器程序。

实际上,我们只是更换了TCPServer的Handler:使用SimpleHTTPServer包中唯一的类SimpleHTTPRequestHandler,而不是我们之前自己定义的MyTCPHandler

SimpleHTTPServer:

# Simple HTTPsERVER

import SocketServer
import SimpleHTTPServer

HOST = ''
PORT = 8000

# Create the server, SimpleHTTPRequestHander is pre-defined handler in SimpleHTTPServer package
server = SocketServer.TCPServer((HOST, PORT), SimpleHTTPServer.SimpleHTTPRequestHandler)
# Start the server
server.serve_forever()

注意,我们这里的程序还不能等效于之前的程序,因为不能处理POST请求。我们会在后面使用CGI来弥补这个缺陷。

但要点是,我们的Python服务器程序已经变得非常简单。

我们将内容存放于静态文件,并根据URL指向的静态文件为客户端提供内容,从而让内容和Python服务器相分离。

这样的话,我们每次更新内容的时候就可以只修改静态文件,而不用停止整个Python服务器。

我们也应该注意到使用这些改进付出的代价。比如说,对于原始程序来说,request中的URL只具有指导意义,我们可以任意规定相应的操作。

而在 SimpleHTTPServer的改进中,response固化成为:读取URL对应文件并将其内容呈现给客户。这大大限制了我们的自由度。

即使在后面我们使用CGI增大了自由度,但相对于原始程序,我们依然是增加了自己的限制。

有时候,程序的便捷与程序的自由度相抵触,程序员需要在两者之间取舍。

对于一个小的项目来说,我们可以跟随已经 制定的标准(比如这里的SimpleHTTPServer,或者使用一个框架),使用这些新的标准可以让开发变得很便捷。

然而对于一个大型的项目来说,我们往往需要争取回自己的自由度,修订成为项目需要的标准。

四 CGIHTTPServer:使用静态文件或者CGI来回应请求

CGIHTTPServer包中的CGIHTTPRequestHandler类继承自SimpleHTTPRequestHandler类,所以可以用来代替上面的例子,来提供静态文件的服务。

此外,CGIHTTPRequestHandler类还可以用来运行CGI脚本。

首先,我们先看看什么是CGI (Common Gateway Interface)。CGI是服务器和应用脚本之间的一套接口标准,目的是让服务器程序运行脚本程序,将程序的输出作为response发送给客户。

通常来说,支持CGI的服务器程在接收到客户的request之后,根据request中的URL,运行对应的脚本文件。

服务器会将HTTP request信息以及socket信息输入给脚本文件,也负责收集脚本的输出,并组装成为合法的HTTP response。

利用CGI,我们可以充分发挥服务器的可编程性,动态的生成response,而不必局限于静态文件。

服务器和CGI脚本之间通过CGI标准作为接口。这样就可以让服务器与不同语言写的CGI脚本相配合,比如说使用Apache服务器与Perl写的CGI脚本,或者Python服务器与shell写的CGI脚本。

到这里为止,我们都在使用TCPServer来构建服务器。为了使用CGI,我们需要使用BaseHTTPServer包中的HTTPServer类来构建服务器。

其实HTTPServer是TCPServer的子类,其使用方法也与TCPServer相同。它只是增加了server_name和server_port两个属性。

但不凑巧的是,我们的CGIHTTPRequestHandler需要调用这两个属性…

Python服务器的改动很简单。

CGIHTTPServer:

# A messy HTTP server based on TCP socket 

import BaseHTTPServer
import CGIHTTPServer

HOST = ''
PORT = 8000

# Create the server, CGIHTTPRequestHandler is pre-defined handler
server = BaseHTTPServer.HTTPServer((HOST, PORT), CGIHTTPServer.CGIHTTPRequestHandler)
# Start the server
server.serve_forever()

CGIHTTPRequestHandler默认当前目录下的cgi-bin和ht-bin文件夹中的文件为CGI脚本,而存放于其他地方的文件被认为是静态文件。

因此,我们需要修改一下index.html,将其中form元素指向的action改为cgi-bin/post.py。

<head>
<title>WOW</title>
</head>
<html>
<p>Wow, Python Server</p>
<IMG src="test.jpg"/>
<form name="input" action="cgi-bin/post.py" method="post">
First name:<input type="text" name="firstname"><br>
<input type="submit" value="Submit">
</form>
</html>

我们创建一个cgi-bin的文件夹,并在cgi-bin中放入如下post.py文件,也就是我们的CGI脚本:

#!/usr/bin/env python

# Written by Vamei
import cgi
form = cgi.FieldStorage()

# Output to stdout, CGIHttpServer will take this as response to the client
print "Content-Type: text/html"     # HTML is following
print                               # blank line, end of headers
print "<p>Hello world!</p>"         # Start of content
print "<p>" +  repr(form['firstname']) + "</p>"

第一行必须要有,以便告诉Python服务器,脚本所使用的语言 (我们这里的CGI是Python,当然也可以是别的语言,比如bash)。

cgi包用于提取request中提交的表格信息(我们暂时不深入cgi包)。脚本只负责将所有的结果输出到标准输出(使用print)。

而CGIHTTPRequestHandler会收集这些输出,并组装成为response传送给客户端。

如果一个请求是POST方法,那么它的URL必须指向一个CGI脚本(也就是在cgi-bin或者ht-bin中的文件)。

CGIHTTPRequestHandler继承自SimpleHTTPRequestHandler,所以也可以处理GET方法和HEAD方法的请求。

此时,如果URL指向CGI脚本时,服务器将脚本的运行结果传送到客户端;当此时URL指向静态文件时,服务器将文件的内容传送到客户端。

我们可以让CGI脚本执行数据库操作,比如将接收到的数据放入到数据库中,以及更丰富的程序操作。

CGI脚本提供了LAMP架构中PHP的作用 (我们的Python服务器相当于LAMP中的Apache)。

时间: 2024-11-08 11:26:48

Python学习笔记20:服务器进阶的相关文章

Python学习笔记20:server先进

我们不依赖于一个框架,CGI如果是,只能使用socket介面.他完成了一个可以处理HTTP要求Pythonserver. 基于,不管是什么的计算机的操作系统(推荐Linux)和Python该计算机可被用作HTTPserver采用.要设置你的网站. 改写上一篇文章中的程序.并引入更高级的Python包,以写出更成熟的Pythonserver. 一 支持POST 我们首先改写原文中的HTTPserver,从而让该server支持更加丰富的HTTP请求. 相对于原程序,这里增添了表格以及相应"POST

python学习笔记20(字符串格式化)

Python中内置有对字符串进行格式化的操作% 模板 格式化字符串时,Python使用一个字符串作为模板.模板中有格式符,这些格式符为真实值预留位置,并说明真实数值应该呈现的格式.Python用一个tuple将多个值传递给模板,每个值对应一个格式符. 比如下面的例子: print "I'm %s. I'm %d year old" % ('Ethon', 99) 上面的例子中,"I'm %s. I'm %d year old" 为我们的模板.%s为第一个格式符,表示

Python学习笔记进阶篇——总览

Python学习笔记——进阶篇[第八周]———进程.线程.协程篇(Socket编程进阶&多线程.多进程) Python学习笔记——进阶篇[第八周]———进程.线程.协程篇(异常处理) Python学习笔记——进阶篇[第八周]———进程.线程.协程篇(多线程与进程池) Python学习笔记——进阶篇[第九周]———线程.进程.协程篇(队列Queue和生产者消费者模型) Python学习笔记——进阶篇[第九周]———协程 Python学习笔记——进阶篇[第九周]———MYSQL操作

python基础教程_学习笔记20:标准库:一些最爱——os

标准库:一些最爱 os os模块为你提供了访问多个操作系统服务的功能. os和它的子模块os.path还包括一些用于检查.构造.删除目录和文件的函数,以及一些处理路径的函数. os模块中一些重要函数和变量 函数/变量 描述 environ 对环境变量进行映射 system(command) 在子shell中执行操作系统命令 sep 路径中的分隔符 pathsep 分隔路径的分隔符 linesep 行分隔符('\n','\r','\r\n') urandom(n) 返回n字节的加密强随机数据 os

python学习笔记[3]-邮件的发送

本文摘抄自:http://www.cnblogs.com/xiaowuyi/archive/2012/03/17/2404015.html 一.相关模块介绍 发送邮件主要用到了smtplib和email两个模块,这里首先就两个模块进行一下简单的介绍:    1.smtplib模块 smtplib.SMTP([host[, port[, local_hostname[, timeout]]]])   SMTP类构造函数,表示与SMTP服务器之间的连接,通过这个连接可以向smtp服务器发送指令,执行

Tornado/Python 学习笔记(二)

部分ssrpc.py代码分析 -- 服务端: 1 #!/usr/bin/python3 2 3 from xmlrpc.client import Fault, dumps, loads 4 import sys 5 from socketserver import ForkingMixIn 6 from xmlrpc.server import SimpleXMLRPCServer 7 8 class VerboseFaultXMLRPCServer(SimpleXMLRPCServer):

Tornado/Python 学习笔记(一)

1.源代码下载及安装:http://www.tornadoweb.org/en/stable/ 2.python中xmlrpc库官方文档:https://docs.python.org/3/library/xmlrpc.html?highlight=xmlrpc 3.xml介绍与学习:http://www.w3school.com.cn/xml/xml_intro.asp XML 被设计为传输和存储数据,其焦点是数据的内容. HTML 被设计用来显示数据,其焦点是数据的外观. HTML 旨在显示

OpenCV之Python学习笔记

OpenCV之Python学习笔记 直都在用Python+OpenCV做一些算法的原型.本来想留下发布一些文章的,可是整理一下就有点无奈了,都是写零散不成系统的小片段.现在看 到一本国外的新书<OpenCV Computer Vision with Python>,于是就看一遍,顺便把自己掌握的东西整合一下,写成学习笔记了.更需要的朋友参考. 阅读须知: 本文不是纯粹的译文,只是比较贴近原文的笔记:         请设法购买到出版社出版的书,支持正版. 从书名就能看出来本书是介绍在Pytho

python学习笔记2—python文件类型、变量、数值、字符串、元组、列表、字典

python学习笔记2--python文件类型.变量.数值.字符串.元组.列表.字典 一.Python文件类型 1.源代码 python源代码文件以.py为扩展名,由pyton程序解释,不需要编译 [[email protected] day01]# vim 1.py #!/usr/bin/python        print 'hello world!' [[email protected] day01]# python 1.py hello world! 2.字节代码 Python源码文件