YouTube subscription API POST 请求 Content-Type 错误
结论
普通的 HTML <form method="post"> 无法发送这类请求。浏览器会将表单编码为 application/x-www-form-urlencoded 或 multipart/form-data,但旧版 YouTube Data API v2 要求请求正文使用 Atom XML,内容类型为:
Content-Type: application/atom+xml
这类请求需要通过服务端代码、curl,或者支持自定义请求头和原始请求正文的 HTTP 客户端发送。
问题中提到的 gdata.youtube.com、Atom XML 和 GData-Version: 2 都属于已经停用的 YouTube Data API v2。下面的 XML 示例仅用于说明旧接口的请求方式,不能保证现在仍可调用。新项目应改用 YouTube Data API v3 的 subscriptions.insert 方法。
为什么 HTML 表单会报错
普通表单提交的内容通常如下:
Content-Type: application/x-www-form-urlencoded
username=GoogleDevelopers
接口要求的却是 XML 正文:
Content-Type: application/atom+xml
<?xml version="1.0" encoding="UTF-8"?>
<entry ...>
...
</entry>
即使为 <form> 设置 enctype,标准 HTML 表单也不能直接生成 application/atom+xml 请求。enctype 常见的可用类型只有:
application/x-www-form-urlencodedmultipart/form-datatext/plain
因此,需要直接构造 HTTP 请求。
旧版 API 的 curl 写法
如果使用的是旧接口仍然有效的环境,可以这样发送请求:
curl --request POST \
'https://gdata.youtube.com/feeds/api/users/default/subscriptions' \
--header 'Content-Type: application/atom+xml; charset=UTF-8' \
--header 'Authorization: Bearer ACCESS_TOKEN' \
--header 'GData-Version: 2' \
--header 'X-GData-Key: key=DEVELOPER_KEY' \
--data-binary '<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns="http://www.w3.org/2005/Atom"
xmlns:yt="http://gdata.youtube.com/schemas/2007">
<category scheme="http://gdata.youtube.com/schemas/2007/subscriptiontypes.cat"
term="channel"/>
<yt:username>GoogleDevelopers</yt:username>
</entry>'
其中:
- 将
ACCESS_TOKEN替换为经过授权的 OAuth 访问令牌。 - 将
DEVELOPER_KEY替换为开发者密钥。 - 将
GoogleDevelopers替换为目标频道或用户。 --data-binary会将 XML 原样作为请求正文发送。curl通常会自动计算Content-Length,不需要手动设置。
PHP 示例
<?php
$url = 'https://gdata.youtube.com/feeds/api/users/default/subscriptions';
$accessToken = 'ACCESS_TOKEN';
$developerKey = 'DEVELOPER_KEY';
$xml = <<<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns="http://www.w3.org/2005/Atom"
xmlns:yt="http://gdata.youtube.com/schemas/2007">
<category scheme="http://gdata.youtube.com/schemas/2007/subscriptiontypes.cat"
term="channel"/>
<yt:username>GoogleDevelopers</yt:username>
</entry>
XML;
$curl = curl_init($url);
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $xml,
CURLOPT_HTTPHEADER => [
'Content-Type: application/atom+xml; charset=UTF-8',
'Authorization: Bearer ' . $accessToken,
'GData-Version: 2',
'X-GData-Key: key=' . $developerKey,
],
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($curl);
$statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($response === false) {
throw new RuntimeException(curl_error($curl));
}
curl_close($curl);
echo "HTTP status: {$statusCode}\n";
echo $response;
这里要将 XML 字符串直接赋给 CURLOPT_POSTFIELDS,不要包装成 PHP 数组。传入数组后,cURL 会改用表单或 multipart/form-data 编码。
JavaScript fetch 示例
fetch 在技术上可以直接发送 XML:
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns="http://www.w3.org/2005/Atom"
xmlns:yt="http://gdata.youtube.com/schemas/2007">
<category scheme="http://gdata.youtube.com/schemas/2007/subscriptiontypes.cat"
term="channel"/>
<yt:username>GoogleDevelopers</yt:username>
</entry>`;
const response = await fetch(
"https://gdata.youtube.com/feeds/api/users/default/subscriptions",
{
method: "POST",
headers: {
"Content-Type": "application/atom+xml; charset=UTF-8",
"Authorization": "Bearer ACCESS_TOKEN",
"GData-Version": "2",
"X-GData-Key": "key=DEVELOPER_KEY"
},
body: xml
}
);
const responseBody = await response.text();
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${responseBody}`);
}
console.log(responseBody);
但从浏览器直接调用时,还可能遇到 CORS 限制。需要保密的凭据也不应写进公开的前端 JavaScript。实际项目通常由后端完成 OAuth 流程并调用 API。
当前应使用 YouTube Data API v3
新项目应发送以下请求:
POST https://www.googleapis.com/youtube/v3/subscriptions?part=snippet
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json
请求正文使用 JSON,通过频道 ID 指定要订阅的频道:
{
"snippet": {
"resourceId": {
"kind": "youtube#channel",
"channelId": "TARGET_CHANNEL_ID"
}
}
}
对应的 curl 示例:
curl --request POST \
'https://www.googleapis.com/youtube/v3/subscriptions?part=snippet' \
--header 'Authorization: Bearer ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"snippet": {
"resourceId": {
"kind": "youtube#channel",
"channelId": "TARGET_CHANNEL_ID"
}
}
}'
访问令牌必须属于实际执行订阅操作的 YouTube 用户,并包含当前接口要求的 OAuth scope。仅使用 API key 不能代表用户创建订阅。
注意事项
- 不要手动填写
Content-Length,HTTP 客户端通常会自动计算。 - XML 中的命名空间、元素名称和大小写必须准确。
Content-Type只用于声明正文格式。仅修改表单数据的请求头,并不会将表单数据自动转换成 XML。- OAuth access token 不应出现在 URL、日志或公开源码中。
- 如果继续请求旧的
gdata.youtube.com地址,即使 XML 和请求头正确,也可能因为 API v2 已停用而失败。 - 开发当前系统时,应以 YouTube Data API v3 的
subscriptions.insert文档和现行 OAuth 要求为准。
备注:内容仅供参考。
