上傳頭像的問題
8月份的時候曾經(jīng)在項目中遇到要上傳圖片到服務器的問題,其實需求很典型:就是用戶需要上傳自己的頭像银酬。我們的項目使用的網(wǎng)絡框架是很流行的Retrofit,而網(wǎng)絡上常見的Retrofit的教程告訴我們正確的定義服務的姿勢是像這樣的:
public interface MusicListService {
@GET("music/listMusic.do")
Observable<List<Music>> getMusicList(@Query("start") int start, @Query("size") int size, @Query("categoryId") long parent_id);
}
這樣形式的接口明顯不能用來上傳頭像凰荚,所以我就需要琢磨怎么實現(xiàn)圖片的上傳灵疮。實際上關于用Retrofit上傳圖片的博客在網(wǎng)上實在太多,為什么我還要單獨寫一篇拟蜻,主要是記錄一下踩過的坑绎签。而且,很多時候我們只知道怎么做酝锅,卻不知道為什么要這樣做诡必,實在不應該。只說怎么做的博客太多搔扁,我想指出來為什么這么做爸舒。
上傳實踐
以下給出一個同時傳遞字符串參數(shù)和一張圖片的服務接口的定義:
public interface UploadAvatarService {
@Multipart
@POST("user/updateAvatar.do")
Call<Response> updateAvatar (@Query("des") String description, @Part("uploadFile\"; filename=\"test.jpg\"") RequestBody imgs );
}
然后在實例化UploadAvatarService的地方,調(diào)用以下代碼實現(xiàn)圖片上傳稿蹲。以下函數(shù)被我刪改過扭勉,可能無法一次完美運行,但大體是沒錯的苛聘。
private void uploadFile(final String filename) {
UploadAvatarService service = RetrofitUtil.createService(getContext(), UploadAvatarService.class);
final File file = new File(filename);
RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);
Call<Response> call = service.updateInfo(msg, requestBody );
call.enqueue(new Callback<Response>() {
@Override
public void onResponse(Call<Response> call, retrofit2.Response<Response> response) {
//涂炎。。设哗。唱捣。。
}
@Override
public void onFailure(Call<Response> call, Throwable t) {
//网梢。震缭。。
}
});
}
POST實際提交的內(nèi)容
歷史上(1995年之前)澎粟,POST上傳數(shù)據(jù)的方式是很單一的蛀序,就像我們用GET方法傳參一樣欢瞪,參數(shù)名=參數(shù)值,參數(shù)和參數(shù)之間用&隔開徐裸。就像這樣:
param1=abc¶m2=def
只不過GET的參數(shù)列表放在URL里面遣鼓,像這樣:
http://url:port?param1=abc¶m2=def
而用POST傳參時,參數(shù)列表放到HTTP報文的請求體里了重贺,此時的請求頭長這樣骑祟。
POST http://www.test.org HTTP/1.1
Content-Type:application/x-www-form-urlencoded; charset=UTF-8
以前POST只支持純文本的傳輸,上傳文件就很惱火气笙,奇技淫巧應該也可以實現(xiàn)上傳文件次企,比如將二進制流當成文本傳輸。后來互聯(lián)網(wǎng)工程任務組(IETF)在1995年11月推出了RFC1867潜圃。在RFC1867中提出了基于表單的文件上傳標準缸棵。
This proposal makes two changes to HTML:
- Add a FILE option for the TYPE attribute of INPUT.
- Allow an ACCEPT attribute for INPUT tag, which is a list of
media types or type patterns allowed for the input.
In addition, it defines a new MIME media type, multipart/form-data,
and specifies the behavior of HTML user agents when interpreting a
form with
ENCTYPE="multipart/form-data" and/or <INPUT type="file">
tags.
簡而言之,就是要增加文件上傳的支持谭期,另外還為<input>標簽添加一個叫做accept的屬性堵第,同時定義了一個新的MIME類型,叫做multipart/form-data隧出。而multipart踏志,則是在我們傳輸非純文本的數(shù)據(jù)時采用的數(shù)據(jù)格式,比如我們上傳一個文件時胀瞪,HTTP請求頭像這樣:
POST http://www.test.org HTTP/1.1
Content-Type:multipart/form-data; boundary=---------------------------7d52b13b519e2
如果要傳輸兩張圖片针余,請求體長這樣:
-----------------------------7d52b13b519e2
Content-Disposition: form-data; name="upload1"; filename="test.jpg"
Content-Type: image/jpeg
/**此處應是test.jpg的二進制流**/
-----------------------------7d52b13b519e2
Content-Disposition: form-data; name="upload2"; filename="test2.jpg"
Content-Type: image/jpeg
/**此處應是test2.jpg的二進制流**/
-----------------------------7d52b13b519e2--
看到請求體里面,分隔兩張圖片的二進制流和描述信息的是一段長長的橫線和一段十六進制表示的數(shù)字凄诞,這個東西稱作boundary圆雁,在RFC1867中提到了:
3.3 use of multipart/form-data
The definition of multipart/form-data is included in section 7. A
boundary is selected that does not occur in any of the data. (This
selection is sometimes done probabilisticly.) Each field of the form
is sent, in the order in which it occurs in the form, as a part of
the multipart stream. Each part identifies the INPUT name within the
original HTML form. Each part should be labelled with an appropriate
content-type if the media type is known (e.g., inferred from the file
extension or operating system typing information) or as
application/octet-stream.
可以看到這串數(shù)字就是為了分隔不同的part的,這串數(shù)字可以是隨機生成的帆谍,但是不能出現(xiàn)在提交的表單數(shù)據(jù)里(這個很好理解摸柄,如果跟表單數(shù)據(jù)中的某一部分沖突了,就起不到分隔的作用了)既忆。
回歸那段代碼
在用Retrofit上傳文件的那段代碼中,使用@Multipart說明將使用Multipart格式提交數(shù)據(jù)嗦玖,而@Part這個注解后面跟的參數(shù)則是以下請求頭中加粗的部分:
-----------------------------7d52b13b519e2
Content-Disposition: form-data; name="upload1"; filename="test.jpg"
Content-Type: image/jpeg
這段加粗的部分放到Java代碼中需要進行轉(zhuǎn)義(對雙引號和分號轉(zhuǎn)義)患雇,就得到了如下的一個參數(shù)聲明:
@Part("uploadFile\"; filename=\"test.jpg\"") RequestBody imgs
所以@Part后面跟的參數(shù)雖然看起來很奇怪,但如果知道了實際傳輸?shù)臄?shù)據(jù)格式宇挫,這一寫法就很好理解了苛吱。