Spring cloud zuul为什么需要FormBodyWrapperFilter

更新日期:2019-03-22
java达人

源码调试web容器:tomcat


Spring cloud zuul里面有一些核心过滤器,以前文章大致介绍了下各个过滤器的作用,武林外传—武三通的zuul之惑。这次重点讲解下FormBodyWrapperFilter,先贴出完整源码:

  • /**

  • * Pre {@link ZuulFilter} that parses form data and reencodes it for downstream services

  • *

  • * @author Dave Syer

  • */

  • public class FormBodyWrapperFilter extends ZuulFilter {

  •    private FormHttpMessageConverter formHttpMessageConverter;

  •    private Field requestField;

  •    private Field servletRequestField;

  •    public FormBodyWrapperFilter() {

  •        this(new AllEncompassingFormHttpMessageConverter());

  •    }

  •    public FormBodyWrapperFilter(FormHttpMessageConverter formHttpMessageConverter) {

  •        this.formHttpMessageConverter = formHttpMessageConverter;

  •        this.requestField = ReflectionUtils.findField(HttpServletRequestWrapper.class,

  •                "req", HttpServletRequest.class);

  •        this.servletRequestField = ReflectionUtils.findField(ServletRequestWrapper.class,

  •                "request", ServletRequest.class);

  •        Assert.notNull(this.requestField,

  •                "HttpServletRequestWrapper.req field not found");

  •        Assert.notNull(this.servletRequestField,

  •                "ServletRequestWrapper.request field not found");

  •        this.requestField.setAccessible(true);

  •        this.servletRequestField.setAccessible(true);

  •    }

  •    @Override

  •    public String filterType() {

  •        return PRE_TYPE;

  •    }

  •    @Override

  •    public int filterOrder() {

  •        return FORM_BODY_WRAPPER_FILTER_ORDER;

  •    }

  •    @Override

  •    public boolean shouldFilter() {

  •        RequestContext ctx = RequestContext.getCurrentContext();

  •        HttpServletRequest request = ctx.getRequest();

  •        String contentType = request.getContentType();

  •        // Don"t use this filter on GET method

  •        if (contentType == null) {

  •            return false;

  •        }

  •        try {

  •            MediaType mediaType = MediaType.valueOf(contentType);

  •            return MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType)

  •                    || (isDispatcherServletRequest(request)

  •                            && MediaType.MULTIPART_FORM_DATA.includes(mediaType));

  •        }

  •        catch (InvalidMediaTypeException ex) {

  •            return false;

  •        }

  •    }

  •    private boolean isDispatcherServletRequest(HttpServletRequest request) {

  •        return request.getAttribute(

  •                DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null;

  •    }

  •    @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;

  •    }

  •    private class FormBodyRequestWrapper extends Servlet30RequestWrapper {

  •        private HttpServletRequest request;

  •        private byte[] contentData;

  •        private MediaType contentType;

  •        private int contentLength;

  •        public FormBodyRequestWrapper(HttpServletRequest request) {

  •            super(request);

  •            this.request = request;

  •        }

  •        @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() {

  •            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();

  •                this.contentData = data.getInput();

  •                this.contentLength = this.contentData.length;

  •            }

  •            catch (Exception e) {

  •                throw new IllegalStateException("Cannot convert form data", e);

  •            }

  •        }

  •        private class FormHttpOutputMessage implements HttpOutputMessage {

  •            private HttpHeaders headers = new HttpHeaders();

  •            private ByteArrayOutputStream output = new ByteArrayOutputStream();

  •            @Override

  •            public HttpHeaders getHeaders() {

  •                return this.headers;

  •            }

  •            @Override

  •            public OutputStream getBody() throws IOException {

  •                return this.output;

  •            }

  •            public byte[] getInput() throws IOException {

  •                this.output.flush();

  •                return this.output.toByteArray();

  •            }

  •        }

  •    }

  • }

  • 正如前面注释中说的,FormBodyWrapperFilter主要是解析表单数据并重新编码,供后续服务使用。

    filterType:pre,可以在请求被路由之前调用

    filterOrder:为-1,越小优先级越高,它是在ServletDetectionFilter和Servlet30WrapperFilter之后执行的。

    shouldFilter:该过滤器仅对两种类请求生效,类是Content-Type为application/x-www-form-urlencoded的请求,第二类是Content-Type为multipart/form-data并且是由Spring的DispatcherServlet处理的请求。为什么是这两类呢,如果研究前面的请求流程,我们会发现,这两类请求在前面的流程中已经被读取处理了,流是不可重复读取的,这意味着zuul在转发这个Request的时候,已经丢失了原本的内容,因此需要把放回去。这也是开发spring cloud gateway的原因之一,因为它没有这些问题(尚未看源码验证,有相关经验者欢迎留言)。以下我们仅以Content-Type=application/x-www-form-urlencoded的请求为例进行讲解。通过断点调试,发现在请求达到ZuulServlet前,具体执行request流读取操作的是org.apache.catalina.connector.RequestFacade类,这是一个包装类,下面是与之关联的关键代码片段:

  • /**

  •     * Parse request parameters.

  •     */

  •    protected void parseParameters() {

  •        parametersParsed = true;

  •        Parameters parameters = coyoteRequest.getParameters();

  •        boolean success = false;

  •        try {

  •            // Set this every time in case limit has been changed via JMX

  •            parameters.setLimit(getConnector().getMaxParameterCount());

  •            // getCharacterEncoding() may have been overridden to search for

  •            // hidden form field containing request encoding

  •            Charset charset = getCharset();

  •            boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();

  •            parameters.setCharset(charset);

  •            if (useBodyEncodingForURI) {

  •                parameters.setQueryStringCharset(charset);

  •            }

  •            // Note: If !useBodyEncodingForURI, the query string encoding is

  •            //       that set towards the start of CoyoyeAdapter.service()

  •            parameters.handleQueryParameters();

  •            if (usingInputStream || usingReader) {

  •                success = true;

  •                return;

  •            }

  •            String contentType = getContentType();

  •            if (contentType == null) {

  •                contentType = "";

  •            }

  •            int semicolon = contentType.indexOf(";");

  •            if (semicolon >= 0) {

  •                contentType = contentType.substring(0, semicolon).trim();

  •            } else {

  •                contentType = contentType.trim();

  •            }

  •            if ("multipart/form-data".equals(contentType)) {

  •                parseParts(false);

  •                success = true;

  •                return;

  •            }

  •            if( !getConnector().isParseBodyMethod(getMethod()) ) {

  •                success = true;

  •                return;

  •            }

  •            if (!("application/x-www-form-urlencoded".equals(contentType))) {

  •                success = true;

  •                return;

  •            }

  •            int len = getContentLength();

  •            if (len > 0) {

  •                int maxPostSize = connector.getMaxPostSize();

  •                if ((maxPostSize >= 0) && (len > maxPostSize)) {

  •                    Context context = getContext();

  •                    if (context != null && context.getLogger().isDebugEnabled()) {

  •                        context.getLogger().debug(

  •                                sm.getString("coyoteRequest.postTooLarge"));

  •                    }

  •                    checkSwallowInput();

  •                    parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);

  •                    return;

  •                }

  •                byte[] formData = null;

  •                if (len < CACHED_POST_LEN) {

  •                    if (postData == null) {

  •                        postData = new byte[CACHED_POST_LEN];

  •                    }

  •                    formData = postData;

  •                } else {

  •                    formData = new byte[len];

  •                }

  •                try {

  •                    if (readPostBody(formData, len) != len) {

  •                        parameters.setParseFailedReason(FailReason.REQUEST_BODY_INCOMPLETE);

  •                        return;

  •                    }

  •                } catch (IOException e) {

  •                    // Client disconnect

  •                    Context context = getContext();

  •                    if (context != null && context.getLogger().isDebugEnabled()) {

  •                        context.getLogger().debug(

  •                                sm.getString("coyoteRequest.parseParameters"),

  •                                e);

  •                    }

  •                    parameters.setParseFailedReason(FailReason.CLIENT_DISCONNECT);

  •                    return;

  •                }

  •                parameters.processParameters(formData, 0, len);

  •            } else if ("chunked".equalsIgnoreCase(

  •                    coyoteRequest.getHeader("transfer-encoding"))) {

  •                byte[] formData = null;

  •                try {

  •                    formData = readChunkedPostBody();

  •                } catch (IllegalStateException ise) {

  •                    // chunkedPostTooLarge error

  •                    parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);

  •                    Context context = getContext();

  •                    if (context != null && context.getLogger().isDebugEnabled()) {

  •                        context.getLogger().debug(

  •                                sm.getString("coyoteRequest.parseParameters"),

  •                                ise);

  •                    }

  •                    return;

  •                } catch (IOException e) {

  •                    // Client disconnect

  •                    parameters.setParseFailedReason(FailReason.CLIENT_DISCONNECT);

  •                    Context context = getContext();

  •                    if (context != null && context.getLogger().isDebugEnabled()) {

  •                        context.getLogger().debug(

  •                                sm.getString("coyoteRequest.parseParameters"),

  •                                e);

  •                    }

  •                    return;

  •                }

  •                if (formData != null) {

  •                    parameters.processParameters(formData, 0, formData.length);

  •                }

  •            }

  •            success = true;

  •        } finally {

  •            if (!success) {

  •                parameters.setParseFailedReason(FailReason.UNKNOWN);

  •            }

  •        }

  •    }

  • /**

  •     * Read post body in an array.

  •     *

  •     * @param body The bytes array in which the body will be read

  •     * @param len The body length

  •     * @return the bytes count that has been read

  •     * @throws IOException if an IO exception occurred

  •     */

  •    protected int readPostBody(byte[] body, int len)

  •        throws IOException {

  •        int offset = 0;

  •        do {

  •            int inputLen = getStream().read(body, offset, len - offset);

  •            if (inputLen <= 0) {

  •                return offset;

  •            }

  •            offset += inputLen;

  •        } while ((len - offset) > 0);

  •        return len;

  •    }

  • 上面仅对application/x-www-form-urlencodedreadPostBody的情况执行readPostBody,请求体中的参数已经被读取解析为map,流已经不可重复读取,因此在转发之前,FormBodyWrapperFilter需要把map中的参数放回请求体中。

    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;

  •    }

  • 最后执行时,wrapper.getContentType()内部执行buildContentData方法,将参数放回请求体中。

  • ctx.getZuulRequestHeaders().put("content-type", wrapper.getContentType());

  • buildContentData方法:

  • private synchronized void buildContentData() {

  •            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();

  •                this.contentData = data.getInput();

  •                this.contentLength = this.contentData.length;

  •            }

  •            catch (Exception e) {

  •                throw new IllegalStateException("Cannot convert form data", e);

  •            }

  •        }

  • formHttpMessageConverter的write如下:

  • @Override

  •    @SuppressWarnings("unchecked")

  •    public void write(MultiValueMap<String, ?> map, MediaType contentType, HttpOutputMessage outputMessage)

  •            throws IOException, HttpMessageNotWritableException {

  •        if (!isMultipart(map, contentType)) {

  •            writeForm((MultiValueMap<String, String>) map, contentType, outputMessage);

  •        }

  •        else {

  •            writeMultipart((MultiValueMap<String, Object>) map, outputMessage);

  •        }

  •    }

  • writeForm如下所示

  • private void writeForm(MultiValueMap<String, String> form, MediaType contentType,

  •            HttpOutputMessage outputMessage) throws IOException {

  •        Charset charset;

  •        if (contentType != null) {

  •            outputMessage.getHeaders().setContentType(contentType);

  •            charset = (contentType.getCharset() != null ? contentType.getCharset() : this.charset);

  •        }

  •        else {

  •            outputMessage.getHeaders().setContentType(MediaType.APPLICATION_FORM_URLENCODED);

  •            charset = this.charset;

  •        }

  •        StringBuilder builder = new StringBuilder();

  •        for (Iterator<String> nameIterator = form.keySet().iterator(); nameIterator.hasNext();) {

  •            String name = nameIterator.next();

  •            for (Iterator<String> valueIterator = form.get(name).iterator(); valueIterator.hasNext();) {

  •                String value = valueIterator.next();

  •                builder.append(URLEncoder.encode(name, charset.name()));

  •                if (value != null) {

  •                    builder.append("=");

  •                    builder.append(URLEncoder.encode(value, charset.name()));

  •                    if (valueIterator.hasNext()) {

  •                        builder.append("&");

  •                    }

  •                }

  •            }

  •            if (nameIterator.hasNext()) {

  •                builder.append("&");

  •            }

  •        }

  •        final byte[] bytes = builder.toString().getBytes(charset.name());

  •        outputMessage.getHeaders().setContentLength(bytes.length);

  •        if (outputMessage instanceof StreamingHttpOutputMessage) {

  •            StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;

  •            streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() {

  •                @Override

  •                public void writeTo(OutputStream outputStream) throws IOException {

  •                    StreamUtils.copy(bytes, outputStream);

  •                }

  •            });

  •        }

  •        else {

  •            StreamUtils.copy(bytes, outputMessage.getBody());

  •        }

  •    }

  • MultiValueMapform从哪里来呢,它通过extractFromRequest方法获取,参数request即是前面的RequestFacade。方法将获取Query Params和从请求体中解析出来的的参数。

  • 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)) {

  •                for (String value : entry.getValue()) {

  •                    builder.add(key, value);

  •                }

  •            }

  •        }

  •        return builder;

  •    }

  • 那spring zuul 怎么保证后续zuul filter处理参数的时候,输入流和参数可以重复读取呢,答案是它重新构造了一个FormBodyRequestWrapper,并把它作为实例变量req放入包装类com.netflix.zuul.http.HttpServletRequestWrapper中,HttpServletRequestWrapper实现了装饰器模式,除了读取request内容(参数、流或reader),其他方法默认通过被包装的request对象调用。这个类提供了缓冲的内容供读取,允许getReader()、getInputStream()和getParameterXXX这些方法安全、重复地被调用,并且返回的结果相同。

    以下是HttpServletRequestWrapper的几个典型方法,其中成员变量req即是传入的FormBodyRequestWrapper。

  • /**

  •     * This method is safe to execute multiple times.

  •     *

  •     * @see javax.servlet.ServletRequest#getParameter(java.lang.String)

  •     */

  •    @Override

  •    public String getParameter(String name) {

  •        try {

  •            parseRequest();

  •        } catch (IOException e) {

  •            throw new IllegalStateException("Cannot parse the request!", e);

  •        }

  •        if (parameters == null) return null;

  •        String[] values = parameters.get(name);

  •        if (values == null || values.length == 0)

  •            return null;

  •        return values[0];

  •    }

  •    /**

  •     * This method is safe.

  •     *

  •     * @see {@link #getParameters()}

  •     * @see javax.servlet.ServletRequest#getParameterMap()

  •     */

  •    @SuppressWarnings("unchecked")

  •    @Override

  •    public Map getParameterMap() {

  •        try {

  •            parseRequest();

  •        } catch (IOException e) {

  •            throw new IllegalStateException("Cannot parse the request!", e);

  •        }

  •        return getParameters();

  •    }

  • parseRequest处理如下:

  • private void parseRequest() throws IOException {

  •        if (parameters != null)

  •            return; //already parsed

  •        HashMap<String, List<String>> mapA = new HashMap<String, List<String>>();

  •        List<String> list;

  •        Map<String, List<String>> query = HTTPRequestUtils.getInstance().getQueryParams();

  •        if (query != null) {

  •            for (String key : query.keySet()) {

  •                list = query.get(key);

  •                mapA.put(key, list);

  •            }

  •        }

  •        if (shouldBufferBody()) {

  •            // Read the request body inputstream into a byte array.

  •            ByteArrayOutputStream baos = new ByteArrayOutputStream();

  •            try {

  •                // Copy all bytes from inputstream to byte array, and record time taken.

  •                long bufferStartTime = System.nanoTime();

  •            //这里的req是FormBodyRequestWrapper,inputstream可以重复被读取。

  •                IOUtils.copy(req.getInputStream(), baos);

  •                bodyBufferingTimeNs = System.nanoTime() - bufferStartTime;

  •                contentData = baos.toByteArray();

  •            } catch (SocketTimeoutException e) {

  •                // This can happen if the request body is smaller than the size specified in the

  •                // Content-Length header, and using tomcat APR connector.

  •                LOG.error("SocketTimeoutException reading request body from inputstream. error=" + String.valueOf(e.getMessage()));

  •                if (contentData == null) {

  •                    contentData = new byte[0];

  •                }

  •            }

  •            try {

  •                LOG.debug("Length of contentData byte array = " + contentData.length);

  •                if (req.getContentLength() != contentData.length) {

  •                    LOG.warn("Content-length different from byte array length! cl=" + req.getContentLength() + ", array=" + contentData.length);

  •                }

  •            } catch(Exception e) {

  •                LOG.error("Error checking if request body gzipped!", e);

  •            }

  •            final boolean isPost = req.getMethod().equals("POST");

  •            String contentType = req.getContentType();

  •            final boolean isFormBody = contentType != null && contentType.contains("application/x-www-form-urlencoded");

  •            // only does magic body param parsing for POST form bodies

  •            if (isPost && isFormBody) {

  •                String enc = req.getCharacterEncoding();

  •                if (enc == null) enc = "UTF-8";

  •                String s = new String(contentData, enc), name, value;

  •                StringTokenizer st = new StringTokenizer(s, "&");

  •                int i;

  •                boolean decode = req.getContentType() != null;

  •                while (st.hasMoreTokens()) {

  •                    s = st.nextToken();

  •                    i = s.indexOf("=");

  •                    if (i > 0 && s.length() > i + 1) {

  •                        name = s.substring(0, i);

  •                        value = s.substring(i + 1);

  •                        if (decode) {

  •                            try {

  •                                name = URLDecoder.decode(name, "UTF-8");

  •                            } catch (Exception e) {

  •                            }

  •                            try {

  •                                value = URLDecoder.decode(value, "UTF-8");

  •                            } catch (Exception e) {

  •                            }

  •                        }

  •                        list = mapA.get(name);

  •                        if (list == null) {

  •                            list = new LinkedList<String>();

  •                            mapA.put(name, list);

  •                        }

  •                        list.add(value);

  •                    }

  •                }

  •            }

  •        }

  •        HashMap<String, String[]> map = new HashMap<String, String[]>(mapA.size() * 2);

  •        for (String key : mapA.keySet()) {

  •            list = mapA.get(key);

  •            map.put(key, list.toArray(new String[list.size()]));

  •        }

  •        parameters = map;

  •    }

  • parseRequest中的req.getInputStream()可以被重复读取。FormBodyRequestWrapper典型方法如下:

  • @Override

  •        public ServletInputStream getInputStream() throws IOException {

  •            if (this.contentData == null) {

  •                buildContentData();

  •            }

  •            return new ServletInputStreamWrapper(this.contentData);

  •        }

  • buildContentData实现代码前面已经贴出,可以知道,contentData是一个byte数组,在buildContentData时已经被赋值,可以重复使用。

  • this.contentData = data.getInput();

  • 在后续route filter转发过程中,当需要获取流数据重新构造请求时,以上的过程就派上用场,比如SimpleHostRoutingFilter中的代码片段。

  • private InputStream getRequestBody(HttpServletRequest request) {

  •        InputStream requestEntity = null;

  •        try {

  •            requestEntity = request.getInputStream();

  •        }

  •        catch (IOException ex) {

  •            // no requestBody is ok.

  •        }

  •        return requestEntity;

  •    }

  • 总结及后续:

    request 流是不可重复读取的,在请求达到ZuulServlet之前,也许Content-Type = application/x-www-form- urlencoding类型的请求,其请求体已经被解析并将参数缓存了,因此转发的时候其请求体中的内容已经丢失,需要重新放回去。放回去后又需要在后续zuul filter中能够重复读取使用。FormBodyWrapperFilter就是出于这个目标诞生的。而当Content-Type = multipart/form-data时,目的也类似,因为如果请求先达到DispatcherServlet,其中的数据也是要被解析处理的,有兴趣可以阅读DispatcherServlet 相关源码:

  • /**

  •     * Convert the request into a multipart request, and make multipart resolver available.

  •     * <p>If no multipart resolver is set, simply use the existing request.

  •     * @param request current HTTP request

  •     * @return the processed request (multipart wrapper if necessary)

  •     * @see MultipartResolver#resolveMultipart

  •     */

  •    protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {

  •        if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {

  •            if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {

  •                logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +

  •                        "this typically results from an additional MultipartFilter in web.xml");

  •            }

  •            else if (hasMultipartException(request) ) {

  •                logger.debug("Multipart resolution failed for current request before - " +

  •                        "skipping re-resolution for undisturbed error rendering");

  •            }

  •            else {

  •                try {

  •                    return this.multipartResolver.resolveMultipart(request);

  •                }

  •                catch (MultipartException ex) {

  •                    if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {

  •                        logger.debug("Multipart resolution failed for error dispatch", ex);

  •                        // Keep processing error dispatch with regular request handle below

  •                    }

  •                    else {

  •                        throw ex;

  •                    }

  •                }

  •            }

  •        }

  •        // If not returned before: return original request.

  •        return request;

  •    }

  • 以上是个人的一些发现,如有不当,欢迎交流指正。

    java达人

    ID:drjava

    (长按或扫码识别)


    今日推荐
    ----------------------------------------
    友情链接:灵虎爱 塞寥尔61比林斯利博士 张鸿涉受贿被公诉 李海进 mexcoaer 吴晗歆 jf 姬流香 野人海岸蓝色叉牙鱼 紫眸皇后 洋人哈哈笑 豪门嫡妻 妙手回心 林淽诺 红杏砸墙 n0608 星辰震天 锵锵三人行 浩米 曾文铁 冒险之星 恋迹社区 李天一强轩女孩照片 科学家当场惊呆 nataya 欧阳希望 西游之焚天 荒城咒怨 水灵珠朝天骄送代理 李丰强 日月凌空简谱 迦西 琴瑟情未了 无锡阿福台交通违章查询 柳暗花明又一村 近理抄 僵尸小子在都市 梦幻西游知识老人 淑女凶猛 浦寨 买双色球 森摩耶 人界重生 卢基亚诺娃 云起天地英雄 胡杭网yrt9 邹容 契约情人 花花皇后 人形帝王兽