使用OpenFeign远程调用时请求头处理报错问题

1. 错误信息


basic.result.exception.OtherException: feign error:系统异常:Content type 'multipart/form-data;boundary=--------------------------679449061975336133574827;charset=UTF-8' not supported.请联系管理员
	at com.assembly.oauth.web.exception.auth.FeignErrorDecoder.decode(FeignErrorDecoder.java:26)
	at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:149)
	at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:78)
	at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103)
	at com.sun.proxy.$Proxy191.addStationModelList(Unknown Source)
	at com.assembly.dg.online.controller.TestController.importStation(TestController.java:326)
	at com.assembly.dg.online.controller.TestController$$FastClassBySpringCGLIB$$1.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:684)
	at com.assembly.dg.online.controller.TestController$$EnhancerBySpringCGLIB$$1.importStation(<generated>)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:189)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:908)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:660)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.boot.actuate.web.trace.servlet.HttpTraceFilter.doFilterInternal(HttpTraceFilter.java:90)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:117)
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:106)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at com.assembly.oauth.web.authorization.interceptor.CrossFilter.doFilter(CrossFilter.java:57)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
	at org.apache.catalina.core.StandardContextValve.__invoke(StandardContextValve.java:96)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:41002)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
	at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:668)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:834)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1417)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:748)

2. 使用场景以及问题出现的原因

在本地微服务中使用Spring MVC 上传文件时, 因为使用了MultipartFile 对象接受, 所以前端设置 请求头"Content-type"为multipart/form-data; ,这在本地 微服务中是没有问题的,

但是在处理请求的过程中, 使用了OpenFeign 远程调用了 其他的微服务保存信息, 就有问题了,

@RequestMapping(value = "importStation")
    @NoAuthorization
    public void importStation(MultipartFile file, HttpServletRequest request) throws IOException {

        // ... 业务处理
        // RPC远程调用 保存信息
        dgRpcRemote.addStationModelList(stationModelList);
      
    }

本项目中使用的是OpenFeign进行远程调用, 为了在调用时,可以携带调用方的请求头信息,例如token等等, 项目中自定义了一个拦截器进行处理:

@Component
public class FeignBasicAuthRequestInterceptor implements RequestInterceptor {
    public FeignBasicAuthRequestInterceptor() {
    }

    public void apply(RequestTemplate requestTemplate) {
        try {
            //获取 ThreadLocal中的 本地Request对象
            ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
            if (attributes == null) {
                return;
            }

            HttpServletRequest request = attributes.getRequest();
            Enumeration<String> headerNames = request.getHeaderNames();
            String type;
            String tenantCode;
            if (headerNames != null) {
                // 全盘拷贝过去
                while(headerNames.hasMoreElements()) {
                    type = (String)headerNames.nextElement();
                    tenantCode = request.getHeader(type);
                    requestTemplate.header(type, new String[]{tenantCode});
                }
            }

            type = TenantContextHolder.getAuthorizationType();
            if (StringUtils.isNotBlank(type)) {
                requestTemplate.header("tenantAuthorization", new String[]{type});
            }

            tenantCode = TenantContextHolder.getTenantCode();
            if (StringUtils.isNotBlank(tenantCode)) {
                requestTemplate.header("tenantCode", new String[]{tenantCode});
            }

            String tokenId = TenantContextHolder.getTokenId();
            if (StringUtils.isNotBlank(tokenId)) {
                requestTemplate.header("tenantToken", new String[]{tokenId});
            }

            requestTemplate.header("Content-Type", new String[]{"application/json;charset=UTF-8"});
        } catch (Exception var8) {
            var8.printStackTrace();
        }

    }
}

只需实现 接口RequestInterceptor 并被Spring扫描到即可,OpenFeign会自动调用,此类重写的 apply 方法中就 将本地的请求Request对象的请求头全部拷贝到 新的请求中用于调用,这样multipart/form-data; 也就拷贝了过去,但是在RPC 调用的接受方,确只是普通的 Rest 接口, 需要的是application/json ,所以就报了上述错误.

如果项目中在使用Feign进行远程调用,并有需要进行请求头传递的需求,可以参考此方式

3. 解决方式

因为在拦截器中获取的是 ThreadLocal 中保存的本地 请求Request 信息,所以 只需保证 在调用时不在请求所在线程即可:

new Thread(() ->dgRpcRemote.addStationModelList(stationModelList) ).start();

在调用时另启动一个线程进行调用即可, 如果有更好的方式,欢迎指教

posted @ 2021-01-07 09:54  哈哈丶丶  阅读(2572)  评论(0编辑  收藏  举报