一次 HTTP 加解密尝试
工作要处理外部系统对接,虽然网络为政务内网,几乎不可能出现中间人攻击或网络嗅探。但传输内容为敏感信息,仍然要进行加密,最后选择使用 SM4 对整个请求体信息进行加密,并与对接系统约定固定密钥。
确定了一个简单的方案(不建议使用,后面会提到,这个方式局限性很强,唯一优点是实现简单),通过配置高优先级的 Servlet Filter 对请求进行预处理,在请求 Servlet 后读取请求体(此处通过调用 Request.getReader 方法),并对请求体进行解密。创建自定义 RequestWrapper 继承 HttpServletRequestWrapper,并将解密后的自定义 RequestWrapper 中,重写 getReader 和 getInputStream 方法。
这样请求在进入 DispatcherServlet 之前就会完成解密。通过同样方式创建 ResponseWrapper 继承 HttpServletResponseWrapper,在请求返回前由 Servlet Filter 实现对返回响应的整体加密。
该方案支持常规的 POST 请求,如 Content-Type 为 application/json 的请求。
事实上,DispatcherServlet 通过调用 RequestMappingHandlerAdapter 选择合适的 Controller 方法并调用。但在调用之前,还需要完成两步,第一步是解析请求体内容并转换为 Java 对象,第二步是根据 Controller 方法入参,匹配对应的 Java 对象再传入 Controller 方法进行调用。
5.0 版本源码
下面是 RequestMappingHandlerAdapter 的 handle 方法:
// 5.0 @Override public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) { HandlerMethod handlerMethod = (HandlerMethod) handler; Assert.state(this.methodResolver != null && this.modelInitializer != null, "Not initialized"); InitBinderBindingContext bindingContext = new InitBinderBindingContext( getWebBindingInitializer(), this.methodResolver.getInitBinderMethods(handlerMethod)); // 设置并获取 Resolver InvocableHandlerMethod invocableMethod = this.methodResolver.getRequestMappingMethod(handlerMethod); Function<Throwable, Mono<HandlerResult>> exceptionHandler = ex -> handleException(ex, handlerMethod, bindingContext, exchange); return this.modelInitializer .initModel(handlerMethod, bindingContext, exchange) // 发起调用 .then(Mono.defer(() -> invocableMethod.invoke(exchange, bindingContext))) .doOnNext(result -> result.setExceptionHandler(exceptionHandler)) .doOnNext(result -> bindingContext.saveModel()) .onErrorResume(exceptionHandler); }
在 invocableMethod#invoke 的内部实现中,通过 getMethodArgumentValues 方法,调用 Resolvers 解析控制器方法参数。这里的 Resolvers 实现了 HandlerMethodArgumentResolverComposite 接口,也就是多个 HandlerMethodArgumentResolver 的复合实现,可以灵活地组合多个参数解析器,以支持更多的参数类型和注解。
而具体的处理逻辑应该看 HandlerMethodArgumentResolver 接口实现类,比如说 @RequestBody 注解的参数通过 RequestBodyMethodArgumentResolver 进行解析。查看 RequestBodyMethodArgumentResolver#resolveArgument 方法可以找到具体实现逻辑。HandlerMethodArgumentResolver 也不仅仅包括解析控制器的方法入参,也能够根据 @PathVariable
、@RequestParam
、@RequestHeader
等注解解析 HTTP 请求的其他信息。
// 5.0 private Mono<Object[]> getMethodArgumentValues( ServerWebExchange exchange, BindingContext bindingContext, Object... providedArgs) { MethodParameter[] parameters = getMethodParameters(); if (ObjectUtils.isEmpty(parameters)) { return EMPTY_ARGS; } List<Mono<Object>> argMonos = new ArrayList<>(parameters.length); for (MethodParameter parameter : parameters) { parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); Object providedArg = findProvidedArgument(parameter, providedArgs); if (providedArg != null) { argMonos.add(Mono.just(providedArg)); continue; } if (!this.resolvers.supportsParameter(parameter)) { return Mono.error(new IllegalStateException( formatArgumentError(parameter, "No suitable resolver"))); } try { // Resolver 处理参数 argMonos.add(this.resolvers.resolveArgument(parameter, bindingContext, exchange) .defaultIfEmpty(NO_ARG_VALUE) .doOnError(ex -> logArgumentErrorIfNecessary(exchange, parameter, ex))); } catch (Exception ex) { logArgumentErrorIfNecessary(exchange, parameter, ex); argMonos.add(Mono.error(ex)); } } return Mono.zip(argMonos, values -> Stream.of(values).map(value -> value != NO_ARG_VALUE ? value : null).toArray()); }
invoke 发放中的入参 providedArgs 默认不传递,若能找到 parameter 匹配的 Resolver,会调用 resolveArgument 方法。假设此处调用了 RequestBodyMethodArgumentResolver 的方法,则可以通过 readBody 方法获取到入参对象。Spring WebFlux 使用 HttpMessageReader 和 HttpMessageWriter 接口来转换 HTTP 请求和响应,因此在 readBody 方法中,可以看到从 reader 获取对象的逻辑:
// 5.0 for (HttpMessageReader<?> reader : getMessageReaders()) { if (reader.canRead(elementType, mediaType)) { Map<String, Object> readHints = Hints.from(Hints.LOG_PREFIX_HINT, exchange.getLogPrefix()); if (adapter != null && adapter.isMultiValue()) { if (logger.isDebugEnabled()) { logger.debug(exchange.getLogPrefix() + "0..N [" + elementType + "]"); } // 使用 HttpMessageReader 读取请求 Flux<?> flux = reader.read(actualType, elementType, request, response, readHints); flux = flux.onErrorResume(ex -> Flux.error(handleReadError(bodyParam, ex))); if (isBodyRequired) { flux = flux.switchIfEmpty(Flux.error(() -> handleMissingBody(bodyParam))); } if (hints != null) { flux = flux.doOnNext(target -> validate(target, hints, bodyParam, bindingContext, exchange)); } return Mono.just(adapter.fromPublisher(flux)); } else { // Single-value (with or without reactive type wrapper) if (logger.isDebugEnabled()) { logger.debug(exchange.getLogPrefix() + "0..1 [" + elementType + "]"); } Mono<?> mono = reader.readMono(actualType, elementType, request, response, readHints); mono = mono.onErrorResume(ex -> Mono.error(handleReadError(bodyParam, ex))); if (isBodyRequired) { mono = mono.switchIfEmpty(Mono.error(() -> handleMissingBody(bodyParam))); } if (hints != null) { mono = mono.doOnNext(target -> validate(target, hints, bodyParam, bindingContext, exchange)); } return (adapter != null ? Mono.just(adapter.fromPublisher(mono)) : Mono.from(mono)); } } }
看到这里会有一个疑惑,就是为什么代码中是 HttpMessageReader 而不是 HttpMessageConverter。HttpMessageReader 是由 5.0 版本引入的,支持响应式编程的接口,支持读取 ReactiveHttpInputMessage 并编码为对象流。
3.0 版本源码
3.0 版本的代码,逻辑则更加清晰:
下面是通过 DispatcherServlet 进入到 RequestMappingHandlerAdapter 的 handleInternal 方法。
// RequestMappingHandlerAdapter @Override protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav; checkRequest(request); // Execute invokeHandlerMethod in synchronized block if required. if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No HttpSession available -> no mutex necessary // 根据请求的内容,解析出方法需要的参数,并准备好这些参数的值 // 通过Java的反射机制,调用目标处理器方法 // 处理器方法执行完毕后,invokeHandlerMethod 会获取返回值,并根据返回值的类型做出相应的处理 mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No synchronization on session demanded at all... // 同上 mav = invokeHandlerMethod(request, response, handlerMethod); } if (!response.containsHeader(HEADER_CACHE_CONTROL)) { if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); } else { prepareResponse(response); } } return mav; }
这里进入 invokeHandlerMethod,该方法会通过传入的 handlerMethod 获取到 invocableMethod,并调用 invocableMethod 的 invokeAndHandle 方法:
// RequestMappingHandlerAdapter @Nullable protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { // 使用HTTP请求和响应对象创建 ServletWebRequest 对象,它封装了请求和响应的相关信息 ServletWebRequest webRequest = new ServletWebRequest(request, response); try { // 根据处理器方法,获取数据绑定工厂和模型工厂,用于处理请求参数的绑定和模型数据的准备 WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); // 根据处理器方法创建 ServletInvocableHandlerMethod 对象 // 它是一个可调用的处理器方法,封装了目标处理器方法的信息 ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); // 如果已配置了参数解析器和返回值处理器,则将它们设置到 ServletInvocableHandlerMethod 对象中 if (this.argumentResolvers != null) { invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); } if (this.returnValueHandlers != null) { invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); } invocableMethod.setDataBinderFactory(binderFactory); invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); // 创建 ModelAndViewContainer 对象,用于存储处理结果和视图信息 // 并且从请求中获取输入的 FlashMap 并添加到 ModelAndViewContainer 中 ModelAndViewContainer mavContainer = new ModelAndViewContainer(); mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); modelFactory.initModel(webRequest, mavContainer, invocableMethod); mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); // 为异步处理准备 AsyncWebRequest 对象 AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); asyncWebRequest.setTimeout(this.asyncRequestTimeout); // 获取当前请求的 WebAsyncManager 对象,用于处理异步请求 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.setTaskExecutor(this.taskExecutor); asyncManager.setAsyncWebRequest(asyncWebRequest); asyncManager.registerCallableInterceptors(this.callableInterceptors); asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors); // 如果存在并发结果(通过异步方式处理的结果),则将其处理,并更新 ServletInvocableHandlerMethod 对象以包含这些结果 if (asyncManager.hasConcurrentResult()) { Object result = asyncManager.getConcurrentResult(); mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0]; asyncManager.clearConcurrentResult(); LogFormatUtils.traceDebug(logger, traceOn -> { String formatted = LogFormatUtils.formatValue(result, !traceOn); return "Resume with async result [" + formatted + "]"; }); invocableMethod = invocableMethod.wrapConcurrentResult(result); } // 调用 ServletInvocableHandlerMethod 对象的 invokeAndHandle 方法,执行目标处理器方法并处理结果 invocableMethod.invokeAndHandle(webRequest, mavContainer); // 检查异步处理是否已经启动,如果是,则返回 null if (asyncManager.isConcurrentHandlingStarted()) { return null; } // 从 ModelAndViewContainer 和 ModelFactory 中获取数据,构建并返回最终的 ModelAndView 对象 return getModelAndView(mavContainer, modelFactory, webRequest); } finally { // 标记请求完成 webRequest.requestCompleted(); } }
invokeAndHandle 方法内通过 invokeForRequest 方法获取到返回值,该方法内部则又看到了熟悉的 getMethodArgumentValues
// ServletInvocableHandlerMethod public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 调用 invokeForRequest 方法执行目标处理器方法 // 传入 ServletWebRequest 对象、ModelAndViewContainer 对象和其他提供的参数 // 方法的返回值保存在 returnValue 变量中 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); // 调用 setResponseStatus 方法来设置响应的状态码 setResponseStatus(webRequest); // 返回值为 null if (returnValue == null) { // 如果 // 1. 请求未被修改(通过判断是否为未修改请求) // 2. 或者已经设置了响应状态码 // 3. 或者 ModelAndViewContainer 标记为已处理请求 // 则禁用内容缓存并将 ModelAndViewContainer 标记为已处理,并直接返回 if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { disableContentCachingIfNecessary(webRequest); mavContainer.setRequestHandled(true); return; } // 返回值不为 null,但是具有响应状态码的原因(通过 getResponseStatusReason 方法判断) } else if (StringUtils.hasText(getResponseStatusReason())) { // 则将 ModelAndViewContainer 标记为已处理请求,并直接返回 mavContainer.setRequestHandled(true); return; } // 如果没有满足上述条件,将 ModelAndViewContainer 标记为未处理请求,并确保 returnValueHandlers 不为 null mavContainer.setRequestHandled(false); Assert.state(this.returnValueHandlers != null, "No return value handlers"); try { // 尝试使用 returnValueHandlers 来处理返回值 // returnValueHandlers 是一组用于处理控制器方法返回值的策略 this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(formatErrorForReturnValue(returnValue), ex); } throw ex; } } ------ // InvocableHandlerMethod @Nullable public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 调用 getMethodArgumentValues 方法获取目标处理器方法的参数值 // 这个方法会解析请求,准备方法调用所需的参数 // 入参包括 NativeWebRequest 对象、ModelAndViewContainer 对象(可能为 null),以及其他提供的参数 // 这些参数将在方法内部被解析和处理,最终得到一个参数值的数组 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Arguments: " + Arrays.toString(args)); } return doInvoke(args); } ------ // InvocableHandlerMethod protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 调用 getMethodParameters 方法获取目标处理器方法的参数信息 MethodParameter[] parameters = getMethodParameters(); // 如果没有参数,则直接返回一个空数组 if (ObjectUtils.isEmpty(parameters)) { return EMPTY_ARGS; } // 遍历方法的参数列表 Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; // 对每个方法参数对象调用 initParameterNameDiscovery 方法初始化参数名发现器,以便后续解析参数名 parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); // 首先尝试在提供的参数数组中查找是否有提供的参数值。如果有则直接将提供的参数值赋给对应的参数变量 args[i] = findProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } // 如果没有提供的参数值,则判断当前方法参数是否由当前的参数解析器(resolvers)支持 if (!this.resolvers.supportsParameter(parameter)) { throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver")); } try { // 如果支持当前参数,则调用参数解析器的 resolveArgument 方法来解析参数值,并将解析结果赋给对应的参数变量 args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); } catch (Exception ex) { // Leave stack trace for later, exception may actually be resolved and handled... if (logger.isDebugEnabled()) { String exMsg = ex.getMessage(); if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { logger.debug(formatArgumentError(parameter, exMsg)); } } throw ex; } } return args; } ------ // HandlerMethodArgumentResolverComposite @Override @Nullable public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { // 获取参数处理器 HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter); if (resolver == null) { throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first."); } // 参数处理 return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); }
最终的链路还是一致的,到了 resolver.resolveArgument 方法。我们还是以 RequestResponseBodyMethodProcessor 为例:
// RequestResponseBodyMethodProcessor // 该方法用于解析控制器方法的参数,特别是用于处理使用 @RequestBody 和 @ResponseBody 注解的方法参数 @Override public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { parameter = parameter.nestedIfOptional(); // 调用 readWithMessageConverters 方法 // 使用 HttpMessageConverters 去解析请求体,并获取参数值。这一步骤将请求体中的数据转换成方法参数所需的类型 Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType()); // 获取参数的名称,用于后续数据绑定 String name = Conventions.getVariableNameForParameter(parameter); // 如果存在 binderFactory(数据绑定工厂) if (binderFactory != null) { // 调用 createBinder 方法创建数据绑定器,并对参数值进行数据绑定 // 数据绑定器会将请求参数的值绑定到方法参数上,并进行验证 WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); // 如果参数值不为空,则进行数据验证,如果验证失败且需要抛出绑定异常,则抛出 MethodArgumentNotValidException 异常 if (arg != null) { validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new MethodArgumentNotValidException(parameter, binder.getBindingResult()); } } // 如果存在 mavContainer(ModelAndViewContainer),则将数据绑定的结果添加到 mavContainer 中 if (mavContainer != null) { mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); } } // 根据需要对参数值进行适应。这一步骤可能涉及对参数值的进一步处理或转换 return adaptArgumentIfNecessary(arg, parameter); } ------ // RequestResponseBodyMethodProcessor @Override protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter, Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { // 从 NativeWebRequest 中获取 HttpServletRequest 对象。这个对象是原生的HTTP请求对象 HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); Assert.state(servletRequest != null, "No HttpServletRequest"); // 创建 ServletServerHttpRequest 对象。这个对象封装了HTTP请求的信息,方便后续对请求体的处理 ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest); // 调用另一个重载的 readWithMessageConverters 方法,传入 ServletServerHttpRequest 对象、方法参数对象和参数类型 // 这个方法是实际执行消息转换的关键步骤,它会尝试使用消息转换器将请求体转换为方法参数所需的类型,并返回转换后的参数值 Object arg = readWithMessageConverters(inputMessage, parameter, paramType); if (arg == null && checkRequired(parameter)) { throw new HttpMessageNotReadableException("Required request body is missing: " + parameter.getExecutable().toGenericString(), inputMessage); } return arg; }
最后进入 readWithMessageConverters 方法,这里也是完成与 HttpMessageConverter 关联的位置:
// AbstractMessageConverterMethodArgumentResolver @Nullable protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { MediaType contentType; boolean noContentType = false; try { // 尝试从输入消息的头部获取内容类型(Content-Type) contentType = inputMessage.getHeaders().getContentType(); } catch (InvalidMediaTypeException ex) { // 如果获取失败,则抛出 HttpMediaTypeNotSupportedException 异常 throw new HttpMediaTypeNotSupportedException(ex.getMessage()); } // 如果没有指定内容类型,则将内容类型设为默认的 MediaType.APPLICATION_OCTET_STREAM。 if (contentType == null) { noContentType = true; contentType = MediaType.APPLICATION_OCTET_STREAM; } // 根据方法参数的类型和目标类型,确定转换后的目标类型 Class<?> contextClass = parameter.getContainingClass(); // 如果目标类型不是一个 Class 类型,则从方法参数中获取相应的 Class 类型 Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null); if (targetClass == null) { ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter); targetClass = (Class<T>) resolvableType.resolve(); } // 如果输入消息是一个 HttpRequest 类型的消息,则获取其中的 HTTP 请求方法 // 这用于后续判断是否支持当前 HTTP 请求方法 HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null); Object body = NO_VALUE; AbstractMessageConverterMethodArgumentResolver.EmptyBodyCheckingHttpInputMessage message; try { // 初始化一些变量,包括 body、message 等 message = new AbstractMessageConverterMethodArgumentResolver.EmptyBodyCheckingHttpInputMessage(inputMessage); // 对注册的消息转换器列表进行遍历,尝试使用每个消息转换器来读取输入消息并进行转换 for (HttpMessageConverter<?> converter : this.messageConverters) { // 对每个消息转换器,判断它是否能够处理当前请求 Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass(); GenericHttpMessageConverter<?> genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null); // 如果能够处理,则根据消息转换器的类型进行相应的读取和转换操作 // genericConverter 不为 null,表示当前转换器是一个通用的消息转换器,可以处理任意类型的目标对象 if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) : // genericConverter 为 null,表示当前转换器不是通用的,此时判断是否支持指定目标类型和内容类型 (targetClass != null && converter.canRead(targetClass, contentType))) { // 输入消息中存在消息体 if (message.hasBody()) { // 调用 beforeBodyRead 方法,对消息体进行预处理 HttpInputMessage msgToUse = getAdvice().beforeBodyRead(message, parameter, targetType, converterType); // 使用消息转换器将输入消息的消息体转换为目标类型的对象 body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) : ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse)); // 对转换后的结果进行后处理 body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType); } else { // 如果输入消息中没有消息体,则调用相应的回调方法处理空消息体 body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType); } break; } } } catch (IOException ex) { throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage); } // 如果没有成功地读取到消息体 if (body == NO_VALUE) { // 并且请求方法不在支持的方法列表中,或者消息体为空且没有指定内容类型 if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) || (noContentType && !message.hasBody())) { // 则返回 null return null; } // 否则,抛出 HttpMediaTypeNotSupportedException 异常 throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes); } MediaType selectedContentType = contentType; Object theBody = body; LogFormatUtils.traceDebug(logger, traceOn -> { String formatted = LogFormatUtils.formatValue(theBody, !traceOn); return "Read \"" + selectedContentType + "\" to [" + formatted + "]"; }); // 如果成功读取到了消息体并进行了转换,则返回转换后的结果 // 在返回之前,记录日志以便跟踪转换的结果和过程 return body; }
为什么用 Filter 不好?
创建自定义 RequestWrapper 继承 HttpServletRequestWrapper 前,就需要读取请求体再进行解密操作。
Servlet 容器(比如 Tomcat、Jetty 等)在接收到 HTTP 请求后,会解析请求头和消息体,并提供一种方式让 Servlet 来读取这些数据。Servlet 通常通过 HttpServletRequest 对象提供的 getReader() 方法(用于读取字符数据)或 getInputStream() 方法(用于读取字节数据)来读取请求体中的数据。
然而,由于 HTTP 协议的设计,一旦读取了请求体中的数据,它们就会从输入流中被读取并且被消费掉了。换句话说,一旦调用了 getReader() 或 getInputStream() 方法,Servlet 容器会从请求中读取消息体,并将它们提供给Servlet。一旦 Servlet 读取完消息体或者关闭了输入流,消息体中的数据就不再可用了。因此,再次调用 getReader() 或getInputStream() 方法将不会返回有效的数据。可以在 Catalina 的 Request 方法中看到,usingReader 字段用于控制 getReader() 或 getInputSream() 方法被调用两次。
// Request @Override public BufferedReader getReader() throws IOException { if (usingInputStream) { throw new IllegalStateException(sm.getString("coyoteRequest.getReader.ise")); } // InputBuffer has no easily accessible reference chain to the Context // to check for a default request character encoding at the Context. // Therefore, if a Context default should be used, it is set explicitly // here. Need to do this before setting usingReader. if (coyoteRequest.getCharacterEncoding() == null) { // Nothing currently set explicitly. // Check the content Context context = getContext(); if (context != null) { String enc = context.getRequestCharacterEncoding(); if (enc != null) { // Explicitly set the context default so it is visible to // InputBuffer when creating the Reader. setCharacterEncoding(enc); } } } usingReader = true; inputBuffer.checkConverter(); if (reader == null) { reader = new CoyoteReader(inputBuffer); } return reader; }
实际在使用 Filter 处理 multipart/form-data 的请求时,对文件流的支持并不好。Controller 参数处理类型为 MultipartFile 时,会使用 StandardServletMultipartResolver
@Override public void cleanupMultipart(MultipartHttpServletRequest request) { if (!(request instanceof AbstractMultipartHttpServletRequest) || ((AbstractMultipartHttpServletRequest) request).isResolved()) { // To be on the safe side: explicitly delete the parts, // but only actual file parts (for Resin compatibility) try { // 调用了 HttpServletRequest.getParts() 方法 for (Part part : request.getParts()) { if (request.getFile(part.getName()) != null) { part.delete(); } } } catch (Throwable ex) { LogFactory.getLog(getClass()).warn("Failed to perform cleanup of multipart items", ex); } } }
StandardServletMultipartResolver 通过调用 getParts() 方法,该方法也会调用 Request.getReader() 导致抛出异常。诚然自定义 RequestWrapper 可以将请求体内容缓存,并重写 getReader() 方法,但是 StandardServletMultipartResolver 中还是会调用 Request.getReader() 方法。