需要对接第三方接口,他们的接口是这样的,需要以form表单的形式传个文件。
但是通过httpclient内置的方法请求,一直返回非法请求。我还特地去下载了fiddler来跟踪本机发出的请求,看到底是啥样的,哪里错了,fiddler跟踪结果如下:
解决方案:把附件的content-type传对!
这里的content-type是文件对应的mineType类型,关于每个文件对应的mineType类型可以去网上搜一下。mineType格式是:单词a/单词b
POST http://127.0.0.1/111/111 HTTP/1.1
Content-Type: multipart/form-data; boundary=f922d808-8f72-46e0-9922-921bc25e5853
Content-Length: 39530
Host: 127.0.0.1
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.14.4
--f922d808-8f72-46e0-9922-921bc25e5853
Content-Disposition: form-data; name="file"; filename="1.wav"
Content-Type: file/*
Content-Length: 39336
内容省略。。。
--f922d808-8f72-46e0-9922-921bc25e5853--
为了搞清楚是为什么,我特地去学习了http协议。
http协议:让不同主机之间建立通信,类似寄信,需要有邮编、地址、寄信人才能送到指定位置。
请求分为3部分
1-请求行
请求方法+请求路径+协议版本【换行】
请求方法:Get、Post、Head(只需要返回头部内容)、Put、Trace(追踪最后发出的请求)、Options(看有哪些方法可以用)
2-请求头
key:value
....
【空行】--标志请求头的结束,无论是否有请求主体都需要。
3-请求主体
请求主体有内容时,需要在请求头中添加Content-length的值,即请求主体的长度。解析时根据这个长度读取请求主体的内容。
以post方式提交,还需要在请求头中添加Content-type的值。是json还是from表单,等等。
像上文中,content-type是multipartFile的,带有boundary,说明要上传附件。附件没法用key-value的形式标准,所以用boundary,表示分隔标志。两个横杠加上boundary,表示附件开始,如果后面再加两个横杠表示结束。
响应也分为三部分
1-响应行
协议+状态码+文字描述
状态码:
301/302 永久、暂时重定向
304 Not Modify
307 重定向保持原来的请求数据
2-响应头
key:value
【空行】
3-主体信息
学完之后回顾,感觉本机发出的请求头依然没什么大毛病,于是去搜索,http请求附件的报文格式,发现人家的content-type怎么画风这么清新,在某篇文章里看到,MIME类型这个词,才去搜索了一下,我要传的文件,对应的类型。
最终我改了正确的类型,成功调通接口。但在此之前,其实还有一个问题。
我们是用httpclient里面的RequestBody来构建请求的,构建这个对象有几种方法。
- 用第一个方法,前端传一个文件过来,后端用MultipartFile类型接收,然后这个对象直接调用getBytes方法,将数组传到这个方法里,即使最后改对了这个contentType,依然是非法请求。
- 用第二个方法,前端传一个路径过来,后端创建一个File类型的对象,直接把这个file对象传入构造方法,改对了content-type以后,就成功调通了。
这两者的区别,道行浅,看不出来。
/** 通过传一个byte数组来构建 */
public static RequestBody create(final @Nullable MediaType contentType, final byte[] content) {
return create(contentType, content, 0, content.length);
}
/** 通过传一个file对象构建 */
public static RequestBody create(final @Nullable MediaType contentType, final File file) {
if (file == null) throw new NullPointerException("file == null");
return new RequestBody() {
@Override public @Nullable MediaType contentType() {
return contentType;
}
@Override public long contentLength() {
return file.length();
}
@Override public void writeTo(BufferedSink sink) throws IOException {
try (Source source = Okio.source(file)) {
sink.writeAll(source);
}
}
};
}
// 成功构造实例
RequestBody requestBody = RequestBody.create(MediaType.parse("audio/x-wav"),file);