Dubbox RestEasy 文件上传乱码解决

#背景
最近开发Dubbox服务,使用了Http协议对PHP系统暴露了一些Service服务,但是在上传时出现了乱码,google没有发现好的解决方案,只能自己debug,发现是配置中缺少一项。

#解决方案
直接说解决方案:

添加一个filter,filter内容如下:


/**
* Servlet Filter设置编解码<br>
*/
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 编码。

问题定位

这个问题的发生原因是 dubbox 使用的是 RestEasy 框架解析上传数据,RestEasy 框架会判断 request 的 content-typecontent-typemultipart/form-data 的 request 交给 resteasy-multipart-provider 包来解析,大致解析过程如下:

  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 作为 http body 内的参数分割符号
      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);
         }
      }
   }

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
要配置Solr的读写分离,可以按照以下步骤进行操作: 1. 在Solr集群管理文件solr.xml中,配置读写分离相关的配置。可以使用命令`cd /export/servers/solr/solr-home/`,然后使用`vi solr.xml`来编辑该文件。 2. 使用Solr提供的命令将配置文件提交给ZooKeeper。首先,上传Solr的安装包,并解压缩它。可以使用以下命令: - `cd /export/software/` - `rz` (上传solr安装包) - `unzip solr-4.10.2.zip` - `mv solr-4.10.2 /export/servers/` 3. 执行上传命令将配置文件提交给ZooKeeper。可以使用以下命令: - `cd /export/servers/solr-4.10.2/example/scripts/cloud-scripts/` - `./zkcli.sh -zkhost node01:2181,node02:2181,node03:2181 -cmd upconfig -confdir /export/servers/solr/solr-home/collection1/conf/ -confname solrconf` 4. 修改Tomcat的Catalina.sh配置文件,指定Solr的solr.solr.home和ZooKeeper的连接主机。可以使用以下命令: - `cd /export/servers/solr/apache-tomcat-7.0.77/bin/` - `vi catalina.sh` - 将`export "JAVA_OPTS=-Dsolr.solr.home=/export/servers/solr/solr-home`替换为`export "JAVA_OPTS=-Dsolr.solr.home=/export/servers/solr/solr-home -DzkHost=node01:2181,node02:2181,node03:2181"` 5. 将Solr目录发送到其他两台Linux服务器上。可以使用以下命令: - `cd /export/servers/` - `scp -r solr root@node02:$PWD` - `scp -r solr root@node03:$PWD` 通过以上步骤,您可以成功配置Solr的读写分离。请注意根据您的具体环境进行相应的修改。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [solr的集群部署及dubbox基本使用](https://blog.csdn.net/weixin_44327435/article/details/96711571)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值