关于zuul中对于POST请求中path参数和form body中重复参数被过滤的情况

在使用zuul做转发功能时,发现当一个Content-Type为application/x-www-form-urlencoded;charset=UTF-8的POST请求中,path参数和body中的参数重复时,转发的时候会丢失body中的参数,下面是定位的过程

从请求进入ZuulServlet的service方法开始

 1 public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
 2         try {
 3             this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
 4             RequestContext context = RequestContext.getCurrentContext();
 5             context.setZuulEngineRan();
 6 
 7             try {
 8                 this.preRoute();
 9             } catch (ZuulException var13) {
10                 this.error(var13);
11                 this.postRoute();
12                 return;
13             }
14 
15             try {
16                 this.route();
17             } catch (ZuulException var12) {
18                 this.error(var12);
19                 this.postRoute();
20                 return;
21             }
22 
23             try {
24                 this.postRoute();
25             } catch (ZuulException var11) {
26                 this.error(var11);
27             }
28         } catch (Throwable var14) {
29             this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName()));
30         } finally {
31             RequestContext.getCurrentContext().unset();
32         }
33     }

开始做了初始化,然后调用了preRoute方法,这个主要是通过FilterProcessor拿到类型为pre的filter

具体可以看FilterProcessor中的preRoute和runFilter方法

public void preRoute() throws ZuulException {
        try {
            this.runFilters("pre");
        } catch (ZuulException var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new ZuulException(var3, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + var3.getClass().getName());
        }
    }

    public Object runFilters(String sType) throws Throwable {
        if (RequestContext.getCurrentContext().debugRouting()) {
            Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
        }

        boolean bResult = false;
        List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
        if (list != null) {
            for(int i = 0; i < list.size(); ++i) {
                ZuulFilter zuulFilter = (ZuulFilter)list.get(i);
                Object result = this.processZuulFilter(zuulFilter);
                if (result != null && result instanceof Boolean) {
                    bResult |= (Boolean)result;
                }
            }
        }

        return bResult;
    }

通过代码可以看到,拿到type为pre的filter后,遍历执行,zuul中的filter主要有下面这些

 

 

 

 

 

 其中pre filter有5个

ServletDetectionFilter

Servlet30WrapperFilter

FormBodyWrapperFilter

DebugFilter

PreDecorationFilter

对于请求包装的主要有Servlet30WrapperFilter和FormBodyWrapperFilter

 首先我们看一下的代码Servlet30WrapperFilter

public class Servlet30WrapperFilter extends ZuulFilter {

    private Field requestField = null;

    public Servlet30WrapperFilter() {
        this.requestField = ReflectionUtils.findField(HttpServletRequestWrapper.class,
                "req", HttpServletRequest.class);
        Assert.notNull(this.requestField,
                "HttpServletRequestWrapper.req field not found");
        this.requestField.setAccessible(true);
    }

    protected Field getRequestField() {
        return this.requestField;
    }

    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return SERVLET_30_WRAPPER_FILTER_ORDER;
    }

    @Override
    public boolean shouldFilter() {
        return true; // TODO: only if in servlet 3.0 env
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        if (request instanceof HttpServletRequestWrapper) {
            request = (HttpServletRequest) ReflectionUtils.getField(this.requestField,
                    request);
            ctx.setRequest(new Servlet30RequestWrapper(request));
        }
        else if (RequestUtils.isDispatcherServletRequest()) {
            // If it's going through the dispatcher we need to buffer the body
            ctx.setRequest(new Servlet30RequestWrapper(request));
        }
        return null;
    }

}

 

发现里面只是把HttpServletRequest封装到了Servlet30RequestWrapper,Servlet30RequestWrapper中没有特别的对参数的处理,排除这个filter,接下来我们看FormBodyWrapperFilter的 run方法的代码

@Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        FormBodyRequestWrapper wrapper = null;
        if (request instanceof HttpServletRequestWrapper) {
            HttpServletRequest wrapped = (HttpServletRequest) ReflectionUtils
                    .getField(this.requestField, request);
            wrapper = new FormBodyRequestWrapper(wrapped);
            ReflectionUtils.setField(this.requestField, request, wrapper);
            if (request instanceof ServletRequestWrapper) {
                ReflectionUtils.setField(this.servletRequestField, request, wrapper);
            }
        }
        else {
            wrapper = new FormBodyRequestWrapper(request);
            ctx.setRequest(wrapper);
        }
        if (wrapper != null) {
            ctx.getZuulRequestHeaders().put("content-type", wrapper.getContentType());
        }
        return null;
    }

可以看到前面也是把HttpServletRequest(从Servlet30RequestWrapper转成HttpServletRequest)封装成FormBodyRequestWrapper,注意最下面这里

if (wrapper != null) {
            ctx.getZuulRequestHeaders().put("content-type", wrapper.getContentType());
        }

调用了wrapper.getContentType(),我们跟踪这个方法发现就是这里对参数做了处理
@Override
        public String getContentType() {
            if (this.contentData == null) {
                buildContentData();
            }
            return this.contentType.toString();
        }

        @Override
        public int getContentLength() {
            if (super.getContentLength() <= 0) {
                return super.getContentLength();
            }
            if (this.contentData == null) {
                buildContentData();
            }
            return this.contentLength;
        }

        public long getContentLengthLong() {
            return getContentLength();
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            if (this.contentData == null) {
                buildContentData();
            }
            return new ServletInputStreamWrapper(this.contentData);
        }

        private synchronized void buildContentData() {
            if (this.contentData != null) {
                return;
            }
            try {
                MultiValueMap<String, Object> builder = RequestContentDataExtractor
                        .extract(this.request);
                FormHttpOutputMessage data = new FormHttpOutputMessage();

                this.contentType = MediaType.valueOf(this.request.getContentType());
                data.getHeaders().setContentType(this.contentType);
                FormBodyWrapperFilter.this.formHttpMessageConverter.write(builder,
                        this.contentType, data);
                // copy new content type including multipart boundary
                this.contentType = data.getHeaders().getContentType();
                byte[] input = data.getInput();
                this.contentLength = input.length;
                this.contentData = input;
            }
            catch (Exception e) {
                throw new IllegalStateException("Cannot convert form data", e);
            }
        }

 

在buildContentData方法中调用了RequestContentDataExtractor的extract方法

public final class RequestContentDataExtractor {

    private RequestContentDataExtractor() {
        throw new AssertionError("Must not instantiate utility class.");
    }

    public static MultiValueMap<String, Object> extract(HttpServletRequest request)
            throws IOException {
        return (request instanceof MultipartHttpServletRequest)
                ? extractFromMultipartRequest((MultipartHttpServletRequest) request)
                : extractFromRequest(request);
    }

    private static MultiValueMap<String, Object> extractFromRequest(
            HttpServletRequest request) throws IOException {
        MultiValueMap<String, Object> builder = new LinkedMultiValueMap<>();
        Set<String> queryParams = findQueryParams(request);

        for (Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
            String key = entry.getKey();

            if (!queryParams.contains(key) && entry.getValue() != null) {
                for (String value : entry.getValue()) {
                    builder.add(key, value);
                }
            }
        }

        return builder;
    }

    private static MultiValueMap<String, Object> extractFromMultipartRequest(
            MultipartHttpServletRequest request) throws IOException {
        MultiValueMap<String, Object> builder = new LinkedMultiValueMap<>();
        Map<String, List<String>> queryParamsGroupedByName = findQueryParamsGroupedByName(
                request);
        Set<String> queryParams = findQueryParams(request);

        for (Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
            String key = entry.getKey();
            List<String> listOfAllParams = stream(request.getParameterMap().get(key))
                    .collect(Collectors.toList());
            List<String> listOfOnlyQueryParams = queryParamsGroupedByName.get(key);

            if (listOfOnlyQueryParams != null) {
                listOfOnlyQueryParams = listOfOnlyQueryParams.stream()
                        .filter(Objects::nonNull)
                        .map(param -> uriDecode(param, Charset.defaultCharset()))
                        .collect(Collectors.toList());
                if (!listOfOnlyQueryParams.containsAll(listOfAllParams)) {
                    listOfAllParams.removeAll(listOfOnlyQueryParams);
                    for (String value : listOfAllParams) {
                        builder.add(key,
                                new HttpEntity<>(value, newHttpHeaders(request, key)));
                    }
                }
            }

            if (!queryParams.contains(key)) {
                for (String value : entry.getValue()) {
                    builder.add(key,
                            new HttpEntity<>(value, newHttpHeaders(request, key)));
                }
            }
        }

        for (Entry<String, List<MultipartFile>> parts : request.getMultiFileMap()
                .entrySet()) {
            for (MultipartFile file : parts.getValue()) {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentDispositionFormData(file.getName(),
                        file.getOriginalFilename());
                if (file.getContentType() != null) {
                    headers.setContentType(MediaType.valueOf(file.getContentType()));
                }

                HttpEntity entity = new HttpEntity<>(
                        new InputStreamResource(file.getInputStream()), headers);
                builder.add(parts.getKey(), entity);
            }
        }

        return builder;
    }

    private static HttpHeaders newHttpHeaders(MultipartHttpServletRequest request,
            String key) {
        HttpHeaders headers = new HttpHeaders();
        String type = request.getMultipartContentType(key);

        if (type != null) {
            headers.setContentType(MediaType.valueOf(type));
        }
        return headers;
    }

    private static Set<String> findQueryParams(HttpServletRequest request) {
        Set<String> result = new HashSet<>();
        String query = request.getQueryString();

        if (query != null) {
            for (String value : tokenizeToStringArray(query, "&")) {
                if (value.contains("=")) {
                    value = value.substring(0, value.indexOf("="));
                }
                result.add(value);
            }
        }

        return result;
    }

    static Map<String, List<String>> findQueryParamsGroupedByName(
            HttpServletRequest request) {
        String query = request.getQueryString();
        if (isEmpty(query)) {
            return emptyMap();
        }
        return UriComponentsBuilder.fromUriString("?" + query).build().getQueryParams();
    }

}

从extract的逻辑看到,会调用extractFromRequest方法,在这个方法中先拿到了queryParams,也就是path的参数,然后通过equest.getParameterMap(),拿到了form参数

这里面有个判断逻辑:if (!queryParams.contains(key) && entry.getValue() != null),当参数在queryParams有的时候不会进行处理,所以到这就真相大白了。至于如何修改,待续......

 

posted @ 2023-03-30 12:47  弘文馆校书  阅读(87)  评论(0编辑  收藏  举报