x-www-form-urlencoded:完整指南 (2026)
关于 application/x-www-form-urlencoded Content-Type 你需要了解的一切
开始使用 Hypereal 构建
通过单个 API 访问 Kling、Flux、Sora、Veo 等。免费积分开始,扩展到数百万。
无需信用卡 • 10万+ 开发者 • 企业级服务
x-www-form-urlencoded:完整指南 (2026)
application/x-www-form-urlencoded 是 HTTP 中最基础的内容类型(Content Type)之一。它是 HTML 表单提交的默认编码方式,并广泛应用于 API、OAuth 流程和支付处理中。尽管它已有数十年历史,但依然无处不在——而对其理解不足往往会导致细微的 Bug。
本指南涵盖了它的工作原理、适用场景、编码规则以及主流语言的代码示例。
什么是 x-www-form-urlencoded?
application/x-www-form-urlencoded 是一种将表单数据编码为请求体中键值对的内容类型。其格式与 URL 查询参数(Query Parameters)相同:
name=John+Doe&email=john%40example.com&age=30
核心规则:
- 键值对之间用
&分隔 - 键和值之间用
=分隔 - 空格被编码为
+(或%20) - 特殊字符进行百分比编码(例如,
@变为%40) - 整个请求体是一个单一的扁平字符串(不支持嵌套)
HTTP 请求示例
POST /api/users HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 47
name=John+Doe&email=john%40example.com&age=30
编码规则
理解编码规则对于避免 Bug 至关重要。
必须编码的字符
| 字符 | 编码为 | 原因 |
|---|---|---|
| 空格 | + 或 %20 |
定界符冲突 |
& |
%26 |
键值对分隔符 |
= |
%3D |
键值分隔符 |
+ |
%2B |
已用于表示空格 |
@ |
%40 |
保留字符 |
# |
%23 |
片段标识符 |
/ |
%2F |
路径分隔符 |
? |
%3F |
查询字符串标记 |
% |
%25 |
编码前缀 |
! |
%21 |
保留字符 |
' |
%27 |
保留字符 |
( |
%28 |
保留字符 |
) |
%29 |
保留字符 |
无需编码的字符
字母数字字符(A-Z, a-z, 0-9)和非保留字符(-, _, ., ~)不需要编码。
空格编码:+ 还是 %20
这是一个常见的困惑点:
- 在 form-urlencoded 请求体中:空格通常编码为
+ - 在 URL 路径和查询字符串中 (RFC 3986):空格被编码为
%20
在 form-urlencoded 请求体中,+ 和 %20 都是有效的,但使用 + 是惯例。大多数库默认使用 +。
Form body: name=John+Doe (正确)
Form body: name=John%20Doe (也正确)
URL path: /users/John%20Doe (正确)
URL path: /users/John+Doe (错误 - + 会被视为字面量)
代码示例
JavaScript / TypeScript (Fetch API)
// 使用 URLSearchParams (推荐)
const params = new URLSearchParams();
params.append("name", "John Doe");
params.append("email", "john@example.com");
params.append("age", "30");
const response = await fetch("https://api.example.com/users", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: params.toString(),
// 输出: name=John+Doe&email=john%40example.com&age=30
});
const data = await response.json();
// 简写:直接将 URLSearchParams 作为 body 传递
const response = await fetch("https://api.example.com/users", {
method: "POST",
body: new URLSearchParams({
name: "John Doe",
email: "john@example.com",
age: "30",
}),
});
// 当 body 为 URLSearchParams 时,Content-Type 会自动设置
Python (requests)
import requests
# requests 使用 data= 参数会自动编码表单数据
response = requests.post(
"https://api.example.com/users",
data={
"name": "John Doe",
"email": "john@example.com",
"age": 30
}
)
print(response.json())
# 使用 urllib 手动编码
from urllib.parse import urlencode
body = urlencode({
"name": "John Doe",
"email": "john@example.com",
"age": 30
})
print(body)
# 输出: name=John+Doe&email=john%40example.com&age=30
cURL
# 使用 -d 标志 (自动设置 Content-Type)
curl -X POST https://api.example.com/users \
-d "name=John+Doe" \
-d "email=john%40example.com" \
-d "age=30"
# 使用 --data-urlencode (自动为你处理编码)
curl -X POST https://api.example.com/users \
--data-urlencode "name=John Doe" \
--data-urlencode "email=john@example.com" \
--data-urlencode "age=30"
Go
package main
import (
"fmt"
"net/http"
"net/url"
"strings"
)
func main() {
data := url.Values{}
data.Set("name", "John Doe")
data.Set("email", "john@example.com")
data.Set("age", "30")
resp, err := http.Post(
"https://api.example.com/users",
"application/x-www-form-urlencoded",
strings.NewReader(data.Encode()),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println("Status:", resp.Status)
}
PHP
<?php
// 使用 cURL
$data = http_build_query([
'name' => 'John Doe',
'email' => 'john@example.com',
'age' => 30
]);
$ch = curl_init('https://api.example.com/users');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
Java
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
public class FormPost {
public static void main(String[] args) throws Exception {
String body = String.join("&",
"name=" + URLEncoder.encode("John Doe", StandardCharsets.UTF_8),
"email=" + URLEncoder.encode("john@example.com", StandardCharsets.UTF_8),
"age=30"
);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpClient client = HttpClient.newHttpClient();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
解析 x-www-form-urlencoded 数据
Node.js / Express
import express from "express";
const app = express();
// 用于解析表单数据的内置中间件
app.use(express.urlencoded({ extended: true }));
app.post("/api/users", (req, res) => {
console.log(req.body);
// { name: 'John Doe', email: 'john@example.com', age: '30' }
// 注意:所有值都是字符串 -- 需手动解析数字
const age = parseInt(req.body.age, 10);
res.json({ received: req.body });
});
Python / Flask
from flask import Flask, request
app = Flask(__name__)
@app.route("/api/users", methods=["POST"])
def create_user():
name = request.form["name"]
email = request.form["email"]
age = int(request.form["age"])
return {"name": name, "email": email, "age": age}
Go (net/http)
func handler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
name := r.FormValue("name")
email := r.FormValue("email")
age := r.FormValue("age")
fmt.Fprintf(w, "Name: %s, Email: %s, Age: %s", name, email, age)
}
x-www-form-urlencoded 与其他内容类型的对比
对比表
| 特性 | x-www-form-urlencoded | multipart/form-data | application/json |
|---|---|---|---|
| 文件上传 | 不支持 | 支持 | 仅限 Base64 |
| 嵌套数据 | 不支持 (扁平键值对) | 不支持 (扁平键值对) | 支持 (原生) |
| 二进制数据 | 不支持 | 支持 | 仅限 Base64 |
| 可读性 | 较好 | 差 | 优 |
| 编码开销 | 中等 | 处理文件时开销低 | 低 |
| 浏览器原生支持 | 是 (默认表单) | 是 (需设置 enctype) | 需要 JavaScript |
| 数组支持 | 基于约定 | 基于约定 | 原生支持 |
何时使用
在以下情况下使用 x-www-form-urlencoded:
- 提交简单的 HTML 表单
- OAuth 2.0 令牌端点(规范要求)
- 支付 API 提交(如 Stripe, PayPal)
- 不含嵌套结构的简单键值对数据
- 旧版 API 兼容性
在以下情况下使用 multipart/form-data:
- 上传文件
- 发送二进制数据
- 将文件与表单字段混合发送
在以下情况下使用 application/json:
- RESTful API 端点
- 嵌套或复杂的数据结构
- 现代前后端通信
- 请求体中包含数组和对象
处理数组和嵌套数据
x-www-form-urlencoded 原生不支持数组或嵌套对象,但有一些通用约定:
数组
# 重复键(最常见)
colors=red&colors=blue&colors=green
# 方括号表示法(PHP 风格)
colors[]=red&colors[]=blue&colors[]=green
# 索引表示法
colors[0]=red&colors[1]=blue&colors[2]=green
// JavaScript: 发送数组
const params = new URLSearchParams();
params.append("colors", "red");
params.append("colors", "blue");
params.append("colors", "green");
console.log(params.toString());
// 输出: colors=red&colors=blue&colors=green
// 读取数组
const colors = params.getAll("colors");
// ["red", "blue", "green"]
嵌套对象
# 方括号表示法(PHP/Rails 风格)
user[name]=John&user[address][city]=NYC&user[address][zip]=10001
并非所有服务器都能解析嵌套的方括号表示法。Express 需要在 urlencoded 中间件中设置 extended: true。如果你需要处理嵌套数据,建议改用 JSON。
常见陷阱
1. 忘记对特殊字符进行编码
// 错误 - 值中的 & 会破坏解析
const body = "company=AT&T&city=Dallas";
// 被解析为: { company: "AT", T: "", city: "Dallas" }
// 正确 - 对 & 进行编码
const body = "company=AT%26T&city=Dallas";
// 被解析为: { company: "AT&T", city: "Dallas" }
// 最佳 - 使用 URLSearchParams
const params = new URLSearchParams({ company: "AT&T", city: "Dallas" });
// 自动编码为: company=AT%26T&city=Dallas
2. 重复编码
// 错误 - 对已经编码的字符串再次编码
const encoded = encodeURIComponent("John+Doe");
// 结果: "John%2BDoe" (+ 再次被编码)
// 正确 - 对原始值编码一次
const params = new URLSearchParams({ name: "John Doe" });
// 结果: name=John+Doe
3. 将值视为非字符串类型
x-www-form-urlencoded 中的所有值都是字符串。数字、布尔值和 null 必须在服务端进行转换:
// 客户端发送: active=true&count=5
// 服务端接收:
req.body.active // "true" (字符串,非布尔值)
req.body.count // "5" (字符串,非数字)
// 务必进行显式解析:
const active = req.body.active === "true";
const count = parseInt(req.body.count, 10);
4. 缺少 Content-Type 请求头
某些框架不会自动设置 Content-Type 请求头:
// 错误 - 缺少 Content-Type,服务器可能无法解析请求体
fetch("/api/data", {
method: "POST",
body: "name=John&age=30",
});
// 正确 - 显式设置 Content-Type
fetch("/api/data", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: "name=John&age=30",
});
// 同样正确 - URLSearchParams 会自动设置它
fetch("/api/data", {
method: "POST",
body: new URLSearchParams({ name: "John", age: "30" }),
});
OAuth 2.0 与 x-www-form-urlencoded
OAuth 2.0 规范要求令牌端点(token endpoints)接收 x-www-form-urlencoded 数据。这是该技术最常见的现实应用场景之一:
// OAuth 2.0 令牌交换
const response = await fetch("https://oauth.provider.com/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
grant_type: "authorization_code",
code: "auth_code_from_redirect",
redirect_uri: "https://yourapp.com/callback",
client_id: "your_client_id",
client_secret: "your_client_secret",
}),
});
const tokens = await response.json();
// { access_token: "...", refresh_token: "...", expires_in: 3600 }
结论
application/x-www-form-urlencoded 是一种简单、成熟的编码方案,对于 HTML 表单、OAuth 流程和许多 API 来说仍然不可或缺。需要记住的关键点是:始终使用库函数进行编码(不要手动构建字符串),留意空格编码的差异(+ 对比 %20),并在服务端将值解析为字符串。
对于现代 API 开发,在处理复杂数据结构时通常首选 JSON,但 x-www-form-urlencoded 依然是简单表单和规范要求场景(如 OAuth)的正确选择。
如果你正在构建处理媒体生成的 API(例如根据文本提示词创建图像、视频或音频),Hypereal AI 提供了基于 JSON 请求/响应格式的直观 REST API。欢迎查阅,为你的应用添加 AI 媒体处理能力。
