上一节已经介绍了一些关于wsgi的一些相关的知识,这节我们手动实现一下我们的web框架。
首先看一下我的项目结构:
其中web_server.py文件是我们的服务器文件,而Application.py文件是我们的web框架文件,里面定义了wsgi接口。
具体项目代码如下:
# -*- coding:utf-8 -*-
import sys,re,socket,gevent
from gevent import monkey; monkey.patch_all()
class WSGIServer(object):
'''定义一个WSGI服务器的类'''
def __init__(self,port,documents_root,app):
self.server_sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.server_sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
self.server_sock.bind(('',port))
self.server_sock.listen(128)
#设定静态资源文件的路径
self.documents_root = documents_root
#设定web框架可以调用的函数
self.app = app #application
def startup(self):
'''运行服务器'''
while True:
client_sock,addr = self.server_sock.accept()
# client_sock.settimeout(3)
gevent.spawn(self.deal_with_request,client_sock)
# gevent.joinall([
# gevent.spawn(self.deal_with_request,client_sock)
# ])
def deal_with_request(self,client_sock):
'''
以短连接的方式,为这个浏览器服务
注意:如果是长连接,频繁的关闭socket会导致错误:1--->[Errno 9] Bad file descriptor
while True:
'''
rev_data = client_sock.recv(4096)
if not rev_data:
print('客户端已经下线了')
client_sock.close()
return
request_lines = rev_data.decode().splitlines()
# GET /static/index.html HTTP/1.1
ret = re.match(r'([^/]*)([^ ]+)', request_lines[0])
if ret:
file_name = ret.group(2)
if file_name == '/':
file_name = '/index.html'
# 如果不是以.html结尾的文件,都认为是普通的文件
# 如果是.html结尾的文件,就让web框架进行处理
if not file_name.endswith('.html'):
try:
# print("./static"+file_name,"*"*100)
with open(self.documents_root + file_name, 'rb') as f:
response_body = f.read()
except:
response_header = 'HTTP/1.1 404 NOT FOUND\r\n\r\n'
# response_header += "Content-Type: text/html;charset=utf-8\r\n"
# response_header += 'Content-Length: %d\r\n\r\n' % len(response_body)
response_body = 'file not found,请输入正确的URL!'
response = response_header + response_body
client_sock.send(response.encode('utf-8'))
else:
response_header = 'HTTP/1.1 200 OK\r\n\r\n'
'''注意:不能设置Content-Type,会导致浏览器不能解析css,js等文件'''
# response_header += "Content-Type: text/html;charset=utf-8\r\n"
# response_header += 'Content-Length: %d\r\n\r\n' % len(response_body.decode())
response = response_header.encode('utf-8') + response_body
client_sock.send(response)
finally:
client_sock.close()
# 以.html结尾的文件,就认为是浏览器需要动态的页面
else:
env = dict(PATH_INFO=file_name)
# 保存 web返回的数据
response_body = self.app(env, self.set_response_headers) # 第2个参数传递的是函数的引用,由web框架来执行。
# print('110 >>>>>>>>',response_body)
response_header = 'HTTP/1.1 %s\r\n' % self.status
# response_header += "Content-Type: text/html;charset=utf-8\r\n"
# response_header += 'Content-Length: %d\r\n' % len(response_body)
for header in self.headers:
response_header += '%s: %s\r\n' % (header[0],header[1]) # *header: 对元祖解包
response_header += '\r\n'
response = response_header + response_body
client_sock.send(response.encode('utf-8')) # Chrome默认编码是gbk,不设置成gbk的话浏览器无法解析符号
client_sock.close()
def set_response_headers(self,status,headers):
'''这个方法会在web框架中被默认调用。'''
# response_header_default = [
# ('Data',time.ctime()),
# ('Server','ItCast-python mini web server')
# ]
#将web框架设置的状态码,头信息存储起来
# self.headers = [status, response_header_default + headers]
self.status = status
self.headers = headers
#设置静态资源访问路径
g_static_document_root = './static'
def main():
if len(sys.argv)==3:
port = sys.argv[1]
if port.isdigit():
port = int(port)
web_frame_module_app_name = sys.argv[2]
else:
print("运行方式如: python3 xxx.py 7890 my_web_frame_name:application")
return
# print('http服务器使用的端口号是:%s' % port)
# my_web_frame_name:application
ret = re.match(r"([^:]*):(.*)",web_frame_module_app_name)
if ret:
web_frame_module_name = ret.group(1)
# print('==',web_frame_module_name)
app_name = ret.group(2)
# print('==',app_name)
web_frame_module = __import__(web_frame_module_name) #return Application
app = getattr(web_frame_module,app_name) # getattr():获取对象的属性(application方法)
# print(app)
http_server = WSGIServer(port,g_static_document_root,app)
http_server.startup()
if __name__ == '__main__':
main()
# -*- coding:utf-8 -*-
import re
from pymysql import connect
from urllib.parse import unquote # unquote 对URL进行解码。
template_root = './templates'
# 用来存放url路由映射
g_url_route = dict()
def connect_mysql():
conn = connect(host='localhost', user='root', password="cz",
database='stock_db', port=3306,
charset='utf8')
cur = conn.cursor()
return cur, conn
# 装饰器,添加路由功能
def route(path):
def func1(func):
g_url_route[path] = func
def func2(*args, **kwargs):
return func(*args, **kwargs)
return func2
return func1
@route(r'/index\.html') # 相当于执行 index = route('/index.html')(index)
def index(file_name, url=None):
try:
with open(template_root + file_name,encoding='utf-8') as f:
content = f.read() # 读取的是HTML文档
except Exception as e:
print('2=====', e)
else:
cur, conn = connect_mysql()
sql = '''select * from info'''
ret = cur.execute(sql)
if ret:
data_from_mysql = cur.fetchall()
# print('>>>',data_from_mysql)
html_template = """
%s
%s
%s
%s
%s
%s
%s
%s
"""
html = ""
for info in data_from_mysql:
html += html_template % (
info[0], info[1], info[2], info[3], info[4], info[5], info[6], info[7], info[1])
# 这是django所使用的模板引擎约定的 {{ 变量 }}, {% 代码段落 %} 表示方法
# {%content%} :前端和后台提前约定好的字段。
content = re.sub(r"\{%content%\}", html, content)
# print('*'*50,content)
cur.close()
conn.close()
return content
@route(r'/center\.html')
def center(file_name, url=None):
try:
with open(template_root + file_name,encoding='utf-8') as f:
content = f.read()
except Exception as e:
print('3======', e)
else:
cur, conn = connect_mysql()
sql = '''select i.code,i.short,i.chg,i.turnover,i.price,i.highs,f.note_info from info as i inner join focus as f where i.id = f.info_id'''
ret = cur.execute(sql)
if ret:
data_from_mysql = cur.fetchall()
# print('>>>', data_from_mysql)
html_template = '''
%s
%s
%s
%s
%s
%s
%s
修改
'''
html = ''
for info in data_from_mysql:
html += html_template % (
info[0], info[1], info[2], info[3], info[4], info[5], info[6], info[0], info[0])
content = re.sub(r'\{%content%\}', html, content)
return content
else:
content = re.sub(r'\{%content%\}',"暂时没有关注信息,请先关注哦"
,content)
return content
@route(r'/update/(\d*)\.html') # 底层实现: update = route(r'/update/(\d*)\.html')(update)
def update(file_name, url):
'''显示更新页面的内容'''
try:
with open(template_root + '/update.html') as f:
content = f.read()
except Exception as e:
print('4======', e)
else:
# print('url-----1------', url)
# print('filename------1----', file_name)
ret = re.match(url, file_name)
if ret:
stock_code = ret.group(1)
cur, conn = connect_mysql()
sql = '''select f.note_info from focus as f inner join info as i on f.info_id=i.id where i.code=%s'''
ret = cur.execute(sql, [stock_code])
if ret:
data_from_mysql = cur.fetchone()
# print('4==>',data_from_mysql)
# print('>>>', data_from_mysql)
content = re.sub(r'\{%code%\}', stock_code, content)
content = re.sub(r'\{%note_info%\}', data_from_mysql[0], content)
cur.close()
conn.close()
return content
@route(r'/update/(\d*)/(.*)\.html')
def update_note_info(file_name, url):
'''进行数据的真正更新'''
ret = re.match(url, file_name)
if ret:
stock_code = ret.group(1)
stock_note_info = ret.group(2)
stock_note_info = unquote(stock_note_info)
cur, conn = connect_mysql()
sql = '''update focus as f inner join info as i on f.info_id=i.id set f.note_info=%s where i.code=%s'''
result = cur.execute(sql, [stock_note_info, stock_code])
if result:
conn.commit()
cur.close()
conn.close()
return '修改股票%s的备注信息成功'%stock_code
@route(r'/add/(\d{6})\.html')
def add(file_name, url):
'''添加关注'''
# print("hahahahahahahaha"*10)
ret = re.match(url, file_name)
if ret:
stock_code = ret.group(1)
# print('=xxxxxxxxx=',stock_code)
# 判断是否已经关注
cur, conn = connect_mysql()
sql = """select * from focus inner join info on focus.info_id=info.id where info.code=%s"""
cur.execute(sql, [stock_code])
if cur.fetchone():
cur.close()
conn.close()
return '已经关注了股票%s,请不要重复关注' % stock_code
# 没有关注,就进行关注
sql = '''insert into focus(info_id) select id from info where info.code=%s'''
ret = cur.execute(sql, [stock_code])
if ret:
conn.commit()
cur.close()
conn.close()
return '关注股票%s成功了' % stock_code
@route(r'/del/(\d*)\.html')
def delete(file_name, url):
'''取消关注'''
ret = re.match(url, file_name)
if ret:
stock_code = ret.group(1)
cur, conn = connect_mysql()
# 如果已经关注,就取消关注
sql = '''delete from focus where info_id = (select id from info where code=%s)'''
ret = cur.execute(sql, [stock_code])
if ret:
conn.commit()
cur.close()
conn.close()
return '取消关注股票%s成功了' % stock_code
def application(environ, start_response): # start_response是服务器传过来的函数的引用,由web框架来执行这个函数
# response_headers = [('Content-Type', 'text/html;charset=utf-8')]
response_headers = [('SERVER_PORT', '8080')]
file_name = environ['PATH_INFO']
for url, func in g_url_route.items(): # url:字典里保存的有正则表达式的字符串,也有普通的路径字符串
ret = re.match(url, file_name)
if ret:
start_response('200 OK', response_headers)
# print('==haha===',file_name,url)
return func(file_name, url)
else:
start_response('404 Not Found', response_headers)
return 'sorry,你访问的资源不存在!'
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>首页 - 个人选股系统 V5.87title>
<link href="/css/bootstrap.min.css" rel="stylesheet">
<script src="/js/jquery-1.12.4.min.js">script>
<script src="/js/bootstrap.min.js">script>
<script>
$(document).ready(function(){
$("input[name='toAdd']").each(function(){
var currentAdd = $(this);
currentAdd.click(function(){
code = $(this).attr("systemIdVaule");
// alert("/add/" + code + ".html");
$.get("/add/" + code + ".html", function(data, status){
alert("数据: " + data + "\n状态: " + status);
});
});
});
});
script>
head>
<body>
<div class="navbar navbar-inverse navbar-static-top ">
<div class="container">
<div class="navbar-header">
<button class="navbar-toggle" data-toggle="collapse" data-target="#mymenu">
<span class="icon-bar">span>
<span class="icon-bar">span>
<span class="icon-bar">span>
button>
<a href="#" class="navbar-brand">选股系统a>
div>
<div class="collapse navbar-collapse" id="mymenu">
<ul class="nav navbar-nav">
<li class="active"><a href="">股票信息a>li>
<li><a href="/center.html">个人中心a>li>
ul>
div>
div>
div>
<div class="container">
<div class="container-fluid">
<table class="table table-hover">
<tr>
<th>序号th>
<th>股票代码th>
<th>股票简称th>
<th>涨跌幅th>
<th>换手率th>
<th>最新价(元)th>
<th>前期高点th>
<th>前期高点日期th>
<th>添加自选th>
tr>
{%content%}
table>
div>
div>
body>
html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>个人中心 - 个人选股系统 V5.87title>
<link href="/css/bootstrap.min.css" rel="stylesheet">
<script src="/js/jquery-1.12.4.min.js">script>
<script src="/js/bootstrap.min.js">script>
<script>
$(document).ready(function(){
$("input[name='toDel']").each(function(){
var currentAdd = $(this);
currentAdd.click(function(){
code = $(this).attr("systemIdVaule");
// alert("/del/" + code + ".html");
$.get("/del/" + code + ".html", function(data, status){
alert("数据: " + data + "\n状态: " + status);
});
window.location.reload()
});
});
});
script>
head>
<body>
<div class="navbar navbar-inverse navbar-static-top ">
<div class="container">
<div class="navbar-header">
<button class="navbar-toggle" data-toggle="collapse" data-target="#mymenu">
<span class="icon-bar">span>
<span class="icon-bar">span>
<span class="icon-bar">span>
button>
<a href="#" class="navbar-brand">选股系统a>
div>
<div class="collapse navbar-collapse" id="mymenu">
<ul class="nav navbar-nav">
<li ><a href="/index.html">股票信息a>li>
<li class="active"><a href="">个人中心a>li>
ul>
div>
div>
div>
<div class="container">
<div class="container-fluid">
<table class="table table-hover">
<tr>
<th>股票代码th>
<th>股票简称th>
<th>涨跌幅th>
<th>换手率th>
<th>最新价(元)th>
<th>前期高点th>
<th style="color:red">备注信息th>
<th>修改备注th>
<th>delth>
tr>
{%content%}
table>
div>
div>
body>
html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>首页 - 个人选股系统 V5.87title>
<link href="/css/bootstrap.min.css" rel="stylesheet">
<script src="/js/jquery-1.12.4.min.js">script>
<script src="/js/bootstrap.min.js">script>
<script>
$(document).ready(function(){
$("#update").click(function(){
var item = $("#note_info").val();
// alert("/update/{%code%}/" + item + ".html");
$.get("/update/{%code%}/" + item + ".html", function(data, status){
alert("数据: " + data + "\n状态: " + status);
self.location='/center.html';
});
});
});
script>
head>
<body>
<div class="navbar navbar-inverse navbar-static-top ">
<div class="container">
<div class="navbar-header">
<button class="navbar-toggle" data-toggle="collapse" data-target="#mymenu">
<span class="icon-bar">span>
<span class="icon-bar">span>
<span class="icon-bar">span>
button>
<a href="#" class="navbar-brand">选股系统a>
div>
<div class="collapse navbar-collapse" id="mymenu">
<ul class="nav navbar-nav">
<li><a href="/index.html">股票信息a>li>
<li><a href="/center.html">个人中心a>li>
ul>
div>
div>
div>
<div class="container">
<div class="container-fluid">
<div class="input-group">
<span class="input-group-addon">正在修改:span>
<span class="input-group-addon">{%code%}span>
<input id="note_info" type="text" class="form-control" aria-label="Amount (to the nearest dollar)" value="{%note_info%}">
<span id="update" class="input-group-addon" style="cursor: pointer">修改span>
div>
div>
div>
body>
html>
具体的static里面的静态css和js文件可以在网上下载。
运行此服务器需要在终端运行,具体运行命令格式如下:
python3 web_server.py 8080 Application:application