Dubbo在2.6版本后合並了dubbox的resteasy代碼后,可以支持rest風格的接口發布,但是在使用form表單上傳文件的時候,獲取的文件名稱是亂碼。
下面通過對源碼分析一下原因,並提供一種可行的解決方法。
首先是一個resteasy上傳的使用代碼

@POST @Path("/upload") @Consumes(MediaType.MULTIPART_FORM_DATA) @Override public Object uploadfile(MultipartFormDataInput input, @Context HttpServletRequest request, @Context HttpServletResponse response) { System.out.println("進入業務邏輯"); // MultipartFormDataReader Map<String, Object> map = null; Map<String, List<InputPart>> uploadForm = input.getFormDataMap(); //取得文件表單名 List<InputPart> inputParts = uploadForm.get("file"); final String DIRCTORY = "D:/temp/datainput/"; initDirectory(DIRCTORY); InputStream inputStream = null; OutputStream outStream = null; for (InputPart inputPart : inputParts) { try { // 文件名稱 String fileName = getFileName(inputPart.getHeaders()); inputStream = inputPart.getBody(InputStream.class, null); //把文件流保存; File file = new File(DIRCTORY + fileName); byte[] buffer = new byte[inputStream.available()]; inputStream.read(buffer); outStream = new FileOutputStream(file); outStream.write(buffer); } catch (IOException e) { e.printStackTrace(); }finally { if(null != inputStream){ try { inputStream.close(); } catch (IOException e) { } } if(null != outStream){ try { outStream.close(); } catch (IOException e) { } } } } return Response.ok().build(); }
resteasy文件上傳使用的Consumes使用的mediattype類型是MULTIPART_FORM_DATE【@Consumes(MediaType.MULTIPART_FORM_DATA)】
這個mediatype使用的Provider使用的是org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataReader,其readForm的入口為

public MultipartFormDataInput readFrom(Class<MultipartFormDataInput> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { String boundary = mediaType.getParameters().get("boundary"); if (boundary == null) throw new IOException(Messages.MESSAGES.unableToGetBoundary()); MultipartFormDataInputImpl input = new MultipartFormDataInputImpl(mediaType, workers); input.parse(entityStream); return input; }
在跟入上面代碼的parse方法 input.parse(entityStream)中的new BinaryMessage()構造函數中,MultipartInputImpl對http的head進行了解析

private static class BinaryMessage extends Message { private BinaryMessage(InputStream is) throws IOException, MimeIOException { try { MimeStreamParser parser = new MimeStreamParser(null); StorageProvider storageProvider; if (System.getProperty(DefaultStorageProvider.DEFAULT_STORAGE_PROVIDER_PROPERTY) != null) { storageProvider = DefaultStorageProvider.getInstance(); } else { StorageProvider backend = new CustomTempFileStorageProvider(); storageProvider = new ThresholdStorageProvider(backend, 1024); } parser.setContentHandler(new BinaryOnlyMessageBuilder(this, storageProvider)); parser.parse(is); // 此處未解析代碼,未傳入指定的字符串編碼方式 } catch (MimeException e) { throw new MimeIOException(e); } } }
在行 parser.parse(is);中,采用的是apache-mime4j-1.6版本的流解析器,由於MultipartInputImpl在調用apache-mime4j的解析方法,沒有可指定字符編碼的方法,此處編碼的設置傳遞會丟失。(PS:MultipartInputImpl中的defaultPartCharset,可以通過攔截器request.setAttribute(InputPart.DEFAULT_CHARSET_PROPERTY, ENCODING_UTF_8);進行指定)。
后續未指定字符編碼的調用鏈中,apache-mime4j對上傳內容的解析采用了默認的ASCII編碼進行處理,對應RawField.parseBody()

private String parseBody() { int offset = colonIdx + 1; int length = raw.length() - offset; return ContentUtil.decode(raw, offset, length); }
該decode方法中使用的是寫死的ASCII編碼進行處理

public static String decode(ByteSequence byteSequence, int offset, int length) { return decode(CharsetUtil.US_ASCII, byteSequence, offset, length); }
所以看到這里,就了解了為什么文件名稱會是亂碼的了,大概也知道其他地方通過攔截器設置編碼格式解決不了文件名稱亂碼的問題了。
所以可行的解決方法可以是(親測可用),將apache-mime4j-1.6的源碼導入工程中,並且修改ContentUtil的decode方法,如下:
public static String decode(ByteSequence byteSequence, int offset, int length) { return decode(CharsetUtil.UTF_8 //修改此處默認編碼 , byteSequence, offset, length); }
這種方法不好的點就是冗余了一份開源代碼到自己項目中,並且項目包路徑會比較奇怪。當然也可以編譯一份修改后的代碼放到自己公司的nexus庫中。