Spring 5 Reactive WebClient 添加请求及响应的interceptor

Spring 5 Reactive WebClient 添加请求及响应的interceptor

百度鹰眼返回的数据使用WebClient发送时不知道为什么是text/javascript类型的,所以想在webclient上添加一个拦截器,把response header的content-type修改成application/json。本来以为只是一个 函数调用的事,在stackoverflow上面也找到了类似的问题,但是答案中的class好像并不对头,所以花了很长时间才搞定,现在记录一下。大体思路就是拦截请求,然后检测返回的response的响应头是否有误,修改成application/json。难点在于理清ClientHttpResponse、DefaultClientResponse、Headers、HttpHeaders等等乱七八糟的类,而且由于有些类或者字段不能访问,所以需要用反射来强行访问。
demo如下:

	@Bean
    public WebClient yingyanClient() {
        WebClient webClient = WebClient.builder()
                .filter(modifyResponseHeader())   // 傻逼百度返回的content-type有时是text/javascript,需要修改成json
                .baseUrl("http://yingyan.baidu.com/api/v3")
                .defaultHeader(HttpHeaders.USER_AGENT,
                        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36")
                .build();
        return webClient;
    }
    
	private ExchangeFilterFunction modifyResponseHeader() {
		 // exchange可以理解为发送请求,返回一个ClientResponse的对象,它的默认实现类是DefaultClientResponse。DefaultClientResponse类中有一个字段代表了实际的响应,就是ClientHttpResponse类型的字段response,它的默认实现类型是ReactorClientHttpResponse,我们需要自定义一个类来代替它。然后我们用一个自定义的ClientResponseWrapper类来代替DefaultClientResponse。至于为什么需要同时修改两个类,那是因为使用bodyToMono或者bodyToFlux时在两个地方判断了Content-Type,一次使用了ClientResponse的方法,一次使用了ClientHttpResponse的方法。很晕=_=
        return ((request, next) -> next.exchange(request).map(ModifyResponseHeader::new));
    }
    
    private class ModifyResponseHeader extends ClientResponseWrapper {
        Headers headers;

        /**
         * Create a new {@code ClientResponseWrapper} that wraps the given response.
         *
         * @param delegate the response to wrap
         */
        public ModifyResponseHeader(ClientResponse delegate) {
            super(delegate);
            // 如果原响应的content-type是text/javascript;charset=UTF-8,则修改之
            if (delegate.headers().contentType().map(t -> t.toString().equals("text/javascript;charset=UTF-8")).get()) {
            	  // 包装一下DefaultClientResponse的错误的header
                this.headers = new HeadersWrapper(delegate.headers()) {
                    @Override
                    public Optional<MediaType> contentType() {
                        return Optional.of(MediaType.APPLICATION_JSON_UTF8);
                    }
                };
                Class<?> clazz = null;
                Field responseField = null;
                Field delegateField = null;
                try {
                    // 获取DefaultClientResponse类,它是包私有的
                    clazz = Class.forName("org.springframework.web.reactive.function.client.DefaultClientResponse");
                    // 获取response类私有字段,
                    responseField = clazz.getDeclaredField("response");
                    responseField.setAccessible(true);

                    // 设置responseField为非final
                    Field modifiersField = Field.class.getDeclaredField("modifiers");
                    modifiersField.setAccessible(true);
                    modifiersField.setInt(responseField, responseField.getModifiers() & ~Modifier.FINAL);

                    delegateField = ClientResponseWrapper.class.getDeclaredField("delegate");
                    delegateField.setAccessible(true);

                    responseField.set(delegate, new ResponseDecorator((ClientHttpResponse) responseField.get(delegate)));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                this.headers = delegate.headers();
            }
        }

		 // 一定不要忘记重载这个方法
        @Override
        public Headers headers() {
            return this.headers;
        }
    }

    private class ResponseDecorator extends ClientHttpResponseDecorator {

        private final HttpHeaders headers;

        /**
         * Create a new {@code ClientResponseWrapper} that wraps the given response.
         *
         * @param delegate the response to wrap
         */
        public ResponseDecorator(ClientHttpResponse delegate) {
            super(delegate);
            this.headers = new HttpHeaders(this.getDelegate().getHeaders());
            this.headers.setContentType(MediaType.APPLICATION_JSON);
        }

        @Override
        public HttpHeaders getHeaders() {
            return headers;
        }
    }

最后说一句,不知道有没有简单的方法,,,这种常用的需求应该有方法吧

PS:再说一句,实际上只需要用反射修改两个响应类的content-type字段就行了,反正用反射能访问到,但是懒得改了。。

你可能感兴趣的:(Spring)