前端日子,在公司开发一个项目的时候,涉及到了微服务之间的文件上传问题,在此跟大家说一说。
首先讲一下这个项目的整体情况,整体是使用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即可。
以上就是这次开发的遇到的一些问题。
有什么不对之处,还望大家指出。