BaseHTTPServer类是在SocketServer的基础上创建出的一个简单的HTTP servers应用类,而通过BaseHTTPRequestHandler方法我们可以直接实现GET、POST等请求。由于其只是一个简单的SocketServer.TCPServer子类,它本身并不支持多线程或多进程,如果想使用多线程或多进程,需要结合threading模块去fork,这在后面也会提到。

一、HTTP GET请求

代码如下:

 1from BaseHTTPServer import BaseHTTPRequestHandler
 2import urlparse
 3class GetHandler(BaseHTTPRequestHandler):
 4    def do_GET(self):
 5        parsed_path = urlparse.urlparse(self.path)
 6        message_parts = [
 7                'CLIENT VALUES:',
 8                'client_address=%s (%s)' % (self.client_address,
 9                                            self.address_string()),
10                'command=%s' % self.command,
11                'path=%s' % self.path,
12                'real path=%s' % parsed_path.path,
13                'query=%s' % parsed_path.query,
14                'request_version=%s' % self.request_version,
15                '',
16                'SERVER VALUES:',
17                'server_version=%s' % self.server_version,
18                'sys_version=%s' % self.sys_version,
19                'protocol_version=%s' % self.protocol_version,
20                '',
21                'HEADERS RECEIVED:',
22                ]
23        for name, value in sorted(self.headers.items()):
24            message_parts.append('%s=%s' % (name, value.rstrip()))
25        message_parts.append('')
26        message = '\r\n'.join(message_parts)
27        self.send_response(200)
28        self.end_headers()
29        self.wfile.write(message)
30        return
31if __name__ == '__main__':
32    from BaseHTTPServer import HTTPServer
33    server = HTTPServer(('localhost', 8080), GetHandler)
34    print 'Starting server, use <ctrl-c> to stop'
35    server.serve_forever()</ctrl-c>

其中wfile方法用于向客户端返回信息,send_response返回对应的http代码。上面的监听地址可以跟据自已的需求进行修改。当我们运行以上代码时,客户端可以通过浏览器或CURL命令返回对应的结果。这里以CURL命令执行。其返回结果如下:

 1$ curl -i http://localhost:8080/?foo=bar
 2HTTP/1.0 200 OK
 3Server: BaseHTTP/0.3 Python/2.7.15
 4Date: Thu, 30 Nov 2017 15:21:05 GMT
 5CLIENT VALUES:
 6client_address=('127.0.0.1', 54886) (localhost.localdomain)
 7command=GET
 8path=/?foo=bar
 9real path=/
10query=foo=bar
11request_version=HTTP/1.1
12SERVER VALUES:
13server_version=BaseHTTP/0.3
14sys_version=Python/2.7.15
15protocol_version=HTTP/1.0
16HEADERS RECEIVED:
17accept=*/*
18host=localhost:8080
19user-agent=curl/7.59.0

二、HTTP POST请求

BaseHTTPServer模块有提供rfile方法读取POST数据,但功能较弱,对于文件上传类的操作,需要借助CGI模块处理。先看下rfile方法,读取POST数据的操作如下:

1datas = self.rfile.read(int(self.headers['content-length']))
2datas = urllib.unquote(datas).decode("utf-8", 'ignore')

再看个复杂的,基于cgi模块的:

 1from BaseHTTPServer import BaseHTTPRequestHandler
 2import cgi
 3class PostHandler(BaseHTTPRequestHandler):
 4    def do_POST(self):
 5        # Parse the form data posted
 6        form = cgi.FieldStorage(
 7            fp=self.rfile,
 8            headers=self.headers,
 9            environ={'REQUEST_METHOD':'POST',
10                     'CONTENT_TYPE':self.headers['Content-Type'],
11                     })
12        # Begin the response
13        self.send_response(200)
14        self.end_headers()
15        self.wfile.write('Client: %s\n' % str(self.client_address))
16        self.wfile.write('User-agent: %s\n' % str(self.headers['user-agent']))
17        self.wfile.write('Path: %s\n' % self.path)
18        self.wfile.write('Form data:\n')
19        # Echo back information about what was posted in the form
20        for field in form.keys():
21            field_item = form[field]
22            if field_item.filename:
23                # The field contains an uploaded file
24                file_data = field_item.file.read()
25                file_len = len(file_data)
26                del file_data
27                self.wfile.write('\tUploaded %s as "%s" (%d bytes)\n' % \
28                        (field, field_item.filename, file_len))
29            else:
30                # Regular form value
31                self.wfile.write('\t%s=%s\n' % (field, form[field].value))
32        return
33if __name__ == '__main__':
34    from BaseHTTPServer import HTTPServer
35    server = HTTPServer(('localhost', 8080), PostHandler)
36    print 'Starting server, use <ctrl-c> to stop'
37    server.serve_forever()</ctrl-c>

按如下命令请求后返回的结果如下:

1# curl http://localhost:8080/ -F name=www.361way.com -F foo=bar -F  datafile=@test.py
2Client: ('127.0.0.1', 55418)
3User-agent: curl/7.59.0
4Path: /
5Form data:
6    Uploaded datafile as "test.py" (1442 bytes)
7    foo=bar
8    name=www.361way.com

不过这个示例也是非常简单的只是读取了上传文件的大小,直接就进行了数据的del,并未将上传的文件进行保存。

三、多线程的使用

这里使用了SocketServer里的ThreadingMixIn方法和threading模块进行的实现。具体代码如下:

 1from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
 2from SocketServer import ThreadingMixIn
 3import threading
 4class Handler(BaseHTTPRequestHandler):
 5    def do_GET(self):
 6        self.send_response(200)
 7        self.end_headers()
 8        message =  threading.currentThread().getName()
 9        self.wfile.write(message)
10        self.wfile.write('\n')
11        return
12class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
13    """Handle requests in a separate thread."""
14if __name__ == '__main__':
15    server = ThreadedHTTPServer(('localhost', 8080), Handler)
16    print 'Starting server, use <ctrl-c> to stop'
17    server.serve_forever()</ctrl-c>

如下每执行一次会打印一次线程信息:

1$ curl http://localhost:8080/
2Thread-1
3$ curl http://localhost:8080/
4Thread-2
5$ curl http://localhost:8080/
6Thread-3

四、错误响应

错误响应使用的send_error方法,具体示例如下:

 1from BaseHTTPServer import BaseHTTPRequestHandler
 2class ErrorHandler(BaseHTTPRequestHandler):
 3    def do_GET(self):
 4        self.send_error(404)
 5        return
 6if __name__ == '__main__':
 7    from BaseHTTPServer import HTTPServer
 8    server = HTTPServer(('localhost', 8080), ErrorHandler)
 9    print 'Starting server, use <ctrl-c> to stop'
10    server.serve_forever()</ctrl-c>

这里不再写输出结果,因为直接返回的404。

五、设置响应头

通过send_header方法,可以增加响应头内容。这里假设要发送一个Last-Modified的响应头,其内容是当前的时间戳,操作方法如下:

 1from BaseHTTPServer import BaseHTTPRequestHandler
 2import urlparse
 3import time
 4class GetHandler(BaseHTTPRequestHandler):
 5    def do_GET(self):
 6        self.send_response(200)
 7        self.send_header('Last-Modified', self.date_time_string(time.time()))
 8        self.end_headers()
 9        self.wfile.write('Response body\n')
10        return
11if __name__ == '__main__':
12    from BaseHTTPServer import HTTPServer
13    server = HTTPServer(('localhost', 8080), GetHandler)
14    print 'Starting server, use <ctrl-c> to stop'
15    server.serve_forever()</ctrl-c>

本篇内容和代码主要基于pymotw翻译学习总结而来。