Dubbox RestEasy 文件上傳亂碼解決

背景

最近開發Dubbox服務,使用了Http協議對PHP系統暴露了一些Service服務,但是在上傳時出現了亂碼,google沒有發現好的解決方案,只能自己debug,發現是配置中缺少一項。

解決方案

直接說解決方案:

添加一個filter,filter內容如下:
“`
/**
* Servlet Filter設置編解碼
*
* @author qiesai
*/
public class CharacterEncodingFilter implements Filter {

private static final String ENCODING_UTF_8 = "UTF-8";

@Override
public void init(FilterConfig config) throws ServletException {

}

@Override
public void destroy() {

}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
    request.setCharacterEncoding(ENCODING_UTF_8);
    response.setCharacterEncoding(ENCODING_UTF_8);
    request.setAttribute(InputPart.DEFAULT_CHARSET_PROPERTY, ENCODING_UTF_8);
    chain.doFilter(request, response);
}

}

“`

這裏的關鍵是InputPart.DEFAULT_CHARSET_PROPERTY這個attribute,設置爲utf-8,會覆蓋掉dubbox默認的us-ascii編碼。

問題定位

這個問題的發生原因還是在於RestEasy框架解析上傳數據的時候發生的,RestEasy使用:resteasy-multipart-provider包解析上傳數據,也就是content-type爲multipart/form-data的request,大致解析過程如下:

  • 1.框架確定Content-Type爲multipart/form-data,這個常量定義在jax支持包的MediaType接口中;
  • 2.框架將請求交給MultipartFormDataReader ,這個Reader註解爲Provider,調用readForm方法,解析請求;
  • 3.MultipartFormDataInputImpl的parse方法解析body內容,讀取數據,亂碼發生在這個過程中

MultipartFormDataInputImpl解析

  • MultipartFormDataInputImpl的parse方法是繼承自父類:MultipartInputImpl的,解析過程稍微有點複雜,但是最終是通過構造PartImpl來表示每一個參數的。
  • InputPart來表示Form表單中每一項參數,PartImpl是InputPart的一個實現類,構造的時候傳入BodyPart,然後做解析,構造過程如下:
  • 框架使用MultipartFormDataInputImpl讀取body內容發生亂碼,源代碼註釋如下。
// 使用註解,框架可以自動發現這個Provider
@Provider
@Consumes("multipart/form-data")
public class MultipartFormDataReader implements MessageBodyReader<MultipartFormDataInput>
{
   protected
   @Context
   Providers workers;

   public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType)
   {
      return type.equals(MultipartFormDataInput.class);
   }

    /**
    * 這個方法是關鍵,RestEasy框架調用這個readFrom方法獲取請求信息,
    **/
   public MultipartFormDataInput readFrom(Class<MultipartFormDataInput> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException
   {
      // 讀取boundary,Content-Type中會包含這個boundary,類似如下:
      // Content-Type:multipart/form-data;boundary=---------------------------7d33a816d302b6
      // 使用---------------------------7d33a816d302b6 作爲參賽見的分割
      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;
   }
}

PartImpl

這個類作用是用來表示body中每個form表單項,比如,你傳遞了name,sex,image,那麼就會有三個PartImpl的實例,對應三個參數。
構造過程如下:

public PartImpl(BodyPart bodyPart)
      {
         this.bodyPart = bodyPart;
         //選擇ContentType,看Client是否傳遞了Content-Type,裏面有可能包含編碼信息;
         for (Field field : bodyPart.getHeader())
         {
            headers.add(field.getName(), field.getBody());
            if (field instanceof ContentTypeField)
            {
               contentType = MediaType.valueOf(field.getBody());
               contentTypeFromMessage = true;
            }
         }
         //如果是null,則用默認的Content-Type,一般Client發送的Content-Type不爲null,但是不包含編碼;
         if (contentType == null)
            contentType = defaultPartContentType;

         //從ContentType中找編碼字符,亂碼時找不到,進入if內邏輯;
         if (getCharset(contentType) == null)
         {
            if (defaultPartCharset != null) //使用框架全局默認的編碼,filter的作用就是設置這個。
            {
               contentType = getMediaTypeWithDefaultCharset(contentType);
            }
            else if (contentType.getType().equalsIgnoreCase("text"))//沒有默認,使用us-ascii編碼,亂碼發生。
            {
               contentType = getMediaTypeWithCharset(contentType, "us-ascii");
            }
         }
      }

MultipartInputImpl : 默認的Conten-Type定義及初始化位置

默認的Content-Type在MultipartInputImpl中,定義如下,這個是成員初始化的默認值,在構造的時候可以覆蓋掉。

// 實際上就是text/plain; charset=us-ascii
protected MediaType defaultPartContentType = MultipartConstants.TEXT_PLAIN_WITH_CHARSET_US_ASCII_TYPE;

默認的defalutPartCharst的初始化時在構造MultipartInputImpl的時候,在構造方法中,這個對象,每次請求都會new一個,所以是和request相關的:

public MultipartInputImpl(MediaType contentType, Providers workers)
   {
      this.contentType = contentType;
      this.workers = workers;
      HttpRequest httpRequest = ResteasyProviderFactory
              .getContextData(HttpRequest.class);
      if (httpRequest != null)
      {
          // 從request的attribute中獲取默認的編碼和contentType
         String defaultContentType = (String) httpRequest
                 .getAttribute(InputPart.DEFAULT_CONTENT_TYPE_PROPERTY);
         if (defaultContentType != null)
            this.defaultPartContentType = MediaType
                    .valueOf(defaultContentType);
         this.defaultPartCharset = (String) httpRequest.getAttribute(InputPart.DEFAULT_CHARSET_PROPERTY);
         if (defaultPartCharset != null)
         {
            this.defaultPartContentType = getMediaTypeWithDefaultCharset(this.defaultPartContentType);
         }
      }
   }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章