微服务之间上传文件,中文文件名乱码问题的解决

前端日子,在公司开发一个项目的时候,涉及到了微服务之间的文件上传问题,在此跟大家说一说。

首先讲一下这个项目的整体情况,整体是使用Spring Cloud搭建的微服务,分为多个模块,文件上传被分割成了独立的独立的模块,主要是通过RestTemplate来进行微服务之间请求的转发。然后就是使用Zuul来作为网关。项目中使用到的Spring的版本是4.3.10.RELEASE。

我遇到的问题主要就是中文文件文件名的乱码问题,当请求达到文件中心的时候,文件名变成了???。当我在页面上点击上传文件按钮的时候,现需要经过网关,然后需要经过与页面有关的微服务,最终才能到达文件中心的微服务。

于是,我进行了debug调试,首先是在网关这一层进行debug,先在网关的Filter中打断点,发现一切正常,放开这个断点进入到页面微服务,发现已经出现了异常——文件名已经乱码了。在网络上查找了一番,确实经过Zuul网关上传文件会出现问题,通用的解决办法是,前端页面发起的请求中需要加上/zuul前缀,网关这里将所有带/zuul请求的请求都走zuulservlet,不带zuul的请求都走spring mvc的dispatchservlet。具体的可查询下面的连接:

https://github.com/spring-cloud/spring-cloud-netflix/issues/546

其实还有一个更为简单的解决方案:就是在zuul的配置文件中添加一个如下的属性:

zuul.servlet-path=/

我采用的是加/zuul前缀的方式,修改完之后,果然到达页面微服务的请求文件名没有乱码,但是却没想到,当请求到达文件中心时,又出现了乱码。于是只能跟进代码。先进到RestTemplate中,先看doExecute方法的代码:

很明显 request.execute是在发送请求,那么我们看一下在请求被发送之前,到底做了些什么操作。也就是requestCallback.doWithRequest(request)这行代码。跟进去:

发现是一个内部类——HttpEntityRequestCallback:最终走到而是下面的这一行代码

继续跟进,最终来到了的如下方法中:

protected void writeContent(Resource resource, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException {
		try {
			InputStream in = resource.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
		}
	}

这个方法的关键之处就是讲文件流拷贝到HttpOutputMessage对象中,在我们这里传递归来的HttpOutputMessage正是前面提到的request(执行request.execute方法的对象)。跟进outputMessage.getBody()——来到了FormHttpMessageConverter的内部类MultipartHttpOutputMessage中,源码中对于这个类的注释如下:

Implementation of {@link org.springframework.http.HttpOutputMessage} used to write a MIME multipart.

可以看到在getBoby()中调用的是writeHeaders方法,而我们的文件的一些信息就放在了请求头 Content-Disposition中,这里面就有文件名。这段代码也很普通的,奇怪之处就在于一个内部方法被硬编码了!

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;
			}
		}

没错就是这个getAsciiBytes方法——

private byte[] getAsciiBytes(String name) {
            try{
                return name.getBytes("US-ASCII");
            } catch(UnsupportedEncodingException ex) {
                throw new IllegalStateException(ex);
            }
			
		}

这里默认使用的是US-ASCII的编码,中文肯定会出现乱码的呀。

解决的办法有两种,一种是将Spring版本升级,我后面使用到的5.0.4.RELEASE版本这个已经改过来了。

如果不想更改Spring的版本的话,可以考虑重写这个类的改方法,使用UTF-8编码。你需要做的很简单,复制类FormHttpMessageConverter中的所有代码,将getAsciiBytes方法里面的编码改成UTF-8,然后就是再创建一个新的类,复制AllEncompassingFormHttpMessageConverter中的所有代码。紧接着就是配置了,在配置RestTemplate的配置类中加入,你自己的AllEncompassingFormHttpMessageConverter即可。

以上就是这次开发的遇到的一些问题。

有什么不对之处,还望大家指出。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值