Python实现简单Web服务器及模拟浏览器
一、基础知识简介
(1)ip地址和域名
- 域名(Domain name):可以理解为一个网址,一个特殊的名字。
- 意义:IP不方便记忆,于是有了域名。
- 构成:字母、数字、中划线(.),长度小于等于255个字符。
下面是一些常用域名后缀及其含义:
域名 | 含义 |
---|---|
.com (商业/公司) | 表示商业、公司、企业。.com是目前国际较广泛流行的通用域名格式,现全球用户超过1.1亿个。 |
.net(网络) | .net 域名诞生于上世纪80年代。.net 域名一般用于从事 Internet 相关的网络服务的机构或公司。 |
.org(组织) | .org 域名是全球第三大国际顶级域名,其历史可以追溯到 1985 年,当时最先引入了 .org 域名和其他六个域名(.com,.us,.edu,.gov,.mil 和 .net),意思是 Organizations(组织) |
.gov(政府) | .gov.XX:由于 .gov 域名是美国政府专用域,其他国家的政府无法注册和使用 .gov 域名,所以其他国家使用 .gov+自己的国别/地区域 的方式作为权威政府域名。 |
.edu(教育) | .edu(education,教育)域名主要供教育机构使用,如大学等院校使用。 |
.cn(中国国别域名) | .cn 域名属于国家地区顶级域名,CN 是 ISO Code,代表中国。 |
(2)DNS服务器
-
DNS(域名解析系统)用于将域名转成对应的IP地址,DNS是一台运行在互联网上的服务器。
-
浏览器访问DNS服务器的过程:
简单来说就是浏览器拿着域名去找DNS服务器,然后DNS会返回给浏览器该域名对应的IP,浏览器再拿着IP去进行访问。
所以浏览器真正访问的对象是IP地址。
但是浏览器在访问DNS之前会首先访问本地DNS缓存,也就是hosts文件,如果没有找到对应的IP,浏览器才会访问DNS。
(3)HTTP协议
- 简介:
超文本传输协议(Hypertext[ Transfer Protocol](https://baike.baidu.com/item/ Transfer Protocol/612755?fromModule=lemma_inlink),HTTP)是一个简单的请求-响应协议,它通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。请求和响应消息的头以ASCII形式给出;而消息内容则具有一个类似MIME的格式。超文本传输协议是一种用于分布式、协作式和超媒体信息系统的应用层协议,是万维网WWW(World Wide Web)的数据通信的基础。
2.工作原理:
HTTP是基于客户/服务器模式,且面向连接的。典型的HTTP事务处理有如下的过程:
(1)客户与服务器建立连接;
(2)客户向服务器提出请求;
(3)服务器接受请求,并根据请求返回相应的文件作为应答;
(4)客户与服务器关闭连接。
具体如下图所示
(4)HTTP协议格式查看
HTTP 协议格式分为请求格式和响应格式
请求报文格式如下:
请求行、请求头、请求空行、请求主体
- 请求行包括:请求方式、请求路径、请求版本
- 请求头包括:host,accept-encoding,cookie等
- 只有在post方式才有请求主体
我们还要先讨论一下请求路径的格式,因为这将涉及到后面资源路径的解析
请求行如下:
GET / HTTP/1.1
GET是请求方式
/ 是请求路径,这里是根目录。根目录是指web服务器所在的目录。
比如 /a/b/index.html 这个路径是指根目录–a文件夹–b文件夹–index.html 。那么我们的任务就是根据这个路径找到相应的资源文件。
响应报文格式如下:
响应行、响应头、响应空行、响应主体
- 响应行包括:协议及版本、响应状态码、响应状态描述
- 响应头包括(协议名:值):server,date等
以上就是相应的基础知识简介。
二、模拟浏览器实现
import socket # 创建套接字
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("一个域名", 相应的端口号比如8080)) #建立TCP连接
request_line = 'GET / HTTP/1.1\r\n' # 请求行
request_header = "" # 这里的请求头要根据实际情况而定
request_space = '\r\n' # 请求空行
send_data = request_line + request_header + request_space # 拼接请求报文
s.send(send_data.encode('utf-8'))
recv_data = s.recv(1024 ** 3) # 接收响应内容
print(recv_data.decode('utf-8'))
就这样,一个简单的浏览器就完成了。虽然很简单,但它已经具备了浏览器最核心的功能。接下来就让我们看看Web服务器的搭建吧。
三、简单Web服务器的搭建
Web服务器要完成以下几个简单的功能:
-
对请求做出响应
-
返回固定页面
-
返回指定页面
以上三个功能的核心思路就是:建立连接,拼接报文,截取请求信息
下面先介绍两个功能模块
"""
该方法由于拼接响应报文
"""
def application(response_body, response_line):
response_header = "Server: Python_Flask_lei\r\nContent-Type: text/html\r\n" # 响应头
response_space = "\r\n" # 响应空行
response_data = response_line + response_header + response_space + response_body # 整个响应报文,其中响应体需要定制
return response_data
"""
下面的方法用于解析路径
"""
def make_response_body(request_data):
global response_line
request_text = request_data.decode()
# 以下都在解析请求路径
loc = request_text.find("\r\n") # 找到请求行的位置
request_line = request_text[:loc] # 得到请求行
request_line_list = request_line.split(" ")
request_path = request_line_list[1]
try:
if request_path == "/": # 如果请求路径是根目录的话,就返回首个网页
file_path = "D:/pythonProject/基础web服务器框架/render.html" # 这是我的首个网页所在的路径
else:
file_path = "D:/pythonProject/基础web服务器框架" + request_path # 如果不是的话,就找到相应路径
with open(file_path, "r", encoding="utf-8", errors="ignore") as file: # 根据请求路径打开相应的文件,将文件内容作为请求体
response_body = file.read()
response_line = "HTTP/1.1 200 OK\r\n" # 这里是正常的响应行
except Exception as e:
response_line = "HTTP/1.1 404 Not Found\r\n" # 这是异常响应行
return response_body, response_line # 返回响应体,响应行
下面我们正式介绍web服务器的框架代码
import socket # 创建套接字
from application.app_1 import application, make_response_body # 导入两个功能模块
class Server(object): # 定义服务器类
def __init__(self, port, host=""): # 属性初始化
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server_socket.bind((host, port))
self.server_socket.listen(128)
def __reqeust_handler(self, client_socket): # 处理请求报文
request_data = client_socket.recv(1024 ** 3)
if not request_data:
client_socket.close()
print(f"客户端{client_socket}已下线")
return
return request_data
def __send(self, client_socket, request_data): # 发送响应报文
response_body, response_line = make_response_body(request_data)
message = application(response_body, response_line).encode("utf-8")
client_socket.send(message)
def start(self): # 定义启动函数
while True:
(client_socket, address) = self.server_socket.accept()
print("新客户端来了:", address)
request_data = self.__reqeust_handler(client_socket)
self.__send(client_socket, request_data)
def main(): # 定义主函数
server = Server(8080)
server.start()
if __name__ == '__main__':
main()
注意:
笔者在进行操作的时候发现了一个小bug,就是在响应浏览器时,浏览器会默认发起 Favicon.ico 请求,这是一个图标生成器,用于快捷标记网页。但如果我们的根目录下没有ico图表的话就会影响正常路径的解析,从而报错。这个请求过滤起来比较难,所以我们可以在根目录下保存一个ico图标,这样问题就迎刃而解。
好了,以上就是这篇文章的全部内容,如果对你有所帮助的话请点个赞吧。新人创作,实属不易,请多多支持!