上傳的方式
本文將介紹2中文件上傳的方式:
1.multipart/from-data方式上傳。
2.binary方式上傳。
multipart上傳方式
html代碼
這中上傳方式是我們最常用的上傳方式。比如我們使用網頁上傳文件,其中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設置為multipart/form-data
方式。如果是多文件的話,html代碼應該是這樣:
<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標簽中的name
就是對應的字段,后臺程序將根據這字段取出文件。
具體的報文
這種方式對應的HTTP報文如下:
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
二進制文件信息
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename=""
Content-Type:
二進制文件信息
------WebKitFormBoundary7MA4YWxkTrZu0gW--
通過上面的報文我們看以看出:multipart/form-data方式的一個重要組成部分請求頭Content-Type必須為:
Content-Type:multipart/form-data;boundarty=一個32字節的隨機數,用來分割每個part
。
然后每個Part之間用“雙橫杠”加bundary來分割,最后一個Part分割符末尾也要加“雙橫杠”
每個Part中必須包含Content-Disposition
字段來注明字段文件名等信息,也可以包含Content-Type
來說明文件的MeidaType。
Java服務端接受代碼
/**
* 單個文件上傳。
* <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;
}
/**
* 多文件上傳,公用一個字段 "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;
}
/**
* 文件+文本一起上傳,其實和多文件上傳一樣的。
* <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服務端通過@RequestParam("file") MultipartFile file
就可以獲得文件信息了,如果客戶端傳的Part類型為String,也可以直接用String類型獲取文本信息。
客戶端使用Retrofit上傳
客戶端這邊使用Retrofit上傳文件可以有2中方式,一種是使用Multipart.Part
上傳,另一種直接使用RequestBody
上傳。需要注意的是如果使用RequestBody
上傳的時候,我們需要在@Part
注解中將 字段名和文件名(filename)拼接出來,如果不拼filename的話,服務端則會報錯。
如果是上傳文本信息的話,可以不用拼接“filename" 也可以不用Multipart.Part
或RequestBody
直接使用String類型就行。
例如:
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直接中不要有參數。
* 如果使用RequestBody需要注意:如果上傳文件name應該包含有filename(文件名)這個字段,負責后臺可能會出錯,
* 比如我們要上傳一個文件我們的name值應該為:file";filename="image.jpg;注意前后沒有雙引號,中間有2個雙引號,
* 這是因為Retrofit會自動幫我們拼接Content-Disposition;它拼接的方式為 form-data; name="我們設置的值",如
* 果用MultipartBody.Part我們則不需要這么費事的拼接,因為Multipart.Part.createFormData()放我們完成了該操
* 作;
* {@link okhttp3.MultipartBody.Part#createFormData(String, String, RequestBody)}
*/
public interface MultipartApi {
/**
* 單個文件上傳
*/
String SINGLE = "upload/single";
/**
* 多個文件上傳(使用同一個字段名)
*/
String MULTI_FILE = "upload/multi_file";
/**
* 文件+文本一起上傳
*/
String MULTI = "upload/multi";
/**
* 單個文件上傳,使用MultipartBody.Part。
*
* @param part
* @return
*/
@Multipart
@POST(SINGLE)
Observable<UpLoadResponse> singlePart(@Part MultipartBody.Part part);
/**
* 單個文件上傳,使用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.單個文件上傳,使用RequestBody,和使用MultiBody.Part方式
2.多個文件上傳(part都使用同一個字段),RequestBody和MulipartBody.Part的寫法。
3.文件+文本上傳,RequestBody和MulipartBody.Part方式的寫法。
使用MulipartBody.Part
具體的實現:
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
的具體實現:
@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
方式的實現:
@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
實現
@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
實現
@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上傳方式,就是整個請求體就是二進制數據!! 就是這么粗暴!
報文形式
POST /upload/binary HTTP/1.1
Host: 192.168.10.63:8080
Content-Type: xxx
Cache-Control: no-cache
二進制數據
Java服務端接收
后臺接受的話,直接拿到HttpServeletRequest#inputStream讀數據就好了!
當然,如果用戶想傳入文件信息,也可以通過請求頭來傳遞。
/**
* 二進制上傳。
*
* @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("二進制上傳成功");
return upLoadResponse;
}
``
### 客戶端代碼
```java
public interface BinaryApi {
/**
* binary方式上傳,這種方式整個請求體直接就是二進制數據。服務端只需要拿到request#iputsteam就可以獲得。
*
* @param body
* @return
*/
@POST("upload/binary")
Observable<UpLoadResponse> binary(@Body RequestBody body);
}
實現代碼:
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();
}
}
}
};
}
}