【20天快速掌握Python】day20-手动搭建HTTP服务器

20 篇文章 0 订阅
19 篇文章 1 订阅
文章介绍了WSGI接口在Python中的作用,展示了一个简单的WSGI应用处理函数的实现。通过wsgiref模块创建了一个基础的WSGI服务器,用于测试和运行Web应用。进一步讨论了如何处理不同的HTTP请求路径,从直接的if条件判断到使用字典映射,再到利用装饰器管理请求路径。同时,文章还提到了requests模块,用于发送HTTP请求并获取响应。
摘要由CSDN通过智能技术生成

演示代码:

 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用一个包含两个strtuple表示。

通常情况下,都应该把Content-Type头发送给浏览器。其他很多常用的HTTP Header也应该发送。

然后,函数的返回值'<h1>Hello, web!</h1>'将作为HTTP响应的Body发送给浏览器。

有了WSGI,我们关心的就是如何从environ这个dict对象拿到HTTP请求信息,然后构造HTML,通过start_response()发送Header,最后返回Body。

整个application()函数本身没有涉及到任何解析HTTP的部分,也就是说,底层代码不需要我们自己编写,我们只负责在更高层次上考虑如何响应请求就可以了。

不过,等等,这个application()函数怎么调用?如果我们自己调用,两个参数environstart_response我们没法提供,返回的str也没法发给浏览器。

所以application()函数必须由WSGI服务器来调用。有很多符合WSGI规范的服务器,我们可以挑选一个来用。但是现在,我们只想尽快测试一下我们编写的application()函数真的可以把HTML输出到浏览器,所以,要赶紧找一个最简单的WSGI服务器,把我们的Web应用程序跑起来。

好消息是Python内置了一个WSGI服务器,这个模块叫wsgiref,它是用纯Python编写的WSGI服务器的参考实现。所谓“参考实现”是指该实现完全符合WSGI标准,但是不考虑任何运行效率,仅供开发和测试使用。

2.新建WSGI服务器

  1. 创建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')]
  1. 创建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)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值