提交POST请求的四种方法

web浏览器、服务器和相应的web应用程序都是通过HTTP协议相互通信的。HTTP使用的是可靠的数据传输协议,它能够确保数据在传输的过程中不会被损坏或产生混乱。Http请求方式有多重,最近遇到了关于post的一些问题,所以这里专注了解下post提交数据的几种方式。

HTTP应用程序发送的数据块就是HTTP报文,它以一些文本形式的元信息开头,这些元信息描述了报文的内容及含义。后面跟着可选的数据部分。

所有HTTP的报文可以分为两类:请求报文(request message)和响应报文(response message)。请求报文会向web服务器请求一个动作,响应报文会将请求的结果返回给客户端。请求报文和响应报文的基本报文结构相同。

请求报文结构:

1
2
3
<method><request-URL> <version>
<headers>
<entity-body>

响应报文结构:

1
2
3
<version> <status> <reson-phrase>
<headers>
<entity-body>

HTTP协议规定POST提交的数据必须放在消息主体(entity-body)中,但协议并没有规定数据必须使用什么样的编码方式,所以开发者可以自主定义消息体的格式,只要最后发送的HTTP请求满足上面的格式就可以。

但是,数据发送出去后,还需要服务端可以解析成功才有意义。一般服务端的语言和框架都可以正常解析常见的数据格式。服务端通常是根据请求头(headers)中的Content-Type字段来获得请求中的消息主题是用何种方式编码,再对主题进行解析。所以POST提交数据的方式,主要是Content-Type和消息主体编码的方式。

application/json

json的请求头基本上已经很常见了。它用来告诉服务端消息主体是序列后的json字符串。由于JSON规范的流行,除了低版本的IE之外各大浏览器都原生支持JSON.stringify,服务端语言也都有处理JSON的函数。一些框架都已经默认支持了。

它的好处是,可以直接用json传输结构化的数据,相比其他只能键值对的传输方式,这一点非常有用。顺便说一句,现在Angular里面默认支持的传输方式直接是application/json,可以直接使用。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
constructor(
private http: HttpClient,
) {

this.http.post('http://baidu.com', {
a: 1,
c: {
d: 5,
e: 6,
}
}).subscribe(res => {
console.log('res', res);
});
}

直接调用httpClient来发起一个请求,直接使用json传递参数,服务端可以直接获取json数据。

application/x-www-form-urlencoded

浏览器的原生<form>表单,如果不设置enctype属性,那么最终就会以application/x-www-form-urlencoded方式提交数据。请求类似下面这样:

1
2
Content-Type: application/x-www-form-urlencoded;charset=utf-8
name=tony&age=18

这个提价方式,参数会以键值对的方式进行传输:key=value&key=value,另外,提交的数据的key和value都进行了url转码。大部分服务端语言都对这种方式有很好的支持。

那在angular中如何将表单的提交方式转换到这种提交呢?毕竟我们使用的HttpClient是用的fetch提交,而不是原生表单的提交方式。

首先,我们需要将post请求方的header报文里面的Content-Type字段设置为application/x-www-form-urlencoded

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
constructor(
private http: HttpClient,
) {

}
ngOnInit(): void {
const params = {
name: 1,
};
this.http.post('http//www.baidu.com', params, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
}
}).subscribe(res => {
console.log('res', res);
});
}

在浏览器开发工具的NetWork里面查看的时候,会发现这个请求的Content-Type已经变成了application/x-www-form-urlencoded,参数那一块内容也变成了form-data。但是,你在服务器里面处理这个请求的时候,没法子接收到参数值的。为什么?因为虽然我们把请求方式变了,但是参数还是json。这里需要对参数也处理一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const params = {name: 'tony', age: 18};
const bodies = [];
for (const key of Object.keys(params)) {
if (params[key] !== null) {
bodies.push(key + '=' + encodeURIComponent(params[key]));
}
}
this.http.post('http//www.baidu.com', bodies.join('&'), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
}
}).subscribe(res => {
console.log('res', res);
});

这样,服务器里面可以接收到参数name=tony&age=18,然后就可以正常解析了。

但是,这样处理是很不方便的,在一个大应用里面,总不能每个请求都这样设置。建议写一个公共方法去处理参数格式化,然后在拦截器里面去处理header,这样可以方便的扩展:

1
2
3
4
5
6
7
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const authReq = req.clone({
headers: req.headers.set('Content-Type', 'application/x-www-form-urlencoded'),
});
}
return next.handle(authReq);
}

multipart/form-data

当使用表单上传文件时,必须让<form表单的enctype等于multipart/form-data,看实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
POST http://www.example.com HTTP/1.1
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA

------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="text"

title
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="file"; filename="chrome.png"
Content-Type: image/png

PNG ... content of chrome.png ...
------WebKitFormBoundaryrGKCBY7qhFd3TrwA--

首先生成了一个个boundary(边界)用于分割不同的字段,为了避免与正文内容重复,boundary很长很复杂。然后Content-Type指明了数据是以multipart-data来编码,本次请求的boundary内容是什么。

这种方式一般用来上传文件。

enctype还支持text/plain,但用的非常少。

text/xml

xml的方式传递参数,太麻烦了,不常用,还是用json吧。哈哈哈哈。。。。😄


其实是最近在开发angular项目的时候,忽然遇到了使用form-data方式提交表单的问题,本来摸索摸索可以提交了,但是当我提交一个比较复杂的字符串的时候服务器老是接收不到,后面才发现是没有进行url编码,所以后端直接爆炸了。也是很囧,正好借助这个机会来梳理一下,同时也是个记录。

参考文章:https://imququ.com/post/four-ways-to-post-data-in-http.html

码字辛苦,打赏个咖啡☕️可好?💘