0 系列目錄#
- WEB請(qǐng)求處理
- WEB請(qǐng)求處理一:瀏覽器請(qǐng)求發(fā)起處理
- WEB請(qǐng)求處理二:Nginx請(qǐng)求反向代理
- WEB請(qǐng)求處理三:Servlet容器請(qǐng)求處理
- WEB請(qǐng)求處理四:Tomcat配置實(shí)踐
為開發(fā)團(tuán)隊(duì)選擇一款優(yōu)秀的MVC框架是件難事兒,在眾多可行的方案中決擇需要很高的經(jīng)驗(yàn)和水平。你的一個(gè)決定會(huì)影響團(tuán)隊(duì)未來(lái)的幾年。要考慮方面太多:
- 簡(jiǎn)單易用,以提高開發(fā)效率。使小部分的精力在框架上,大部分的精力放在業(yè)務(wù)上。
- 性能優(yōu)秀,這是一個(gè)最能吸引眼球的話題。
- 盡量使用大眾的框架(避免使用小眾的、私有的框架),新招聘來(lái)的開發(fā)人員有一些這方面技術(shù)積累,減低人員流動(dòng)再適應(yīng)的影響。
如果你還在為這件事件發(fā)愁,本文最適合你了。選擇Spring MVC吧。本篇文章主要以Spring MVC為例,基本上市面上的MVC框架處理流程都大同小異,主流程都基本相同。Spring MVC比較成熟、使用也比較廣泛,設(shè)計(jì)理念也非常棒,所以,本文重點(diǎn)以Spring MVC講解為主。
先說(shuō)下在整個(gè)WEB請(qǐng)求處理過(guò)程中,本篇文章講述的是哪個(gè)流程模塊。為直觀明了,先上一張圖,紅色部分為本章所述模塊:
1 Spring MVC核心類與接口#
先來(lái)了解一下,幾個(gè)重要的接口與類。現(xiàn)在不知道他們是干什么的沒(méi)關(guān)系,先混個(gè)臉熟,為以后認(rèn)識(shí)他們打個(gè)基礎(chǔ)。
- DispatcherServlet -- 前置控制器
Spring提供的前置控制器,所有的請(qǐng)求都經(jīng)過(guò)它來(lái)統(tǒng)一分發(fā)。在DispatcherServlet將請(qǐng)求分發(fā)給Spring Controller之前,需要借助于Spring提供的HandlerMapping定位到具體的Controller。
- HandlerMapping接口 -- 處理請(qǐng)求的映射
HandlerMapping接口的實(shí)現(xiàn)類:
SimpleUrlHandlerMapping 通過(guò)配置文件,把一個(gè)URL映射到Controller類上;
DefaultAnnotationHandlerMapping 通過(guò)注解,把一個(gè)URL映射到Controller類上;
- HandlerAdapter接口 -- 處理請(qǐng)求的映射
AnnotationMethodHandlerAdapter類,通過(guò)注解,把一個(gè)URL映射到Controller類的方法上;
- Controller接口 -- 控制器
由于我們使用了@Controller注解,添加了@Controller注解的類就可以擔(dān)任控制器(Action)的職責(zé),所以我們并沒(méi)有用到這個(gè)接口。
需要為并發(fā)用戶處理請(qǐng)求,因此實(shí)現(xiàn)Controller接口時(shí),必須保證線程安全并可重用。Controller將處理用戶請(qǐng)求,這和Struts Action扮演的角色是一致的。一旦Controller處理完用戶請(qǐng)求,則返回ModelAndView對(duì)象給DispatcherServlet前置控制器,ModelAndView中包含了模型(Model)和視圖(View)。從宏觀角度考慮,DispatcherServlet是整個(gè)Web應(yīng)用的控制器;從微觀考慮,Controller是單個(gè)Http請(qǐng)求處理過(guò)程中的控制器,而ModelAndView是Http請(qǐng)求過(guò)程中返回的模型(Model)和視圖(View)。
- HandlerInterceptor接口 -- 攔截器
無(wú)圖,我們自己實(shí)現(xiàn)這個(gè)接口,來(lái)完成攔截的器的工作。
- ViewResolver接口的實(shí)現(xiàn)類
Spring提供的視圖解析器(ViewResolver)在Web應(yīng)用中查找View對(duì)象,從而將相應(yīng)結(jié)果渲染給客戶。
UrlBasedViewResolver類,通過(guò)配置文件,把一個(gè)視圖名交給到一個(gè)View來(lái)處理;
InternalResourceViewResolver類,比上面的類,加入了JSTL的支持;
- View接口
JstlView類
- LocalResolver接口
- HandlerExceptionResolver接口 -- 異常處理
SimpleMappingExceptionResolver實(shí)現(xiàn)類
- ModelAndView類
2 DispatcherServlet初始化過(guò)程#
當(dāng)Web項(xiàng)目啟動(dòng)時(shí),做初始化工作,所以我們大部分是配置在Web.xml里面,這樣項(xiàng)目一啟動(dòng),就會(huì)執(zhí)行相關(guān)的初始化工作,下面是Web.xml代碼:
<servlet>
<servlet-name>SpringMVCDispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring-mvc.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVCDispatcher</servlet-name>
<url-pattern>*.jhtml</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>HessianDispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:hessian-service.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>HessianDispatcher</servlet-name>
<url-pattern>/service/*</url-pattern>
</servlet-mapping>
這里配置了兩個(gè)DispatcherServlet,后面會(huì)介紹到,怎么各自處理,有各自的上下文容器。
最早我們開始學(xué)習(xí)MVC結(jié)構(gòu)時(shí),就是學(xué)servlet,都是繼承了HttpServlet 類,也是重新了init、doGet、doPost、destroy方法,我這邊就不介紹HttpServlet類,DispatcherServlet也是間接最高繼承了HttpServlet,類繼承結(jié)構(gòu)如圖所示:
我們先了解項(xiàng)目啟動(dòng),DispatcherServlet和父類都做了什么事情呢?這是我們本節(jié)的重點(diǎn)。
2.1 第一步:HttpServletBean類init()方法##
DispatcherServlet繼承了FrameworkServlet,F(xiàn)rameworkServlet繼承了HttpServletBean,HttpServletBean繼承了HttpServlet 類,而HttpServletBean類有一個(gè)入口點(diǎn)就是重寫了init方法,如圖所示:
init方法做了什么事情呢?接下來(lái)我們來(lái)具體分析:
- PropertyValues:獲取Web.xml里面的servlet的init-param(web.xml)
/**
* Create new ServletConfigPropertyValues.
* @param config ServletConfig we'll use to take PropertyValues from
* @param requiredProperties set of property names we need, where
* we can't accept default values
* @throws ServletException if any required properties are missing
*/
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
throws ServletException {
Enumeration en = config.getInitParameterNames();
while (en.hasMoreElements()) {
String property = (String) en.nextElement();
Object value = config.getInitParameter(property);
addPropertyValue(new PropertyValue(property, value));
}
}
說(shuō)明:
Enumeration en = config.getInitParameterNames();
獲取了init-param的param-name和param-value值,并設(shè)置配置參數(shù)到PropertyValue,如圖所示:
- BeanWrapper:封裝了bean的行為,提供了設(shè)置和獲取屬性值,它有對(duì)應(yīng)的BeanWrapperImpl,如圖所示:
- ResourceLoader:接口僅有一個(gè)getResource(String location)的方法,可以根據(jù)一個(gè)資源地址加載文件資源。classpath:這種方式指定SpringMVC框架bean配置文件的來(lái)源。
ResourcePatternResolver擴(kuò)展了ResourceLoader接口,獲取資源:
ResourcePatternResolver resolver =new PathMatchingResourcePatternResolver();
resolver.getResources("classpath:spring-mvc.xml");
總結(jié):
- 先通過(guò)PropertyValues獲取web.xml文件init-param的參數(shù)值;
- 然后通過(guò)ResourceLoader讀取.xml配置信息;
- BeanWrapper對(duì)配置的標(biāo)簽進(jìn)行解析和將系統(tǒng)默認(rèn)的bean的各種屬性設(shè)置到對(duì)應(yīng)的bean屬性;
2.2 第二步:FrameworkServlet類initServletBean()方法##
在init方法里還調(diào)用了initServletBean();
這里面又實(shí)現(xiàn)了什么。HttpServletBean在為子類提供模版、讓子類根據(jù)自己的需求實(shí)現(xiàn)不同的ServletBean的初始化工作,這邊是由HttpServletBean的子類FrameworkServlet來(lái)實(shí)現(xiàn)的,如圖所示:
this.webApplicationContext = initWebApplicationContext();
初始化SpringMVC 上下文容器,servlet的上下文容器是ServletContext。對(duì)initWebApplicationContext();進(jìn)行跟蹤,查看這個(gè)方法做了什么事情?
protected WebApplicationContext initWebApplicationContext() {
//1. 根節(jié)點(diǎn)上下文,是通過(guò)ContextLoaderListener加載的,服務(wù)器啟動(dòng)時(shí),最先加載的
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
//2. 要對(duì)上下文設(shè)置父上下文和ID等
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
//3. Servlet不是由編程式注冊(cè)到容器中,查找servletContext中已經(jīng)注冊(cè)的WebApplicationContext作為上下文
if (wac == null) {
wac = findWebApplicationContext();
}
//4. 如果都沒(méi)找到時(shí),就用根上下文就創(chuàng)建一個(gè)上下文有ID
if (wac == null) {
wac = createWebApplicationContext(rootContext);
}
//5. 在上下文關(guān)閉的情況下調(diào)用refesh可啟動(dòng)應(yīng)用上下文,在已經(jīng)啟動(dòng)的狀態(tài)下,調(diào)用refresh則清除緩存并重新裝載配置信息
if (!this.refreshEventReceived) {
onRefresh(wac);
}
//6. 對(duì)不同的請(qǐng)求對(duì)應(yīng)的DispatherServlet有不同的WebApplicationContext、并且都存放在ServletContext中
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
總結(jié):
initWebApplicationContext初始化上下文,并作為值放到了ServletContext里,因?yàn)椴煌腄ispatherServlet有對(duì)應(yīng)的各自的上下文,而且上下文有設(shè)置父上下文和id屬性等。上下文項(xiàng)目啟動(dòng)時(shí)會(huì)調(diào)用createWebApplicationContext()方法,如下圖所示。
然后會(huì)初始化,設(shè)置父上下文和id屬性等,如圖所示:
獲取ContextLoaderListener加載的上下文并標(biāo)示為根上下文,如果是編程式傳入,沒(méi)初始化,以根節(jié)點(diǎn)為父上文,并設(shè)置ID等信息,然后初始化。
如果上下文是為空的,Servlet不是由編程式注冊(cè)到容器中,查找servletContext中已經(jīng)注冊(cè)的WebApplicationContext作為上下文,如果都沒(méi)找到時(shí),就用根上下文就創(chuàng)建一個(gè)上下文ID,在上下文關(guān)閉的情況下調(diào)用refesh可啟動(dòng)應(yīng)用上下文,在已經(jīng)啟動(dòng)的狀態(tài)下,調(diào)用refresh則清除緩存并重新裝載配置信息。
對(duì)不同的請(qǐng)求對(duì)應(yīng)的DispatherServlet有不同的WebApplicationContext、并且都存放在ServletContext中。以servlet-name為key保存在severtContext,前面有配置了兩個(gè)DispatherServlet,都有各自的上下文容器,如下圖所示。
2.3 第三步:DispatcherServlet類onRefresh()方法##
回調(diào)函數(shù)onRefresh還做了一些提供了SpringMVC各種編程元素的初始化工作, onRefresh在為子類提供模版、讓子類根據(jù)自己的需求實(shí)現(xiàn)不同的onRefresh的初始化工作,這邊是由FrameworkServlet的子類DispatcherServlet來(lái)實(shí)現(xiàn)的,如圖所示:
我們現(xiàn)在來(lái)分析SpringMVC組件進(jìn)行初始化,并封裝到DispatcherServlet中:
// 初始化上傳文件解析器
initMultipartResolver(context);
// 初始化本地解析器
initLocaleResolver(context);
// 初始化主題解析器
initThemeResolver(context);
// 初始化映射處理器
initHandlerMappings(context);
// 初始化適配器處理器
initHandlerAdapters(context);
// 初始化異常處理器
initHandlerExceptionResolvers(context);
// 初始化請(qǐng)求到視圖名翻譯器
initRequestToViewNameTranslator(context);
// 初始化視圖解析器
initViewResolvers(context);
- initHandlerMappings初始化映射處理器:
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
OrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
}
}
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
說(shuō)明:
1)detectAllHandlerMappings默認(rèn)是true,根據(jù)類型匹配機(jī)制查找上下文及父容器上下文中所有類型為HandlerMapping的bean,將它們作為該類型組件,并放到ArrayList<HandlerMapping>中。
2)detectAllHandlerMappings如果是false時(shí),查找key為handlerMapping的HandlerMapping類型的bean為該類組件,而且 Collections.singletonList只有一個(gè)元素的集合。
3)List<HandlerMapping> 是為空的話,使用BeanNameUrlHandleMapping實(shí)現(xiàn)類創(chuàng)建該類的組件。
initHandlerMapping會(huì)初始化了handlerMethods請(qǐng)求方法的映射,HandlerMapping是處理請(qǐng)求的映射的如圖所示:
- initHandlerAdapters適配器處理器:
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
if (this.detectAllHandlerAdapters) {
Map<String, HandlerAdapter> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
// We keep HandlerAdapters in sorted order.
OrderComparator.sort(this.handlerAdapters);
}
}
else {
try {
HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
this.handlerAdapters = Collections.singletonList(ha);
}
catch (NoSuchBeanDefinitionException ex) {
}
}
if (this.handlerAdapters == null) {
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
}
}
}
initHandlerAdapters適配器處理器初始化原理跟initHandlerMappings初始化映射處理器一樣。
當(dāng)DispatcherServlet初始化后,就會(huì)自動(dòng)掃描上下文的bean,根據(jù)名稱或者類型匹配的機(jī)制查找自定義的組件,找不到則使用DispatcherServlet。Properties定義了默認(rèn)的組件。
HttpServletBean、FrameworkServlet、DispatcherServlet三個(gè)不同的類層次,SpringMVC對(duì)三個(gè)以抽象和繼承來(lái)實(shí)現(xiàn)不用的功能,分工合作,實(shí)現(xiàn)了解耦的設(shè)計(jì)原則。
我們?cè)诨仡櫼幌拢髯宰隽耸裁词虑椋?/strong>
- HttpServletBean 主要做一些初始化的工作,將web.xml中配置的參數(shù)設(shè)置到Servlet中。比如servlet標(biāo)簽的子標(biāo)簽init-param標(biāo)簽中配置的參數(shù)。
- FrameworkServlet 將Servlet與Spring容器上下文關(guān)聯(lián)。其實(shí)也就是初始化FrameworkServlet的屬性webApplicationContext,這個(gè)屬性代表SpringMVC上下文,它有個(gè)父類上下文,既web.xml中配置的ContextLoaderListener監(jiān)聽器初始化的容器上下文。
- DispatcherServlet 初始化各個(gè)功能的實(shí)現(xiàn)類。比如異常處理、視圖處理、請(qǐng)求映射處理等。
3 DispatcherServlet處理請(qǐng)求過(guò)程#
Spring MVC請(qǐng)求處理流程描述:
- 用戶向服務(wù)器發(fā)送請(qǐng)求,請(qǐng)求被Spring 前置控制Servelt DispatcherServlet捕獲;
- DispatcherServlet對(duì)請(qǐng)求URL進(jìn)行解析,得到請(qǐng)求資源標(biāo)識(shí)符(URI)。然后根據(jù)該URI,調(diào)用HandlerMapping獲得該Handler配置的所有相關(guān)的對(duì)象(包括Handler對(duì)象以及Handler對(duì)象對(duì)應(yīng)的攔截器),最后以HandlerExecutionChain對(duì)象的形式返回;
- DispatcherServlet 根據(jù)請(qǐng)求獲得Handler,選擇一個(gè)合適的HandlerAdapter。(附注:如果成功獲得HandlerAdapter后,此時(shí)將開始執(zhí)行攔截器的preHandler(...)方法)
- 提取Request中的模型數(shù)據(jù),填充Handler入?yún)ⅲ_始執(zhí)行Handler(Controller)。 在填充Handler的入?yún)⑦^(guò)程中,根據(jù)你的配置,Spring將幫你做一些額外的工作:
HttpMessageConveter:將請(qǐng)求消息(如Json、xml等數(shù)據(jù))轉(zhuǎn)換成一個(gè)對(duì)象,將對(duì)象轉(zhuǎn)換為指定的響應(yīng)信息;
數(shù)據(jù)轉(zhuǎn)換:對(duì)請(qǐng)求消息進(jìn)行數(shù)據(jù)轉(zhuǎn)換。如String轉(zhuǎn)換成Integer、Double等;
數(shù)據(jù)根式化:對(duì)請(qǐng)求消息進(jìn)行數(shù)據(jù)格式化。 如將字符串轉(zhuǎn)換成格式化數(shù)字或格式化日期等;
數(shù)據(jù)驗(yàn)證: 驗(yàn)證數(shù)據(jù)的有效性(長(zhǎng)度、格式等),驗(yàn)證結(jié)果存儲(chǔ)到BindingResult或Error中;- Handler執(zhí)行完成后,向DispatcherServlet 返回一個(gè)ModelAndView對(duì)象;
- 根據(jù)返回的ModelAndView,選擇一個(gè)適合的ViewResolver(必須是已經(jīng)注冊(cè)到Spring容器中的ViewResolver)返回給DispatcherServlet;
- ViewResolver結(jié)合Model和View,來(lái)渲染視圖;
- 將渲染結(jié)果返回給客戶端;
HttpServlet提供了service方法用于處理請(qǐng)求,service使用了模板設(shè)計(jì)模式,在內(nèi)部對(duì)于http get方法會(huì)調(diào)用doGet方法,http post方法調(diào)用doPost方法:
進(jìn)入processRequest方法看下:
其中注冊(cè)的監(jiān)聽器類型為ApplicationListener接口類型。繼續(xù)看DispatcherServlet覆寫的doService方法:
最終就是doDispatch方法。doDispatch方法功能簡(jiǎn)單描述一下:
- 首先根據(jù)請(qǐng)求的路徑找到HandlerMethod(帶有Method反射屬性,也就是對(duì)應(yīng)Controller中的方法);
- 然后匹配路徑對(duì)應(yīng)的攔截器,有了HandlerMethod和攔截器構(gòu)造個(gè)HandlerExecutionChain對(duì)象。HandlerExecutionChain對(duì)象的獲取是通過(guò)HandlerMapping接口提供的方法中得到;
- 有了HandlerExecutionChain之后,通過(guò)HandlerAdapter對(duì)象進(jìn)行處理得到ModelAndView對(duì)象;
- HandlerMethod內(nèi)部handle的時(shí)候,使用各種HandlerMethodArgumentResolver實(shí)現(xiàn)類處理HandlerMethod的參數(shù),使用各種HandlerMethodReturnValueHandler實(shí)現(xiàn)類處理返回值;
- 最終返回值被處理成ModelAndView對(duì)象,這期間發(fā)生的異常會(huì)被HandlerExceptionResolver接口實(shí)現(xiàn)類進(jìn)行處理;
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 根據(jù)請(qǐng)求,獲取HandlerExecutionChain對(duì)象
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// 獲取HandlerAdapter對(duì)象
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 實(shí)際執(zhí)行行handle,返回ModelAndView對(duì)象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}