演示代码:
import re import socket from multiprocessing import Process class WSGIServer(): def __init__(self, server, port, root): self.server = server self.port = port self.root = root self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.server_socket.bind((self.server, self.port)) self.server_socket.listen(128) def handle_socket(self, socket): data = socket.recv(1024).decode('utf-8').splitlines()[0] file_name = re.match(r'[^/]+(/[^ ]*)', data)[1] # print(file_name) if file_name == '/': file_name = self.root + '/index.html' else: file_name = self.root + file_name try: file = open(file_name, 'rb') except IOError: response_header = 'HTTP/1.1 404 NOT FOUND \r\n' response_header += '\r\n' response_body = '========Sorry,file not found======='.encode('utf-8') else: response_header = 'HTTP/1.1 200 OK \r\n' response_header += '\r\n' response_body = file.read() finally: socket.send(response_header.encode('utf-8')) socket.send(response_body) def forever_run(self): while True: client_socket, client_addr = self.server_socket.accept() # self.handle_socket(client_socket) p = Process(target=self.handle_socket, args=(client_socket,)) p.start() client_socket.close() if __name__ == '__main__': ip = '0.0.0.0' port = 8899 server = WSGIServer(ip, port, './pages') print('server is running at {}:{}'.format(ip, port)) server.forever_run()
1.WSGI接口
WSGI接口定义非常简单,它只要求Web开发者实现一个函数,就可以响应HTTP请求。我们来看一个最简单的Web版本的“Hello, web!”:
def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return '<h1>Hello, web!</h1>'
上面的application()
函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:
-
environ:一个包含所有HTTP请求信息的
dict
对象; -
start_response:一个发送HTTP响应的函数。
在application()
函数中,调用:
start_response('200 OK', [('Content-Type', 'text/html')])
就发送了HTTP响应的Header,注意Header只能发送一次,也就是只能调用一次start_response()
函数。start_response()
函数接收两个参数,一个是HTTP响应码,一个是一组list
表示的HTTP Header,每个Header用一个包含两个str
的tuple
表示。
通常情况下,都应该把Content-Type
头发送给浏览器。其他很多常用的HTTP Header也应该发送。
然后,函数的返回值'<h1>Hello, web!</h1>'
将作为HTTP响应的Body发送给浏览器。
有了WSGI,我们关心的就是如何从environ
这个dict
对象拿到HTTP请求信息,然后构造HTML,通过start_response()
发送Header,最后返回Body。
整个application()
函数本身没有涉及到任何解析HTTP的部分,也就是说,底层代码不需要我们自己编写,我们只负责在更高层次上考虑如何响应请求就可以了。
不过,等等,这个application()
函数怎么调用?如果我们自己调用,两个参数environ
和start_response
我们没法提供,返回的str
也没法发给浏览器。
所以application()
函数必须由WSGI服务器来调用。有很多符合WSGI规范的服务器,我们可以挑选一个来用。但是现在,我们只想尽快测试一下我们编写的application()
函数真的可以把HTML输出到浏览器,所以,要赶紧找一个最简单的WSGI服务器,把我们的Web应用程序跑起来。
好消息是Python内置了一个WSGI服务器,这个模块叫wsgiref,它是用纯Python编写的WSGI服务器的参考实现。所谓“参考实现”是指该实现完全符合WSGI标准,但是不考虑任何运行效率,仅供开发和测试使用。
2.新建WSGI服务器
-
创建
hello.py
文件,用来实现WSGI应用的处理函数。
def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) print(environ) return ['<h1>Hello, web!</h1>'.encode('utf-8'),'hello'.encode('utf-8')]
-
创建
server.py
文件,用来启动WSGI服务器,加载appliction
函数
# 从wsgiref模块导入: from wsgiref.simple_server import make_server # 导入我们自己编写的application函数: from hello import application # 创建一个服务器,IP地址为空,端口是8000,处理函数是application: httpd = make_server('', 8000, application) print("Serving HTTP on port 8000...") # 开始监听HTTP请求: httpd.serve_forever()
3.使用if管理请求路径
文件结构:
├── server.py ├── utils.py ├── pages └── index.html └── templates └── info.html
utlis.py文件
PAGE_ROOT = './pages' TEMPLATE_ROOT = './templates' def load_html(file_name, start_response, root=PAGE_ROOT): """ 加载HTML文件时调用的方法 :param file_name: 需要加载的HTML文件 :param start_response: 函数,用来设置响应头。如果找到文件,请求头设置为200,否则设置为410 :param root: HTML文件所在的目录。默认PAGE_ROOT表示静态HTML文件,TEMPLATE_ROOT表示的是模板文件 :return: 读取HTML文件成功的话,返回HTML文件内容;读取失败提示资源被删除 """ file_name = root + file_name try: file = open(file_name, 'rb') except IOError: start_response('410 GONE', [('Content-Type', "text/html;charset=utf-8")]) return ['资源被删除了'.encode('utf-8')] else: start_response('200 OK', [('Content-Type', "text/html;charset=utf-8")]) content = file.read() return [content] def load_template(file_name, start_respone, **kwargs): """ 加载模板文件 :param file_name: 需要加载的模板文件名 :param start_respone: 函数,用来设置响应头。如果找到文件,请求头设置为200,否则设置为410 :param kwargs: 用来设置模板里的变量 :return: 读取HTML文件成功的话,返回HTML文件内容;读取失败提示资源被删除 """ content = load_html(file_name, start_respone, root=TEMPLATE_ROOT) html = content[0].decode('utf-8') if html.startswith('<!DOCTYPE html>'): return [html.format(**kwargs).encode('utf-8')] else: return content
service.py文件
from wsgiref.simple_server import make_server from utils import load_html, load_template def show_home(start_response): return load_html('/index.html', start_response) def show_test(start_response): start_response('200 OK', [('Content-Type', "text/html;charset=utf-8")]) return ['我是一段普通的文字'.encode('utf-8')] def show_info(start_response): return load_template('/info.html', start_response, name='张三',age=18}) def application(environ, start_response): path = environ.get('PATH_INFO') # 处理首页请求(加载一个HTML文件) if path == '/' or path == '/index.html': result = show_home(start_response) return result # 处理test.html请求(返回一个普通的字符串) elif path == '/test.html': return show_test(start_response) # 处理info.html请求(加载一个模板并且返回) elif path == '/info.html': return show_info(start_response) # 其它请求暂时无法处理,返回404 else: start_response('400 NOT FOUND', [('Content-Type', "text/html;charset=utf-8")]) return ['页面未找到'.encode('utf-8')] httpd = make_server('', 8000, application) print("Serving HTTP on port 8000...") httpd.serve_forever()
4.使用字典管理请求路径
文件结构:
├── server.py ├── utils.py ├── urls.py ├── pages └── index.html └── templates └── info.html
urls.py文件:该文件里只有一个字典对象,用来保存请求路径和处理函数之间的对应关系。
urls = { '/': 'show_home', '/index.html': 'show_home', '/test.html': 'show_test', '/info.html': 'show_info' }
server.py文件:
from wsgiref.simple_server import make_server from urls import urls from utils import load_html, load_template def show_home(start_response): return load_html('/index.html', start_response) def show_test(start_response): start_response('200 OK', [('Content-Type', "text/html;charset=utf-8")]) return ['我是一段普通的文字'.encode('utf-8')] def show_info(start_response): return load_template('/info.html', start_response, name='张三',age=18}) def application(environ, start_response): path = environ.get('PATH_INFO') # 这里不再是一大堆的if...elif语句了,而是从urls字典里获取到对应的函数 func = urls.get(path) if func: return eval(func)(start_response) # 其它请求暂时无法处理,返回404 else: start_response('400 NOT FOUND', [('Content-Type', "text/html;charset=utf-8")]) return ['页面未找到'.encode('utf-8')] httpd = make_server('', 8000, application) print("Serving HTTP on port 8000...") httpd.serve_forever()
5.使用装饰器管理请求路径
from wsgiref.simple_server import make_server from utils import load_html, load_template g_url_route = {} def route(url): def handle_action(action): g_url_route[url] = action def do_action(start_response): return action(start_response) return do_action return handle_action @route('/index.html') @route('/') def show_home(start_response): return load_html('/index.html', start_response) @route('/test.html') def show_test(start_response): start_response('200 OK', [('Content-Type', "text/html;charset=utf-8")]) return ['我是一段普通的文字'.encode('utf-8')] @route('/info.html') def show_info(start_response): return load_template('/info.html', start_response, name='张三', age=18) def application(environ, start_response): file_name = environ.get('PATH_INFO') try: return g_url_route[file_name](start_response) except Exception: start_response('404 NOT FOUND', [('Content-Type', 'text/html;charset=utf-8')]) return ['对不起,界面未找到'.encode('utf-8')] if __name__ == '__main__': httpd = make_server('', 8000, application) print("Serving HTTP on port 8000...") httpd.serve_forever()
6.requests模块的介绍
除了使用浏览器给服务器发送请求以外,我们还可以使用第三方模块requests用代码来给服务器发送器请求,并获取结果。
url = 'https://www.apiopen.top/satinApi' params = {'type': 1, 'page': 2} response = requests.get(url, params) print(response) # 方法二: 只能用于get请求 url = 'https://www.apiopen.top/satinApi?type=1&page=1' response = requests.get(url) # print(response) # 2.获取请求结果 print(response.headers) # 2)响应体(数据) # a.获取二进制对应的原数据(数据本身是图片、压缩文件、视频等文件数据) content = response.content print(type(content)) # b.获取字符类型的数据 text = response.text print(type(text)) # c.获取json数据(json转换成python对应的数据) json = response.json() print(type(json)) print(json)