web实质
1 一个最简单版的web服务
web服务端是一个socket服务,浏览器是一个socket客户端,通过浏览器访问指定网页,其实就是一个socket客户端跟socket服务端的通信过程,代码如下:
# socket服务端,实现在浏览器页面上显示'hello python'
import socket
server = socket.socket()
server.bind(('127.0.0.1', 9001))
server.listen()
while True:
conn, _ = server.accept()
data = conn.recv(1024)
print(data.decode('utf-8'))
conn.send(b'HTTP/1.1 200 ok\r\n\r\nhello python') # http的返回格式+返回数据,可分开写
# conn.send(b'HTTP/1.1 200 ok\r\n\r\n') # 将http格式和数据分开写,http格式内容
# conn.send(b'hello python') # 将http格式和数据分开写,数据内容
conn.close()
server.close()
此时,我们在浏览器中输入IP地址和端口(127.0.0.1:9001)就可以看到发送的socket服务端发送过来的数据'hello python'了,截图如下:
2 访问磁盘中文件的web服务
网页中显示的内容,一般是存储在文件中,下面我们展示如何把存储在服务器上的文件显示在浏览器页面中。
通过上面的最简化版的web服务我们可以知道,要实现将文件test.txt的信息展示在浏览器页面上,只需要socket服务端读取文件test.txt内容,然后将读取到的test.txt的内容发送到浏览器即可。
文件内容如下:
# 文件名:test.txt
God helps those who help themselves.
socket服务端代码如下:
import socket
server = socket.socket()
server.bind(('127.0.0.1', 9001))
server.listen()
while True:
conn, _ = server.accept()
data = conn.recv(1024)
print(data.decode('utf-8'))
conn.send(b'HTTP/1.1 200 ok\r\n\r\n')
with open('test.txt', 'rb') as f: # 读文件
data = f.read()
conn.send(data) # 发送文件中的内容到浏览器
conn.close()
server.close()
浏览器访问后,截图如下:
3. 在浏览器中展示html文件
接下来,我们将展示如何将html文件(html文件也是个文件,因此跟上面将普通文件显示在浏览器中毫无区别)显示在浏览器中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1 style="color:red">web文件</h1>
<p style="color:green">这是一个简单的html文件,将显示在浏览器中</p>
</body>
</html>
socket服务端代码如下:
import socket
server = socket.socket()
server.bind(('127.0.0.1', 9001))
server.listen()
while True:
conn, _ = server.accept()
data = conn.recv(1024)
print(data.decode('utf-8'))
conn.send(b'HTTP/1.1 200 ok\r\n\r\n')
with open('index.html', 'rb') as f: # 只需要将文件名修改成html文件即可
data = f.read()
conn.send(data)
conn.close()
server.close()
运行代码后,打开浏览器,访问结果如下:
在上面的代码运行过程中,我们打印了浏览器请求的信息,截图如下:
4. 通过在浏览器中输入不同url,返回不同的页面内容
我们在使用浏览器访问服务器的时候,会看到不同得url路经,不同的url路经将显示不同的页面内容,下面我们将实验使用的一个test.txt文件index.html文件分别显示出来,由于将要显示不同路经下的两个文件,因此我们可以通过if判断进行区别,并将放回的页面信息封装到函数中
import socket
server = socket.socket()
server.bind(('127.0.0.1', 9001))
server.listen()
def test(conn):
with open('test.txt', 'rb') as f:
data = f.read()
conn.send(data)
conn.close()
def index(conn):
with open('index.html', 'rb') as f:
data = f.read()
conn.send(data)
conn.close()
def err():
err = b'404 not found!'
conn.send(err)
conn.close()
while True:
conn, addr = server.accept()
from_b_msg = conn.recv(1024)
str_msg = from_b_msg.decode('utf-8')
path = str_msg.split('\r\n')[0].split(' ')[1]
# 将客户端发送的请求信息进行分割,获取到浏览器访问的文件路径,再通过路径判断将用户请求页面返回
print('path>>>', path)
conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
print(from_b_msg)
if path == '/index.html':
index(conn)
elif path == '/test.txt':
test(conn)
else:
err()
访问html文件,结果如下:
访问txt文件,结果如下:
访问其他路径地址,则显示错误提示,结果如下:
5 多线程版的web框架:
import socket
from threading import Thread
server = socket.socket()
server.bind(('127.0.0.1', 9001))
server.listen()
def test(conn):
with open('test.txt', 'rb') as f:
data = f.read()
conn.send(data)
conn.close()
def index(conn):
with open('index.html', 'rb') as f:
data = f.read()
conn.send(data)
conn.close()
def err():
err = b'404 not found!'
conn.send(err)
conn.close()
while True:
conn, addr = server.accept()
from_b_msg = conn.recv(1024)
str_msg = from_b_msg.decode('utf-8')
path = str_msg.split('\r\n')[0].split(' ')[1]
print('path>>>', path)
conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
print(str_msg)
if path == '/index.html':
t = Thread(target=index, args=(conn,))
t.start()
elif path == '/test.txt':
t = Thread(target=test, args=(conn,))
t.start()
else:
err()
上面的代码虽然能实现功能,但是,如果有成百上千的文件,是否要写成百上千个if判断呢?因此代码还可以进行如下的优化
import socket
from threading import Thread
server = socket.socket()
server.bind(('127.0.0.1', 9001))
server.listen()
def test(conn):
with open('test.txt', 'rb') as f:
data = f.read()
conn.send(data)
conn.close()
def index(conn):
with open('index.html', 'rb') as f:
data = f.read()
conn.send(data)
conn.close()
path_list = [
('/index.html', index),
('/test.txt', test)
]
def fun(path, conn):
for url in path_list:
if path == url[0]:
t = Thread(target=url[1], args=(conn,))
t.start()
while True:
conn, _ = server.accept()
from_b_msg = conn.recv(1024)
str_msg = from_b_msg.decode('utf-8')
path = str_msg.split('\r\n')[0].split(' ')[1]
print('path>>>', path)
conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
print(str_msg)
fun(path, conn)
经过这样的优化后,以后有新的文件,只需要将文件信息添加到path_list列表中了,不用再写if判断了。
6 展示动态web页面
该案例中,我们将在浏览器中显示当前时间,用户不断刷新浏览器,浏览器将自动更新当前时间
import socket, time
from threading import Thread
server = socket.socket()
server.bind(('127.0.0.1', 9001))
server.listen()
def test(conn):
with open('test.txt', 'rb') as f:
data = f.read()
conn.send(data)
conn.close()
def index(conn):
time_flag = str(time.strftime("%Y-%m-%d %H:%M:%S")) # 先将当前时间拿到
with open('index.html', 'r', encoding="utf8") as f:
data = f.read()
data = data.replace('web文件', '北京实时时间') # 将html的文件内容做替换
data = data.replace('这是一个简单的html文件,将显示在浏览器中', time_flag).encode('utf-8') # 将html的文件内容做替换,显示我们指定的内容
conn.send(data)
conn.close()
path_list = [
('/index.html', index),
('/test.txt', test)
]
def fun(path, conn):
for url in path_list:
if path == url[0]:
t = Thread(target=url[1], args=(conn,))
t.start()
while True:
conn, _ = server.accept()
from_b_msg = conn.recv(1024)
str_msg = from_b_msg.decode('utf-8')
path = str_msg.split('\r\n')[0].split(' ')[1]
print('path>>>', path)
conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
print(str_msg)
fun(path, conn)
运行代码后,刷新页面将显示当前实时时间
再次刷新,页面如下:
7 从数据库读取数据并返回到浏览器
接下来的案例,我们将展示,连接MySQL数据库,将MySQL数据库中的信息展示到浏览器页面上
首先连接到数据库,获取到test数据库userinfo表中的内容
# 连接数据库文件 connectDB.py
import pymysql
def show_db():
db = pymysql.connect(host='127.0.0.1',
user='root',
password="2048",
database='test',
port=13306)
cursor = db.cursor(pymysql.cursors.DictCursor)
sql = 'select name,age from userinfo;'
cursor.execute(sql)
db_data = cursor.fetchall() # 获取到test数据库中userinfo表中的name和age的数据
cursor.close()
db.close()
content_list = [] # 定义一个列表,用于存放我们修改过后的表数据
for row in db_data: # 将userinfo表中的每一行内容取到
tp = '<tr><td>%s</td><td>%s</td></tr>' % (row['name'], row['age']) # 将userinfo表中的name和age字段替换成html指定的显示格式
content_list.append(tp)
content = ''.join(content_list)
return content
新建一个用于用户访问的html文件模板,只定义表头信息(如果不定义表头的话,也可以使用特定字符串,再socket服务端将这些字符串替换成将要显示的表头信息即可,这里为了容易理解,因此将表头信息定义出来),表数据信息用特殊字符串4个'@'代替,便于socket客户端替换
# 浏览器访问的页面文件index.html
<!DOCTYPE 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>Title</title>
</head>
<body>
<table border="1">
<thead>
<tr>
<td>姓名</td>
<td>年龄</td>
</tr>
</thead>
<tbody>
@@@@
</tbody>
</table>
</body>
</html>
socket客户端需要做三件事:
- 连接到数据库,拿到数据库中userinfo表的所有信息
- 用数据库中表的信息替换掉html模板中的信息
- 将替换掉的数据返回给浏览器
# socket服务端
import socket
from threading import Thread
from connectDB import show_db
server = socket.socket()
server.bind(('127.0.0.1', 9001))
server.listen()
def test(conn):
with open('test.txt', 'rb') as f:
data = f.read()
conn.send(data)
conn.close()
def index(conn):
userinfo_data = show_db()
with open('db.html', 'r', encoding="utf8") as f:
data = f.read()
data = data.replace('@@@@', userinfo_data).encode('utf-8')
conn.send(data)
conn.close()
path_list = [
('/index.html', index),
('/test.txt', test)
]
def fun(path, conn):
for url in path_list:
if path == url[0]:
t = Thread(target=url[1], args=(conn,))
t.start()
while True:
conn, _ = server.accept()
from_b_msg = conn.recv(1024)
str_msg = from_b_msg.decode('utf-8')
path = str_msg.split('\r\n')[0].split(' ')[1]
print('path>>>', path)
conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
print(str_msg)
fun(path, conn)
访问浏览器,截图如下:
修改数据库数据数据
重新访问浏览器
8 使用jinja2模板渲染的web框架案例
上文中从数据库中取出数据,然后通过字符串替换,再显示到浏览器页面上,字符串替换的过程可以使用现成的模块jinja2来完成,代码如下。
from wsgiref.simple_server import make_server
from jinja2 import Template
import pymysql
def show_db():
db = pymysql.connect(host='127.0.0.1',
user='root',
password="2048",
database='test',
port=13306)
cursor = db.cursor(pymysql.cursors.DictCursor)
sql = 'select name,age from userinfo;'
cursor.execute(sql)
db_data = cursor.fetchall()
cursor.close()
db.close()
return db_data
def index():
userinfo_data = show_db()
print(userinfo_data)
with open('JINJA2.html', 'r', encoding="utf-8") as f:
data = f.read()
template = Template(data)
print(template)
data = template.render({'user_list': userinfo_data})
return [bytes(data, encoding="utf8"), ]
path_list = [
('/index.html', index),
]
def run_server(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
url = environ['PATH_INFO']
func = None
for item in path_list:
if item[0] == url:
func = item[1]
break
if func:
return func()
else:
return [bytes("404 not found", encoding="utf8"), ]
if __name__ == '__main__':
httpd = make_server('', 9001, run_server)
print("Serving HTTP on port 9001...")
httpd.serve_forever()
html模板文件如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table border="1">
<thead>
<tr>
<th>name</th>
<th>age</th>
</tr>
</thead>
<tbody>
{% for i in user_list %}
<tr>
<td>{{ i['name'] }}</td>
<td>{{ i['age'] }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
浏览器访问后,显示内容如下: