IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 从0实现web server,part1 -> 正文阅读

[网络协议]从0实现web server,part1

原教程 https://defn.io/2018/02/25/web-app-from-scratch-01/

HTTP协议是怎么工作的呢?

(这个是不是看一下TCP/IP协议这本书会比较好?)

  • HTTP客户端通过网络连接到HTTP服务器,并发送一系列的请求,服务器解释了这些请求之后向客户端做出回应。RFC2616文档有具体过程(但是我并不想看)

request格式(客户端的请求)

HTTP method(比如 get、post) + 路径 +HTTP协议版本+回车键或者换行符\n\r诸如此类
比如: GET /some-path HTTP/1.1\r\n
request后面可以跟很多header,每一句header都要跟着他的名字,大概长这样:

            Host: example.com\r\n
            Accept: text/html\r\n
			 \r\n 

最后结尾的地方要跟着\r\n (我们平时在写的时候 回车就是\n但是在html里回车是\r\n,所以要转义一下)

举个例子:(header name有多少种,分别是什么意思呢?)
在这里插入图片描述

Response格式(服务器的回应)

第一行是状态栏: http协议版本+response status code
比如:HTTP/1.1 200 OK\r\n
后面跟着header line(具体格式和request里面的一样)
header line之后跟一个空的行 ,然后才是具体的回应主体
打个栗子:
在这里插入图片描述

实现一个简单的server,可以读取服务器上的文件

先知道socket是什么东西

具体看csapp中的网络编程
这个网址讲的也蛮清楚的:https://www.ibm.com/docs/en/i/7.3?topic=programming-how-sockets-work

典型的socket连接图:
在这里插入图片描述

  1. 服务端要先创建或者绑定一个地址(port)这样客户端就可以找到服务器想要发送数据的那个门

  2. 当绑定好地址之后,服务端等待客户端

  3. 当客户端也绑定好这个地址之后,就建立了连接,可以进行数据交换了

具体代码实现过程

预先定义部分:
step1:首先要定义一些server会发送给client的response
这里要注意的是http的response回应长什么样(见上)

step2:再定义一个Request类,这个函数用于读取由client传来的request
这里可以调用一个读行的函数,将client传过来的request逐行读取

step3:再将原本的request转化成所需的request类,其中包括(method,path,headers)

step4:定义一个文件服务函数,如果client访问的路径是正确的,将传送文件的response发送给client,并且将文件传送过去(在这个例子当中是用html充当本地文件)

step5:现在可以server创建socket通信了,先绑定已经定义好的地址和端口了;对这个端口进行监听;一直到有client对他进行访问(也就是监听到了客户端的socket和客户端的地址),调用文件服务函数

import socket
import typing
import mimetypes
import os

HOST = "127.0.0.1"
PORT = 9000
# # 假设服务器给客户端的回应长这样
# # 网络编程中,服务器和浏览器只认bytes 类型数据,所以字符串前面都要加个b
SERVER_ROOT = os.path.abspath("www")

FILE_RESPONSE_TEMPLATE = """\
HTTP/1.1 200 OK
Content-type:{content_type}
Content-length:{content_length}

""".replace("\n","\r\n")
BAD_REQUEST_RESPONSE = b"""\
HTTP/1.1 400 Bad Request
Content-type: text/html
Content-length: 11

Bad Request""".replace(b"\n",b"\r\n")
NOT_FOUND_RESPONSE = b"""\
HTTP/1.1 404 Not Found
Content-type: text/plain
Content-length: 9

Not Found""".replace(b"\n", b"\r\n")
# 除了get请求之外的请求暂时都用这个
METHOD_NOT_ALLOWED_RESPONSE = b"""\
HTTP/1.1 405 Method Not Allowed
Content-type: text/plain
Content-length: 17

Method Not Allowed""".replace(b"\n", b"\r\n")


# iter_lines功能:从套接字里读取数据并产生单独的每一行
def iter_lines(sock:socket.socket,bufsize:int = 16_384) ->typing.Generator[bytes,None,bytes]:
    buff = b""
    while True:
        data = sock.recv(bufsize)
        if not data:
            return b""

        buff += data
        while True:
            try:
                i = buff.index(b"\r\n")
                line,buff = buff[:i],buff[i+2:]
                if not line:
                    return buff
                yield line
            except IndexError:
                break
#为了使得一个request可以产生多个

#将iter_lines读到的数据,产生一个request类,包括路径,header,methods
class Request(typing.NamedTuple):
    method:str
    path:str
    headers:typing.Mapping[str,str]

    @classmethod
    def from_socket(cls,sock:socket.socket)->"Request":
        lines = iter_lines(sock)
        try:
            request_line = next(lines).decode("ascii")
        except StopIteration:
            raise ValueError("Requesting line missing.")

        try:
            method,path,_=request_line.split(" ")
        except ValueError:
            raise ValueError(f"Malformed request line {request_line!r}")

        headers = {}

        for line in lines:
            try:
                name,_,value = line.decode("ascii").partition(":")
                headers[name.lower()] = value.lstrip()
            except ValueError:
                raise ValueError(f"Malformed header line {line!r}")

        return  cls(method = method.upper(), path = path, headers = headers)


def serve_file(sock:socket.socket,path:str)->None:
    if path =="/":
        path = "/index5.html"
    abspath = os.path.normpath(os.path.join(SERVER_ROOT,path.lstrip("/")))
    # 如果路径的头不是www就是说路径输入错误呀!所以要报not found
    if not abspath.startswith(SERVER_ROOT):
        sock.sendall(NOT_FOUND_RESPONSE)
        return
    try:
        with open(abspath,"rb") as f:
            stat = os.fstat(f.fileno())
            content_type,encoding = mimetypes.guess_type(abspath)
            if content_type is None:
                content_type = "application/cotet-stream"
            if encoding is not None:
                content_type += f";charset = {encoding}"
            response_headers = FILE_RESPONSE_TEMPLATE.format(
                content_type = content_type,
                content_length = stat.st_size,
            ).encode("ascii")
            sock.sendall(response_headers)
            print(response_headers)
            sock.sendfile(f)

    except FileExistsError:
        sock.sendall(NOT_FOUND_RESPONSE)
        return

# 默认设置,建立一个TCPsockets(这是啥?)
with socket.socket() as server_sock:
    # 在等待状态的时候重用套接字
    server_sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    # 告诉socket要绑定哪个地址
    server_sock.bind((HOST,PORT))

    # 监听0,0是一个挂起的状态,代表着这个socket之前可能有进程再用
    # 如果9000这个端口已经有进程再用了,那么就要拒绝新的连接
    # 保证每次都只有一个连接
    server_sock.listen(0)
    print(f"Listening on {HOST}:{PORT}...")

    while True:
        client_sock, client_addr = server_sock.accept()
        print(f"New connection from {client_addr}.")
        # client_sock.sendall(RESPONSE)
        # 如果这个时候客户端在监听9000端口,那彼此就连接上了
        # 向客户端发送刚才定义的response
        # 语法:以 f开头表示在字符串内支持大括号内的python 表达式
        with client_sock:
            try:
                request = Request.from_socket(client_sock)
                if request.method !="GET":
                    client_sock.sendall(METHOD_NOT_ALLOWED_RESPONSE)
                    continue
                #将刚才写好的传送文件的函数放进来,文件路径的解析由request产生
                serve_file(client_sock,request.path)

            except Exception as e:
                print(f"Failed to parse request:{e}")
                client_sock.sendall(BAD_REQUEST_RESPONSE)




  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-11-18 11:30:16  更:2021-11-18 11:31:08 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 6:32:07-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码