[Java SE/JDK/Servlet/Tomcat] 核心源码精讲:javax.servlet.http.HttpServletRequest
0 引言
- 参见本文:
CASE: HttpServletRequest#getHeader(headerNameWithIgnoreCase):获取header时将不区分大小写章节
1 概述 : HttpSerletRequest //TODO
2 核心API
- request.getSession().getId()
服务端创建,一个浏览器独占一个session对象(默认情况下)
在多实例部署的时候,用户第一次登录的时候,我们可以将request.getSession().getId()作为key,然后将用户的信息作为value,存储到redis中,以方便下次请求需要权限验证的接口的时候的验证。
request.getSession().getId() E9BC57198DC995860E3DA208642E5372 (32位)
- request.getRequestedSessionId()
客户端的,也就是浏览器里面的。
比如在用户第一次登录的时候,这里是null,当用户登录了之后,服务端的request.getSession().getId()就会同步到这里。这里就会有了。
该值和服务端的request.getSession().getId()是保持一致的。
request.getRequestedSessionId() null
2.X 使用示例
ApplicationProjectName:MedicineMs
login.jsp[action:login|method:get] to loginServlet
Output:
request.getAuthType() null
request.getCharacterEncoding() UTF-8
request.getContentLength() -1
request.getContextPath() /MedicineMS
request.getLocalAddr() 0:0:0:0:0:0:0:1
request.getLocalName() 0:0:0:0:0:0:0:1
request.getLocalPort() 8080
request.getMethod() GET
request.getPathInfo() null
request.getProtocol() HTTP/1.1
request.getQueryString() accountNo=staff002&password=12345678
request.getRealPath() D:\Tomcat\MY_WEBAPPS\MedicineMS
request.getRequestURI() /MedicineMS/login
request.getRequestedSessionId() 3210E9844068DDD59A7B3DAB0E195393 (32位)
request.getScheme() http
request.getServletPath() /login
request.getSession() org.apache.catalina.session.StandardSessionFacade@33d55ddc
request.getUserPrincipal() null
3 源码分析
CASE: HttpServletRequest#getHeader(headerNameWithIgnoreCase):获取header时将不区分大小写
[1] 环境/版本
- springboot: 2.3.12.RELEASE
- org.springframework:spring-webmvc: 5.2.15.RELEASE
即 : springmvc
- tomcat-embed: 9.0.46 (springboot内嵌的tomcat)
- javax.servlet:javax.servlet-api:4.0.1
javax.servlet.http.HttpServletRequest的 所属jar包
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
- 调试工具: IDEA
[2] 故事背景: HttpServletRequest#getHeader(headerNameWithIgnoreCase):获取header时不区分大小写的需求
- 最近项目上有个业务需求,翻译成技术需求,即:将
request.headers中的几个header入参转换成request.body(pageRequest)中的内置参数。
为便于灵活配置,header 参数名称是动态可配置的(存放于nacos配置中心),比如:
sysCode、Accept-Language
- 技术实现,主要就
springmvc的org.springframework.web.bind.WebDataBinder,并结合javax.servlet.http.HttpServletRequest,实现将header中的指定参数转发至request.body(pageRequest).params中
核心代码如下:
[2-1] com.xxx.biz.common.util.WebDataBinderUtils
package com.xxx.biz.common.util;
import com.xxx.common.dto.page.PageRequest;
import com.xxx.common.dto.serviceconfig.ForwardHeaderToParamConfig;
import com.xxx.common.dto.serviceconfig.ForwardedHeaders;
import com.xxx.common.dto.serviceconfig.ServiceConfig;
import com.xxx.common.exception.CommonErrEnum;
import com.xxx.common.utils.CollectionUtil;
import com.alibaba.fastjson2.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.WebDataBinder;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
/**
* @author xxx
* @version v1.0
* @create-time 2025/8/6 14:44
* @update-time 2025/8/6 14:44
* @description ...
* @refrence-doc
* @gpt-promt
*/
public class WebDataBinderUtils {
private final static Logger log = LoggerFactory.getLogger( WebDataBinderUtils.class );
/**
* 将 指定的 headers 数据转发至 request.body.params 中
* @note
* 1. 将被应用到所有 @RequestMapping 注解方法,在其执行之前初始化数据绑定器
* 2. 此方法不稳定/不可靠,建议弃用 (2025-10-14 408675)
* @reference-doc
* [0] https://zhuanlan.zhihu.com/p/115902823 - CSDN - https://blog.csdn.net/airhhh/article/details/104905923 【推荐】
* [1] SpringMVC Databinding(数据绑定) - jianshu - https://www.jianshu.com/p/eda34afd9b8a
* [2] SpringBoot2教程29整合SpringMVC之@InitBinder处理请求参数的绑定(一) - Zhihu - https://zhuanlan.zhihu.com/p/115902823
* [3] SpringBoot教程30整合SpringMVC之WebDataBinder处理请求参数绑定(二) - Zhihu - https://zhuanlan.zhihu.com/p/117306638
* [4] SpringMVC常见组件之DataBinder数据绑定器分析 - CSDN - https://blog.csdn.net/J080624/article/details/121163715
* [5] SpringMVC-@InitBinder - segmentfault - https://segmentfault.com/a/1190000040157393
* [6] SpringMVC之WebDataBinder处理请求参数绑定(二) - CSDN - https://blog.csdn.net/qianfeng_dashuju/article/details/105706264
* @param webDataBinder
* @param request
*/
@Deprecated
public static void forwardRequestHeadersToParamsDataBinder(
WebDataBinder webDataBinder, HttpServletRequest request, ServiceConfig serviceConfig
){
String requestUrl = request.getRequestURI().toString(); //eg: "/bdp/public/api/V2/bdp-data-service/100031/v1.4", "/jqueryLearn/resources/request.jsp" , ...
try {
PageRequest<Map<String,Object>> pageRequest = (PageRequest<Map<String, Object>>) webDataBinder.getTarget();
forwardRequestHeadersToParamsDataBinder(pageRequest, request, serviceConfig);
} catch (Exception exception) {
String errorMessage = String.format("Forward headers to params by DataBinder fail!errorCode:%s,requestUrl:%s", CommonErrEnum.FORWARD_REQUEST_HEADERS_TO_PARAMS.name(), requestUrl);
log.error(errorMessage + ", exception:", exception);
throw new RuntimeException(errorMessage, exception);
}
}
/**
* 数据绑定:转发指定的请求头到 pageRequest 中
* @param pageRequest 接口请求对应的页面请求对象(追加的请求头将直接写入此页面请求对象中)
* @param request 接口请求对应的 原始HTTP请求对象
* @param serviceConfig 应用服务配置
*/
public static void forwardRequestHeadersToParamsDataBinder(
Object pageRequest, HttpServletRequest request, ServiceConfig serviceConfig
){
String requestUrl = request.getRequestURI().toString(); //eg: "/sms/xxx/xx-list/v1.0", "/jqueryLearn/resources/request.jsp" , ...
try {
ForwardedHeaders forwardedHeaders = serviceConfig.getForwardedHeaders();//com.xxx.biz.dataservice.controller.v2.CommonSearchController#serviceConfig;
if (ObjectUtils.isEmpty(forwardedHeaders) || forwardedHeaders.getEnable().equals(Boolean.FALSE)) {
log.debug("Fail to forward headers to body's params because that `service-config.forwardedHeaders` be empty or not enabled!requestUrl:{}", requestUrl);
return;
}
if (log.isDebugEnabled()) {
log.debug("Start to forward request's headers to request body params with `service-config.forwardedHeaders`,config data as follows : {}!requestUrl:{},request.getPathInfo: {}", requestUrl, forwardedHeaders, request.getPathInfo());
}
//获取被绑定对象: PageRequest
//PageRequest<Map<String,Object>> pageRequest = (PageRequest<Map<String, Object>>) webDataBinder.getTarget();
Map<String, Object> dynamicParams = null;
if(pageRequest instanceof PageRequest){
PageRequest<Map<String,Object>> pageRequest2 = (PageRequest<Map<String, Object>>) pageRequest;
dynamicParams = pageRequest2.getParams();
} else if(pageRequest instanceof com.xxx.common.dto.page.PageRequest){
com.xxx.common.dto.page.PageRequest<Map<String,Object>> pageRequest2 = (com.xxx.common.dto.page.PageRequest<Map<String, Object>>) pageRequest;
dynamicParams = pageRequest2.getParams();
} else {//不支持的 pageRequest 类型
throw new RuntimeException("Not support the type of page request!PageRequest.class:" + pageRequest.getClass().getCanonicalName() );
}
if (ObjectUtils.isEmpty(pageRequest)) {
log.debug("Fail to forward headers to body's params because that `request.body(pageRequest)` is empty!requestUrl:{},pageRequest:{}", requestUrl, JSON.toJSONString(pageRequest));
return;
} else {// pageRequest 非空
if (dynamicParams == null) {
//排除掉( params != null && params.size() == 0 )的情况: pageRequest:{"currentPage":1,"needPaging":true,"pageSize":10,"params":{}}
log.info("Forward headers to body's params because that `request.body(pageRequest).params` is null!requestUrl:{},pageRequest:{}", requestUrl, JSON.toJSONString(pageRequest));
}
}
List<ForwardHeaderToParamConfig> forwardHeaderToParamConfigList = forwardedHeaders.getHeaders();
StringBuilder targetHeadersLog = new StringBuilder();
Map<String, Object> finalDynamicParams = dynamicParams;
forwardHeaderToParamConfigList.stream().forEach(forwardedHeaderConfig -> {
String headerName = forwardedHeaderConfig.getHeader();
String headerValue = request.getHeader(forwardedHeaderConfig.getHeader());
String paramName = forwardedHeaderConfig.getParam();
if (ObjectUtils.isEmpty(headerValue)) {
targetHeadersLog.append(String.format("| header(%s) is empty! |", headerName));
} else {
targetHeadersLog.append(String.format("| headerName:%s, headerValue:%s, paramName:%s |", headerName, headerValue, paramName));
finalDynamicParams.put(paramName, headerValue);
}
});
if (log.isDebugEnabled()) {
log.debug(
" requestUrl: {}, targetHeaders: {}, request.headers: {}, request.body(pageRequest).params:{}"
, requestUrl, targetHeadersLog.toString(), CollectionUtil.enumerationToList(request.getHeaderNames()), JSON.toJSONString(finalDynamicParams)
);
}
} catch (Exception exception) {
String errorMessage = String.format("Forward headers to params fail!errorCode:%s,requestUrl:%s", CommonErrEnum.FORWARD_REQUEST_HEADERS_TO_PARAMS.name(), requestUrl);
log.error(errorMessage + ", exception:", exception);
throw new RuntimeException(errorMessage, exception);
}
}
}
[2-2] CommonSearchController
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.WebDataBinder;
import javax.servlet.http.HttpServletRequest;
@RestController("com.controller.v2.CommonSearchController")
@Validated
@Api(tags = "DATA2API Controller # V2")
public class CommonSearchController implements DataServiceOpenApi {
// ...
@Autowired
private ServiceConfig serviceConfig;
// ...
@InitBinder
public void forwardedHeadersToParamsDataBinder(WebDataBinder binder, HttpServletRequest request) {
WebDataBinderUtils.forwardRequestHeadersToParamsDataBinder(
webDataBinder, request , serviceConfig
);
}
// ...
}


- 那么,我写这篇博客的目的是什么呢?
- 你有没有这么一个疑惑:
request.getHeader(headerName),这个基于Tomcat.catalina实现的方法,是否区分headerName的大小写?
如果不区分大小写,那还好办;如果区分大小写,就尴尬了————我将需要将
Accept-Language的每一种字母大小写的可能性都要一一进行配置!
经过源码分析,答案是:request.getHeader(headerName)不区分大小写!
感兴趣的朋友,可以进入第2章节,一起看看源码
[3] 源码分析
Step1 javax.servlet.http.HttpServletRequest : request.getHeader("Accept-Language")
import javax.servlet.http.HttpServletRequest;
//...
request.getHeader("Accept-Language")
//...

Step2 javax.servlet.http.HttpServletRequest#getHeader
javax.servlet.http.HttpServletRequest#getHeader

Step3 org.apache.catalina.connector.Request#getHeader
特别说明:
org.apache.catalina.connector.Request#getHeader其实现了接口:javax.servlet.http.HttpServletRequest#getHeader
org.apache.catalina.connector.Request#getHeader

Step4 org.apache.coyote.Request#getHeader
org.apache.coyote.Request#getHeader

Step5 org.apache.tomcat.util.http.MimeHeaders#getHeader
org.apache.tomcat.util.http.MimeHeaders#getHeader

Step6 org.apache.tomcat.util.http.MimeHeaders#getValue(java.lang.String)
org.apache.tomcat.util.http.MimeHeaders#getValue(java.lang.String)

X 参考文献
无
本文作者:
千千寰宇
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!

浙公网安备 33010602011771号