几天前,工作中有一个小的需求,对接其他公司的人脸识别api。这个需求本身并不复杂,但是中间居然也隐藏了一些不小的坑,最终也让我学到了不少有关于POST、网络请求方面的冷知识,也算是因祸得福了吧。
首先,让我们来看一下对接的文档(未涉及到的参数部分做了删除处理)
基本信息
Path: /service/faceidentify/v3
Method: POST
请求参数
Headers
| 参数名称 | 参数值 | 是否必须 | 示例 | 备注 |
|---|---|---|---|---|
| Content-Type | application/x-www-form-urlencoded | 是 |
Body
| 参数名称 | 参数类型 | 是否必须 | 示例 | 备注 |
|---|---|---|---|---|
| baseimg64 | text | 是 | /9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGB | 不需要base64文件头 |
涉及到的就是这些参数了,起初我一看到请求方式POST,立马就拿出之前写的WebHelper工具类,选择了一个POST发送方法来测试发送请求,选择的方法如下:
public static HttpResult POST(string url, string data, int timeout = 100)
{
HttpHelper helper = new HttpHelper();
HttpItem item = new HttpItem();
item.Timeout = timeout * 1000;
item.Encoding = Encoding.UTF8;
item.POSTEncoding = Encoding.UTF8;
item.URL = url;
item.Method = "POST";
item.POSTDataType = POSTDataType.String;
item.POSTdata = data;
item.ContentType = "application/x-www-form-urlencoded";
HttpResult result = helper.GetHtml(item);
return result;
}
返回的结果:414 Request URI too large,看到这个结果我就知道自己错的离谱!!!这种方式的POST其实和get请求没什么区别,都是把参数放到url,只是请求方式是POST而已,我也看到请求参数那里写的是body,更说明了这个接口实际是需要把参数写进body内,url应该就是基本信息中的path,更直观的体现二者差别,可以看看postman这个接口调试软件对二者的区分:


这个接口上传的图片需要转换成base64格式的字符串,于是这种直接把参数写在url内的方式自然就会因为url太长导致对方服务器返回异常了。
于是我又找了另一种方式发送POST请求:
public static string POST(string url, Dictionary<string, string> dic)
{
string result = "";
try
{
//添加POST 参数
System.Text.StringBuilder builder = new System.Text.StringBuilder();
int i = 0;
foreach (var item in dic)
{
if (i > 0)
builder.Append("&");
builder.AppendFormat("{0}={1}", item.Key, item.Value);
i++;
}
byte[] POSTData = System.Text.Encoding.UTF8.GetBytes(builder.ToString());
System.Net.HttpWebRequest _webRequest = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url);
_webRequest.Method = "POST";
//_webRequest.ContentType = "application/json";
//内容类型
_webRequest.ContentType = "application/x-www-form-urlencoded";
_webRequest.Timeout = 1000 * 30;
_webRequest.ContentLength = POSTData.Length;
using (System.IO.Stream reqStream = _webRequest.GetRequestStream())
{
reqStream.Write(POSTData, 0, POSTData.Length);
reqStream.Close();
}
System.Net.HttpWebResponse resp = (System.Net.HttpWebResponse)_webRequest.GetResponse();
System.IO.Stream stream = resp.GetResponseStream();
//获取响应内容
using (System.IO.StreamReader reader = new System.IO.StreamReader(stream, System.Text.Encoding.UTF8))
{
result = reader.ReadToEnd();
}
}
catch (Exception ex)
{
LogHelper.WriteLog("发送POST请求发生异常", ex);
}
return result;
}
可以看到这种方式发送POST请求的时候,是将参数写入到流里面,然后将流作为请求的body发送。请求之后得到以下返回:

根据返回的数据可以判断:
1. 这次服务器确实收到了我们的请求,并收到了返回
2. 图片参数baseimg64有问题,导致服务器端解析失败
3. 服务器内部问题,这个可能性很小
于是我使用postman重新发送了一遍请求,发现居然识别成功

这样基本就把问题定位在baseimg64这个参数本身了,由于postman的参数是从请求处截下来的,所以能够确认图片转的base64值是没有问题的,那么就是我发的参数和服务器接收的参数不一样,而且postman发送的值虽然和我一样,但是服务器接收到postman发送的值就是正确的。
为了对比我和postman发同样请求时,服务器接受到我们发送的参数有什么不同,我使用flask在我的服务器写了一个类似的接口,将获取到的参数直接在flask的后台打印出来,从而做个对比,接口得python代码如下:
from flask import Flask
from flask import request
import requests
import os
app = Flask(__name__)
@app.route('/',methods=["POST"])
def DownLoad():
username=request.form['baseimg64']
print(username)
return username
if __name__ == '__main__':
app.run(debug=True,port=1234,host="0.0.0.0")
OK,接下来就分别使用postman和我的程序调用我服务器的flask接口,并将后台打印的获取到的参数值进行对比:

答案跃然屏幕之上了,是”+”,”+”在我请求的参数中都被替换成为了空格,导致服务器解析的base64图片并不是我发送的图片。根据之前url转译方面的了解,URL中默认的将”+”号转义了。”+”为POST特殊字符,所以传递后会丢失,那么处理方式也就很简单了,要么手动将客户端带”+”的参数中的”+”全部替换为”2B%”,这样参数传到服务器端时就能得到”+”了,要么就使用System.Web.HttpUtility.UrlEncode方法将参数先进行编码,从而使服务器端正确的转译出”+”。
小结
这次由工作需求引发的有关POST请求的研究,让我开始关注那些拿来就用的工具类方法,大多数时候拿来就用是没有问题的,或者误打误撞没有错误,但是一旦遇到问题就容易四处起火。同时也希望未来的工作以及编程生涯中,我都能不小看任何一行代码,也不畏惧任何一行代码。