上傳的方式
本文將介紹2中文件上傳的方式:
1.multipart/from-data方式上傳。
2.binary方式上傳。
multipart上傳方式
html代碼
這中上傳方式是我們最常用的上傳方式。比如我們使用網(wǎng)頁(yè)上傳文件,其中html代碼大致為這樣:
<form method="post" enctype="multipart/form-data" action="/upload/single">
文件:<input name="file" type="file"> <br/>
<input name="submit" type="submit" value="提交">
</form>
其中 enctype設(shè)置為multipart/form-data方式。如果是多文件的話,html代碼應(yīng)該是這樣:
<form method="post" enctype="multipart/form-data" action="/upload/multi_file">
<input name="file" type="file"><br>
<input name="file" type="file"><br/>
...
<input name="submit" type="submit" value="提交">
</form>
input標(biāo)簽中的name就是對(duì)應(yīng)的字段,后臺(tái)程序?qū)⒏鶕?jù)這字段取出文件。
具體的報(bào)文
這種方式對(duì)應(yīng)的HTTP報(bào)文如下:
POST /upload/binary HTTP/1.1
Host: 192.168.10.63:8080
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Cache-Control: no-cache
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename=""
Content-Type: image/jpeg
二進(jìn)制文件信息
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename=""
Content-Type:
二進(jìn)制文件信息
------WebKitFormBoundary7MA4YWxkTrZu0gW--
通過(guò)上面的報(bào)文我們看以看出:multipart/form-data方式的一個(gè)重要組成部分請(qǐng)求頭Content-Type必須為:
Content-Type:multipart/form-data;boundarty=一個(gè)32字節(jié)的隨機(jī)數(shù),用來(lái)分割每個(gè)part。
然后每個(gè)Part之間用“雙橫杠”加bundary來(lái)分割,最后一個(gè)Part分割符末尾也要加“雙橫杠”
每個(gè)Part中必須包含Content-Disposition字段來(lái)注明字段文件名等信息,也可以包含Content-Type來(lái)說(shuō)明文件的MeidaType。
Java服務(wù)端接受代碼
/**
* 單個(gè)文件上傳。
* <p>
* 字段名為 file。
*
* @param file 文件
* @return json
*/
@RequestMapping(method = RequestMethod.POST, value = "/single")
public UpLoadResponse singleReceive(@RequestParam("file") MultipartFile file) {
List<PartInfo> partInfos = new ArrayList<PartInfo>();
PartInfo partInfo = new PartInfo();
partInfo.setMediaType(file.getContentType());
partInfo.setSize(file.getSize());
partInfos.add(partInfo);
FileUtil.saveFile(file);
System.out.println("接受到文件====" + file.getOriginalFilename());
UpLoadResponse upLoadResponse = new UpLoadResponse();
upLoadResponse.setPartInfos(partInfos);
upLoadResponse.setMsg("上傳成功");
return upLoadResponse;
}
/**
* 多文件上傳,公用一個(gè)字段 "file"
*
* @param files 文件
* @return json
*/
@RequestMapping(method = RequestMethod.POST, value = "/multi_file")
public UpLoadResponse multiFileReceive(@RequestParam("file") MultipartFile[] files) {
List<PartInfo> partInfos = new ArrayList<PartInfo>();
for (MultipartFile file : files) {
PartInfo partInfo = new PartInfo();
partInfo.setSize(file.getSize());
partInfo.setMediaType(file.getContentType());
partInfos.add(partInfo);
FileUtil.saveFile(file);
System.out.println("接受到文件====" + file.getOriginalFilename());
}
UpLoadResponse upLoadResponse = new UpLoadResponse();
upLoadResponse.setPartInfos(partInfos);
upLoadResponse.setMsg("上傳成功");
return upLoadResponse;
}
/**
* 文件+文本一起上傳,其實(shí)和多文件上傳一樣的。
* <p>
* 字段名為 file、text
*
* @param file 文件
* @param text 文本
* @return json
*/
@RequestMapping(method = RequestMethod.POST, value = "/multi")
public UpLoadResponse multiReceive(@RequestParam("file") MultipartFile file,
@RequestParam("text") String text) {
List<PartInfo> partInfos = new ArrayList<PartInfo>();
PartInfo partInfo = new PartInfo();
partInfo.setMediaType(file.getContentType());
partInfos.add(partInfo);
partInfo.setSize(file.getSize());
// 保存文件
FileUtil.saveFile(file);
System.out.println("接受到文件====" + file.getOriginalFilename());
PartInfo partInfo1 = new PartInfo();
partInfo.setText(text);
partInfo.setSize(text.length());
partInfos.add(partInfo1);
System.out.println("接受到文本====" + text);
UpLoadResponse upLoadResponse = new UpLoadResponse();
upLoadResponse.setPartInfos(partInfos);
upLoadResponse.setMsg("提交成功");
return upLoadResponse;
}
上面的代碼演示了,Java服務(wù)端通過(guò)@RequestParam("file") MultipartFile file就可以獲得文件信息了,如果客戶端傳的Part類(lèi)型為String,也可以直接用String類(lèi)型獲取文本信息。
客戶端使用Retrofit上傳
客戶端這邊使用Retrofit上傳文件可以有2中方式,一種是使用Multipart.Part上傳,另一種直接使用RequestBody上傳。需要注意的是如果使用RequestBody上傳的時(shí)候,我們需要在@Part注解中將 字段名和文件名(filename)拼接出來(lái),如果不拼filename的話,服務(wù)端則會(huì)報(bào)錯(cuò)。
如果是上傳文本信息的話,可以不用拼接“filename" 也可以不用Multipart.Part或RequestBody直接使用String類(lèi)型就行。
例如:
package com.blueberry.multipart.api;
import com.blueberry.multipart.entity.UpLoadResponse;
import java.util.HashMap;
import java.util.List;
import io.reactivex.Observable;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;
import retrofit2.http.PartMap;
/**
* Created by blueberry on 7/6/2017.
* <p>
* 如果使用Multipart.Part需要注意 @Part直接中不要有參數(shù)。
* 如果使用RequestBody需要注意:如果上傳文件name應(yīng)該包含有filename(文件名)這個(gè)字段,負(fù)責(zé)后臺(tái)可能會(huì)出錯(cuò),
* 比如我們要上傳一個(gè)文件我們的name值應(yīng)該為:file";filename="image.jpg;注意前后沒(méi)有雙引號(hào),中間有2個(gè)雙引號(hào),
* 這是因?yàn)镽etrofit會(huì)自動(dòng)幫我們拼接Content-Disposition;它拼接的方式為 form-data; name="我們?cè)O(shè)置的值",如
* 果用MultipartBody.Part我們則不需要這么費(fèi)事的拼接,因?yàn)镸ultipart.Part.createFormData()放我們完成了該操
* 作;
* {@link okhttp3.MultipartBody.Part#createFormData(String, String, RequestBody)}
*/
public interface MultipartApi {
/**
* 單個(gè)文件上傳
*/
String SINGLE = "upload/single";
/**
* 多個(gè)文件上傳(使用同一個(gè)字段名)
*/
String MULTI_FILE = "upload/multi_file";
/**
* 文件+文本一起上傳
*/
String MULTI = "upload/multi";
/**
* 單個(gè)文件上傳,使用MultipartBody.Part。
*
* @param part
* @return
*/
@Multipart
@POST(SINGLE)
Observable<UpLoadResponse> singlePart(@Part MultipartBody.Part part);
/**
* 單個(gè)文件上傳,使用RequestBody。
*
* @param body
* @return
*/
@Multipart
@POST(SINGLE)
Observable<UpLoadResponse> singleRequestBody(@Part("file\";filename=\"image.jpg") RequestBody body);
/**
* 多文件上傳使用 List<MultipartBody.Part>。
*
* @param parts
* @return
*/
@Multipart
@POST(MULTI_FILE)
Observable<UpLoadResponse> multiFilePart(@Part List<MultipartBody.Part> parts);
/**
* 多文件上傳使用 HashMap<String, RequestBody> map。
*
* @param map
* @return
*/
@Multipart
@POST(MULTI_FILE)
Observable<UpLoadResponse> multiFileRequestBody(@PartMap HashMap<String, RequestBody> map);
/**
* 文件+文本上傳。文件使用 RequestBody
*
* @param body
* @param string
* @return
*/
@Multipart
@POST(MULTI)
Observable<UpLoadResponse> multiRequestBody(@Part("file\";filename=\"image.jpg") RequestBody body,
@Part("text") String string);
/**
* 文件+文本上傳。文件使用 MultipartBody.Part上傳
*
* @param part
* @param string
* @return
*/
@Multipart
@POST(MULTI)
Observable<UpLoadResponse> multiPart(@Part MultipartBody.Part part,
@Part("text") String string);
}
上面演示了好幾種方式提交文件,包含:
1.單個(gè)文件上傳,使用RequestBody,和使用MultiBody.Part方式
2.多個(gè)文件上傳(part都使用同一個(gè)字段),RequestBody和MulipartBody.Part的寫(xiě)法。
3.文件+文本上傳,RequestBody和MulipartBody.Part方式的寫(xiě)法。
使用MulipartBody.Part具體的實(shí)現(xiàn):
private MultipartApi mService;
private MultipartRepositoryPartImpl() {
mService = RetrofitHelper
.getInstance()
.getRetrofit()
.create(MultipartApi.class);
}
@Override
public void singleFileUpload(File file, Observer<UpLoadResponse> observer) {
mService.singlePart(MultipartBody.Part
.createFormData("file", "temp.jpg",
RequestBody.create(MediaType.parse("image/jpg"), file)))
.compose(TransformUtil.<UpLoadResponse>applySchedulerTransformer())
.subscribe(observer);
}
使用RequestBody的具體實(shí)現(xiàn):
@Override
public void singleFileUpload(File file, Observer<UpLoadResponse> observer) {
mService.singleRequestBody(RequestBody.create(MediaType.parse("image/jpg"), file))
.compose(TransformUtil.<UpLoadResponse>applySchedulerTransformer())
.subscribe(observer);
}
多文件上傳,RequestBodyt方式的實(shí)現(xiàn):
@Override
public void multiFileUpload(final File[] files, Observer<UpLoadResponse> observer) {
mService.multiFileRequestBody(new HashMap<String, RequestBody>() {
{
for (File file : files) {
put("file\";filename=\"" + file.getName(),
RequestBody.create(MediaType.parse("image/jpg"), file));
}
}
}).compose(TransformUtil.<UpLoadResponse>applySchedulerTransformer())
.subscribe(observer);
}
多文件上傳,MultipartBody.Part方式上傳
@Override
public void multiFileUpload(final File[] files, Observer<UpLoadResponse> observer) {
mService.multiFilePart(new ArrayList<MultipartBody.Part>() {
{
for (File file : files) {
add(MultipartBody.Part.createFormData("file", file.getName(),
RequestBody.create(MediaType.parse("image/jpg"), file)));
}
}
}).compose(TransformUtil.<UpLoadResponse>applySchedulerTransformer())
.subscribe(observer);
}
文件+文本RequestBody實(shí)現(xiàn)
@Override
public void multiUpload(File file, String text, Observer<UpLoadResponse> observer) {
mService.multiRequestBody(RequestBody.create(MediaType.parse("image/jpg"), file), text)
.compose(TransformUtil.<UpLoadResponse>applySchedulerTransformer())
.subscribe(observer);
}
文件+文本MultiaprtBody.Part實(shí)現(xiàn)
@Override
public void multiUpload(File file, String text, Observer<UpLoadResponse> observer) {
mService.multiPart(MultipartBody.Part.createFormData("file", file.getName(), RequestBody
.create(MediaType.parse("image/jpg"), file)), text)
.compose(TransformUtil.<UpLoadResponse>applySchedulerTransformer())
.subscribe(observer);
}
Binary方式上傳
Binary上傳方式,就是整個(gè)請(qǐng)求體就是二進(jìn)制數(shù)據(jù)?。?就是這么粗暴!
報(bào)文形式
POST /upload/binary HTTP/1.1
Host: 192.168.10.63:8080
Content-Type: xxx
Cache-Control: no-cache
二進(jìn)制數(shù)據(jù)
Java服務(wù)端接收
后臺(tái)接受的話,直接拿到HttpServeletRequest#inputStream讀數(shù)據(jù)就好了!
當(dāng)然,如果用戶想傳入文件信息,也可以通過(guò)請(qǐng)求頭來(lái)傳遞。
/**
* 二進(jìn)制上傳。
*
* @param request
* @return
*/
@RequestMapping(method = RequestMethod.POST, value = "/binary")
public UpLoadResponse binaryReceive(HttpServletRequest request) {
String contentType = request.getHeader("Content-Type");
String size = request.getHeader("Content-Length");
try {
InputStream in = request.getInputStream();
FileUtil.saveInputStream(in);
} catch (IOException e) {
e.printStackTrace();
}
List<PartInfo> partInfos = new ArrayList<PartInfo>();
PartInfo partInfo = new PartInfo();
partInfo.setMediaType(contentType + "");
partInfo.setSize(Integer.parseInt(size));
partInfos.add(partInfo);
UpLoadResponse upLoadResponse = new UpLoadResponse();
upLoadResponse.setPartInfos(partInfos);
upLoadResponse.setMsg("二進(jìn)制上傳成功");
return upLoadResponse;
}
``
### 客戶端代碼
```java
public interface BinaryApi {
/**
* binary方式上傳,這種方式整個(gè)請(qǐng)求體直接就是二進(jìn)制數(shù)據(jù)。服務(wù)端只需要拿到request#iputsteam就可以獲得。
*
* @param body
* @return
*/
@POST("upload/binary")
Observable<UpLoadResponse> binary(@Body RequestBody body);
}
實(shí)現(xiàn)代碼:
public void uploadBinary(File file, Observer<UpLoadResponse> observer){
RetrofitHelper.getInstance()
.getRetrofit()
.create(BinaryApi.class)
.binary(RequestBody.create(MediaType.parse("image/jpg"),file))
.compose(TransformUtil.<UpLoadResponse>applySchedulerTransformer())
.subscribe(observer);
}
我們也可以直接傳流,例如:
public void uploadBinary(InputStream input, Observer<UpLoadResponse> observer){
RetrofitHelper.getInstance()
.getRetrofit()
.create(BinaryApi.class)
.binary(RequestBodyUtil.create(MediaType.parse("image/jpg"),input))
.compose(TransformUtil.<UpLoadResponse>applySchedulerTransformer())
.subscribe(observer);
}
RequestBodyUtil.java
public class RequestBodyUtil {
public static RequestBody create(final MediaType mediaType, final InputStream inputStream) {
return new RequestBody() {
@Override
public MediaType contentType() {
return mediaType;
}
@Override
public long contentLength() throws IOException {
return inputStream.available();
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
Source source=null;
try {
source = Okio.source(inputStream);
sink.writeAll(source);
}finally {
if(source!=null){
source.close();
}
}
}
};
}
}