HTTP协议
http协议:超文本传输协议,基于tcp通信协议
作用:浏览器和服务器通讯的数据格式
URL
概念:统一资源定位符/网络资源地址/网址
组成:协议部分、域名部分、资源路径部分、查询参数部分
http请求方式: GET:没有请求体 POST:有请求体
http请求报文格式
1.请求行
2.请求头
3.空行
4.请求体
注意点: 请求报文的原始格式是有\r\n的
请求行是由三部分组成:
请求方式
请求资源路径
HTTP协议版本
GET方式的请求报文没有请求体,只有请求行、请求头、空行组成。
POST方式的请求报文可以有请求行、请求头、空行、请求体四部分组成,注意:POST方式可以允许没有请求体,但是这种格式很少见。
响应报文格式
响应行\r\n
响应头\r\n
空行\r\n
响应体\r\n
状态码 说明
200 请求成功
307 重定向
400 错误的请求,请求地址或者参数有误
404 请求资源在服务器不存在
500 服务器内部源代码出现错误
搭建一个静态的web服务器
1.编写一个TCP服务端程序
2.获取浏览器发送的http请求报文数据
3.读取固定页面数据,把页面数据组装成HTTP响应报文数据发送给浏览器。
4.HTTP响应报文数据发送完成以后,关闭服务于客户端的套接字。
代码示例:这里的服务器不能发送不同的响应,无论用户输入什么资源路径,只要ip地址和端口号正确,返回的页面都是同一个
读者可以参考我的代码,如果需要验证结果请点击页面文件夹,然后访问本地ip(127.0.0.1:端口号)。
import socket
if __name__ == '__main__':
# 1.创建socket套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2.设置端口重用
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
# 3. 绑定本地端口
server_socket.bind(("", 9090))
# 4. 设置最大监听数,由主动变为被动
server_socket.listen(2)
while True:
# 连接成功,产生一个新的套接字专门为客户端服务
new_socket_client, client_address_infor = server_socket.accept()
new_socket_client.recv(4096)
# 打开返回的文件,读取文件
with open("./static/index.html", "rb") as file:
file_data = file.read()
# 创建响应行
response_line = "HTTP/1.1 200 OK\r\n"
# 创建响应头
response_head = "Server: NBWS/1.1\r\n"
# 创建响应体
response_body = file_data
# 拼接响应数据
response_data = (response_line + response_head + "\r\n").encode() + response_body
# 发送相应数据
new_socket_client.send(response_data)
new_socket_client.close()
这里我们要升级,搭建一个随着输入不同的资源路径,返回不同的页面
代码示例:
import socket
if __name__ == '__main__':
# 创建服务器的socket套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
server_socket.bind(("", 9090))
server_socket.listen(2)
while True:
new_socket_client, client_address_info = server_socket.accept()
request_data = new_socket_client.recv(4096)
# 判断客户端是否退出链接,避免客户端断开导致服务器崩溃
if len(request_data) == 0:
new_socket_client.close()
continue
# 从客户端的请求行里面获取到资源路径
request_list = request_data.decode().split(" ", maxsplit=2)
file_path = request_list[1]
# 判断资源路径是否为空,如果为空,则返回一个错误404页面(别忘了修改状态码)
if file_path == "/":
file_path = "/index.html"
# try一下避免客户端用户访问的资源路径不存在导致程序崩溃
try:
with open("static" + file_path, "rb") as file:
file_data = file.read()
except Exception as e:
with open("static/error.html", "rb") as file:
file_data = file.read()
# 拼接响应提数据(下同)
response_line = "HTTP/1.1 404 OK\r\n"
response_head = "Server: NBWS/1.1\r\n"
response_body = file_data
response_data = (response_line + response_head + "\r\n").encode() + response_body
new_socket_client.send(response_data)
new_socket_client.close()
else:
response_line = "HTTP/1.1 200 OK\r\n"
response_head = "Server: NBWS/1.1\r\n"
response_body = file_data
response_data = (response_line + response_head + "\r\n").encode() + response_body
new_socket_client.send(response_data)
new_socket_client.close()
实现了返回不同页面的mini服务器之后,就升级实现多任务版的服务器
多任务web服务器就是能够支持多个浏览器同事访问咋们的服务器,之前我写过多任务版的tcp服务端的开发代码,其实多任务web服务器就是多任务tcp服务端里面加上了http协议,即tcp是返回数据,web服务器返回的是http超文本(二进制),若有不清楚多任务tcp开发的话,请移步至[tcp多任务编程(多线程)](www.jianshu.com/p/88b7a5372de1)
多任务mini-web服务器代码示例:
# 1.实现tcp服务器端
# 1.1 创建socket套接字
# 1.2 绑定端口
# 1.3 设置成监听模式
# 1.4 等待客户端链接
# 1.5 收发数据
# 2.接收浏览器发送过来的请求报文
# 2.1 取出资源路径
# GET /index.html HTTP/1.1\r\n ....\r\n ... \r\n
# 按照空格分割字符串
# 取出索引[1]的数据 -> file_path
# 3.打开指定文件(”static“ + file_path),返回http响应报文
# 响应行:
# HTTP/1.1 200 OK\r\n
# 响应头
# Server: NBW/1.1\r\n
# 空行
# \r\n
# 响应体
# file_data
# 4.关闭链接
import socket
from threading import Thread
def client_handler(server_socket):
new_socket_client, client_address_info = server_socket.accept()
request_data = new_socket_client.recv(4096)
# 判断客户端是否退出链接,避免客户端断开导致服务器崩溃
if len(request_data) == 0:
new_socket_client.close()
return
# 从客户端的请求行里面获取到资源路径,请求行的构成:"GET(POST) /index.html(资源路径) HTTP/1.1",这里应用split方法分割字符串两次,就可以得到我们想要的资源路径了。
request_list = request_data.decode().split(" ", maxsplit=2)
file_path = request_list[1]
# 判断资源路径是否为空,如果为空,则返回一个错误404页面(别忘了修改状态码)
if file_path == "/":
file_path = "/index.html"
# try一下避免客户端用户访问的资源路径不存在导致程序崩溃
try:
with open("static" + file_path, "rb") as file:
file_data = file.read()
except Exception as e:
with open("static/error.html", "rb") as file:
file_data = file.read()
# 拼接响应提数据(下同)
response_line = "HTTP/1.1 404 OK\r\n"
response_head = "Server: NBWS/1.1\r\n"
response_body = file_data
response_data = (response_line + response_head + "\r\n").encode() + response_body
new_socket_client.send(response_data)
new_socket_client.close()
else:
response_line = "HTTP/1.1 200 OK\r\n"
response_head = "Server: NBWS/1.1\r\n"
response_body = file_data
response_data = (response_line + response_head + "\r\n").encode() + response_body
new_socket_client.send(response_data)
new_socket_client.close()
def main():
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
server_socket.bind(("", 9090))
server_socket.listen(2)
while True: # 这里用一个死循环,就是有多少个浏览器来访问我,我就会产生多少个线程和new_socket_client来为浏览器服务
client_thread = Thread(target=client_handler, args=(server_socket,))
# 设置守护主线程,为了防止服务器不能彻底关闭
client_thread.setDaemon(True)
# 开启子线程
client_thread.start()
if __name__ == '__main__':
# 创建服务器的socket套接字
main()
读者是不是发现我这段代码跟上面的前面的差不多,那是因为我就是复制上面的代码改写的,因为学习这个是具有连贯性的,有的东西不需要我们重复去敲,当然读者想重复去敲的话那当然更好啦。毕竟写代码还是需要多敲的。
注意点:1.读者千万记得写响应体的时候一定要加上"\r\n",作者在学习的时候也因为忘记了,走了很多弯路。切记切记!!!
到这里我们的一个基本的mini-web静态服务器就搭建好了,这里用的是面向过程的编程思想,接下来就来用面向对象编程吧
同上,还是该代码,示例代码:
import socket
from threading import Thread
class WebServer:
def __init__(self):
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
server_socket.bind(("", 9090))
server_socket.listen(2)
self.server_socket = server_socket
def start(self):
while True: # 这里用一个死循环,就是有多少个浏览器来访问我,我就会产生多少个线程和new_socket_client来为浏览器服务
new_socket_client, client_address_info = self.server_socket.accept()
client_thread = Thread(target=self.client_handler, args=(new_socket_client,))
# 设置守护主线程,为了防止服务器不能彻底关闭
client_thread.setDaemon(True)
# 开启子线程
client_thread.start()
def client_handler(self, new_socket_client):
request_data = new_socket_client.recv(4096)
# 判断客户端是否退出链接,避免客户端断开导致服务器崩溃
if len(request_data) == 0:
new_socket_client.close()
return
# 从客户端的请求行里面获取到资源路径
request_list = request_data.decode().split(" ", maxsplit=2)
file_path = request_list[1]
# 判断资源路径是否为空,如果为空,则返回一个错误404页面(别忘了修改状态码)
if file_path == "/":
file_path = "/index.html"
# try一下避免客户端用户访问的资源路径不存在导致程序崩溃
try:
with open("static" + file_path, "rb") as file:
file_data = file.read()
except Exception as e:
with open("static/error.html", "rb") as file:
file_data = file.read()
# 拼接响应提数据(下同)
response_line = "HTTP/1.1 404 OK\r\n"
response_head = "Server: NBWS/1.1\r\n"
response_body = file_data
response_data = (response_line + response_head + "\r\n").encode() + response_body
new_socket_client.send(response_data)
new_socket_client.close()
else:
response_line = "HTTP/1.1 200 OK\r\n"
response_head = "Server: NBWS/1.1\r\n"
response_body = file_data
response_data = (response_line + response_head + "\r\n").encode() + response_body
new_socket_client.send(response_data)
new_socket_client.close()
if __name__ == '__main__':
# 创建服务器的socket套接字
nb_server = WebServer()
nb_server.start()
对于面向对象编程,读者一定要多多体会,多试试。
拓展:到这里我们的服务器就算搭建好了,但是还有一点要注意这里面的端口号我们不能自定义,也不能由不懂python的人来更改,如果我们的服务器上已经运行一个程序正占用着当前(9090)这个端口号的话,那么我们的服务器是开不起来的,又不能随便去关闭服务器的其他程序,这怎么办呢?接下来就来教你在命令行自定义端口号
对于sys标准库里面的argv的使用:
代码示例:
#此文件名:study_sys_argv.py windows:在命令行运行 ubuntu:在终端运行
import sys
a = sys.argv
print(a)
运行结果如下:
python@PC:~ python3 ~/Desktop/web_static_server/study_sys_argv.py a b c d
输出(终端运行的内容):['/home/python/Desktop/web_static_server/study_sys_argv.py', 'a', 'b', 'c', 'd']
这里读者可能就明白了,下来我们就把他运用到自定义端口上去。
动态自定义端口mini-web多任务服务器代码示例:
import socket
from threading import Thread
import sys
class WebServer:
def __init__(self, port):
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
server_socket.bind(("", port))
server_socket.listen(2)
self.server_socket = server_socket
def start(self):
while True: # 这里用一个死循环,就是有多少个浏览器来访问我,我就会产生多少个线程和new_socket_client来为浏览器服务
new_socket_client, client_address_info = self.server_socket.accept()
client_thread = Thread(target=self.client_handler, args=(new_socket_client,))
# 设置守护主线程,为了防止服务器不能彻底关闭
client_thread.setDaemon(True)
# 开启子线程
client_thread.start()
@staticmethod
def client_handler(new_socket_client):
request_data = new_socket_client.recv(4096)
# 判断客户端是否退出链接,避免客户端断开导致服务器崩溃
if len(request_data) == 0:
new_socket_client.close()
return
# 从客户端的请求行里面获取到资源路径
request_list = request_data.decode().split(" ", maxsplit=2)
file_path = request_list[1]
# 判断资源路径是否为空,如果为空,则返回一个错误404页面(别忘了修改状态码)
if file_path == "/":
file_path = "/index.html"
# try一下避免客户端用户访问的资源路径不存在导致程序崩溃
try:
with open("./static" + file_path, "rb") as file:
file_data = file.read()
except Exception as e:
with open("./static/error.html", "rb") as file:
file_data = file.read()
# 拼接响应提数据(下同)
response_line = "HTTP/1.1 404 OK\r\n"
response_head = "Server: NBWS/1.1\r\n"
response_body = file_data
response_data = (response_line + response_head + "\r\n").encode() + response_body
new_socket_client.send(response_data)
new_socket_client.close()
else:
response_line = "HTTP/1.1 200 OK\r\n"
response_head = "Server: NBWS/1.1\r\n"
response_body = file_data
response_data = (response_line + response_head + "\r\n").encode() + response_body
new_socket_client.send(response_data)
new_socket_client.close()
if __name__ == '__main__':
argv_list = sys.argv
str_port = argv_list[1]
port = int(str_port)
print(port)
nb_server = WebServer(port)
nb_server.start()