web应用本质、HTTP协议、初级的web框架实现(1-3)
web应用本质、HTTP协议、初级的web框架实现(1-3)
我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端,基于请求做出响应,客户都先请求,服务端做出对应的响应,按照http协议的请求协议发送请求,服务端按照http协议的响应协议来响应请求,这样的网络通信,我们就可以自己实现Web框架了。
关于HTTP协议的内容可以看这几篇博客:
[HTTP协议web开发知识点](https://www.cnblogs.com/clschao/articles/9230431.html/" HTTP1")
[白话http和代码交互的那点事](https://www.cnblogs.com/Eva-J/p/6656954.html/" HTTP2")
[关于HTTP协议,一篇就够了](https://www.cnblogs.com/ranyonsue/p/5984001.html/" HTTP3")
1、简单的带并发效果的web服务端程序
代码如下:
import socket
from threading import Thread
def run_server(conn):
msg = conn.recv(65105).decode('utf-8')
print(msg)
#需要先根据协议向浏览器发送响应的内容
conn.sendall(b'HTTP/1.1 200 OK \r\n\r\n')
conn.sendall(b'Hello World')
conn.close()
if __name__ == '__main__':
server = socket.socket()
#设置端口重复利用
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(('127.0.0.1',8990))
server.listen()
#建立连接循环
#由于HTTP是无连接的协议,因此这里必须加连接循环
while 1:
conn,_ = server.accept()
t = Thread(target=run_server,args=(conn,))
t.start()
然后,我们在浏览器冲输入网址:1270.0.0.1:8990
就可以看到server端发送的信息‘Hello World’了
这里解释一下我们给浏览器发送Hello World之前:conn.sendall(b'HTTP/1.1 200 OK \r\n\r\n')
的意思:
socket是应用层和传输层之间的抽象层,每次都有协议,协议就是消息格式,那么传输层的消息格式我们不用管,因为socket帮我们搞定了,但是应用层的协议还是需要咱们自己遵守的,所以再给浏览器发送消息的时候,如果没有按照应用层的消息格式来写,那么你返回给浏览器的信息,浏览器是没法识别的。而应用层的协议就是我们的HTTP协议,所以我们按照HTTP协议规定的消息格式来给浏览器返回消息就没有问题了,关于HTTP看上面的链接即可。
其实这句话也可以合在一起写:
conn.sendall(b'HTTP/1.1 200 OK \r\n\r\nHelloWorld')
浏览器的请求过程:
看一下浏览器的请求过程
目前我们还没有写如何返回一个html文件给浏览器,所以这里暂时不用管它,那么我们点开这个127.0.0.1看看:
我们在python文件中打印一下浏览器发送过来的请求信息是啥:
重启我们的代码,然后在网址中输入这个:
再重启我们的代码,然后在网址中输入这个:
浏览器发过来一堆的消息,我们给浏览器回复(响应)信息的时候,也要按照一个消息格式来写,这些都是http协议规定的,关于http协议可以返回上面看~
2、返回HTML文件的web框架
准备工作:新建一个html文件:index.html,从本地找到一个图片1.jpg与一个图标文件favicon.ico,index.html文件的内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--引入本地的ico文件-->
<link rel="=icon" href="favicon.ico">
<style>
.content{color:red;}
.d1{width:123px;height:123px;}
img{width:100%;height:100%;}
</style>
</head>
<body>
<h1 class="content">你好!世界!</h1>
<div class="d1">
<!--引入本地的图片-->
<img src="1.jpg" alt="">
</div>
<script>
alert('你好世界!')
</script>
</body>
</html>
server端的代码如下:
import socket
from threading import Thread
def run_server(conn):
msg = conn.recv(65105).decode('utf-8')
print(msg)
#需要先根据协议向浏览器发送响应的内容
conn.sendall(b'HTTP/1.1 200 OK \r\n\r\n')
with open('index.html','rb')as f:
data = f.read()
conn.sendall(data)
conn.close()
if __name__ == '__main__':
server = socket.socket()
#设置端口重复利用
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(('127.0.0.1',8991))
server.listen()
#建立连接循环
#由于HTTP是无连接的协议,因此这里必须加连接循环
while 1:
conn,_ = server.accept()
t = Thread(target=run_server,args=(conn,))
t.start()
可以看到,server端是将html文件返回给了浏览器,然后我们在浏览器中输入127.0.0.1:8991
看一下结果:
你会发现:alert弹窗与h1标签的效果都有,但是网页中没有显示图片与ico图标!
这是因为:弹窗与h1标签的效果我们随着html文件发送给了浏览器,但是图片与图标还在server本地,并没有发送给浏览器,浏览器渲染不出来!
如果我们把js效果与css都写在外部的文件中而没有发送给浏览器的话,浏览器是无法渲染出相应的效果的,类似的情况如下:
在下来我们在network里面点击那个test.css文件,看看请求是什么:
还有就是当我们直接在浏览器上保存某个页面的时候,随便一个页面,我们到页面上点击右键另存为,然后存到本地的一个目录下,你会发现这个页面的html、css、js、图片等文件都跟着保存下来了,我保存了一下博客园首页的页面,看,是一个文件夹和一个html文件:
我们点开博客园那个文件夹看看里面都有什么:
发现js、css还有图片什么的都被保存了下来,说明什么?
说明这些文件本身就存在浏览器上了,哦,原来就是将html页面需要的css、js、图片等文件也发送给浏览器就可以了,并且这些静态文件都是浏览器单独过来请求的,其实和标签的属性有有关系,css文件是link标签的href属性:<link rel="stylesheet" href="test.css">,js文件是script标签的src属性:<script src="test.js"></script>,图片文件是img标签的src属性:<img src="meinv.png" alt="" width="100" height="100"> ,那个.ico文件是link标签的属性:<link rel="icon" href="wechat.ico">,其实这些属性都会在页面加载的时候,单独到自己对应的属性值里面取请求对应的文件数据,而且我们如果在值里面写的都是自己本地的路径,那么都会来自己的本地路径来找,如果我们写的是相对路径,就会到我们自己的网址+文件名称,这个路径来找它需要的文件,所以我们只需要将这些请求做一些响应,将对应的文件数据相应给浏览器就可以了!并且我们通过前面的查看,能够发现,浏览器url的请求路径我们知道是什么,静态文件不是也这样请求的吗,好,我们针对不同的路径给它返回不同的文件,非常好!我们来尝试一下!
3、返回静态文件的高级web框架
既然浏览器可以根据link标签的href
、img标签的src
、script标签的src
后面的值向服务器端请求对应的文件,那我们完全可以根据这些请求信息将对应为文件发送给浏览器,这样浏览器拿到我们发给它的文件后进行渲染,就可以展现出对应的效果了:
import socket
from threading import Thread
def run_server(conn):
msg = conn.recv(65105).decode('utf-8')
# print(msg)
# 通过http协议我们知道,浏览器请求的时候,有一个请求内容的路径,
# 通过对请求信息的分析,这个路径我们在请求的所有请求信息中可以提炼出来,下面的path就是我们提炼出来的路径
path = msg.split('\r\n\r\n')[0].split()[1]
print('path>>>:',path)
#需要先根据协议向浏览器发送响应的内容
conn.sendall(b'HTTP/1.1 200 OK \r\n\r\n')
#根据不同的路径返回响应的内容
#返回html文件
if path == '/':
with open('index.html','rb')as f:
data = f.read()
conn.sendall(data)
conn.close()
#返回ico文件——是固定的
#注意图标必须叫favicon.ico
if path == '/favicon.ico':
with open('favicon.ico','rb')as f:
data = f.read()
conn.sendall(data)
conn.close()
#返回图片文件
if path == '/1.jpg':
with open('1.jpg','rb')as f:
data = f.read()
conn.sendall(data)
conn.close()
if __name__ == '__main__':
server = socket.socket()
#设置端口重复利用
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(('127.0.0.1',8991))
server.listen()
#建立连接循环
#首先浏览器相当于给我们发送了多个请求,一个是请求我们的html文件,
#而我们的html文件里面的引入文件的标签又给我们这个网站发送了请求静态文件的请求,
#所以我们要将建立连接的过程循环起来,才能接受多个请求
while 1:
conn,_ = server.accept()
t = Thread(target=run_server,args=(conn,))
t.start()