springweb项目自定义拦截器修改请求报文头

面向切面,法力无边,任何脏活累活,都可以从干干净净整齐划一的业务代码中抽出来,无非就是加一层,项目里两个步骤间可以被分层的设计渗透成筛子。
举个例子:
最近我们对接某银行接口,我们的web服务都是标准的restful请求,所以所有的controller层代码都是整整齐齐的

@Slf4j
@RestController
@RequestMapping("xxx")
@NoticeGroup
public class XXXController {

    @Autowired
    XXXService xxxService;

    @PostMapping("/fff")
    public XXXRespVo fff(@RequestBody xxxReqVo reqVo){
        log.info("传入参数:{}",reqVo);
        return xxxService.doSomething(reqVo));
    }
}

这样的话请求就是标准的Post ,json ,

不成想,该银行回调我方的报文竟然是一串字符串,而且请求头是 text/plain,好家伙所有的请求都要改,这不成,队伍要整齐,来加一层在过滤器中,不修改的话调用逻辑如此?

我们拿到报文后需要先进行解密,好的,如果有100个接口那解密100次也不现实,

  • 我们可以自定义一个过滤器,在过滤器中将报文解密重新塞回请求体

  • 实现起来还是比较简单的上网上摘抄即可:

    由于request请求体流读一次之后就不能再读了,所以要做一个包装类,相当于将断开的流重新接起来,好比再管道上打个口子,取出东西来再放回这个东西。但是我们对这个东西做了点什么,什么也不做也行,就只是检查下。

参考代码

public class RequestWrapper extends HttpServletRequestWrapper {

    private String body;

    
    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);

        StringBuilder sb =new StringBuilder();
        InputStream inputStream=null;
        BufferedReader reader=null;
        try{
            inputStream=cloneInputStream(request.getInputStream());
            reader =new BufferedReader(new InputStreamReader(inputStream));
            String line="";
            while((line=reader.readLine())!=null){
                sb.append(line);
            }

        }catch (IOException e){
            e.printStackTrace();
        }finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                }
                catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (reader != null) {
                try {
                    reader.close();
                }
                catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        body=sb.toString();
    }
    public InputStream cloneInputStream(ServletInputStream inputStream) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        try {
            while ((len = inputStream.read(buffer)) > -1) {
                byteArrayOutputStream.write(buffer, 0, len);
            }
            byteArrayOutputStream.flush();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        InputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        return byteArrayInputStream;
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
        ServletInputStream servletInputStream = new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }
            @Override
            public boolean isReady() {
                return false;
            }
            @Override
            public void setReadListener(ReadListener readListener) {}
            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }
        };
        return servletInputStream;

    }
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

    public String getBody() {
        return this.body;
    }

    public void setBody(String body) throws UnsupportedEncodingException {
        this.body = body;
    }
}
  • 过滤器类怎么写呢?如下,@WebFilter(urlPatterns = "/XXX/") 注解起的作用是拦截匹配真个url的请求,这里需要再启动类里面配套加一个@ServletComponentScan(basePackages = "xxx.xxx.xxx.")

不加的话会把所有的请求都拦截,我在当初的某一版本上就没加,只能在下面doFilter方法里面加补丁,判断请求url是不是我想要的否则用不走解密的逻辑

@Order(12)
@Slf4j
@WebFilter(urlPatterns = "/XXX/*")
public class XXXFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;

        try {
            RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest) request);
            String bodyString = requestWrapper.getBody();
            requestWrapper.setBody(decrypt(bodyString));
            try {
                filterChain.doFilter(requestWrapper, servletResponse);
            } catch (Exception e) {
                log.info("业务中的异常", e.getMessage());
                throw e;
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @SneakyThrows
    public String decrypt(String encrypedStr) {
        String result = "根据密文解密后的数据巴拉巴拉";
        return result;
    }
}
  • 这样程序运行的逻辑就成如下,我们在拦截器中把报文变漂亮了。返回的时候也可以在这里处理,嘿嘿,我们在对外提供服务的时候有使用需要把返回的东西加密,就在这里处理了。

  • 这样只是解决的报文明文的问题,还没有解决报文头,但是我们能修改报文体就能修改报文头不是么?代码如下: 塞进requestWapper里面

    @Override
    public  Enumeration<String>  getHeaders(String name) {
        List<String> list=Collections.list(super.getHeaders(name));
				//处理一下大小写,可能是content-type 或 Content-Type
        if(name.equalsIgnoreCase(HttpHeaders.CONTENT_TYPE)){
            list=new ArrayList<>();
            list.add(MediaType.APPLICATION_JSON_VALUE);
        }
        Enumeration<String> re= Collections.enumeration(list);
        return re;
    }
  • 为啥这么写呢? debug追溯可以找到类:AbstractMessageConverterMethodArgumentResolver
@Nullable
	protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
			Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

		MediaType contentType;
		boolean noContentType = false;
		try {
			// 这里的inputMessage
			contentType = inputMessage.getHeaders().getContentType();
		}
			...
			}


@Override
	public HttpHeaders getHeaders() {
		if (this.headers == null) {
			this.headers = new HttpHeaders();

			for (Enumeration<?> names = this.servletRequest.getHeaderNames(); names.hasMoreElements();) {
				String headerName = (String) names.nextElement();
				for (Enumeration<?> headerValues = this.servletRequest.getHeaders(headerName);
						headerValues.hasMoreElements();) {
					String headerValue = (String) headerValues.nextElement();
					this.headers.add(headerName, headerValue);
				}
			}
        ......
	}

看上面的代码,这里是 requestWapper 作为构造参数在创建inputMessage的时候弄进去的。 this.servletRequest==requestWapper, 这样 this.headers = new HttpHeaders();其实取值的时候就是

this.servletRequest.getHeaders(headerName); 这段代码了,我们wapper中重写的也就是这一块,将原先的报文头强行修改为了application/json。

到此就完成了。

Enumeration是个啥呢?

Enumeration接口中定义了一些方法,通过这些方法可以枚举(一次获得一个)对象集合中的元素。

这种传统接口已被迭代器取代,虽然Enumeration 还未被遗弃,但在现代代码中已经被很少使用了。尽管如此,它还是使用在诸如Vector和Properties这些传统类所定义的方法中,除此之外,还用在一些API类,并且在应用程序中也广泛被使用。

public interfaceEnumeration<E> {
/**
     * Tests if this enumeration contains more elements.
     *
     *@return<code>true</code>if and only if this enumeration object
     *           contains at least one more element to provide;
     *<code>false</code>otherwise.
     */
booleanhasMoreElements();

/**
     * Returns the next element of this enumeration if this enumeration
     * object has at least one more element to provide.
     *
     *@returnthe next element of this enumeration.
     *@exceptionNoSuchElementExceptionif no more elements exist.
     */
E nextElement();
}

实现类虽然多但是没有我认识的呵呵,

Collections.enumeration(list); 这个方法,如下,看来就是用Iterator 来实现。这种写法还挺妙的。

public static <T> Enumeration<T> enumeration(final Collection<T> c) {
        return new Enumeration<T>() {
            private final Iterator<T> i = c.iterator();

            public boolean hasMoreElements() {
                return i.hasNext();
            }

            public T nextElement() {
                return i.next();
            }
        };
    }
原文地址:https://www.cnblogs.com/shuiliuhualuo/p/15190924.html