Python网络编程2

作者:

在

创建http服务器

这是固定格式,通过HTTPServer创建服务,创建时给出ip地址、端口号和自定义处理类。

在处理类中,http请求方法是通过do_[http_method]构成,例如do_Get,那所有的get请求都会被此函数处理,do_Post那么所有的post请求都会被此函数处理。

​ ● do_*函数调用的时机是,当客户端触发请求,服务端接受到后执行

​ ● do_*函数中处理的主要是,定义响应报文状态码,构造响应头,构造响应体,大部分情况下,end_headers()必须调用,且放置于send_response之后

from http.server import BaseHTTPRequestHandler, HTTPServer

class MyHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b'{"msg":"Get"}')

    def do_POST(self):
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b'{"msg":"Post"}')
# 127.0.0.1:8080
# localhost:8080
# ip:8080
server = HTTPServer(("0.0.0.0", 8080), MyHandler)
server.serve_forever()

image-20260601150528242

==总结==:服务端程序启动后不会自动退出,而是持续等待客户端的连接请求

  1. 绑定端口:服务端会在某个端口(比如8080)上开启服务,操作系统会为它保留这个通道

  2. 阻塞等待:会一直停在accept( )这个步骤,如果没有客户端连接,会陷入等待状态

  3. 唤醒处理:一旦有客户端发起请求,会唤醒服务端,处理完再次回到等待状态

  4. 永不退出:==会一直持续,除非手动停止服务==

  5. (”0.0.0.0″)和以上三个ip,==本质==:服务端监听时允许哪些客户端访问的区别. 0.0.0.0是一个特殊的通配地址:监听本机所有可用的网络接口

  6. 三个注释 IP 的含义

    · 127.0.0.1:本地回环地址。只有本机自己的程序能访问(比如用浏览器打开 http://127.0.0.1:8080),其他设备不行。 · localhost:域名,默认解析为 127.0.0.1(有时也会解析到 IPv6 的 ::1)。和 127.0.0.1 效果相同,只能本机访问。 · ip:8080:这里的“ip”通常指你机器的实际网络 IP(如局域网 IP 192.168.1.100,或公网 IP)。只有通过那个具体 IP 访问的客户端才能连上。

  7. 每请求一次都会生成一个日志:自己客户端的ip–请求日期,请求行,响应码

http接口的定义(api)

一般在后端发开中,会把 【请求方法+请求路径】 定义为一个http的接口,通过请求方法和请求路径的组合,来确定一个唯一的服务端处理逻辑。

例如deepseek的接口文档中,查询余额和查询可用模型分别为

​ ● Get+/user/balance——意味着,后端服务实现的时候,对应了一个查询用户可用余额的逻辑

​ ● Get+/models——意味着,后端服务实现的时候,对应了一个返回模型列表的逻辑

那如何用上述MyHandler做到这样的效果呢?

字典的HTTP路由分发设计

核心思想:将不同的URL路径对应的处理函数存储在一个字典(路由表)中,在请求处理时根据self.path查找并调用相应函数,实现请求的分发与响应的返回

==总结==:处理函数的参数(req):本质上是MyHandler的实例,通过该实例可以调用send_response、send_header、wfile.write,还有请求路径在路由表中,每个路径的响应逻辑独立封装在单独的函数中,易于新增路径:新增路径只需在GET_ROUTER字典中添加一个键值对,并实现对应的处理函数.

from http.server import BaseHTTPRequestHandler, HTTPServer

class MyHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        print(self.path)
        if self.path == "/models":
            self.send_response(200)
            self.end_headers()
            self.wfile.write(b'{"msg":"models"}')
        elif self.path == "/user/balance":
            self.send_response(200)
            self.end_headers()
            self.wfile.write(b'{"msg":"balance"}')
        else:
            self.send_response(404)
            self.end_headers()
            self.wfile.write(b'{"msg":"Not Found"}')

    def do_POST(self):
        self.send_response(200)
        self.send_header("Content-type", "application/json")
        self.end_headers()
        self.wfile.write(b'{"msg":"Post"}')


server = HTTPServer(("0.0.0.0", 8080), MyHandler)
server.serve_forever()

在原有的基础上,你可以传递handler对象

from http.server import BaseHTTPRequestHandler, HTTPServer


def reshandle(httpObj):
    httpObj.send_response(200)
    httpObj.send_header("Content-type", "application/json")
    httpObj.end_headers()
    httpObj.wfile.write(b'{"msg":"okk"}')


class MyHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        print(self.path)
        reshandle(self)

    def do_POST(self):
        self.send_response(200)
        self.send_header("Content-type", "application/json")
        self.end_headers()
        self.wfile.write(b'{"msg":"Post"}')


server = HTTPServer(("0.0.0.0", 8080), MyHandler)
server.serve_forever()

==总结==:通过URL和请求方法可以区分出后端的处理函数,所以一个接口真正的区分来自于后端处理函数的区分

客户端ip+端口号可变,但是服务端ip+端口号不可变

如何获取http请求参数

获取查询参数

从self.path中解析

from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import parse_qs, urlparse

class MyHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        print(self.path)
        parsed_path = urlparse(self.path)
        # 解析查询参数
        query_params = parse_qs(parsed_path.query)
        print(query_params)
        self.send_response(200)
        self.send_header("Content-type", "application/json")
        self.end_headers()
        self.wfile.write(b'{"msg":"Post"}')


server = HTTPServer(("0.0.0.0", 8080), MyHandler)
server.serve_forever()

扩展(URL查询参数a={ }怎么编码到查询参数里)

原因:URL查询参数对字符的”容忍度”非常低,而且不同环境的标准还不统一,如果不编码,你传的东西很可能会被误解甚至弄丢。

==方法==:

  • 序列化:(对象—–>字符串),把字典/对象转成JSON字符串
import json
data = {"name": "张三", "age": 20}
json_str = json.dumps(data)  # 结果:'{"name": "张三", "age": 20}'
  • URL编码(字符串——>==百分号编码==(URL编码))

用 quote( ) 把 JSON 字符串里的特殊字符全部转成%XX形式

from urllib.parse import quote
encoded_value = quote(json_str)  # 结果:'%7B%22name%22%3A%20%22%E5%BC%A0%E4%B8%89%22%2C%20%22age%22%3A%2020%7D'
  • 拼接到URL
url = f"https://example.com/api?a={encoded_value}"
# 完整结果:https://example.com/api?a=%7B%22name%22%3A%20%22%E5%BC%A0%E4%B8%89%22%2C%20%22age%22%3A%2020%7D
  • 服务端解码(还原)
from urllib.parse import unquote
import json

decoded_str = unquote(encoded_value)        # 还原JSON字符串
original_dict = json.loads(decoded_str)     # 还原字典对象
不要做的事 正确做法
直接拼接 ?a={‘name’:’张三’} 先转JSON再编码
只编码不序列化 先序列化成字符串再编码
编码整个URL 只编码参数值
…………………………… 用标准库 quote()

获取请求头和请求体

from http.server import BaseHTTPRequestHandler, HTTPServer


def reshandle(httpObj):
    httpObj.send_response(200)
    httpObj.send_header("Content-type", "application/json")
    httpObj.end_headers()
    httpObj.wfile.write(b'{"msg":"okk"}')


class MyHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        print("-------")
        # 获取所有的请求头部字段
        print(self.headers)
        print(self.headers.get("Content-Length", 0))
        length = int(self.headers.get("Content-Length", 0))
        print("-------")
        # 获取请求体,注意,read必须指定长度,否则会一直阻塞
        # 至于请求体怎么解析,需要和客户端协商好,比如是json字符串,那就可以通过json转成字典
        # 如果是文件,那可以配合文件api完成文件写入
        print(self.rfile.read(length))
        print("-------")
        reshandle(self)

    def do_POST(self):
        self.send_response(200)
        self.send_header("Content-type", "application/json")
        self.end_headers()
        self.wfile.write(b'{"msg":"Post"}')


server = HTTPServer(("0.0.0.0", 8080), MyHandler)
server.serve_forever()

转存文件示例

这里文件直接以二进制字节流传输

from http.server import BaseHTTPRequestHandler, HTTPServer


def reshandle(httpObj):
    httpObj.send_response(200)
    httpObj.send_header("Content-type", "application/json")
    httpObj.end_headers()
    httpObj.wfile.write(b'{"msg":"okk"}')


class MyHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        print("-------")
        length = int(self.headers.get("Content-Length", 0))
        fileBytes = self.rfile.read(length)
        with open("test.txt", "wb") as f:
            f.write(fileBytes)
        print(fileBytes)
        print("-------")
        reshandle(self)

    def do_POST(self):
        self.send_response(200)
        self.send_header("Content-type", "application/json")
        self.end_headers()
        self.wfile.write(b'{"msg":"Post"}')


server = HTTPServer(("0.0.0.0", 8080), MyHandler)
server.serve_forever()

如何返回请求数据

控制响应头

send_header(key, value)

返回json

self.wfile.write(
    json.dumps({"success": True, "data": {"name": "张三"}}).encode()
)

返回文件

图解HTTP.pdf

返回图片,pdf,附件等形式

content-type,类型非常多,但一般常见的就是image、pdf、json、plain等,下图中是常见的

image-20260603154157092

以附件形式返回,强制下载

from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import quote


class MyHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        with open("图解HTTP.pdf", "rb") as f:
            data = f.read()
            self.send_response(200)
            self.send_header(
                "Content-Disposition",  # 可以强行以附件形式下载,静止客户端渲染
                f"attachment; filename={quote('图解HTTP.pdf')}",
            )
            self.end_headers()
            self.wfile.write(data)


server = HTTPServer(("0.0.0.0", 8080), MyHandler)
server.serve_forever()

flask框架

第一个http服务器

from flask import Flask     # 导入Flask类

app = Flask(__name__)      #创建Flask应用实例,__name__表示当前模块名

# 匹配http url
@app.route("/user",methods=["GET"])     # url和请求方法用装饰器就搞定了,相当于路由分发,根据装饰器后面的路径参数,和请求方法来触发服务端处理函数
def hello_world():                      # 处理函数通过上面的装饰器触发
    return "<p>Hello, World!</p>"  

# 启动服务器,监听所有ip地址的3000端口
app.run(host="0.0.0.0", port=3000)

==总结==:

  • @装饰器(’/user’,methods=[“GET”])匹配服务端接口,客户按照接口要求构造请求才可得到响应

  • def hello_world( )是服务端处理函数,构造响应体

整体参数收集和响应

from flask import Flask, request

app = Flask(__name__)

@app.route("/models/<model_id>", methods=["GET"])
def hello_world(model_id):  # 路径参数
    print(request.args)  # 查询参数
    print(request.headers)  # 请求头
    # 获取 JSON 请求体
    bodyJson = request.get_json()
    print(bodyJson)
    # 获取表单数据(application/x-www-form-urlencoded,multipart/form-data普通字段)
    bodyFormData = request.form
    print(bodyFormData)
    # 获取原始请求体
    bodyRaw = request.get_data()
    print(bodyRaw)
    # 获取文件数据(multipart/form-data)
    bodyFiles = request.files
    print(bodyFiles)
    # 第一个参数响应体,第二个参数状态码,第三个参数响应头
    return {"success": True, "model_id": model_id}, 400, {"cusHeader": "aaaaa"}

app.run()

返回文件

from flask import Flask, send_file

app = Flask(__name__)

@app.route("/download/file")
def download_image():
    # 下载图片并在浏览器显示,文件是流式传输的
    return send_file(
        "图解HTTP.pdf",  # 不需要我们转url编码
        mimetype="application/pdf",
        as_attachment=True,  # 浏览器内显示而不是下载
    )

app.run()

文件的流式处理

无论是客户端还是服务端,凡是设计到文件读写,都需要考虑能不能一次性把文件全部加载到内存,之后再处理。一般小文件可以直接全部加载至内存,但是对于几个g的大文件,我们没法这样处理,会导致内存溢出oom,所以,在客户端通过http传递大文件,要采用分块传输,在服务端通过http传递大文件时,同样需要分块传输。

而客户端传输分块动作,requests帮我们做了,只需要指定文件对象,它自动分块传输;同样,服务端的分块动作,send_file函数照样帮我们做了;但是==客户端如何接收流式的文件以及服务端如何接收流式的文件,均需要自行处理==

​ 1. 客户端接收

import requests

def receive_stream_to_file():
    url = 'http://example.com/stream'
    response = requests.get(url, stream=True)

    # 以二进制写入模式打开文件
    with open('output.txt', 'wb') as f:
        for chunk in response.iter_content(chunk_size=1024):
            if chunk:
                f.write(chunk)  # 直接写入文件    
    print("文件保存完成")

# 使用
receive_stream_to_file()

​ 2. 服务端接收

from flask import Flask, request, jsonify
import os

app = Flask(__name__)

@app.route('/stream', methods=['POST'])
def test_stream():
    """流式接收并写入文件"""
    filename = 'uploaded_file.bin'
    chunk_count = 0
    total_bytes = 0

    # 边接收边写入文件
    with open(filename, 'wb') as f:
        for chunk in request.stream:
            if chunk:
                f.write(chunk)
                chunk_count += 1
                total_bytes += len(chunk)

    return jsonify({
        "method": "stream", 
        "chunks": chunk_count, 
        "size": total_bytes,
        "saved_to": filename
    })

==原理==:

  1. chunk(bytes字节串)是编程中的通用术语,意为”数据块”或”片断”,在流式传输的代码中,chunk特指:从一个完整的数据流中,切分出来的一小段二进制数据

  2. 避免一次加载整个文件到内存

  3. stream=True ,让requests从”先全部下载再处理”变成”边下载边处理”

  4. 适用于大文件传输(防止内存爆掉)


==总结==:

常见接收类型

  1. form_data中非文件类型的字段(正常的普通键值对)和urlencode里的普通字段就用==request.form==接收 ; form_data还可以传

(一般放在字段value里),那就用==request.files==接收

  1. 报文改变请求体中数据编码的格式,而你==后端用不同的函数接收,就是后端的框架自动将客户端发过来的请求报文中请求体的内容做了对应的转换,从而让我们站在服务端的角度非常容易拿到客户端数据,flask框架写接口太简单了,前端无论怎么传数据传文件,要想转存数据直接用save( )==

  2. 传json直接get_json(),传的是form_data就用form, form_data里放文件就用file接收

  3. 如果用urlencode就用form接受,如果是查询参数直接用args接收

  4. 路径参数就用==标识符==,用变量的方式接收

  5. 这些都是拿客户端数据


flask框架控制响应码,响应头,响应体 ;浏览器是客户端带渲染

  1. send_file配合as_attachment=True就可以让客户端访问浏览器下载文件了

后端接口(API)和数据库

如何通过后端接口(API)从 数据库中获取数据,并返回给客户端. Flask + SQL

一、整体流程

  1. 客户端 → 发送 HTTP 请求(如 GET /api/users)
  2. 后端(Flask) → 接收请求,解析参数
  3. 后端 → 连接数据库,执行 SQL 查询
  4. 数据库 → 返回查询结果
  5. 后端 → 将数据转换为 JSON 格式
  6. 客户端 ← 收到 JSON 数据

流程:

后端接口——>数据库——>返回 JSON模式

SDK和HTTP

SDK(Software Development kit ,软件开发工具包)是对底层HTTP API 的封装。它把鉴权,请求构造,参数校验,序列化,错误重试等通用逻辑写在一个库里面,==对外暴露更符合开发习惯的函数或类方法==

你==只需要关心业务参数(Bucket,文件路径)==,不需要自己构造签名,拼接URL,处理分块上传等复杂逻辑

二.

SDK VS 原生HTTP请求

特性 直接 HTTP 请求 SDK
鉴权 手动拼 Token / 签名 SDK 自动计算并添加
参数校验 自己 if 判断 SDK 提前检查,报错更友好
请求构造 拼接 URL、编码 body 调用函数,传入对象即可
响应解析 response.json() 再取值 SDK 直接返回 Python 对象
错误处理 判断状态码,解析错误码 SDK 抛出具体异常,信息明确
重试/超时 自己写循环 SDK 内置可配置的重试策略
版本兼容 API 升级后要改代码 SDK 升级后内部适配,调用方代码不变

==总结==:

  1. SDK = 让你像调用本地函数一样调用远程API,屏蔽了HTTP的细节

  2. SDK 是在 HTTP 之上的一层封装,它把“协议细节”变成了“函数调用”,让接口请求逻辑更聚焦于业务。

​ · 理解 HTTP 是基础(URL、方法、header、body、状态码)。 ​ · 使用 SDK 是提升效率的手段(省去机械重复的请求代码)。 ​ · 一个优秀的 SDK 会帮你处理:鉴权、参数校验、序列化、错误处理、重试、连接池、日志等所有非功能性需求

​ 如果你是 API 提供方,提供 SDK 可以极大降低接入成本;如果你是 API 调用方,优先找官方 SDK,没有的话再自己封装 HTTP。

套壳deepseek

from flask import Flask, send_file, request

from openai import OpenAI


app = Flask(__name__)
client = OpenAI(api_key="sk-6429bd0a0d184d4dbe811055f95a8cf8", base_url="https://api.deepseek.com")

# 匹配http url
app.route("/download", methods=["GET"])
def hello_world():
    # print(request.args['name'])
    # print(request.headers['Host'])
    return send_file("图解HTTP.pdf", as_attachment=True)

@app.route("/ai/chat", methods=["GET"])
def get_time():
    question = request.args['q']
    response = client.chat.completions.create(
    model="deepseek-v4-pro",
    messages=[
    {"role": "system", "content": "You are a helpful assistant"},
    {"role": "user", "content": question},
    ],
    max_tokens=1024,
    temperature=0.7,
    stream=False,
    )
    print(response.choices[0].message.content)
    #deepseek请求放到这里,SDK
    return {"success": True, "answer": response.choices[0].message.content}

app.run(host="0.0.0.0", port=8080)

浏览器返回的响应体

image-20260603095529456

==原因==:

flask 框架在 return { },以字典构造json字符串时,flask会自动帮你把字典(python对象)序列化为json字符串,但是默认会有ensure_ascii=True,但是所有数据(中文字符,英文字符)存储数据都是以二进制字节形式存储,所以返回的其实是这个,但是要转为ascii,二进制字节超过了ascii的码表范围,所以转为了unicode转义序列,转义序列可以全部转为ascii,如果不想要转义序列,就直接去掉默认的ensure_ascii=True,在创建应用实例后面ensure_ascii=Flase,就可以返回二进制响应体然后经浏览器自动解析为中文字符

任务

  1. 使用requests库,调用deepseek,完成一轮ai对话

  2. 在flask项目下创建一个文件夹,里面放一些文件,通过/files获取到所有文件的下载路径

​ a. 浏览器访问下载路径,可以下载文件

  1. 理解网站静态资源的本质,使用2中的程序,完成网站资源部署

flask托管静态文件

from pathlib import Path

from flask import Flask

staticPath = Path(__file__).parent / "static"
print(staticPath)

# /static url路径
app = Flask(__name__, static_url_path="/static", static_folder=staticPath)

app.run(host="0.0.0.0", port=3000)

实现静态文件托管

from pathlib import Path

from flask import Flask, send_file

app = Flask(__name__)

# 路径参数
@app.route("/static/<file>")
def hello_world(file):
    filePath = Path(__file__).parent / "static" / file
    if not filePath.exists():
        return "<p>File not found</p>", 404
    return send_file(filePath)


app.run(host="0.0.0.0", port=3000)

image-20260603153905017

实现文件列表

from pathlib import Path

from flask import Flask, send_file

app = Flask(__name__)


@app.route("/static/<file>")
def hello_world(file):
    filePath = Path(__file__).parent / "static" / file
    if not filePath.exists():
        return "<p>File not found</p>", 404
    return send_file(filePath)


@app.route("/static")
def getFileList():
    filePath = Path(__file__).parent / "static"
    files = [f.name for f in filePath.iterdir() if f.is_file()]
    print(files)
    [f"<a href='/static/{i}'>{i}</a>" for i in files]
    return "<br/>".join([f"<a href='/static/{i}'>{i}</a>" for i in files])


app.run(host="0.0.0.0", port=3000)

文件托管和下载原理

本质是两个处理函数:第一个触发每个文件的请求路径,第二个是可以点击触发第二个处理函数来下载文件

第二个服务端处理函数

image-20260603162723364

image-20260603165114505

image-20260603165155942

部署静态网站

website.zip

from pathlib import Path

from flask import Flask, send_file

app = Flask(__name__)


@app.route("/")
def index():
    indexPath = Path(__file__).parent / "static" / "website" / "index.html"
    return send_file(indexPath)


@app.route("/images/<file>")
def getImgList(file):
    filePath = Path(__file__).parent / "static" / "website" / "images" / file
    if not filePath.exists():
        return "<p>File not found</p>", 404
    return send_file(filePath)


@app.route("/scripts/<file>")
def getScript(file):
    filePath = Path(__file__).parent / "static" / "website" / "scripts" / file
    if not filePath.exists():
        return "<p>File not found</p>", 404
    return send_file(filePath)


@app.route("/css/<file>")
def getCSS(file):
    filePath = Path(__file__).parent / "static" / "website" / "css" / file
    if not filePath.exists():
        return "<p>File not found</p>", 404
    return send_file(filePath)


app.run(host="0.0.0.0", port=3000)

==使用flask内置功能==

from flask import Flask

app = Flask(__name__, static_folder="static/website", static_url_path="/")


app.run(host="0.0.0.0", port=3000)

实现:

image-20260603220757176

正常访问

==总结==:

  1. 手动搭建一个静态文件服务器来托管前端网站
  • 浏览器渲染机制

首次访问返回index.html后,浏览器解析HTML时会自动发起新的HTTP请求,去获取页面中引用的图片,CSS,JS,音视频等静态资源

  • 这些二次请求必须能正确返回对应文件,否则页面显示不全(出现404)
  1. 托管文件
  • 将所有前端文件(HTML,CSS,JS,图片等)统一放在一个目录下,例如 website/
  • 在服务端配置==静态文件中间件==,将该目录声明为根路径 / 的静态资源目录
  • 访问根路径 / 时返回 index.html ; 访问其他路径(如/ style.css 、/ image.png)时,直接在目录中查找对应文件并返回
  1. 实现要点
  • 不需要为每个文件单独写路由,==只需要一个通配处理==:将请求的URL的路径部分作为相对于 website/ 的路径,读取文件并返回
  • 这样就能实现:一次入口请求 + 浏览器自动二次请求所有依赖资源,从而完整渲染整个网站
  1. 本质目标:

独立用代码实现一个静态文件服务器(类似 serve 、http-server 的功能),把前端项目完整托管起来,达到”输入根路径即可访问整个网站”的效果

复现

image-20260603201656959


浏览器显示


打开前端浏览器看看网络,刷新下看看哪些没拿到

image-20260603202600255


需要的请求路径

image-20260603202953638

怎样修复

因为没拿到相应的请求路径,所以有的没显示出来,所以==为了恢复应拿到的,应该把文件路径全部托管起来==

  1. 修复图片:请求/image的/<image_name>的图片能找到这个文件,并把这个文件返回给你,解决404问题

image-20260603204732714

恢复了图片

image-20260603205213689


  1. 恢复css数据

定义一个css接口

image-20260603205523723

恢复了css

image-20260603205723679


  1. 恢复scripts

image-20260603210249951

恢复后的scripts

image-20260603210415267

原理

  • 都是先请求静态资源文件,然后由浏览器解析它,代码是web开发工程师写出来的

​ 这就是部署一个前端网站

  • 浏览器中只要把index_html给它,浏览器解析这个时,会自动加载非常多的东西,你只需要把对应文件全部托管好,就可以了

==总结==:

原理:

  1. 装饰器参数
  • 装饰器参数 是指写在装饰器(如 @app.route( ))括号内的配置项,用于告诉 Web 框架(如 Flask)这个被装饰的函数应该匹配什么样的 HTTP 请求规则。

    常见的装饰器参数包括:

    · URL 路径模式(如 ‘/files/<file_name>’) · 请求方法(如 methods=[‘GET’, ‘POST’])

    核心作用:声明式配置,将“如何匹配请求”的元数据附加在函数上,而不需要修改函数内部代码

  1. 客户端标识符
  • 客户端标识符 是指客户端(如浏览器、移动应用、API 调用者)在发起 HTTP 请求时,在 URL 路径中实际填入的动态值。

    例如:

    · 装饰器中定义的路由模板是 /users/<user_id> · 客户端请求的 URL 是 /users/12345 · 那么 12345 就是客户端标识符

    核心作用:作为真实数据,标识具体要操作的资源(如某个用户、某篇文章、某个文件)

  1. ==动态路由映射==

​ 动态路由映射 是 Flask 框架提供的一种机制: ​ 将 URL 路径中可变的部分(通过装饰器参数中的 <变量名> 标记)自动提取出来,并作为同名参数传递给被装饰的函数。

​ 简单说:把 URL 里的动态值“映射”成函数的参数

  1. 三者从动态路由映射角度的关系

​ 在动态路由映射中的角色 ​ 装饰器参数 定义“映射的规则”——告诉 Flask 哪个位置是动态的,变量名叫什么 ​ 客户端标识符 提供“映射的源数据”——URL 中实际出现的具体值 ​ 动态路由映射 执行“映射的动作”——把客户端标识符的值提取出来,填进装饰器参数定义的变量名里,然后传给函数

重定向

本质:是一种“资源寻址转交”机制:服务端不直接返回请求的资源,而是返回资源的“新地址”,由客户端再次发起请求,两次发起请求

  1. 访问 http://IP:端口(没有额外路径)时,一般不使用重定向机制。 它请求的就是根路径 /,服务器直接返回对应的资源(比如 index.html),整个过程是一次请求、一次响应。

  2. 在 URL 路径中,/ 表示根路径。 你定义接口为 /,就是让服务器处理对根路径的请求。

    当你访问 http://IP:端口(浏览器地址栏只输入 IP 和端口,没有 /),浏览器自动补全为 http://IP:端口/,因为斜杠是路径的起始符号。 所以最终到达服务器的请求就是 GET /。

    此时服务器直接返回文件(如 HTML),不需要重定向,状态码是 200 OK。

  3. 什么时候会用到重定向?

    在静态服务器中,重定向通常出现在访问 没有尾随斜杠的目录路径 时。例如:

    · 你的文件目录里有一个 images/ 文件夹 · 用户请求 http://IP:端口/images(没有最后的 /)

    很多静态服务器(如 Nginx、Express 的 serve-static)会返回 301 或 302,重定向到 http://IP:端口/images/ .

  4. “访问 IP+端口没有出现后面斜杠,就是用重定向机制” —— 不是的。根路径本身不需要重定向。重定向主要用于 目录路径缺少尾随斜杠 的场景,目的是保证 URL 语义和资源定位的正确性

==总结==:只有请求路径为目录(文件夹)并且后面没有 / 时,才会触发重定向q

socket

# server.py 代码
import socket

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server.bind(("127.0.0.1", 8080))

server.listen(5)

while True:

    conn, client_addr = server.accept()
    print(conn, client_addr, sep="n")

    from_client_data = conn.recv(1024)
    print(from_client_data.decode("utf-8"))

    conn.send(from_client_data.upper())

    conn.close()

粘包问题

tcp的buffer是每个socket描述符一个,所以多客户端之间的数据互不干扰。而粘包需要解决的是,同一个客户端消息边界怎么区分

img

设计一个协议,区分消息边界,比如

大端和小端

大端是指:字节高位放在低地址

小端是指:字节地位放在低地址

img

python中对字节操作

import struct

# < 小端字节序
# > 大端字节序
# 一般把字节从左到右当作内存从低地址到高地址
data = struct.pack("> i", 0x1234)
print(data.hex())

val = struct.unpack("> i", data)[0]
print(hex(val))

协议设计

TP协议
+-----------------+--------------------------+
|  4 bytes (LE)   |      N bytes (文本)       |
|  内容长度(N)     |       实际内容             |
+-----------------+--------------------------+

示例

import json
import struct


def encode(msg):
    data = msg.encode("utf-8")
    return struct.pack("<I", len(data)) + data


def decode(data):
    length = struct.unpack("<I", data[:4])[0]
    return data[4 : 4 + length].decode("utf-8")


buf = encode(json.dumps({"name": "张三"}, ensure_ascii=False))
print(buf.hex())
print(decode(buf))

可以在此网站配置尝试看看编码结果是否正确字符 编码/解码 – 锤子在线工具

课件中解析实例大家自己练习了

挑战:先使用flask托管static文件,写一个接口,通过此接口上传文件,并返回文件的下载地址

post /upload/file 请求体中放文件

response: {“success”:true,downloadUrl:””}

静态文件服务器

定义 静态文件服务器是一种专门用于提供==不会动态变化== 的文件的服务器。

核心特点

· 文件内容固定,无论谁访问、何时访问,返回都一样 · 不涉及数据库查询、用户登录、实时计算等后端逻辑 · 服务器只做一件事:==根据 URL 路径找到对应的文件,读出来,返回给客户端==

总结 把硬盘上的文件通过 HTTP 协议共享出去,让浏览器能直接访问 —— 这就是静态文件服务器。

Nginx和Apache之后会学到

HTML(超文本标记语言)

HTML(超文本标记语言,HyperText Markup Language) 是一种用于描述网页结构和内容的标记语言。它使用成对的标签(如 <h1>、<p>)来标注文本、图片、链接等元素,==浏览器按标准解析这些标签并渲染成可视化网页。==

核心作用

  1. 搭建网页结构

定义标题、段落、列表、表格、表单、图片、视频等内容的布局与层次.

  1. 承载超文本与多媒体

通过超链接(<a>)实现页面跳转;嵌入图片、音频、视频等非文本资源。

  1. 跨平台展示

同一份 HTML 文件可在不同操作系统(Windows、macOS、Linux、手机)的浏览器中正常显示。

  1. 作为前端基础

与 CSS(样式)、JavaScript(交互)协作,构成完整网页或应用。HTML 只负责结构,样式与行为由 CSS/JS 处理。

总结:

HTML 是所有网页的骨架,它用标签定义内容含义,浏览器自动渲染成可见界面。

Web

Web 是一个通过 HTTP 协议访问、使用 URL 定位资源、以超链接互联的分布式信息系统。

核心要素: · HTTP/HTTPS 协议:统一的数据传输规则。 · URL(统一资源定位符):每个资源的唯一地址。 · HTML:页面结构和内容的描述语言。 · 超链接:资源之间相互关联的纽带。 · 本质:Web 是运行在互联网之上的信息空间,通过浏览器访问网页、在线应用、视频等资源。

二、前端与后端的定义

· 前端:用户直接交互的部分,运行在浏览器中。负责页面展示、接收用户操作、发送请求并更新界面。核心技术:HTML、CSS、JavaScript。 · 后端:运行在服务器上的程序,用户不可见。负责处理业务逻辑、数据存取、安全验证、返回响应。常见技术:Java、Python、Node.js、Go 等,配合数据库

三、前端与后端相互联系的过程(完整链路)

整个过程遵循 HTTP 协议

  1. 用户触发 → 前端监听用户动作(点击、输入等)。
  2. 前端构建请求 → 向后端的特定 URL(API 地址)发送 HTTP 请求,包含: · 请求方法(GET、POST 等) · 请求头(如身份令牌 Token、Content-Type) · 请求体(如 JSON 格式的用户数据)
  3. 网络传输 → 请求经互联网送达后端服务器。
  4. 后端处理 → · 解析请求,验证身份和参数。 · 调用业务逻辑(如计算、查询数据库)。 · 生成响应数据(通常为 JSON 或 HTML)。
  5. 后端返回响应 → 发送 HTTP 响应,包含: · 状态码(200 成功、404 未找到等) · 响应头 · 响应体(数据或错误信息)
  6. 前端接收并更新界面 → · 解析响应数据。 · 根据结果动态修改 DOM(如显示内容、跳转页面或弹出错误提示)。

关键点总结:

· 前后端通过 API 文档 约定接口格式。 · 现代 Web 采用 前后端分离:后端只提供数据接口,前端独立渲染。

一句话描述联系过程:

前端向后端发送 HTTP 请求,后端处理请求并返回数据,前端根据返回的数据更新用户界面。