httpservletrequest 没有提供修改/删除的 api
httpservletrequest中定义的对 header 的操作全是只读,没有修改。
代码实现如下:
public interface httpservletrequest extends servletrequest {
...
public long getdateheader(string name);
public string getheader(string name);
public enumeration<string> getheaders(string name);
public enumeration<string> getheadernames();
public int getintheader(string name);
...
}
httpservletrequest 只是一个接口,实现由 servlet 容器提供。
不管是任何容器,实现类,肯定是要把请求的 header 存储在某个地方,于是可以通过反射来对存储header的容器进行增删。
先定义一个测试的 controller
这个 controller 很简单,把客户端的所有 header,以 json 形似响应给客户端。
import java.util.arraylist;
import java.util.enumeration;
import java.util.linkedhashmap;
import java.util.list;
import java.util.map;
import javax.servlet.http.httpservletrequest;
import org.springframework.web.bind.annotation.getmapping;
import org.springframework.web.bind.annotation.requestmapping;
import org.springframework.web.bind.annotation.restcontroller;
@restcontroller
@requestmapping("/demo")
public class democontroller {
// 遍历所有请求header,响应给客户端。 map<string, string[]>
@getmapping
public object demo (httpservletrequest request) {
map<string, list<string>> headers = new linkedhashmap<>();
enumeration<string> nameenumeration = request.getheadernames();
while (nameenumeration.hasmoreelements()) {
string name = nameenumeration.nextelement();
list<string> values = headers.get(name);
if (values == null) {
values = new arraylist<>();
headers.put(name, values);
}
enumeration<string> valueenumeration = request.getheaders(name);
while (valueenumeration.hasmoreelements()) {
values.add(valueenumeration.nextelement());
}
}
return headers;
}
}
使用 tomcat 作为容器
tomcat 对 httpservletrequest 的实现
tomcat 使用了外观模式(facade),这个实现稍微有一点点复杂
org.apache.catalina.connector.requestfacade
|-org.apache.catalina.connector.request
|-org.apache.coyote.request
|-org.apache.tomcat.util.http.mimeheaders
首先是 org.apache.catalina.connector.requestfacade 实现,它有一个org.apache.catalina.connector.request 的对象。
这个对象又有一个org.apache.coyote.request的对象,这个对象又有一个org.apache.tomcat.util.http.mimeheaders 字段,它就是存储了客户端请求头的容器,只要通过反射获取到这个mimeheaders,对它进行修改即可。
org.apache.catalina.connector.requestfacade
public class requestfacade implements httpservletrequest {
protected org.apache.catalina.connector.request request = null;
...
}
org.apache.catalina.connector.request
public class request implements httpservletrequest {
protected org.apache.coyote.request coyoterequest;
...
}
org.apache.coyote.request coyoterequest
public final class request {
private final org.apache.tomcat.util.http.mimeheaders headers = new mimeheaders();
}
在 filter 中通过反射对请求 header 进行增删
假设的场景是,需要对请求 header 统一添加一个x-request-id,通过这个 id 来从日志中定位每一个请求。
import java.io.ioexception;
import java.lang.reflect.field;
import java.util.uuid;
import javax.servlet.filterchain;
import javax.servlet.servletexception;
import javax.servlet.annotation.webfilter;
import javax.servlet.http.httpfilter;
import javax.servlet.http.httpservletrequest;
import javax.servlet.http.httpservletresponse;
import org.apache.catalina.connector.request;
import org.apache.catalina.connector.requestfacade;
import org.apache.tomcat.util.http.mimeheaders;
import org.springframework.core.annotation.order;
import org.springframework.stereotype.component;
import org.springframework.util.reflectionutils;
@webfilter(urlpatterns = "/*")
@component
@order(-999)
public class requestidgenfilter extends httpfilter {
/**
*
*/
private static final long serialversionuid = 1787347739651657706l;
@override
protected void dofilter(httpservletrequest req, httpservletresponse res, filterchain chain) throws ioexception, servletexception {
try {
// 从 requestfacade 中获取 org.apache.catalina.connector.request
field connectorfield = reflectionutils.findfield(requestfacade.class, "request", request.class);
connectorfield.setaccessible(true);
request connectorrequest = (request) connectorfield.get(req);
// 从 org.apache.catalina.connector.request 中获取 org.apache.coyote.request
field coyotefield = reflectionutils.findfield(request.class, "coyoterequest", org.apache.coyote.request.class);
coyotefield.setaccessible(true);
org.apache.coyote.request coyoterequest = (org.apache.coyote.request) coyotefield.get(connectorrequest);
// 从 org.apache.coyote.request 中获取 mimeheaders
field mimeheadersfield = reflectionutils.findfield(org.apache.coyote.request.class, "headers", mimeheaders.class);
mimeheadersfield.setaccessible(true);
mimeheaders mimeheaders = (mimeheaders) mimeheadersfield.get(coyoterequest);
this.mineheadershandle(mimeheaders);
} catch (exception e) {
throw new runtimeexception(e);
}
super.dofilter(req, res, chain);
}
protected void mineheadershandle (mimeheaders mimeheaders) {
// 添加一个header,随机生成请求id
mimeheaders.addvalue("x-request-id").setstring(uuid.randomuuid().tostring());;
// 移除一个header
mimeheaders.removeheader("user-agent");
}
}
请求 controller 获取响应结果

可以看到成功添加了x-request-id header,并且删除了user-agent header。
使用 undertow 作为容器
越来越多人使用 undertow 作为 servlet 容器,据说性能比 tomcat 要好很多
springboot 替换 tomcat 为 undertow
只需要把spring-boot-starter-web中的spring-boot-starter-tomcat排除,然后手动添加spring-boot-starter-undertow即可
<dependencies>
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-web</artifactid>
<exclusions>
<exclusion>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-tomcat</artifactid>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-undertow</artifactid>
</dependency>
</dependencies>
undertow 中的httpservletrequest实现
它的实现就比较简单,结构如下:
io.undertow.servlet.spec.httpservletrequestimpl |-io.undertow.server.httpserverexchange |-io.undertow.util.headermap
io.undertow.servlet.spec.httpservletrequestimpl实现类中有一个属性对象io.undertow.server.httpserverexchange,这个属性对象又包含了一个io.undertow.util.headermap,headermap就是请求 header 的存储容器,反射获取它就行。
io.undertow.servlet.spec.httpservletrequestimpl
public final class httpservletrequestimpl implements httpservletrequest {
private final io.undertow.server.httpserverexchange exchange;
}
io.undertow.server.httpserverexchange
public final class httpserverexchange extends abstractattachable {
private final headermap requestheaders;
}
在 filter 中通过反射对请求 header 进行增删
import java.io.ioexception;
import java.lang.reflect.field;
import java.util.uuid;
import javax.servlet.filterchain;
import javax.servlet.servletexception;
import javax.servlet.annotation.webfilter;
import javax.servlet.http.httpfilter;
import javax.servlet.http.httpservletrequest;
import javax.servlet.http.httpservletresponse;
import org.springframework.core.annotation.order;
import org.springframework.stereotype.component;
import org.springframework.util.reflectionutils;
import io.undertow.server.httpserverexchange;
import io.undertow.servlet.spec.httpservletrequestimpl;
import io.undertow.util.headermap;
import io.undertow.util.httpstring;
@webfilter(urlpatterns = "/*")
@component
@order(-999)
public class requestidgenfilter extends httpfilter {
/**
*
*/
private static final long serialversionuid = 1787347739651657706l;
@override
protected void dofilter(httpservletrequest req, httpservletresponse res, filterchain chain) throws ioexception, servletexception {
try {
// 从httpservletrequestimpl中获取httpserverexchange
field exchangefield = reflectionutils.findfield(httpservletrequestimpl.class, "exchange", httpserverexchange.class);
exchangefield.setaccessible(true);
httpserverexchange httpserverexchange = (httpserverexchange) exchangefield.get(req);
// 从httpserverexchange中获取headermap
field headermapfield = reflectionutils.findfield(httpserverexchange.class, "requestheaders", headermap.class);
headermapfield.setaccessible(true);
headermap requestheadermap = (headermap) headermapfield.get(httpserverexchange);
this.handlerequestheadermap(requestheadermap);
} catch (exception e) {
throw new runtimeexception(e);
}
super.dofilter(req, res, chain);
}
private void handlerequestheadermap(headermap requestheadermap) {
// 添加header
requestheadermap.add(new httpstring("x-request-id"), uuid.randomuuid().tostring());
// 移除header
requestheadermap.remove("user-agent");
}
}
请求 controller 获取结果

总结
还有其他的 servlet 容器,例如 jetty。只要熟悉基本的反射,实现对请求头的增删,都很简单。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论