Ubuntu TechHive
from-networks-to-http-and-apis.md
从网络到 HTTP 和 API
article.细节

从网络到 HTTP 和 API

reading.进展 6 分钟阅读数

从网络到 HTTP 和 API 的描述

从网络到 HTTP 和 API

设置

[tool.poetry]
name = "from-networks-to-http-apis"
version = "0.1.0"
description = "从网络到 HTTP 和 API"
authors = ["user "]
license = "MIT"
readme = "README.md"
#packages = [{include = "devops_from_scratch_manual_process"}]

[tool.poetry.dependencies]
#python = "^3.10.6"
python = "^3.11"
sortedcontainers = "^2.4.0"
httpx = "^0.23.0"
pyzmq = "^25.1.0"
fastapi = "^0.97.0"
uvicorn = "^0.22.0"
requests = "^2.31.0"
openai = "^0.27.8"
whitenoise = "^6.2.0"
fontawesomefree = "^6.4.0"
typer = { version = "^0.9.0", extras = ["all"] }

#[tool.poetry.group.dev.dependencies]
#black = "^23.3.0"
#pytest = "7.3.1"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

网络拓扑

发布 / 订阅 (Pub / Sub)

graph LR

sub1((订阅者 1)) -- 订阅 --> TopicA((消息代理))
sub2((订阅者 2)) -- 订阅 --> TopicB((消息代理))
sub3((订阅者 3)) -- 订阅 --> TopicA((消息代理))
sub4((订阅者 4)) -- 订阅 --> TopicC((消息代理))

pub1((发布者 1)) -- 发布 --> TopicA((消息代理))
pub2((发布者 2)) -- 发布 --> TopicB((消息代理))
pub3((发布者 3)) -- 发布 --> TopicC((消息代理))

TopicA((消息代理)) -- 发布 --> sub1((订阅者 1))
TopicA((消息代理)) -- 发布 --> sub3((订阅者 3))
TopicB((消息代理)) -- 发布 --> sub2((订阅者 2))
TopicC((消息代理)) -- 发布 --> sub4((订阅者 4))

中文说明

PUB/SUB(发布/订阅)是一种在分布式系统中常用的消息传递模式,用于实现多个发布者和多个订阅者之间的通信。它为发布者提供了一种无需了解订阅者即可发送消息的方式,同时也让订阅者能够在无需与发布者直接交互的情况下,从多个发布者那里接收消息。

在 PUB/SUB 模式中,发布者将消息(称为“发布内容”)发送到特定的主题或频道。订阅者通过订阅特定主题来表达其接收消息的意愿。随后,订阅者将收到所有发送至其已订阅主题的发布内容。

以下是 PUB/SUB 关键组件的分解:

  1. /发布者 (Publisher)/:负责向特定主题发送消息或发布内容的实体。它不了解订阅者及其身份。发布者可以向一个或多个主题发布消息。
  2. /订阅者 (Subscriber)/:对接收特定主题的消息或发布内容感兴趣的实体。订阅者通过订阅一个或多个主题来表明其兴趣。他们将收到发布到其已订阅主题的消息。
  3. /主题 (Topic)/:与消息或发布内容相关联的逻辑频道或类别。发布者将消息发送到特定主题,订阅者从其已订阅的主题接收消息。主题允许订阅者过滤他们接收到的消息,并专注于与其兴趣相关的内容。
  4. /消息 (Message)/:由发布者发送并由订阅者接收的信息。它可以是任何对相关应用程序有意义的数据结构或有效载荷。
  5. /消息代理 (Message Broker)/:负责将消息从发布者路由到订阅者的中间组件。它根据订阅者的主题订阅情况来管理消息的分发。

在 PUB/SUB 系统中,发布者和订阅者是异步运行的。发布者可以随时发送消息,而无需知道当前是否有订阅者处于活跃状态。订阅者可以随时加入或离开系统,而不会影响发布者。

PUB/SUB 消息传递模式常用于各种场景,例如实时数据流、事件驱动架构、聊天应用程序以及需要将发布者和订阅者解耦的分布式系统。

PyZMQ(ZeroMQ 的 Python 绑定)为在 Python 应用程序中实现 PUB/SUB 通信提供了一种便捷的方式。

Français

(此处保留原文法语内容)

Code

import zmq

context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind("tcp://*:5555")

while True:
    topic = input("输入主题: ")
    message = input("输入消息: ")
    socket.send_multipart([topic.encode(), message.encode()])
import zmq

context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.connect("tcp://localhost:5555")
socket.setsockopt_string(zmq.SUBSCRIBE, '')

while True:
    topic, message = socket.recv_multipart()
    print(f"已接收: {topic.decode()} - {message.decode()}")

推送 / 拉取 (Push / Pull) 与队列

graph TB

subgraph 推送者
  P((推送者))
end

subgraph 设备
  F[(前端)]
  B[(后端)]
  D(设备)
end

subgraph 拉取者
  Q((拉取者))
end

P -- 发送消息 --> F
F -- 转发消息 --> B
B --> Q

中文说明

此网络配置由三个组件组成:推送者 (Pusher)、拉取者 (Puller) 和设备 (Device)。这些组件利用 PyZMQ(ZeroMQ 的 Python 绑定)建立了一个基于 PUSH/PULL 模式的消息系统。

  1. /推送者 (Pusher)/:推送者是负责向网络发送消息的组件。在提供的代码 (pusher.py) 中,使用 PyZMQ 创建了一个 PUSH 套接字。该套接字绑定到 TCP 地址 tcp://*:5555,允许其监听端口 5555 上的传入连接。推送者会重复提示用户输入消息,每条消息都通过 socket.send_string(message) 发送。
  2. /拉取者 (Puller)/:拉取者是接收网络消息的组件。在提供的代码 (puller.py) 中,使用 PyZMQ 创建了一个 PULL 套接字。与推送者类似,该套接字绑定到 TCP 地址 tcp://*:5556,使其能够监听端口 5556 上的传入连接。拉取者持续等待通过 socket.recv_string() 接收消息。一旦收到消息,它就会将其打印到控制台。
  3. /设备 (Device)/:设备充当推送者和拉取者之间的中间组件。它将推送者的输出连接到拉取者的输入,允许消息在组件之间流动。在提供的代码 (device.py) 中,设备创建了两个 ZMQ 套接字:一个称为“前端”的 PULL 套接字和一个称为“后端”的 PUSH 套接字。前端套接字绑定到 tcp://*:5555,后端套接字绑定到 tcp://*:5556。ZMQ 设备函数 (zmq.device) 连接这两个套接字,充当消息队列。设备前端套接字接收到的任何消息都会自动转发到后端套接字,然后由后端套接字将消息传递给拉取者。

通过同时运行推送者、拉取者和设备脚本,您可以创建一个网络配置,其中推送者将消息发送到设备的前端套接字,而拉取者从设备的后端套接字接收消息。设备充当消息队列,确保消息从推送者传递到拉取者。这种配置允许将推送者和拉取者解耦,从而实现异步消息交换。

Français

(此处保留原文法语内容)

Code

import zmq

context = zmq.Context()
socket = context.socket(zmq.PUSH)
socket.connect("tcp://*:5555")

while True:
    message = input("输入消息: ")
    socket.send_string(message)
import zmq

context = zmq.Context()
socket = context.socket(zmq.PULL)
socket.connect("tcp://*:5556")

while True:
    message = socket.recv_string()
    print(f"已接收: {message}")
import zmq

context = zmq.Context()
frontend = context.socket(zmq.PULL)
backend = context.socket(zmq.PUSH)
frontend.bind("tcp://*:5555")
backend.bind("tcp://*:5556")

zmq.device(zmq.QUEUE, frontend, backend)

请求 / 回复 (Request / Reply)

graph TD
  A[请求者] -->|请求| B((REQ 套接字))
  B -->|请求| C((服务器))
  C -->|回复| B
  B -->|回复| A

中文说明

REQ/REPLY(请求/回复)是一种在分布式系统中常用的消息传递模式,用于实现客户端(请求者)和服务器(响应者)之间的同步通信。它建立了一种简单的一对一通信模式,即客户端发送请求,服务器返回回复。

在 REQ/REPLY 模式中:

  1. 请求者 (REQ):请求者是向服务器发送请求的客户端组件。它使用 PyZMQ 创建一个 REQ 套接字,并使用 `socket.connect()` 连接到服务器地址。请求者可以通过使用 `socket.send_string()` 发送请求消息来按顺序发送多个请求。
  2. 响应者 (REP):响应者是接收来自客户端的请求并提供回复的服务器组件。它使用 PyZMQ 创建一个 REP 套接字,并使用 `socket.bind()` 将其绑定到特定地址。响应者使用 `socket.recv_string()` 接收请求消息。一旦收到请求,它就会处理该请求并制定回复。然后,回复通过 `socket.send_string()` 发送回请求者。

REQ/REPLY 模式遵循同步请求-响应模型。客户端(请求者)发送请求并等待服务器(响应者)的相应回复,然后才能继续。它确保每个请求都与特定的回复配对,从而实现可靠且顺序的通信。

该模式在客户端需要服务器特定响应的场景中非常有用,例如客户端-服务器架构、RPC(远程过程调用)或同步分布式系统。

需要注意的是,REQ/REPLY 模式是一种阻塞模式,这意味着请求者将被阻塞,直到收到回复。如果发送了多个请求而没有等待回复,服务器将按顺序处理它们,请求者将在发送下一个请求之前等待每个回复。

PyZMQ 为在 Python 应用程序中实现 REQ/REPLY 模式提供了一种简单的方法,允许客户端和服务器之间进行同步的请求-响应通信。

Français

(此处保留原文法语内容)

Code

import zmq

context = zmq.Context()
socket = context.socket(zmq.REQ)
socket.connect("tcp://localhost:5555")

while True:
    message = input("输入消息: ")
    socket.send_string(message)

    reply = socket.recv_string()
    print(f"收到回复: {reply}")
import zmq

context = zmq.Context()
socket = context.socket(zmq.REP)
socket.bind("tcp://*:5555")

while True:
    message = socket.recv_string()
    print(f"收到请求: {message}")

    reply_message = input("输入回复: ")
    socket.send_string(reply_message)

HTTP 1.1 协议基础

规范

请求 / 响应对象

中文说明

当然!我将以适合初学者的方式解释 HTTP 1.1 规范的基础知识。

HTTP 代表超文本传输协议 (Hypertext Transfer Protocol),它是万维网通信的基础。HTTP 1.1 是该协议的一个版本,相较于其前身 HTTP 1.0,它引入了多项改进。以下是一些关键概念:

  1. 客户端-服务器通信:HTTP 1.1 遵循客户端-服务器模型。客户端(通常是 Web 浏览器)向服务器发送请求,服务器返回相应的响应。
  2. 请求结构:客户端请求由几个部分组成:

    • 方法 (Method):表示客户端希望在服务器上执行的操作,例如 GET、POST、PUT、DELETE 等。
    • URL/URI:指定客户端希望交互的服务器上资源的位置。
    • 标头 (Headers):提供有关请求的附加信息,例如客户端可以接受的内容类型、缓存指令、身份验证凭据等。
    • 正文 (Body):包含客户端可以发送给服务器的可选数据,通常与 POST 或 PUT 请求一起使用,用于提交表单数据或更新资源。
  3. 响应结构:服务器响应也由几个部分组成:

    • 状态码 (Status Code):指示请求的结果,例如成功 (200)、重定向 (3xx)、客户端错误 (4xx) 或服务器错误 (5xx)。
    • 标头 (Headers):提供有关响应的附加信息,例如内容类型、长度、缓存指令等。
    • 正文 (Body):包含客户端请求的实际数据或资源,例如 HTML 页面、图像或 JSON 数据。
  4. 持久连接:HTTP 1.1 引入了持久连接的概念,即可以在单个连接上发送多个请求和响应。这有助于减少为每个请求建立新连接的开销,从而提高性能。
  5. 缓存:HTTP 1.1 增加了增强的缓存机制,允许客户端和中间件(如代理服务器)存储和重用以前获取的资源。这减少了重复获取相同资源的需要,从而提高了效率。
  6. 内容协商:HTTP 1.1 提供了客户端和服务器根据客户端的能力和偏好协商资源最佳表示形式的能力。这允许根据客户端的需求以不同的语言或格式交付内容。
  7. 安全性:虽然 HTTP 1.1 不提供固有的加密,但它可以与 HTTPS 等安全协议结合使用,以建立加密连接并确保数据的机密性和完整性。

这些是 HTTP 1.1 的一些基本方面。它是一种实现客户端(如 Web 浏览器)和服务器之间通信的协议,允许在互联网上交换资源、数据和信息。

请记住,这只是一个简化的概述,关于 HTTP 及其复杂性还有很多需要学习的地方。不过,希望这能为您提供 HTTP 1.1 及其在 Web 通信中作用的基本了解。

Français

(此处保留原文法语内容)

Code

def parse_http_request(request):
    parsed_request = {}

    lines = request.split("\r\n")
    method, path, protocol = lines[0].split(" ")

    parsed_request["method"] = method
    parsed_request["path"] = path
    parsed_request["protocol"] = protocol

    headers_end_index = lines.index("")
    headers = lines[1:headers_end_index]

    parsed_request["headers"] = {}
    for header in headers:
        key, value = header.split(": ", 1)
        parsed_request["headers"][key] = value

    body = lines[headers_end_index + 1:]

    if parsed_request["method"] == "POST":
        body = body[-1]  # 仅考虑 POST 请求正文的最后一行
        if "=" in body:
            data_pairs = body.split("&")
            parsed_request["data"] = {}
            for pair in data_pairs:
                key, value = pair.split("=")
                parsed_request["data"][key] = value

    return parsed_request


# 示例测试

# GET 请求
get_request = """GET /path HTTP/1.1\r\nHost: fintech.com\r\nAccept: text/html\r\n\r\n"""
parsed_get_request = parse_http_request(get_request)
print(parsed_get_request["method"])  # 输出: GET
print(parsed_get_request["path"])  # 输出: /path
print(parsed_get_request["protocol"])  # 输出: HTTP/1.1
print(parsed_get_request["headers"])  # 输出: {'Host': 'example.com', 'Accept': 'text/html'}
print(parsed_get_request.get("data"))  # 输出: None

# POST 请求
post_request = """POST /login HTTP/1.1\r\nHost: fintech.com\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\nemail=blash@gmail.com&password=value2"""
parsed_post_request = parse_http_request(post_request)
print(parsed_post_request["method"])  # 输出: POST
print(parsed_post_request["path"])  # 输出: /submit
print(parsed_post_request["protocol"])  # 输出: HTTP/1.1
print(parsed_post_request["headers"])  # 输出: {'Host': 'example.com', 'Content-Type': 'application/x-www-form-urlencoded'}
print(parsed_post_request["data"])  # 输出: {'key1': 'value1', 'key2': 'value2'}
class HTTPRequest:
    def __init__(self, method, path, protocol):
        self.method = method
        self.path = path
        self.protocol = protocol
        self.headers = {}
        self.data = {}


def parse_http_request(request):
    lines = request.split("\r\n")
    method, path, protocol = lines[0].split(" ")

    http_request = HTTPRequest(method, path, protocol)

    headers_end_index = lines.index("")
    headers = lines[1:headers_end_index]

    for header in headers:
        key, value = header.split(": ", 1)
        http_request.headers[key] = value

    body = lines[headers_end_index + 1:]

    if http_request.method == "POST":
        body = body[-1]  # 仅考虑 POST 请求正文的最后一行
        if "=" in body:
            data_pairs = body.split("&")
            for pair in data_pairs:
                key, value = pair.split("=")
                http_request.data[key] = value

    return http_request


# 示例测试

# GET 请求
get_request = """GET /path HTTP/1.1\r\nHost: example.com\r\nAccept: text/html\r\n\r\n"""
parsed_get_request = parse_http_request(get_request)
print(parsed_get_request.method)  # 输出: GET
print(parsed_get_request.path)  # 输出: /path
print(parsed_get_request.protocol)  # 输出: HTTP/1.1
print(parsed_get_request.headers)  # 输出: {'Host': 'example.com', 'Accept': 'text/html'}
print(parsed_get_request.data)  # 输出: {}

# POST 请求
post_request = """POST /submit HTTP/1.1\r\nHost: example.com\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\nkey1=value1&key2=value2"""
parsed_post_request = parse_http_request(post_request)
print(parsed_post_request.method)  # 输出: POST
print(parsed_post_request.path)  # 输出: /submit
print(parsed_post_request.protocol)  # 输出: HTTP/1.1
print(parsed_post_request.headers)  # 输出: {'Host': 'example.com', 'Content-Type': 'application/x-www-form-urlencoded'}
print(parsed_post_request.data)  # 输出: {'key1': 'value1', 'key2': 'value2'}

API:服务器与客户端

FastAPI

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Msg(BaseModel):
    txt: str

@app.get("/hello")
def get_hello():
    return {"message": "你好,GET 请求!"}

@app.post("/hello")
def post_hello(msg: Msg):
    print(f"消息: {msg}")
    return {"message": f"已接收: {msg.txt}"}
uvicorn server:app --reload
import requests

# GET 请求
get_response = requests.get("http://localhost:8000/hello")
print(get_response.json())  # 输出: {'message': '你好,GET 请求!'}

# POST 请求
post_response = requests.post("http://localhost:8000/hello", json=dict(txt="Lambert"))
print(post_response.json())  # 输出: {'message': '你好,POST 请求!'}

import requests
import json

url = "http://35.203.26.163/graphql"

payload = "{\"query\":\"query {\\n  pageByName(page: \\\"register-page\\\") {\\n    data {\\n      id\\n      attributes {\\n        name\\n        page\\n        title\\n        slug \\n        body {\\n        ... on ComponentFormRegisterForm {\\n          id\\n          form {\\n            data {\\n              id\\n              attributes {\\n                name\\n                title\\n                formHeaderText\\n                formEntry {\\n                  data {\\n                    attributes {\\n                      name\\n                      formFields {\\n                        id\\n                        name\\n                        type\\n                        value\\n                        defaultValue\\n                        isRequired\\n                        errorText\\n                        errorIcon {\\n                          data {\\n                            attributes {\\n                              name\\n                              alternativeText\\n                            }\\n                          }\\n                        }\\n                      }\\n                    }\\n                  }\\n                }\\n                formFooterText\\n                formErrorText\\n                showFormErrorsOnTop\\n                showFormAsPopup\\n                formErrorIcon {\\n                  data {\\n                    attributes {\\n                      name\\n                      alternativeText\\n                    }\\n                  }\\n                }\\n              }\\n            }            \\n          }\\n        }\\n        }\\n        subHeader {\\n          data {\\n            id\\n            attributes {\\n              subHeader {\\n                id\\n                title\\n                tabs {\\n                  id \\n                  name\\n                  url\\n                }\\n              }\\n            }\\n          }\\n        }      \\n      }\\n    }\\n  }\\n}\",\"variables\":{}}"
headers = {
  'Content-Type': 'application/json'
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)

Requests

作为契约的 API

JSON Schema

通过契约提示 LLM