SpringCloud+Feign环境下文件上传与form-data同时存在的解决办法(2)
书接上文。
上文中描述了如何在 SpringCloud+Feign环境下上传文件与form-data同时存在的解决办法,实践证明基本可行,但却会引入其他问题。
主要导致的后果是:
1. 无法与普通Feign方法并存
2. 几率性(不确定条件下)导致其他form-data类型参数无法识别,无法正常工作,错误信息大致如下:
- org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'file' is not present
分析原因发现是Feign的Encoder体系中缺乏对应的配置从而无法工作;但将这些Encoder一一补上却过于困难,因此,决定换一个思路,使用Spring技术解决该问题。
该问题的本质是controller层传参时参数的编解码问题,因此,应该属于HttpMessageConverter范畴的事情,这参考SpringEncoder的代码即可得知。
最终,解决方案如下:
1. 去掉依赖,因为不再需要
- <dependency>
- <groupId>io.github.openfeign.form</groupId>
- <artifactId>feign-form-spring</artifactId>
- <version>3.2.2</version>
- </dependency>
- <dependency>
- <groupId>io.github.openfeign.form</groupId>
- <artifactId>feign-form</artifactId>
- <version>3.2.2</version>
- </dependency>
2. 去掉类 FeignSpringFormEncoder ,不再需要
3. 注解 RequestPartParam 、类 RequestPartParamParameterProcessor 以及 bean 的定义均与上文保持一致
4. 参考类 FormHttpMessageConverter 添加类 LinkedHashMapFormHttpMessageConverter ,并注意使用 @Conponent注解进行Spring托管。代码如下:
- import org.springframework.core.io.Resource;
- import org.springframework.http.HttpEntity;
- import org.springframework.http.HttpHeaders;
- import org.springframework.http.HttpInputMessage;
- import org.springframework.http.HttpOutputMessage;
- import org.springframework.http.MediaType;
- import org.springframework.http.StreamingHttpOutputMessage;
- import org.springframework.http.converter.AbstractHttpMessageConverter;
- import org.springframework.http.converter.ByteArrayHttpMessageConverter;
- import org.springframework.http.converter.HttpMessageConverter;
- import org.springframework.http.converter.HttpMessageNotReadableException;
- import org.springframework.http.converter.HttpMessageNotWritableException;
- import org.springframework.http.converter.ResourceHttpMessageConverter;
- import org.springframework.http.converter.StringHttpMessageConverter;
- import org.springframework.stereotype.Component;
- import org.springframework.util.Assert;
- import org.springframework.util.MimeTypeUtils;
- import org.springframework.web.multipart.MultipartFile;
- import javax.mail.internet.MimeUtility;
- import java.io.IOException;
- import java.io.OutputStream;
- import java.io.UnsupportedEncodingException;
- import java.nio.charset.Charset;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.LinkedHashMap;
- import java.util.List;
- import java.util.Map;
- /**
- * 参考 FormHttpMessageConverter
- */
- @Component
- public class LinkedHashMapFormHttpMessageConverter implements HttpMessageConverter<LinkedHashMap<String, ?>> {
- public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
- private List<MediaType> supportedMediaTypes = new ArrayList<MediaType>();
- private List<HttpMessageConverter<?>> partConverters = new ArrayList<HttpMessageConverter<?>>();
- private Charset charset = DEFAULT_CHARSET;
- private Charset multipartCharset;
- public LinkedHashMapFormHttpMessageConverter() {
- this.supportedMediaTypes.add(MediaType.MULTIPART_FORM_DATA);
- StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
- stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
- this.partConverters.add(new ByteArrayHttpMessageConverter());
- this.partConverters.add(stringHttpMessageConverter);
- this.partConverters.add(new ResourceHttpMessageConverter());
- MultipartFileHttpMessageConverter multipartFileHttpMessageConverter = new MultipartFileHttpMessageConverter();
- this.partConverters.add(multipartFileHttpMessageConverter);
- applyDefaultCharset();
- }
- @Override
- public List<MediaType> getSupportedMediaTypes() {
- return Collections.unmodifiableList(this.supportedMediaTypes);
- }
- public void setPartConverters(List<HttpMessageConverter<?>> partConverters) {
- Assert.notEmpty(partConverters, "'partConverters' must not be empty");
- this.partConverters = partConverters;
- }
- public void addPartConverter(HttpMessageConverter<?> partConverter) {
- Assert.notNull(partConverter, "'partConverter' must not be null");
- this.partConverters.add(partConverter);
- }
- public void setCharset(Charset charset) {
- if (charset != this.charset) {
- this.charset = (charset != null ? charset : DEFAULT_CHARSET);
- applyDefaultCharset();
- }
- }
- private void applyDefaultCharset() {
- for (HttpMessageConverter<?> candidate : this.partConverters) {
- if (candidate instanceof AbstractHttpMessageConverter) {
- AbstractHttpMessageConverter<?> converter = (AbstractHttpMessageConverter<?>) candidate;
- // Only override default charset if the converter operates with a charset to begin with...
- if (converter.getDefaultCharset() != null) {
- converter.setDefaultCharset(this.charset);
- }
- }
- }
- }
- public void setMultipartCharset(Charset charset) {
- this.multipartCharset = charset;
- }
- @Override
- public boolean canRead(Class<?> clazz, MediaType mediaType) {
- return false;
- }
- @Override
- public boolean canWrite(Class<?> clazz, MediaType mediaType) {
- if (!LinkedHashMap.class.isAssignableFrom(clazz)) {
- return false;
- }
- if (mediaType == null || MediaType.ALL.equals(mediaType)) {
- return false;
- }
- for (MediaType supportedMediaType : getSupportedMediaTypes()) {
- if (supportedMediaType.isCompatibleWith(mediaType)) {
- return true;
- }
- }
- return false;
- }
- @Override
- public LinkedHashMap<String, String> read(Class<? extends LinkedHashMap<String, ?>> clazz,
- HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
- throw new HttpMessageNotReadableException("Not supportted for read.");
- }
- @Override
- @SuppressWarnings("unchecked")
- public void write(LinkedHashMap<String, ?> map, MediaType contentType, HttpOutputMessage outputMessage)
- throws IOException, HttpMessageNotWritableException {
- writeMultipart((LinkedHashMap<String, Object>) map, outputMessage);
- }
- private void writeMultipart(final LinkedHashMap<String, Object> parts, HttpOutputMessage outputMessage) throws IOException {
- final byte[] boundary = generateMultipartBoundary();
- Map<String, String> parameters = Collections.singletonMap("boundary", new String(boundary, "US-ASCII"));
- MediaType contentType = new MediaType(MediaType.MULTIPART_FORM_DATA, parameters);
- HttpHeaders headers = outputMessage.getHeaders();
- headers.setContentType(contentType);
- if (outputMessage instanceof StreamingHttpOutputMessage) {
- StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
- streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {
- @Override
- public void writeTo(OutputStream outputStream) throws IOException {
- writeParts(outputStream, parts, boundary);
- writeEnd(outputStream, boundary);
- }
- });
- }
- else {
- writeParts(outputMessage.getBody(), parts, boundary);
- writeEnd(outputMessage.getBody(), boundary);
- }
- }
- private void writeParts(OutputStream os, LinkedHashMap<String, Object> parts, byte[] boundary) throws IOException {
- for (Map.Entry<String, Object> entry : parts.entrySet()) {
- String name = entry.getKey();
- Object part = entry.getValue();
- if (part != null) {
- writeBoundary(os, boundary);
- writePart(name, getHttpEntity(part), os);
- writeNewLine(os);
- }
- }
- }
- @SuppressWarnings("unchecked")
- private void writePart(String name, HttpEntity<?> partEntity, OutputStream os) throws IOException {
- Object partBody = partEntity.getBody();
- Class<?> partType = partBody.getClass();
- HttpHeaders partHeaders = partEntity.getHeaders();
- MediaType partContentType = partHeaders.getContentType();
- for (HttpMessageConverter<?> messageConverter : this.partConverters) {
- if (messageConverter.canWrite(partType, partContentType)) {
- HttpOutputMessage multipartMessage = new MultipartHttpOutputMessage(os);
- multipartMessage.getHeaders().setContentDispositionFormData(name, getFilename(partBody));
- if (!partHeaders.isEmpty()) {
- multipartMessage.getHeaders().putAll(partHeaders);
- }
- ((HttpMessageConverter<Object>) messageConverter).write(partBody, partContentType, multipartMessage);
- return;
- }
- }
- throw new HttpMessageNotWritableException("Could not write request: no suitable HttpMessageConverter " +
- "found for request type [" + partType.getName() + "]");
- }
- protected byte[] generateMultipartBoundary() {
- return MimeTypeUtils.generateMultipartBoundary();
- }
- protected HttpEntity<?> getHttpEntity(Object part) {
- return (part instanceof HttpEntity ? (HttpEntity<?>) part : new HttpEntity<Object>(part));
- }
- protected String getFilename(Object part) {
- if (part instanceof Resource) {
- Resource resource = (Resource) part;
- String filename = resource.getFilename();
- if (filename != null && this.multipartCharset != null) {
- filename = MimeDelegate.encode(filename, this.multipartCharset.name());
- }
- return filename;
- } else if (part instanceof MultipartFile) {
- MultipartFile multipartFile = (MultipartFile) part;
- String filename = multipartFile.getName();
- if (filename == null || filename.isEmpty()) {
- filename = multipartFile.getOriginalFilename();
- }
- return filename;
- }
- else {
- return null;
- }
- }
- private void writeBoundary(OutputStream os, byte[] boundary) throws IOException {
- os.write('-');
- os.write('-');
- os.write(boundary);
- writeNewLine(os);
- }
- private static void writeEnd(OutputStream os, byte[] boundary) throws IOException {
- os.write('-');
- os.write('-');
- os.write(boundary);
- os.write('-');
- os.write('-');
- writeNewLine(os);
- }
- private static void writeNewLine(OutputStream os) throws IOException {
- os.write('\r');
- os.write('\n');
- }
- private static class MultipartHttpOutputMessage implements HttpOutputMessage {
- private final OutputStream outputStream;
- private final HttpHeaders headers = new HttpHeaders();
- private boolean headersWritten = false;
- public MultipartHttpOutputMessage(OutputStream outputStream) {
- this.outputStream = outputStream;
- }
- @Override
- public HttpHeaders getHeaders() {
- return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
- }
- @Override
- public OutputStream getBody() throws IOException {
- writeHeaders();
- return this.outputStream;
- }
- private void writeHeaders() throws IOException {
- if (!this.headersWritten) {
- for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) {
- byte[] headerName = getAsciiBytes(entry.getKey());
- for (String headerValueString : entry.getValue()) {
- byte[] headerValue = getAsciiBytes(headerValueString);
- this.outputStream.write(headerName);
- this.outputStream.write(':');
- this.outputStream.write(' ');
- this.outputStream.write(headerValue);
- writeNewLine(this.outputStream);
- }
- }
- writeNewLine(this.outputStream);
- this.headersWritten = true;
- }
- }
- private byte[] getAsciiBytes(String name) {
- try {
- return name.getBytes("US-ASCII");
- }
- catch (UnsupportedEncodingException ex) {
- // Should not happen - US-ASCII is always supported.
- throw new IllegalStateException(ex);
- }
- }
- }
- private static class MimeDelegate {
- public static String encode(String value, String charset) {
- try {
- return MimeUtility.encodeText(value, charset, null);
- }
- catch (UnsupportedEncodingException ex) {
- throw new IllegalStateException(ex);
- }
- }
- }
- }
5. 参考类 ResourceHttpMessageConverter 添加类 MultipartFileHttpMessageConverter ,代码如下:
- import org.springframework.http.HttpInputMessage;
- import org.springframework.http.HttpOutputMessage;
- import org.springframework.http.MediaType;
- import org.springframework.http.converter.AbstractHttpMessageConverter;
- import org.springframework.http.converter.HttpMessageNotReadableException;
- import org.springframework.http.converter.HttpMessageNotWritableException;
- import org.springframework.util.StreamUtils;
- import org.springframework.web.multipart.MultipartFile;
- import java.io.FileNotFoundException;
- import java.io.IOException;
- import java.io.InputStream;
- public class MultipartFileHttpMessageConverter extends AbstractHttpMessageConverter<MultipartFile> {
- public MultipartFileHttpMessageConverter() {
- super(MediaType.APPLICATION_OCTET_STREAM);
- }
- @Override
- protected boolean supports(Class<?> clazz) {
- return MultipartFile.class.isAssignableFrom(clazz);
- }
- @Override
- protected MultipartFile readInternal(Class<? extends MultipartFile> clazz, HttpInputMessage inputMessage)
- throws IOException, HttpMessageNotReadableException {
- throw new HttpMessageNotReadableException("Not supportted for read.");
- }
- @Override
- protected MediaType getDefaultContentType(MultipartFile multipartFile) {
- try {
- String contentType = multipartFile.getContentType();
- MediaType mediaType = MediaType.valueOf(contentType);
- if (mediaType != null) {
- return mediaType;
- }
- } catch (Exception ex) {
- }
- return MediaType.APPLICATION_OCTET_STREAM;
- }
- @Override
- protected Long getContentLength(MultipartFile multipartFile, MediaType contentType) throws IOException {
- long contentLength = multipartFile.getSize();
- return (contentLength < 0 ? null : contentLength);
- }
- @Override
- protected void writeInternal(MultipartFile multipartFile, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
- writeContent(multipartFile, outputMessage);
- }
- protected void writeContent(MultipartFile multipartFile, HttpOutputMessage outputMessage)
- throws IOException, HttpMessageNotWritableException {
- try {
- InputStream in = multipartFile.getInputStream();
- try {
- StreamUtils.copy(in, outputMessage.getBody());
- }
- catch (NullPointerException ex) {
- // ignore, see SPR-13620
- }
- finally {
- try {
- in.close();
- }
- catch (Throwable ex) {
- // ignore, see SPR-12999
- }
- }
- }
- catch (FileNotFoundException ex) {
- // ignore, see SPR-12999
- }
- }
- }
按照上述配置即可工作,但需要严格注意:
由于使用 LinkedHashMapFormHttpMessageConverter 的原因,会导致:
当某次http请求的参数或者返回值中包含 LinkedHashMap 且其请求的MediaType兼容 MULTIPART_FORM_DATA 时,该次参数或者返回值会错误的被 LinkedHashMapFormHttpMessageConverter 处理,但此时很显然是不正确的处理方式。
对应的办法:避开该情况,或者有更好的办法请务必分享给我,不胜感激。
SpringCloud+Feign环境下文件上传与form-data同时存在的解决办法(2)的更多相关文章
- SpringCloud+Feign环境下文件上传与form-data同时存在的解决办法
最近项目转型使用SpringCloud框架下的微服务架构,各微服务之间使用Feign进行调用.期间,发现若被调用方法涉及到文件上传且仅存在单个文件时,一切正常,代码片段如下: @RequestMapp ...
- 不同环境下文件上传Uncaught SyntaxError: Unexpected end of input
很奇怪的问题,相同的代码和相同的数据,在两台linux服务器上执行文件上传,一台正常上传,一台在ftl页面 报:Uncaught SyntaxError: Unexpected end of inpu ...
- ie下文件上传无权访问的问题
最近项目遇到个问题,ie下文件上传无权访问,在网上找了很久才找到答案,原来是因为ie下不能用js触发input=file的点击事件,必须手动点击才可以.
- [原创]Struts2奇葩环境任意文件上传工具(解决菜刀无法传文件或上传乱码等问题)
上面这问题问得好 1 不知道大家有没碰到有些Strus2站点 上传JSP后访问404 或者503 注意我说的是404或503不是403(要是403换个css/img等目录或许可以) 但 ...
- Web下文件上传下载的路径问题
工程结构
- linux 下文件上传的两种工具(XFTP5和Putty之pscp)方式
一.使用XFTP(,需要先在LINUX上安装启用FTP服务) 然后,在WINDOWS上启动XFPT6客户端,将下载的文件上传至LINUX 指定目录: 二.使用PUTTY软件安装目录下的PSCP命令 1 ...
- asp.net mvc下文件上传
典型的文件上传表单 <form action="/File" enctype="multipart/form-data" method="pos ...
- SpringBoot下文件上传与下载的实现
原文:http://blog.csdn.net/colton_null/article/details/76696674 SpringBoot后台如何实现文件上传下载? 最近做的一个项目涉及到文件上传 ...
- IE下文件上传, SCRIPT5: 拒绝访问 问题
最近遇到一个比较奇葩的问题,某些ie浏览器在页面中上传文件时,无法上传.查看控制台报错: SCRIPT5: 拒绝访问. jquery-3.2.1.min.js, 行4 字符5725 .并且我的最新版I ...
随机推荐
- OpenCL科普及在ubuntu 16.04 LTS上的安装
OpenCL(Open Computing Language,开放计算语言)是一个为异构平台编写程序的框架,此异构平台可由CPU.GPU.DSP.FPGA或其他类型的处理器與硬體加速器所组成.Open ...
- 【原创】InputStream has already been read - do not use InputStreamResource if a stream needs to be read multiple times
一.背景 基于SpringBoot 构建了一个http文件下载服务,检查tomcat access 发现偶尔出现500 状态码的请求,检查抛出的异常堆栈 2019-03-20 10:03:14,273 ...
- [视频]K8飞刀 shellcode loader演示教程
[视频]K8飞刀 shellcode loader演示教程 https://pan.baidu.com/s/1eQ77lPw
- 什么是js的严格模式
设立严格模式的原因: - 消除Javascript语法的一些不合理.不严谨之处,减少一些怪异行为; - 消除代码运行的一些不安全之处,保证代码运行的安全: - 提高编译器效率,增加运行速度: - 为未 ...
- html css 其他常用 onclick跳转
opacity: 0.5. 0-1 透明度 cursor: pointer;手指 clear:both 清楚浮动 我是医生不是人 文本内容超出框word-wrap:break-word; word-b ...
- 如何将打包好的文件做成一个APP
本文主要是用来简短的对做成一个APP进行说明,内容可能不是多详细,但会给出具体思路.(仅供参考) 因为各种打包和生成APP的方式多样,今天这里仅仅对用Hbuilder打包进行说明. 1.首先当然需要一 ...
- redhat 下搭建网站
1.修改yum源 把iso重新挂载到/media路径下,media是个只读的文件 vi /etc/yum.repos.d/rhel-source.repo //编辑yum源文件 ...
- VirtualBox centos7扩容
有时候扩容还真不如重新建立一个大硬盘的系统,但是如果你安装了好多东西的话,那还是来扩容一下吧. 查看磁盘格式 在virtualBox中右键点击虚拟机->设置->存储,如 ...
- 深入理解RDD原理
首先我们来了解一些Spark的优势:1.每一个作业独立调度,可以把所有的作业做一个图进行调度,各个作业之间相互依赖,在调度过程中一起调度,速度快.2.所有过程都基于内存,所以通常也将Spark称作是基 ...
- CSS学习笔记11 CSS背景
background-color:背景色 前面我们经常用background-color这个属性来设置元素的背景色,例如下面这条css可将段落的背景色设置为灰色 p {background-color ...