在与第三方系统对接时,XML仍然是非常主流的数据交换格式,比如银联支付网关、顺丰快递下单接口、各类政府和企业开放平台,很多都要求客户端把XML格式的报文放在HTTP请求体里,以POST方式发送过去。不少初次接触这类接口的同学会卡在Content-Type怎么填、编码怎么处理、服务端为什么收不到数据这些问题上。本文把HTTP POST上传XML数据的完整流程拆开讲清楚,并给出多种语言下的可运行代码。

一、发送XML数据前必须搞懂的Content-Type
HTTP发送XML数据本质上就是发送一段文本,关键在于告诉服务器这段文本是什么格式。这就要靠Content-Type请求头来完成。XML对应的MIME类型主要有两种:text/xml和application/xml。两者在大多数服务器上都能被正常解析,但语义上有细微差别。text/xml强调这是普通文本,默认字符集是US-ASCII,如果不显式指定charset,某些严格的服务端会按ASCII解析,遇到中文就可能出乱码。application/xml则更通用,推荐优先使用。
实际写法通常是:Content-Type: application/xml; charset=UTF-8。这里强烈建议把charset显式带上,并且要和XML声明部分的encoding保持一致。比如XML第一行声明是<?xml version="1.0" encoding="UTF-8"?>,那么请求头的charset也必须是UTF-8。如果声明是GBK而请求头写UTF-8,或者反过来,服务端解析时就会出现中文乱码甚至直接报解析错误,这是联调阶段最常见的坑。
另外一个容易混淆的点是,不要把XML内容放在表单字段里用application/x-www-form-urlencoded发送,除非接口文档明确要求这么做。有些老系统确实要求把XML塞进一个叫xmlData的字段里再表单提交,这属于接口约定问题,需要严格按文档来,不能想当然。
二、用curl命令行快速测试XML接口
在写代码之前,先用curl把接口跑通是最省事的调试方式。curl通过-d或--data参数指定请求体,-H参数指定请求头,一条命令就能完成XML的POST提交。下面是一个典型示例:
curl -X POST "https://api.example-server.com/v1/order" \ -H "Content-Type: application/xml; charset=UTF-8" \ -d '<?xml version="1.0" encoding="UTF-8"?><order><orderId>20240501001</orderId><amount>199.00</amount></order>'
如果XML内容比较长,写在命令行里不方便,可以把它保存成文件,然后用-d @文件路径的方式提交,例如-d @request.xml。这样修改报文时只需要编辑文件,不用反复拼接命令,调试效率高很多。注意Windows下的cmd对引号和换行的处理和Linux不同,长XML建议统一用文件方式,避免转义地狱。
三、用Python发送XML数据的两种方式
Python生态里最常用的HTTP库是requests,发送XML非常直接,把XML字符串作为data参数传入即可。这里有个细节需要注意:如果传的是字符串,requests会自动编码;如果传的是bytes,则原样发送。为了确保编码可控,建议自己先编码成bytes。
import requests
url = "https://api.example-server.com/v1/order"
xml_data = '''<?xml version="1.0" encoding="UTF-8"?>
<order>
<orderId>20240501001</orderId>
<amount>199.00</amount>
<customer><name>张三</name><phone>13800138000</phone></customer>
</order>'''
headers = {
"Content-Type": "application/xml; charset=UTF-8"
}
response = requests.post(url, data=xml_data.encode("utf-8"), headers=headers)
print(response.status_code)
print(response.text)如果XML报文需要动态拼接,不建议直接用字符串拼接,容易产生非法字符或者标签闭合错误。更稳妥的做法是先用xml.etree.ElementTree构建树结构,再用tostring方法序列化输出,这样能保证XML结构始终合法。另外,返回的响应如果也是XML,同样可以用ElementTree解析,比用正则去匹配标签可靠得多。
四、Java和Node.js下的实现示例
Java 11之后JDK自带了新的HttpClient,用起来比老HttpURLConnection简洁不少。下面的例子演示了如何发送一段UTF-8编码的XML并读取响应:
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class XmlPostDemo {
public static void main(String[] args) throws Exception {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<order><orderId>20240501001</orderId><amount>199.00</amount></order>";
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example-server.com/v1/order"))
.header("Content-Type", "application/xml; charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofString(xml))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
}
}Node.js下的写法也很简单,用内置的fetch或者axios都可以。要注意的是Node.js默认字符串编码是UTF-8,与服务端对齐基本不会有问题:
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<order>
<orderId>20240501001</orderId>
<amount>199.00</amount>
</order>`;
fetch("https://api.example-server.com/v1/order", {
method: "POST",
headers: { "Content-Type": "application/xml; charset=UTF-8" },
body: xml
})
.then(res => res.text())
.then(text => console.log(text))
.catch(err => console.error(err));五、服务端接收与常见问题排查
服务端接收XML时,不能像接收表单那样用request.getParameter取值,XML数据在请求体里是原始字节流,需要直接读取body再交给XML解析器。以Java的Servlet为例,通过request.getInputStream读取字节流,再用DOM或SAX解析;Python的Flask则用request.data拿到原始字节串后交给ElementTree处理。
排查问题时可以按以下顺序检查:第一,确认请求头Content-Type是否正确,必要时抓包看实际发出的报文;第二,确认XML声明中的encoding、请求头charset、实际发送的字节编码三者一致;第三,检查XML内容是否包含需要转义的特殊字符,比如报文里出现&、<时必须转义成&和<,否则解析必然失败;第四,如果接口要求签名,确认签名算法和拼接顺序是否符合文档。把这些点都核对一遍,绝大多数XML接口联调问题都能定位出来。