继续今天的python学习
昨天实现了WEB服务器的编写,今天进一步的写一下WEB框架,什么是框架?
emmmm,框架可以理解为一种可以直接拿来用的东西,写好之后大家都可以拿来用。
昨天的代码我们实现了静态和动态的服务器,今天的任务就是把静态和动态放到一起,不管是静态还是动态,我们都可以用同样的方法去访问
# WebFramework.py
import time
from webserver import HTTPserver
class Application(object):
def __init__(self, urls):
self.urls = urls
def __call__(self, env, start_response):
path = env.get("PATH_INFO", "/")
for url, app in urls:
if path == url:
return app(env, start_response)
# 如果出现错误,返回错误信息
status = "404 Not Found"
headers = [
("Content-Type", "Text/plain")
]
start_response(status, headers)
return "Not Found"
def ctime(env, start_response):
status = "200 OK"
headers = [
("Content-Type", "Text/plain")
]
start_response(status, headers)
return time.ctime()
def say_hello(env, start_response):
status = "200 OK"
headers = [
("Content-Type", "Text/plain")
]
start_response(status, headers)
return "Hello World"
if __name__ == "__main__":
urls = [
("/", ctime),
("/ctime", ctime),
("/sayhello", say_hello)
]
app = Application(urls)
http_server = HTTPserver(app)
http_server.bind(8080)
http_server.start()
这就是写好的框架,我们来解读一下
首先定义了一个Application类,我们昨天是学习了一个叫做WSGI的东西,它规定了一个application函数,今天我们把它变成一个类,方便复用。昨天我们把想要实现的每一个功能都单独存储在一个文件当中,但是可想而知,当我们要实现的功能不断增大,那么我们要处理的文件数就不断增多,这是不方便的,所以我们想的是,能否把这些功能放到一个文件当中,变成不同的函数,我们要实现时去调用就可以了。所以有了urls这个列表,列表的每一个元素都是一个元组,元组的第一个元素是文件名,第二个元素是我们定义要实现的函数名,我们把每一个文件名对应成一个函数名,这样就做到了单文件多功能。
所以在Application类中定义了__call__()方法,把实例对象当成是函数来调用。今天env就派上了用场。
注:要调用py文件的功能要在Headers中添加"Content-Type: Text/plain",不然会直接提示下载文件。
框架写好了我们在昨天代码的基础上做一些修改,删除一些不必要的代码
# webserver.py
import socket
from multiprocessing import Process
import re
import os
import sys
# 设置静态文件根目录
HTML_ROOT_DIR = "./html"
WSGI_PYTHON_DIR = "./wsgipython"
class HTTPserver(object):
""""""
def __init__(self, application):
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.app = application
# self.server.listen(128)
# 开启服务器
def start(self):
self.server.listen(128)
while True:
cli_socket, cli_address = self.server.accept()
print("%s:%s已连接..." % cli_address)
p = Process(target=self.handle_client, args=(cli_socket,))
p.start()
cli_socket.close()
# WSGI的规定,在这里报文的头,因为后面的application返回的只是报文的body
def start_response(self, status, headers):
# 这是我们自己设置有的要响应的信息
server_headers = [
("Server", "MyServer")
]
# 设置报文头首行
response_headers = "HTTP/1.1 " + status + os.linesep
# 开始添加报文头
for each in server_headers:
response_headers += "%s: %s" % each
response_headers += os.linesep
for header in headers:
response_headers += "%s: %s" % header
response_headers += os.linesep
self.response_headers = response_headers
def handle_client(self, cli_socket):
'''处理客户端请求'''
# 获取用户请求数据
data = cli_socket.recv(1024)
request_header_lines = data.splitlines()
for line in request_header_lines:
print(line.decode("utf-8"))
# 解析请求的报文
request_start_line = request_header_lines[0].decode("utf-8")
# 提取用户请求的文件名
file_name = re.match(r"\w+ +(/[^ ]* )", request_start_line).group(1)
file_name = file_name.strip()
env = {
"PATH_INFO":file_name
}
# application是WSGI规定的统一的接口,目的是保证调用脚本的通用性
response_body = self.app(env, self.start_response)
response = self.response_headers + os.linesep + response_body
cli_socket.send(response.encode("utf-8"))
cli_socket.close()
def bind(self, port):
self.server.bind(("", port))
def main():
# 在这里需要把模块添加到path中才能导入,否则找不到
sys.path.insert(1, WSGI_PYTHON_DIR)
http_server = HTTPserver()
http_server.bind(8080)
http_server.start()
if __name__ == "__main__":
main()
对比昨天的代码是不是清爽了许多?
接下来看下运行结果(注意,这次要在WebFramework.py当中运行的哦~)
实现了一个文件多个函数,但是美中不足,我们还要修改一下代码,访问静态文件
# WebFramework.py
import time
from webserver import HTTPserver
HTML_DIR = "./html"
class Application(object):
def __init__(self, urls):
self.urls = urls
def __call__(self, env, start_response):
path = env.get("PATH_INFO", "/")
if "/" == path:
path = "/static/index.html"
if path.startswith("/static"):
file_name = path[7:]
try:
f = open(HTML_DIR + file_name, "rb")
except:
status = "404 Not Found"
headers = [
("Content-Type", "Text/plain")
]
start_response(status, headers)
return "NOT FOUND"
else:
status = "200 OK"
headers = [
("Content-Type", "Text/html")
]
start_response(status, headers)
data = f.read().decode("utf-8")
f.close()
return data
for url, app in urls:
if path == url:
return app(env, start_response)
# 如果出现错误,返回错误信息
status = "404 Not Found"
headers = [
("Content-Type", "Text/plain")
]
start_response(status, headers)
return "Not Found"
def ctime(env, start_response):
status = "200 OK"
headers = [
("Content-Type", "Text/plain")
]
start_response(status, headers)
return time.ctime()
def say_hello(env, start_response):
status = "200 OK"
headers = [
("Content-Type", "Text/plain")
]
start_response(status, headers)
return "Hello World"
if __name__ == "__main__":
urls = [
("/ctime", ctime),
("/sayhello", say_hello)
]
app = Application(urls)
http_server = HTTPserver(app)
http_server.bind(8080)
http_server.start()
我们要访问静态文件时,要以/static开头
运行结果:
注意代码中的细节,访问html文件,Headers要修改,"Content-Type: Text/html",不然显示的就是你html的代码
实现了昨天我们要的功能,但是又来了一个问题,正常情况下应该启动webserver.py而不是框架,毕竟我们要运行的是服务器,那么接下来我们要修改代码,把服务器作为启动文件
# webserver2.py
import socket
from multiprocessing import Process
import re
import os
import sys
# 设置静态文件根目录
HTML_ROOT_DIR = "./html"
WSGI_PYTHON_DIR = "./wsgipython"
class HTTPserver(object):
""""""
def __init__(self, application):
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.app = application
# self.server.listen(128)
# 开启服务器
def start(self):
self.server.listen(128)
while True:
cli_socket, cli_address = self.server.accept()
print("%s:%s已连接..." % cli_address)
p = Process(target=self.handle_client, args=(cli_socket,))
p.start()
cli_socket.close()
# WSGI的规定,在这里报文的头,因为后面的application返回的只是报文的body
def start_response(self, status, headers):
# 这是我们自己设置有的要响应的信息
server_headers = [
("Server", "MyServer")
]
# 设置报文头首行
response_headers = "HTTP/1.1 " + status + os.linesep
# 开始添加报文头
for each in server_headers:
response_headers += "%s: %s" % each
response_headers += os.linesep
for header in headers:
response_headers += "%s: %s" % header
response_headers += os.linesep
self.response_headers = response_headers
def handle_client(self, cli_socket):
'''处理客户端请求'''
# 获取用户请求数据
data = cli_socket.recv(1024)
request_header_lines = data.splitlines()
for line in request_header_lines:
print(line.decode("utf-8"))
# 解析请求的报文
request_start_line = request_header_lines[0].decode("utf-8")
# 提取用户请求的文件名
file_name = re.match(r"\w+ +(/[^ ]* )", request_start_line).group(1)
file_name = file_name.strip()
env = {
"PATH_INFO":file_name
}
# application是WSGI规定的统一的接口,目的是保证调用脚本的通用性
response_body = self.app(env, self.start_response)
response = self.response_headers + os.linesep + response_body
cli_socket.send(response.encode("utf-8"))
cli_socket.close()
def bind(self, port):
self.server.bind(("", port))
def main():
# 在这里需要把模块添加到path中才能导入,否则找不到
sys.path.insert(1, WSGI_PYTHON_DIR)
if len(sys.argv) < 2:
sys.exit("Format: python modulename:Appname")
module_name, app_name = sys.argv[1].split(":")
m = __import__(module_name)
app = getattr(m, app_name)
http_server = HTTPserver(app)
http_server.bind(8080)
http_server.start()
if __name__ == "__main__":
main()
# webframework.py
import time
from webserver import HTTPserver
HTML_DIR = "./html"
class Application(object):
def __init__(self, urls):
self.urls = urls
def __call__(self, env, start_response):
path = env.get("PATH_INFO", "/")
if "/" == path:
path = "/static/index.html"
if path.startswith("/static"):
file_name = path[7:]
try:
f = open(HTML_DIR + file_name, "rb")
except:
status = "404 Not Found"
headers = [
("Content-Type", "Text/plain")
]
start_response(status, headers)
return "NOT FOUND"
else:
status = "200 OK"
headers = [
("Content-Type", "Text/html")
]
start_response(status, headers)
data = f.read().decode("utf-8")
f.close()
return data
for url, app in urls:
if path == url:
return app(env, start_response)
# 如果出现错误,返回错误信息
status = "404 Not Found"
headers = [
("Content-Type", "Text/plain")
]
start_response(status, headers)
return "Not Found"
def ctime(env, start_response):
status = "200 OK"
headers = [
("Content-Type", "Text/plain")
]
start_response(status, headers)
return time.ctime()
def say_hello(env, start_response):
status = "200 OK"
headers = [
("Content-Type", "Text/plain")
]
start_response(status, headers)
return "Hello World"
urls = [
("/ctime", ctime),
("/sayhello", say_hello)
]
app = Application(urls)
运行命令:
python3 webserver2.py webframework:app
运行结果同上。