POST请求实现文件传输(浏览器怎么发送post请求)
通过POST
请求以form-data
形式在前后端以及后端服务间传递文件。
通过设置http
请求response
的content-type
,后端以二进制流传递数据给前端。
1 以form-data传数据给后端
1.1 后端数据接收接口定义
后端接口/form-data
定义
@Slf4j @RestController @RequestMapping(value = "/rest") public class FileTransmit { @PostMapping(value = "/form-data") public String formData(HttpServletRequest request, @RequestParam(value = "email", required = false) String email, @RequestParam(value = "file_excel", required = false) MultipartFile multipartFile, UserInfo userInfo) { StandardMultipartHttpServletRequest standardMultipartRequest = (StandardMultipartHttpServletRequest) request; log.info("multipartFile in @RequestParam={}", multipartFile.getOriginalFilename()); MultiValueMap<String, MultipartFile> multiFileMap = standardMultipartRequest.getMultiFileMap(); for (String paramKey : multiFileMap.keySet()) { List<MultipartFile> multipartFileList = multiFileMap.get(paramKey); log.info("MultipartFile key={}, size={}", paramKey, multipartFileList.size()); for (MultipartFile curMultipartFile : multipartFileList) { log.info("MultipartFile key={}, file name={}", paramKey, curMultipartFile.getOriginalFilename()); log.info("MultipartFile in request equals to file in @RequestParam={}", multipartFile.equals(curMultipartFile)); } } log.info("email in @RequestParam={}", email); log.info("userInfo in RequestParam={}", userInfo); Map<String, String[]> paramMap = standardMultipartRequest.getParameterMap(); for (String paramKey : paramMap.keySet()) { log.info("param key={}, size={}, value={}", paramKey, paramMap.get(paramKey).length, paramMap.get(paramKey)); } return "Form Data processed finished!"; } } 复制代码
1.2 postman调试接口
设置请求body
为form-data
类型,可以同时传递文本数据和文件数据,文件key
允许包含多个文件(file_excel
),key
也允许重复(id
, name
, email
)。
从运行结果来看,@RequestParam
注解可以获取form-data
中指定key
的数据,而HttpServletRequest request
则包含了所有的form-data
数据。
由于@RequestParam
修饰的email
和multipartFile
不是数组或者List
类型,文本类型的email
取拼接值,而文件类型的multipartFile
则取第1个value
值。修改接口定义,改为List<MultipartFile> multipartFile
就可以接收多个文件。
1.3 前端传递form-data
以简单的html
演示在页面填写数据,点击提交时调用1.1节定义的接口并把数据以form-data
形式传递给后端。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Upload FormData</title> </head> <body> <form id="Form1" action="http://localhost:8080/rest/form-data" method="post" enctype="multipart/form-data"> <div style="text-align: left"> ID: <input name="id" type="text" /> <br /> ID: <input name="id" type="text" /> <br /> Name: <input name="name" type="text" /> <br /> Name: <input name="name" type="text" /> <br /> Email: <input name="email" type="text" /> <br /> Email: <input name="email" type="text" /> <br /> File: <input id="1" name="file_excel" type="file" multiple="multiple" /> <br /> File: <input id="2" name="file_img" type="file" /> <br /> <input type="submit" value="submit" /> <input type="reset" value="reset" /> </div> </form> </body> </html> 复制代码
1.4 后端传递form-data
展示如何后端直接封装form-data
格式的数据,然后调用1.1节定义的接口,实现后端服务间的文件数据传递。
通过HttpHeaders
设置请求传递的数据类型为form-data
,通过HttpEntity
封装requset header
和request body
,通过RestTemplate
的postForObject
方法调用POST
接口。
使用MultiValueMap
封装form-data
格式的数据,文件类型数据采用FileSystemResource
。
public class FileTransmitTest { private RestTemplate restTemplate; private final String URL_ROOT = "http://localhost:8080/rest"; @Before public void init() { restTemplate = new RestTemplate(); } @Test public void FormDataTest() { MultiValueMap<String, Object> requestBody = new LinkedMultiValueMap(); requestBody.add("file_excel", new FileSystemResource(new File("E:\dataJava\data\city_info.xlsx"))); requestBody.add("file_excel", new FileSystemResource(new File("E:\dataJava\data\user_info.xlsx"))); requestBody.add("file_img", new FileSystemResource(new File("E:\dataJava\data\github.png"))); requestBody.add("email", "town@163.com"); requestBody.add("email", "harden@163.com"); requestBody.add("id", "123"); requestBody.add("id", "12345"); requestBody.add("name", "zhangsan"); requestBody.add("name", "kuangtu"); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA); HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(requestBody, httpHeaders); String response = restTemplate.postForObject(URL_ROOT+"/form-data", requestEntity, String.class); System.out.println(response); } } 复制代码
1.5 传单个文件给后端
演示后端接收单个文件并保存到指定目录。
接口定义
@ResponseBody @PostMapping(value = "/receive/single") public String receiveSingle(MultipartFile multipartFile) throws IOException { if (multipartFile == null) { return "received file is null!"; } log.info("文件content-type={}", multipartFile.getContentType()); log.info("文件大小={}", multipartFile.getSize()); log.info("文件名={}", multipartFile.getName()); log.info("文件原始名={}", multipartFile.getOriginalFilename()); // 保存接收的文件到本地 File destFile = new File("E:\Download\"+multipartFile.getOriginalFilename()); multipartFile.transferTo(destFile); return "Form Data processed finished!"; } 复制代码
接口调用
由于接口定义中没有使用@RequestParam
注解,当form-data
的key
不为参数名multipartFile
时,变量multipartFile
无法初始化,为null
。只有当form-data
的key
设置为multipartFile
时,才能正常初始化。
如果修改接口定义为
public String receiveSingle(@RequestParam(value = "file") MultipartFile multipartFile) throws IOException 复制代码
那么form-data
的key
只能设置为file
才能完成multipartFile
变量的初始化。因此建议@RequestParam
中value
值和变量名保存一致,接口调用时封装参数的key
也用变量名。
1.6 总结
以
form-data
传输数据给后端,需要设置request header
中content-type="multipart/form-data"
。form-data
可以同时传递文本和文件给后端。对于文件类型的
form-data
数据,后端接口以MultiPartFile
类型接收,并可以通过MultiPartFile
的transferTo
将文件保存到本地。对于文本类型的
form-data
数据,可以定义类接收,也可以用基本类型变量逐个接收。Spring
工程后端接口中HttpServletRequest request
包含了所有的form-data
数据。
2 后端传文件给前端
这里以后端传图片给前端使用为例,方法包括以下3种:
后端把图片保存在文件存储服务器上,返回图片的url给前端,前端设置img标签的src=url即可。这种方式应该是主流,但是不想再搞个存储服务器,没有采用这种方式。
后端以把图片的base64编码以字符串的形式返给前端,前端再解析为图片展示。这种方式虽然存在大图片有可能被截断的肯,但是来得方便,选用了这种方式。
后端以把图片的二进制流以字节数组的形式返给前端,前端再解析为图片展示。返回二进制流不仅适用于图片,还适用于音视频。
2.1 以base64编码传输
后端接口定义
@CrossOrigin //允许跨域访问 @GetMapping(value = "/get-img-code") public String getImageBase64(String imageName) throws IOException { log.info("request param={}", imageName); String imgRootPath = "E:\dataJava\data\"; File imgFile = new File(imgRootPath + imageName + ".png"); InputStream inStream =new FileInputStream(imgFile); byte[] imgBytes = new byte[(int) imgFile.length()]; //创建合适文件大小的数组 inStream.read(imgBytes); //读取文件里的内容到b[]数组 inStream.close(); log.info("image size={}", imgBytes.length); BASE64Encoder encoder = new BASE64Encoder(); return encoder.encodeBuffer(imgBytes); } 复制代码
2.2 以二进制流传输
后端接口定义
@CrossOrigin //允许跨域访问 @GetMapping(value = "/get-img-byte") public void getImageByte(HttpServletRequest request, HttpServletResponse response, String imageName) throws IOException { String imgRootPath = "E:\dataJava\data\"; File imgFile = new File(imgRootPath + imageName + ".png"); InputStream inStream =new FileInputStream(imgFile); byte imgBytes[] = new byte[(int) imgFile.length()]; //创建合适文件大小的数组 inStream.read(imgBytes); //读取文件里的内容到b[]数组 inStream.close(); response.setContentType("application/octet-stream;charSet=UTF-8"); response.setContentLength(imgBytes.length); try (InputStream inputStream = new ByteArrayInputStream(imgBytes); OutputStream outputStream = response.getOutputStream()) { IOUtils.copy(inputStream, outputStream); outputStream.flush(); }catch (IOException e) { log.error("{}", e.getMessage()); } } 复制代码
2.3 html接收并展示
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"> </script> </head> <body> <div id="main" style="width:100%;"> <div id="left" style="width:50%;float:left;"> <p>base64 image</p> <p><button id="getBase64" onclick="getImgBase64()">获取并展示图片</button> </p> <img id="base64_img" src="" alt="" width="" height=""> </div> <div id="right" style="width:50%;float:left;"> <p>byte image</p> <img id="byte_img" src="http://localhost:8080/rest/get-img-byte?imageName=github" alt="" width="70%" height="70%"> </div> </div> <script> function getImgBase64() { $.ajax({ url : "http://localhost:8080/rest/get-img-code?imageName=github", type : 'GET', contentType : false, //必须false才会自动加上正确的Content-Type success : function(result) { //jquery请求返回的结果好像都是字符串类型 console.log("reponse result:", result); var src = 'data:image/png;base64,' + result; $("#base64_img").attr('src', src); $("#base64_img").css("width", "70%"); $("#base64_img").css("height", "70%"); }, error : function(result) { console.log("reponse result:", result); alert("Post Faile!"); } }); } </script> </body> </html> 复制代码
运行效果:
附:HTTP Headers
HTTP 消息头允许客户端和服务器通过 request和 response传递附加信息。一个请求头由名称(不区分大小写)后跟一个冒号“:”,冒号后跟具体的值(不带换行符)组成。
根据不同上下文,可将消息头分为:
General headers: 同时适用于请求和响应消息,但与最终消息主体中传输的数据无关的消息头。
Request headers: 包含更多有关要获取的资源或客户端本身信息的消息头。
Response headers: 包含有关响应的补充信息,如其位置或服务器本身(名称和版本等)的消息头。
Entity headers: 包含有关实体主体的更多信息,比如主体长(Content-Length)度或其MIME类型。
Request headers中
Accept:表示浏览器告诉服务端,浏览器可以接收的数据类型。
Accept-Encoding:表示浏览器告诉服务端,浏览器可以接收的数据的编码方式。
Accept-Language:表示浏览器告诉服务端,浏览器可以接收的数据语言,通常用于做国际化。
Content-Type:POST和PUT请求body的文件类型。
Reponse headers中
Content-Type:接口返回的数据类型。
Content-Length:octets (8-bit bytes)中返回body的长度
作者:town
链接:https://juejin.cn/post/7048869287716454407