mini-Web业务逻辑及其实现 / Business Logic for mini-web Frame

mini-Web业务逻辑及其实现

为什么需要WSGI?

当服务器收到浏览器请求后, 需要判断请求的是静态资源还是动态资源。如果是静态,则直接返回静态页面给浏览器;如果是动态,则需要处理数据后返回给浏览器。由于要保证服务器长期稳定运行,通常处理数据的这部分功能需要独立出来,因此会用到应用程序框架。

另外,为了不同web服务器和不同web框架之间的通配性以及可移植性,因此需要遵循WSGI(Web Server Gateway Interface,服务器网关接口)。 WSGI没有官方的实现,因为WSGI更像一个协议,遵循该协议的WSGI应用都可以在任何服务器上运行。

定义WSGI接口

我们只需要在web框架中定义一个符合WSGI标准的HTTP处理函数,即可响应HTTP请求。

1
2
3
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return 'Hello there!'

application()函数接收两个参数

  • environ: 一个字典对象, 包含所有HTTP请求信息
  • start_response: 一个函数的应用,用来发送HTTP响应头信息

application()函数必须有返回值,返回响应body信息。

application()函数由WSGI服务器调用,参数亦在服务器中传入, 所有的HTTP解析、响应头和响应体信息的拼接都在服务器中完成,在框架中我们只需要完成应用程序的逻辑部分即可。

server端

web_server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import socket
import multiprocessing
import re
# import dynamic.mini_frame
import sys


class WSGIserver(object):
def __init__(self, port, app, static_path):
# 1. 创建套接字
self.tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2. 绑定本地信息
self.tcp_socket.bind(("", port))
# 3. 套接字转为被动接听
self.tcp_socket.listen(128)

self.application = app
self.static_path = static_path

def serve_client(self, new_socket):
"""为这个客户端返回数据"""
request = new_socket.recv(1024).decode("utf-8")
request_lines = request.splitlines()
print("") # for test
print(">" * 20)
print(request_lines)

file_name = ""
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
if ret:
file_name = ret.group(1)
if file_name == "/":
file_name = "/index.html"

# 不以.py结尾的就是静态页面, 直接返回数据给浏览器
# 这里修改为判断'.html', 为了实现伪静态
if not file_name.endswith('.html'):
try:
f = open(self.static_path + file_name, 'rb')
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response += 'Content-Type:text/html;charset=utf-8'
response += "\r\n"
response += "ooops 找不到页面了"
new_socket.send(response.encode('utf-8'))
else:
html_content = f.read()
f.close()

response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"

new_socket.send(response.encode('utf-8'))
new_socket.send(html_content)
# 其他都是以.py结尾的,动态资源请求给web框架处理
# 现在这里为是以.html结尾
else:
env = dict()
env['PATH_INFO'] = file_name # 请求头
# {'PATH_INFO': "/index.py}
# body = dynamic.mini_frame.application(env, self.set_response_header)
body = self.application(env, self.set_response_header)
header = "HTTP/1.1 %s\r\n" % self.status
# self.headers 框架返回的回应头
for temp in self.headers:
header += "%s:%s\r\n" % (temp[0], temp[1])
header += "\r\n"
response = header+body
new_socket.send(response.encode('utf-8'))

def set_response_header(self, status, headers):
# 框架返回状态和回应头
self.status = status
self.headers = [("server", "mini_web v1.0")]
self.headers += headers

def run_forever(self):
"""用来完成整体控制"""
# 4. 等待新客户端的链接
while True:
new_socket, client_addr = self.tcp_socket.accept()
# 5. 为这个客户端服务
p = multiprocessing.Process(target=self.serve_client, args=(new_socket,))
p.start()

new_socket.close()


def main():
if len(sys.argv) == 3:
try:
port = int(sys.argv[1]) # 7890
frame_app_name = sys.argv[2] # mini_frame:application
except Exception as err:
print("端口输入错误.")
else:
print("请按照以下方式运行:")
print("python3 xxxx.py 7890 mini_frame:application")
return

# mini_frame: application
ret = re.match(r"([^:]+):(.*)", frame_app_name)
if ret:
frame_name = ret.group(1)
app_name = ret.group(2)
else:
print("请按照以下方式运行:")
print("python3 xxxx.py 7890 mini_frame:application")
return

with open("./web_server.conf") as f:
conf_info = eval(f.read())
# 此时config_info是一个字典

sys.path.append(conf_info['dynamic_path']) # 需要将web_server.py所在的文件夹添加到查询路径中

# import frame_name 不能这样import,因为import不会将frame_name当作变量名,而是在文件夹中找frame_name.py
frame = __import__(frame_name) # 返回值标记着导入的这个模板
app = getattr(frame, app_name) # app指向了mini_frame中的application

server = WSGIserver(port, app, conf_info['static_path'])
server.run_forever()

if __name__ == '__main__':
main()

框架端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
import re
from pymysql import connect
import urllib.parse

# 创建一个空字典,用来存储路由
URL_ROUTE = {}


# 定义一个装饰器函数
def route(url):
def wrapper_func(func):
URL_ROUTE[url] = func

def call_func(*args, **kwargs):
return func(*args, **kwargs)

return call_func

return wrapper_func


@route(r'/add/(\d+).html')
def add_choose(file_name, url):
"""添加自选功能"""

# 先通过正则判断,取出url里html前面的一部分,即股票代码
ret = re.match(url, file_name)
if ret:
stock_code = ret.group(1)

db = connect(host='localhost', port=3306, user='root', password='mysql', database='stock_db', charset='utf8')
cursor = db.cursor()
# 判断该股票是否已在关注列表
sql = """select i.code from info as i inner join focus as f on i.id=f.info_id where i.code=%s;"""
cursor.execute(sql, (stock_code,))
if cursor.fetchone():
cursor.close()
db.close()
return "该股票(%s)已在自选中了,请不要重复添加" % stock_code

# 不在关注列表,则添加到关注列表中
sql = """INSERT INTO focus (info_id) select id from info where code=%s;"""
cursor.execute(sql, (stock_code,))
db.commit()
cursor.close()
db.close()

return "(%s)添加成功!" % stock_code


@route(r'/del/(\d+).html')
def del_choose(file_name, url):
"""删除自选"""
ret = re.match(url, file_name)
if ret:
stock_code = ret.group(1)

db = connect(host='localhost', port=3306, user='root', password='mysql', database='stock_db', charset='utf8')
cursor = db.cursor()
# 判断该股票是否不在关注列表
sql = """select i.code from info as i inner join focus as f on i.id=f.info_id where i.code=%s;"""
cursor.execute(sql, (stock_code,))
if not cursor.fetchone():
cursor.close()
db.close()
return "在自选中没有该股票(%s),无法删除" % stock_code

# 在关注列表,则从数据库focus中删除该记录
sql = """delete from focus where info_id=(select id from info where code=%s);"""
cursor.execute(sql, (stock_code,))
db.commit()
cursor.close()
db.close()

return "(%s)删除成功!" % stock_code


@route(r'/update/(\d+).html')
def show_update_page(file_name, url):
"""显示update页面"""
ret = re.match(url, file_name)
if ret:
stock_code = ret.group(1)

with open("./templates/update.html") as f:
content = f.read()

db = connect(host='localhost', port=3306, user='root', password='mysql', database='stock_db', charset='utf8')
cursor = db.cursor()
sql = '''select note_info from focus where info_id=(select id from info where code=%s) ;'''
cursor.execute(sql, [stock_code])
note_info = cursor.fetchone()

cursor.close()
db.close()

# 使用正则替换content中的{%code%}
content = re.sub(r"\{%code%\}", stock_code, content)
# 使用正则替换content中的{%note_info%} fetchone()取出来的是元祖,需要转换为str
content = re.sub(r"\{%note_info%\}", str(note_info[0]), content)

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)
note_info = ret.group(2)

# 浏览器会对中文进行url编码, 这里需要先解码
note_info = urllib.parse.unquote(note_info)

db = connect(host='localhost', port=3306, user='root', password='mysql', database='stock_db', charset='utf8')
cursor = db.cursor()
sql = '''UPDATE focus set note_info=%s where info_id=(select id from info where code=%s);'''
cursor.execute(sql, [note_info, stock_code])
db.commit()
cursor.close()
db.close()

return "(%s)修改成功!" % stock_code


@route('/index.html')
def index(file_name, url):
"""返回index.html的内容"""
with open("./templates/index.html") as f:
content = f.read()

# 这里替换为mysql中查询到的数据
db = connect(host='localhost', port=3306, user='root', password='mysql', database='stock_db', charset='utf8')
cursor = db.cursor()
sql = "select * from info;"
cursor.execute(sql)
info_data = cursor.fetchall()

cursor.close()
db.close()

html_template = '''
<tr>
<td>%s</td>
<td>%s</td>
<td>%s</td>
<td>%s</td>
<td>%s</td>
<td>%s</td>
<td>%s</td>
<td>%s</td>
<td>
<input type="button" value="添加" id="toAdd" name="toAdd" systemidvaule="%s">
</tr>
'''

table_content = ""
for line in info_data:
table_content += html_template % (
line[0], line[1], line[2], line[3], line[4], line[5], line[6], line[7], line[1])

# 使用正则替换content中的{%content%}
content = re.sub(r"\{%content%\}", table_content, content)
return content


@route('/center.html')
def center(file_name, url):
with open("./templates/center.html") as f:
content = f.read()

# my_stock_info = "BUG制造机"
db = connect(host='localhost', port=3306, user='root', password='mysql', database='stock_db', charset='utf8')
cursor = db.cursor()
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 on f.info_id=i.id;
'''
cursor.execute(sql)
info_data = cursor.fetchall()

cursor.close()
db.close()

html_template = '''
<tr>
<td>%s</td>
<td>%s</td>
<td>%s</td>
<td>%s</td>
<td>%s</td>
<td>%s</td>
<td>%s</td>
<td>
<a type="button" class="btn btn-default btn-xs" href="/update/%s.html"> <span class="glyphicon glyphicon-star" aria-hidden="true"></span> 修改 </a>
</td>
<td>
<input type="button" value="删除" id="toDel" name="toDel" systemidvaule="%s">
</td>
</tr>
'''

table_content = ""
for line in info_data:
table_content += html_template % (
line[0], line[1], line[2], line[3], line[4], line[5], line[6], line[0], line[0])

# 使用正则替换content中的{%content%}
content = re.sub(r"\{%content%\}", table_content, content)
return content


# environ 是从服务器接受到的包含HTTP请求信息的dict对象, start_response指向服务器中的函数 发送HTTP报文信息
def application(environ, start_response):
# start_response函数传入两个参数
# 第一个是报文信息,第二个是报文头,列表[]
start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
# env = {'PATH_INFO': "/index.py"}
file_name = environ['PATH_INFO'] # 取出"/index.py"

try:
# 把URL字典拆成两个元素,key是url,value是指向的方法
for url, call_func in URL_ROUTE.items():
# 将浏览器请求的url地址和字典里的url进行匹配, 如果成功调用方法
ret = re.match(url, file_name)
print(ret)
if ret:
# 将请求的地址和url当作参数传递给调用的函数
return call_func(file_name, url)
else:
return "没有访问的页面 ---> %s" % file_name
# return URL_ROUTE[file_name]()
except Exception as err:
return "----->>>%s" % str(err)

本文标题:mini-Web业务逻辑及其实现 / Business Logic for mini-web Frame

文章作者:Vincent Zheng

发布时间:2018年07月20日 - 19:07

最后更新:2018年07月20日 - 20:07

原始链接:https://zws910.github.io/2018/07/20/mini-web/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

0%