21天挑战学习-Day10http协议简单web服务器的实现

发布于:2023-01-20 ⋅ 阅读:(5) ⋅ 点赞:(0) ⋅ 评论:(0)


活动地址:CSDN21天学习挑战赛

学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。各位小伙伴,如果您:
想系统/深入学习某技术知识点…
一个人摸索学习很难坚持,想组团高效学习…
想写博客但无从下手,急需写作干货注入能量…
热爱写作,愿意让自己成为更好的人…

目录

一、http协议

 二、简单的web服务器的实现

1.简单的http服务器(实现基本应答)

结果

 解析

总结

 2.tcp三次握手四次挥手

前言

 问题

解决

 3.解决服务器端口占用问题

 总结

4.返回一个页面

结果

解析

 总结

 5.使浏览器可以互动式请求

部分结果截屏 ​编辑

 代码讲解

 总结

6.关于解码方式

 三、预告


一、http协议

http协议:超文本传输协议,基于tcp的协议 ,协议即规范

 浏览器------>服务器发送的请求的格式如下:

GET / HTTP/1.1 # 表示用户通过浏览器问你要什么 GET /xxx.html HTTP/1.1

Host: 127.0.0.1:8080  # 字符串,表示服务器的ip以及端口
# User-Agent:表示客户端浏览器的版本(标记普通用户和爬虫)---返爬虫
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0 # Accept表示浏览器可以接受什么样的格式
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 

# 可以接收的语言
Accept-Encoding: gzip, deflate, br  # 可以接收的压缩格式
DNT: 1
Connection: keep-alive  # 表示长链接
Upgrade-Insecure-Requests: 1  #

 

  服务器------>浏览器发送的请求的格式如下:

header部分:

# "HTTP/1.1"---服务器版本 "200 OK"---即200的意思为ok服务器找了你要的资源发现有就会200ok
# 第一部分请求头header告诉浏览器要设置的相关信息
HTTP/1.1 200 OK  
Cache-Control: no-cache  # 你的缓存是私有的还是
Connection: keep-alive
Content-Encoding: gzip  # 压缩编码格式
Content-Type: text/html;charset=utf-8
Coremonitorno: 0
Date: Fri, 08 Jul 2022 06:11:38 GMT  # 服务器当前的时间
Server: apache  # 百度服务器的简称
# 网站通过cookie追踪用户来进行自定义推荐
Set-Cookie: ...

body部分:

<!DOCTYPE html><!--STATUS OK--><html><head><meta http-equiv="Content-Type"
.....

补充:如何判断哪个是header哪个是body?

服务器给浏览器的响应=header+\n+body

只要空行前面的就是header后面的就是body部分

 二、简单的web服务器的实现

1.简单的http服务器(实现基本应答)

import socket


def service_client(new_socket):
    """为这个客户端服务数据"""
    # 接收浏览器发送过来的请求,即http请求
    # GET / HTTP1.1
    request = new_socket.recv(1024)
    print("对方发来的请求数据为", request)
    # 返回http格式的数据给浏览器
    # 准备发送的数据header
    response = "HTTP/1.1 200 OK\r\n"  # 正规的浏览器解析数据换行\r\n
    response += "\r\n"  # 一个空行
    # 准备发送的数据body
    body = "ni hao "
    response += body
    new_socket.send(response.encode("utf-8"))
    new_socket.close()
    print("对其服务结束")


def main():
    """用来完成整体控制"""
    # 1.创建套接字
    tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 2.绑定本地ip及port
    tcp_server.bind(("", 7890))
    # 3.设置为listen模式
    tcp_server.listen(128)
    print("正在监听")
    while True:
        # 4.等待新客户端的链接
        new_socket, client_address = tcp_server.accept()
        print(client_address, "来了")
        # 5.为这个客户端服务
        service_client(new_socket)


if __name__ == "__main__":
    main()

 

 

结果

正在监听
('127.0.0.1', 58637) 来了
对方发来的请求数据为 b'GET / HTTP/1.1\r\nHost: 127.0.0.1:7890\r\nConnection: keep-alive\r\nsec-ch-ua: ".Not/A)Brand";v="99", "Google Chrome";v="103", "Chromium";v="103"\r\nsec-ch-ua-mobile: ?0\r\nsec-ch-ua-platform: "Windows"\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\nSec-Fetch-Site: none\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-User: ?1\r\nSec-Fetch-Dest: document\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\n\r\n'
对其服务结束
('127.0.0.1', 58638) 来了
对方发来的请求数据为

b'GET /favicon.ico HTTP/1.1

Host: 127.0.0.1:7890

Connection: keep-alive

sec-ch-ua: ".Not/A)Brand";v="99", "Google Chrome";v="103", "Chromium";v="103"

sec-ch-ua-mobile: ?0

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36

sec-ch-ua-platform: "Windows"

Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8\r\nSec-Fetch-Site: same-origin

Sec-Fetch-Mode: no-cors

Sec-Fetch-Dest: image

Referer: http://127.0.0.1:7890/

Accept-Encoding: gzip, deflate, br

Accept-Language: zh-CN,zh;q=0.9\r\n\r\n'
对其服务结束

...

 解析

对于基本的网络通信内容请先转至:关于网络编程的总结_.GRIT.的博客-CSDN博客_网络编程总结

 

总结

不难发现本段代码只实现了无论你浏览器对你的请求信息如何我都只回应"ni hao"

 2.tcp三次握手四次挥手

前言

因为如果想真正弄明白要花很多篇幅而本次主要内容与其并无很大关系,故直接将有关部分

 问题

tcp的四次挥手导致先调用close()的要停留资源2-5分钟才可以解除该端口资源的占用。
对于客户端而言没有绑定端口,所以如果是客户端先close()导致其2-5分钟资源停留,电脑会自动换一个端口。

解决

但是服务器就不行了,怎么解决?
tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

 3.解决服务器端口占用问题

import socket


def service_client(new_socket):

    """为这个客户端服务数据"""
    # 接收浏览器发送过来的请求,即http请求
    # GET / HTTP1.1
    request = new_socket.recv(1024)
    print("对方发来的请求数据为", request)
    # 返回http格式的数据给浏览器
    # 准备发送的数据header
    response = "HTTP/1.1 200 OK\r\n"  # 正规的浏览器解析数据换行\r\n
    response += "\r\n"
    # 准备发送的数据body
    body = input("请输入你想要回的数据")
    response += body
    new_socket.send(response.encode("utf-8"))
    new_socket.close()
    print("对其服务结束")


def main():
    """用来完成整体控制"""
    # 创建套接字
    tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 设置当服务器先close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即绑定7788端口
    tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 绑定本地ip及port
    tcp_server.bind(("", 7890))
    # 设置为listen模式
    tcp_server.listen(128)
    print("正在监听")
    while True:
        # 等待新客户端的链接
        new_socket, client_address = tcp_server.accept()
        print(client_address, "来了")
        # 为这个客户端服务
        service_client(new_socket)


if __name__ == "__main__":
    main()
# 设置当服务器先close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即绑定7788端口
# tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

 总结

相比于上一段代码本次解决了:服务器因为tcp三次握手四次挥手而照成的端口资源2-5分钟占用而不释放问题,设置当服务器先close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时可以立即绑定7788端口,而不会报错
tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

4.返回一个页面

import socket


def service_client(new_socket):
    """为这个客户端服务数据"""
    # 接收浏览器发送过来的请求,即http请求
    # GET / HTTP1.1
    request = new_socket.recv(1024)
    print("对方发来的请求数据为", request)
    # 返回http格式的数据给浏览器
    # 准备发送的数据header
    response = "HTTP/1.1 200 OK\r\n"  # 正规的浏览器解析数据换行\r\n
    response += "\r\n"
    # 准备发送的数据body
    file = open("./html/index.html", "rb")
    html_body = file.read()
    file.close()
    new_socket.send(response.encode("utf-8"))
    new_socket.send(html_body)
    print("对其服务结束")


def main():
    """用来完成整体控制"""
    # 1.创建套接字
    tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 2.绑定本地ip及port
    tcp_server.bind(("", 7899))
    # 3.设置为listen模式
    tcp_server.listen(128)
    print("正在监听")
    while True:
        # 4.等待新客户端的链接
        new_socket, client_address = tcp_server.accept()
        print("")
        print(">>"*10, client_address, "来了")
        # 5.为这个客户端服务
        service_client(new_socket)


if __name__ == "__main__":
    main()

结果

解析

本次返回的是一个页面,但是为什么左上角一直转圈?

 总结

解决了发送一个页面的需求但是如何实现发送多项内容使浏览器可以页面互动

 5.使浏览器可以互动式请求

import socket
import re
import urllib.parse


def service_client(new_socket):
    """为这个客户端服务数据"""
    # 接收浏览器发送过来的请求,即http请求
    # GET / HTTP1.1
    request = new_socket.recv(1024)

    # print("对方发来的请求数据为", request)
    request_lines = request.splitlines()
    x = urllib.parse.unquote(request_lines[0])
    # GET /index.html HTTP/1.1
    try:
        ret = re.match(r"[^/]+/([^ ]+) ", x)
        file_name = ret.group(1)
        count = file_name.find("?")
        # if file_name[-8:-1] == "?v=4.1.":
        #     file_name = file_name[:-8]
        if count != -1:
            file_name = file_name[:count]
        print("经正则处理请求的数据为", file_name)

    except:
        file_name = "index.html"
        print("正则无效已强制匹配")
    # 返回http格式的数据给浏览器
    # 准备发送的数据header
    response = "HTTP/1.1 200 OK\r\n"  # 正规的浏览器解析数据换行\r\n
    response += "\r\n"
    # 准备发送的数据body
    with open("./html/"+file_name, "rb") as file:
        html_body = file.read()
        print("数据成功取出")
    new_socket.send(response.encode("utf-8"))
    new_socket.send(html_body)
    print("对其服务结束")


def main():
    """用来完成整体控制"""
    # 1.创建套接字
    tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 2.绑定本地ip及port
    tcp_server.bind(("", 7890))
    # 3.设置为listen模式
    tcp_server.listen(128)
    print("正在监听")
    while True:
        # 4.等待新客户端的链接
        new_socket, client_address = tcp_server.accept()
        print("")
        print("")
        print(">"*40, client_address, "来了")
        # 5.为这个客户端服务
        service_client(new_socket)


if __name__ == "__main__":
    main()

部分结果截屏

 代码讲解

 

 

 总结

本代码的主要思想是因为浏览器发过来的请求GET /(请求文件的路径和名字) HTTP/1.1

所以就直接通过正则表达式将路径部分提取出来并进行针对的发送。

6.关于解码方式

 关于网页“%xxxxx的如何进行解码?”

import urllib.parse
a = "Cannot POST /%E5%B0%B1%E4%B8%9A%E7%8F%AD-%E5%89%8D%E7%AB%AF/3.%E8%A1%A8%E5%8D%95/02%E8%A1%A8%E5%8D%95%E6%8F%90%E4%BA%A4.html"
w = urllib.parse.unquote(a)
print(w)  # 02day/04-pycharm的集成vim.html

导入 urllib.parse
编码 urllib.parse.quote()
解码 urllib.parse.unquote()

 三、预告

本次主要是实现了让浏览器访问本地端口,python实现了服务端对浏览器的服务。但是服务器并不是多任务的,所以明天将把多任务应用到服务器里面,同时补充一下相关内容。


网站公告

欢迎关注微信公众号

今日签到

点亮在社区的每一天
签到