聊聊jdk http的HeaderFilter
序
本文主要研究一下jdk http的HeaderFilter。
FilterFactory
java.net.http/jdk/internal/net/http/FilterFactory.java
class FilterFactory {
// Strictly-ordered list of filters.
final LinkedList<Class<? extends HeaderFilter>> filterClasses = new LinkedList<>();
public void addFilter(Class<? extends HeaderFilter> type) {
filterClasses.add(type);
}
LinkedList<HeaderFilter> getFilterChain() {
LinkedList<HeaderFilter> l = new LinkedList<>();
for (Class<? extends HeaderFilter> clazz : filterClasses) {
try {
// Requires a public no arg constructor.
HeaderFilter headerFilter = clazz.getConstructor().newInstance();
l.add(headerFilter);
} catch (ReflectiveOperationException e) {
throw new InternalError(e);
}
}
return l;
}
}
- 提供了addFilter及getFilterChain方法,前者添加filter class,后者使用反射实例化filter。
HttpClientImpl
java.net.http/jdk/internal/net/http/HttpClientImpl.java
private HttpClientImpl(HttpClientBuilderImpl builder,
SingleFacadeFactory facadeFactory) {
id = CLIENT_IDS.incrementAndGet();
dbgTag = "HttpClientImpl(" + id +")";
if (builder.sslContext == null) {
try {
sslContext = SSLContext.getDefault();
} catch (NoSuchAlgorithmException ex) {
throw new InternalError(ex);
}
} else {
sslContext = builder.sslContext;
}
Executor ex = builder.executor;
if (ex == null) {
ex = Executors.newCachedThreadPool(new DefaultThreadFactory(id));
isDefaultExecutor = true;
} else {
isDefaultExecutor = false;
}
delegatingExecutor = new DelegatingExecutor(this::isSelectorThread, ex);
facadeRef = new WeakReference<>(facadeFactory.createFacade(this));
client2 = new Http2ClientImpl(this);
cookieHandler = builder.cookieHandler;
connectTimeout = builder.connectTimeout;
followRedirects = builder.followRedirects == null ?
Redirect.NEVER : builder.followRedirects;
this.userProxySelector = Optional.ofNullable(builder.proxy);
this.proxySelector = userProxySelector
.orElseGet(HttpClientImpl::getDefaultProxySelector);
if (debug.on())
debug.log("proxySelector is %s (user-supplied=%s)",
this.proxySelector, userProxySelector.isPresent());
authenticator = builder.authenticator;
if (builder.version == null) {
version = HttpClient.Version.HTTP_2;
} else {
version = builder.version;
}
if (builder.sslParams == null) {
sslParams = getDefaultParams(sslContext);
} else {
sslParams = builder.sslParams;
}
connections = new ConnectionPool(id);
connections.start();
timeouts = new TreeSet<>();
try {
selmgr = new SelectorManager(this);
} catch (IOException e) {
// unlikely
throw new InternalError(e);
}
selmgr.setDaemon(true);
filters = new FilterFactory();
initFilters();
assert facadeRef.get() != null;
}
private void initFilters() {
addFilter(AuthenticationFilter.class);
addFilter(RedirectFilter.class);
if (this.cookieHandler != null) {
addFilter(CookieFilter.class);
}
}
private void addFilter(Class<? extends HeaderFilter> f) {
filters.addFilter(f);
}
final LinkedList<HeaderFilter> filterChain() {
return filters.getFilterChain();
}
- HttpClientImpl的构造器创建了FilterFactory,并调用addFilter添加默认的filter
- filterChain方法则调用了FilterFactory的getFilterChain()方法,使用反射实例化这些filter
MultiExchange
java.net.http/jdk/internal/net/http/MultiExchange.java
/**
* MultiExchange with one final response.
*/
MultiExchange(HttpRequest userRequest,
HttpRequestImpl requestImpl,
HttpClientImpl client,
HttpResponse.BodyHandler<T> responseHandler,
PushPromiseHandler<T> pushPromiseHandler,
AccessControlContext acc) {
this.previous = null;
this.userRequest = userRequest;
this.request = requestImpl;
this.currentreq = request;
this.previousreq = null;
this.client = client;
this.filters = client.filterChain();
this.acc = acc;
this.executor = client.theExecutor();
this.responseHandler = responseHandler;
if (pushPromiseHandler != null) {
Executor executor = acc == null
? this.executor.delegate()
: new PrivilegedExecutor(this.executor.delegate(), acc);
this.pushGroup = new PushGroup<>(pushPromiseHandler, request, executor);
} else {
pushGroup = null;
}
this.exchange = new Exchange<>(request, this);
}
private CompletableFuture<Response> responseAsyncImpl() {
CompletableFuture<Response> cf;
if (attempts.incrementAndGet() > max_attempts) {
cf = failedFuture(new IOException("Too many retries", retryCause));
} else {
if (currentreq.timeout().isPresent()) {
responseTimerEvent = ResponseTimerEvent.of(this);
client.registerTimer(responseTimerEvent);
}
try {
// 1. apply request filters
// if currentreq == previousreq the filters have already
// been applied once. Applying them a second time might
// cause some headers values to be added twice: for
// instance, the same cookie might be added again.
if (currentreq != previousreq) {
requestFilters(currentreq);
}
} catch (IOException e) {
return failedFuture(e);
}
Exchange<T> exch = getExchange();
// 2. get response
cf = exch.responseAsync()
.thenCompose((Response response) -> {
HttpRequestImpl newrequest;
try {
// 3. apply response filters
newrequest = responseFilters(response);
} catch (IOException e) {
return failedFuture(e);
}
// 4. check filter result and repeat or continue
if (newrequest == null) {
if (attempts.get() > 1) {
Log.logError("Succeeded on attempt: " + attempts);
}
return completedFuture(response);
} else {
this.response =
new HttpResponseImpl<>(currentreq, response, this.response, null, exch);
Exchange<T> oldExch = exch;
return exch.ignoreBody().handle((r,t) -> {
previousreq = currentreq;
currentreq = newrequest;
expiredOnce = false;
setExchange(new Exchange<>(currentreq, this, acc));
return responseAsyncImpl();
}).thenCompose(Function.identity());
} })
.handle((response, ex) -> {
// 5. handle errors and cancel any timer set
cancelTimer();
if (ex == null) {
assert response != null;
return completedFuture(response);
}
// all exceptions thrown are handled here
CompletableFuture<Response> errorCF = getExceptionalCF(ex);
if (errorCF == null) {
return responseAsyncImpl();
} else {
return errorCF;
} })
.thenCompose(Function.identity());
}
return cf;
}
private void requestFilters(HttpRequestImpl r) throws IOException {
Log.logTrace("Applying request filters");
for (HeaderFilter filter : filters) {
Log.logTrace("Applying {0}", filter);
filter.request(r, this);
}
Log.logTrace("All filters applied");
}
private HttpRequestImpl responseFilters(Response response) throws IOException
{
Log.logTrace("Applying response filters");
Iterator<HeaderFilter> reverseItr = filters.descendingIterator();
while (reverseItr.hasNext()) {
HeaderFilter filter = reverseItr.next();
Log.logTrace("Applying {0}", filter);
HttpRequestImpl newreq = filter.response(response);
if (newreq != null) {
Log.logTrace("New request: stopping filters");
return newreq;
}
}
Log.logTrace("All filters applied");
return null;
}
- MultiExchange在构造器里头调用了client.filterChain(),完成filters的初始化
- 在responseAsyncImpl方法里头,执行请求之前调用requestFilters,得到response之后调用responseFilters
- requestFilters是按顺序执行,而responseFilters则取的是descendingIterator,逆序执行
HeaderFilter
java.net.http/jdk/internal/net/http/HeaderFilter.java
/**
* A header filter that can examine or modify, typically system headers for
* requests before they are sent, and responses before they are returned to the
* user. Some ability to resend requests is provided.
*/
interface HeaderFilter {
void request(HttpRequestImpl r, MultiExchange<?> e) throws IOException;
/**
* Returns null if response ok to be given to user. Non null is a request
* that must be resent and its response given to user. If impl throws an
* exception that is returned to user instead.
*/
HttpRequestImpl response(Response r) throws IOException;
}
- 可以看到HeaderFilter接口定义了request以及response方法
- 对于response方法,如果对header处理没问题就返回null,有异常抛异常,需要重新发送的则会返回HttpRequestImpl
- HeaderFilter有三个实现类,分别是AuthenticationFilter、RedirectFilter、CookieFilter
AuthenticationFilter
java.net.http/jdk/internal/net/http/AuthenticationFilter.java
@Override
public void request(HttpRequestImpl r, MultiExchange<?> e) throws IOException {
// use preemptive authentication if an entry exists.
Cache cache = getCache(e);
this.exchange = e;
// Proxy
if (exchange.proxyauth == null) {
URI proxyURI = getProxyURI(r);
if (proxyURI != null) {
CacheEntry ca = cache.get(proxyURI, true);
if (ca != null) {
exchange.proxyauth = new AuthInfo(true, ca.scheme, null, ca);
addBasicCredentials(r, true, ca.value);
}
}
}
// Server
if (exchange.serverauth == null) {
CacheEntry ca = cache.get(r.uri(), false);
if (ca != null) {
exchange.serverauth = new AuthInfo(true, ca.scheme, null, ca);
addBasicCredentials(r, false, ca.value);
}
}
}
// TODO: refactor into per auth scheme class
private static void addBasicCredentials(HttpRequestImpl r,
boolean proxy,
PasswordAuthentication pw) {
String hdrname = proxy ? "Proxy-Authorization" : "Authorization";
StringBuilder sb = new StringBuilder(128);
sb.append(pw.getUserName()).append(':').append(pw.getPassword());
String s = encoder.encodeToString(sb.toString().getBytes(ISO_8859_1));
String value = "Basic " + s;
if (proxy) {
if (r.isConnect()) {
if (!Utils.PROXY_TUNNEL_FILTER.test(hdrname, value)) {
Log.logError("{0} disabled", hdrname);
return;
}
} else if (r.proxy() != null) {
if (!Utils.PROXY_FILTER.test(hdrname, value)) {
Log.logError("{0} disabled", hdrname);
return;
}
}
}
r.setSystemHeader(hdrname, value);
}
@Override
public HttpRequestImpl response(Response r) throws IOException {
Cache cache = getCache(exchange);
int status = r.statusCode();
HttpHeaders hdrs = r.headers();
HttpRequestImpl req = r.request();
if (status != UNAUTHORIZED && status != PROXY_UNAUTHORIZED) {
// check if any authentication succeeded for first time
if (exchange.serverauth != null && !exchange.serverauth.fromcache) {
AuthInfo au = exchange.serverauth;
cache.store(au.scheme, req.uri(), false, au.credentials);
}
if (exchange.proxyauth != null && !exchange.proxyauth.fromcache) {
AuthInfo au = exchange.proxyauth;
URI proxyURI = getProxyURI(req);
if (proxyURI != null) {
cache.store(au.scheme, proxyURI, true, au.credentials);
}
}
return null;
}
//......
}
- 可以用于添加basic authentication的header
RedirectFilter
java.net.http/jdk/internal/net/http/RedirectFilter.java
@Override
public synchronized void request(HttpRequestImpl r, MultiExchange<?> e) throws IOException {
this.request = r;
this.client = e.client();
this.policy = client.followRedirects();
this.method = r.method();
this.uri = r.uri();
this.exchange = e;
}
@Override
public synchronized HttpRequestImpl response(Response r) throws IOException {
return handleResponse(r);
}
/**
* Checks to see if a new request is needed and returns it.
* Null means response is ok to return to user.
*/
private HttpRequestImpl handleResponse(Response r) {
int rcode = r.statusCode();
if (rcode == 200 || policy == HttpClient.Redirect.NEVER) {
return null;
}
if (rcode == HTTP_NOT_MODIFIED)
return null;
if (rcode >= 300 && rcode <= 399) {
URI redir = getRedirectedURI(r.headers());
String newMethod = redirectedMethod(rcode, method);
Log.logTrace("response code: {0}, redirected URI: {1}", rcode, redir);
if (canRedirect(redir) && ++exchange.numberOfRedirects < max_redirects) {
Log.logTrace("redirect to: {0} with method: {1}", redir, newMethod);
return HttpRequestImpl.newInstanceForRedirection(redir, newMethod, request);
} else {
Log.logTrace("not redirecting");
return null;
}
}
return null;
}
- 主要用于处理3xx跳转,这个时候满足条件的话会返回新的HttpRequestImpl实例
CookieFilter
java.net.http/jdk/internal/net/http/CookieFilter.java
@Override
public void request(HttpRequestImpl r, MultiExchange<?> e) throws IOException {
HttpClientImpl client = e.client();
Optional<CookieHandler> cookieHandlerOpt = client.cookieHandler();
if (cookieHandlerOpt.isPresent()) {
CookieHandler cookieHandler = cookieHandlerOpt.get();
Map<String,List<String>> userheaders = r.getUserHeaders().map();
Map<String,List<String>> cookies = cookieHandler.get(r.uri(), userheaders);
// add the returned cookies
HttpHeadersBuilder systemHeadersBuilder = r.getSystemHeadersBuilder();
if (cookies.isEmpty()) {
Log.logTrace("Request: no cookie to add for {0}", r.uri());
} else {
Log.logTrace("Request: adding cookies for {0}", r.uri());
}
for (Map.Entry<String,List<String>> entry : cookies.entrySet()) {
final String hdrname = entry.getKey();
if (!hdrname.equalsIgnoreCase("Cookie")
&& !hdrname.equalsIgnoreCase("Cookie2"))
continue;
List<String> values = entry.getValue();
if (values == null || values.isEmpty()) continue;
for (String val : values) {
if (Utils.isValidValue(val)) {
systemHeadersBuilder.addHeader(hdrname, val);
}
}
}
} else {
Log.logTrace("Request: No cookie manager found for {0}", r.uri());
}
}
@Override
public HttpRequestImpl response(Response r) throws IOException {
HttpHeaders hdrs = r.headers();
HttpRequestImpl request = r.request();
Exchange<?> e = r.exchange;
Log.logTrace("Response: processing cookies for {0}", request.uri());
Optional<CookieHandler> cookieHandlerOpt = e.client().cookieHandler();
if (cookieHandlerOpt.isPresent()) {
CookieHandler cookieHandler = cookieHandlerOpt.get();
Log.logTrace("Response: parsing cookies from {0}", hdrs.map());
cookieHandler.put(request.uri(), hdrs.map());
} else {
Log.logTrace("Response: No cookie manager found for {0}",
request.uri());
}
return null;
}
- 用于请求以及响应的cookie相关的处理
小结
- FilterFactory使用了简单的责任链模式,getFilterChain方法使用反射实例化各种filter
- HeaderFilter定义了request及response两个方法,分别作用于请求前及获得响应之后
- HeaderFilter有三个实现类,分别是AuthenticationFilter、RedirectFilter、CookieFilter
- MultiExchange在responseAsyncImpl方法里头,执行请求之前调用requestFilters,得到response之后调用responseFilters。其中requestFilters是按顺序执行,而responseFilters则取的是descendingIterator,逆序执行
doc
聊聊jdk http的HeaderFilter的更多相关文章
- 聊聊 JDK 非阻塞队列源码(CAS实现)
正如上篇文章聊聊 JDK 阻塞队列源码(ReentrantLock实现)所说,队列在我们现实生活中队列随处可见,最经典的就是去银行办理业务,超市买东西排队等.今天楼主要讲的就是JDK中安全队列的另一种 ...
- 有点深度的聊聊JDK动态代理
在接触SpringAOP的时候,大家一定会被这神奇的功能所折服,想知道其中的奥秘,底层到底是如何实现的.于是,大家会通过搜索引擎,知道了一个陌生的名词:动态代理,慢慢的又知道了动态代理有多种实现方式, ...
- 聊聊 JDK 阻塞队列源码(ReentrantLock实现)
项目中用到了一个叫做 Disruptor 的队列,今天楼主并不是要介绍 Disruptor 而是想巩固一下基础扒一下 JDK 中的阻塞队列,听到队列相信大家对其并不陌生,在我们现实生活中队列随处可见, ...
- Java并发(10)- 简单聊聊JDK中的七大阻塞队列
引言 JDK中除了上文提到的各种并发容器,还提供了丰富的阻塞队列.阻塞队列统一实现了BlockingQueue接口,BlockingQueue接口在java.util包Queue接口的基础上提供了pu ...
- 关于JDK源码:我想聊聊如何更高效地阅读
简介 大家好,我是彤哥,今天我想和大家再聊聊JDK源码的几个问题: 为什么要看JDK源码 JDK源码的阅读顺序 JDK源码的阅读方法 为什么要看JDK源码 一,JDK源码是其它所有源码的基础,看懂了J ...
- 死磕Java之聊聊ThreadLocal源码(基于JDK1.8)
记得在一次面试中被问到ThreadLocal,答得马马虎虎,所以打算研究一下ThreadLocal的源码 面试官 : 用过ThreadLocal吗? 楼主答 : 用过,当时使用ThreadLocal的 ...
- java基础(1):java概述、jdk的安装、环境变量的配置、helloworld案例
1. Java开发环境搭建 1.1 java概述 众所周知Java是一门编程语言,编程语言就是用来编写软件的.那么使用Java到底能用来编写什么软件呢?你所熟知的很多软件都可以用Java来编写,例如: ...
- 聊聊ThreadLocal源码(基于JDK1.8)
原文:https://cloud.tencent.com/developer/article/1333298 聊聊JDK源码中ThreadLocal的实现 主要方法: ThreadLocal的get方 ...
- 第1天 Java基础语法
Java基础语法 今日内容介绍 Java开发环境搭建 HelloWorld案例 注释.关键字.标识符 数据(数据类型.常量) Java开发环境搭建 Java概述 众所周知Java是一门编程语言,编程语 ...
随机推荐
- 软件-UlitraEdit:UIitraEdit
ylbtech-软件-UlitraEdit:UIitraEdit UltraEdit 是一套功能强大的文本编辑器,可以编辑文本.十六进制.ASCII 码,完全可以取代记事本(如果电脑配置足够强大),内 ...
- 自己动手编写JEECMS自定义栏目统计标签
今天想在给Java客二级版面加入栏目文章统计效果,如下图, 查看JEECMS的源代码发现开发者版本还没有类似现成的统计标签,一种解决的办法是使用现有的JEECMS标签,像这样Struts( [@cms ...
- 事务一致性理解 事务ACID特性的完全解答
A 原子性 事务管理者多个小操作,他们同时完成或者同时不完成就是原子性 C 一致性 一致性,是一个很相对的,很主观的概念, 一致性 描述的是 事务 从一个一致的状态变成 另一个一致的状态. 一致性需 ...
- 如何收缩Mysql的ibdata1文件
ibdata1是MySQL数据库中一个数据文件了,你会发现它来越大了,下面我来介绍收缩Mysql的ibdata1文件大小方法. 如果你有使用InnoDB来存储你的Mysql表,使用默认设置应该会碰到个 ...
- JDBC连接整个过程
1.导入驱动(放在lib下) connector-java-5.0.8-bin.jar 2.导入配置文件(放在src下) jdbc.properties driverClass=com.mysql.j ...
- 通过游戏学python 3.6 第一季 第七章 实例项目 猜数字游戏--核心代码--猜测次数--随机函数和屏蔽错误代码--优化代码及注释--简单账号密码登陆--账号的注册查询和密码的找回修改--锁定账号
#猜数字--核心代码--猜测次数--随机函数和屏蔽错误代码---优化代码及注释--简单账号密码登陆--账号的注册查询和密码的找回修改--锁定账号 #猜数字--核心代码--猜测次数--随机函数和屏蔽错误 ...
- Oracle基础知识:DECODE、NVL
select 1 from PMADW.GET_WX_DATAPUSH_NEW_CHECK A INNER JOIN PMADW.V_EXCEPTION_QTY_MAIN B on DECODE( A ...
- 公司-广告-WPP:WPP
ylbtech-公司-广告-WPP:WPP WPP集团 (LSE:WPP) (NASDAQ:WPPGY),是世界上最大的传播集团,总部位于英国伦敦.WPP集团拥有 60 多个子公司,主要服务于本地.跨 ...
- UVA1204 Fun Game
Fun Game https://odzkskevi.qnssl.com/8d698323a1e07d605cdeea708ee8a01d?v=1508703139 [题解] 不难发现如果一个串的原串 ...
- Codeforces 849D.Rooter's Song
D. Rooter's Song time limit per test 2 seconds memory limit per test 256 megabytes input standard in ...