Jetty9源码剖析 - Connection组件 - HttpChannel

转载自ph0ly:http://www.ph0ly.com

一、概念

HttpChannel主要协助HttpConnection触发Server或Handler的处理操作,它内部会包装本次请求对应的Request、Response,并控制它们的生命周期。HttpChannelOverHttp是增强的HttpChannel,它对外暴露请求的参数处理接口(包括请求行、请求头、请求体等)。HttpChannel同时会提供Servlet3的异步特性,由于文章篇幅有限,这里会省略异步的分析,Servlet3异步特性将会在后续的专题文章中讲解

二、继承体系

继承体系

继承体系比较简洁,HttpChannel实现了HttpOutput.Interceptor,也就是可以拦截HttpOutput的写入操作,同时自己是一个Runnable,而对于HttpChannelOverHttp,除了继承自HttpChannel外,还实现了HttpParser.RequestHandler,HttpParser.ComplianceHandler,其实最核心的还是HttpParser.HttpHandler接口,这个接口抽象了当HttpParser解析完每个模块时需要调用的方法,这样在HttpParser解析完每一部分的时候,就能向HttpChannelOverHttp发起调用,从而将数据与HttpChannelOverHttp关联

三、总体架构

总体架构

HttpChannel主要就是串联Request、Response,而HttpChannelOverHttp在HttpChannel之上进一步提供增加Request需要的MetaData,HttpParser解析完成请求行会将数据放到MetaData.Request里面,而请求头解析完成后,会放到HttpFields,而这个HttpFields还会和MetaData.Request建立关联,这样MetaData就包含了一次请求需要的基础数据,Response也类似,在Response类会维护一个HttpFields对象,表示响应的头部,而响应行如果没有指定,默认会创建一个200 OK的行,这样后续HttpGenerator会拿着这些元数据生成响应报文。另外值得一提的是在Jetty里面每条连接都是共享一个Request、Response,因为HTTP/1.x是顺序执行,因此也就不存在在同一连接并发争抢这些对象,所以Jetty优化了这两个对象的生命周期,当Connection创建的时候,会创建HttpChannel,这样其实Request、Response都创建好了,对于每次请求处理完成后,HttpChannel会清空Request、Response的所有数据,这样又恢复到创建之初的数据,这个优化在大量请求的情况下,是非常有效的,要知道频繁的new对象一方面会增加内存分配的耗时,同时分配后GC的操作也会非常频繁,性能也会有一些影响,所以Jetty的这个优化还是不无道理的

四、源码剖析

1. 创建

创建-1

HttpChannelOverHttp看起来还是比较简单,主要就是调super了

创建-2

super自然就是HttpChannel,这里我们可以看到创建了HttpChannelState,这个主要用于Servlet3异步调用处理(这篇文章将会从同步的角度来分析),同时这里会创建Request、Response,并创建HttpInput、HttpOutput与它们两者关联,可以看到一个HttpChannel唯一映射一个Request、Response,而一个HttpChannel唯一映射一个HttpConnection,所以在同一条连接上,Request、Response是复用的(具体原因上面也提到过)

2. HTTP参数准备

前面的文章提到过,HttpParser在解析数据后,会调用HttpParser.RequestHandler,而HttpChannelOverHttp实现了这个接口,因此会触发

2.1 准备请求行

startRequest

startRequest方法会在HttpParser请求行解析完成后调用,将请求行的方法、URI、Http版本号存下来

2.2 添加请求头

parsedHeader-1

parsedHeader-2

parsedHeader表示已经完成解析的请求头,HttpField表示一个请求头,里面包含请求头的K/V值
这里会判断一些特殊的头,例如Connection这样的,可能是要求处理完后Close,也可能是Keep-Alive,Host这种也需要拿到放到MetaData
之后将这个请求头放到HttpFileds类,保存下来

2.3 请求头完成

headerComplete-1

headerComplete-2

headerComplete-3

当HttpParser完成头部解析后,会触发HttpChannelOverHttp.headerComplete,这里会根据不同的协议请求头,例如对于HTTP/1.0,需要判断是否是持久连接,而对于HTTP/1.1,默认连接就是持久,那就需要判断是否带了Connection: Close,处理完立即关闭
如果不是持久连接,这里会把HttpGenerator的持久连接设置为false,以便后续Response完成后,关闭连接
然后调用onRequest,将MetaData设置到Request里面,如下图

onRequest

这里会判断客户端是否要求服务器发送时间,如果是就直接将这个时间加到了Response的HttpFields了
中间我们可以看到Request接收了MetaData.Request,后续应用层就能从Request拿数据了

2.4 请求体处理与完成

content

当HttpParser对HTTP请求体解析的时候,解析到每一块请求体缓冲都将放到Request里面的HttpInput的缓冲队列(inputQ)里面去,如下图

onContent

通常情况对于HttpParser来说是不会立即处理请求体的,在应用层代码获取请求体时才会触发请求体解析,而请求体的解析其实就是一个个缓冲,在需要的时候挨个从EndPoint读出来,这样性能也就不存在问题

contentComplete

当整个请求体也处理完成了,这个时候HttpParser会调用contentComplete、messageComplete,其中messageComplete会通知HttpInput已经EOF了,这样就完成了整个请求体数据的处理

3. 调用

handle

这里我们主要研究同步模式下的调用,异步模式涉及了多状态轮转,比较复杂,后续的专题文章将会讲解
HttpChannel对外提供的一个handle的核心方法,也就是HttpConnection解析完成后调用的
上图就是核心的同步模式下调用的逻辑,即action=DISPATCH。在DISPATCH分支里面会做一些准备,校验MetaData、设置Request为未处理,重新设置HttpOutput状态,然后把DispatcherType改为REQUEST,在利用用户自定义的调整Request里面的一些配置
核心的就是下面那一行,如果请求未被处理,就会调用getServer().handle(this),也就是让前面我们定义的Server来处理,而Server的handle方法会拿到之前设置的Handler来处理这个请求,这样就打通了整个调用链路

4. 完成

COMPLETED

当Server触发Handler调用完成整个链路(Handler -> Filter -> Servlet),最后在Servlet触发往HttpOutput写数据,数据将会被放到缓冲,调用结束,结束后进入最下面的_state.unhandle,这时候action切换为COMPLETE,也就是进入上图的分支
如果这个请求进入COMPLETE还没完成处理,直接给404,如果处理完成了,关闭Response的HttpOutput(ServletOutputStream),HttpOutput.close会将积攒下来的Buffer全部刷出去,这个时候会调用HttpChannel.write方法(之前提到HttpChannel实现了HttpOutput.Interceptor)

write

HttpChannel.write实现如上图,其实就是简单记个数,然后调sendResponse

sendResponse

sendResponse会触发_transport来发送数据,前面提到过_transport其实就是HttpConnection。可以看到这里会将MetaData.Response传给HttpConnection.send,其实也就是将响应状态行、响应头传递下去了,而请求体作为另一个content参数传进来,同时附带一个回调,完成写入后调用

最后整个数据刷完后,会将HttpChannelState改为COMPLETED,已完成,然后调用自身onCompleted

onCompleted

这里会调用HttpConnection.onCompleted,我们一起来回顾下

onCompleted-1

onCompleted-2

从上面看这个方法逻辑很长,不过第一个分支在statusCode为101才会触发,通常我们也是>200,这里就忽略
可以看到调用了_channel.recycle,_generator.reset,_parser.reset,将这些复用组件全部置为初始状态。我们再来看下HttpChannel.recycle在干什么,准确来说HttpChannelOverHttp.recycle

recycle-1

HttpChannelOverHttp.recycle会把metadata回收了,以及HttpFields头也全部清空,再来看下super的回收

recycle-2

HttpChannel将Request、Response全部回收,而Request回收时会把HttpInput回收,Response回收时会把HttpOutput回收,至此整个HttpChannel请求调用完成调用

五、总结

HttpChannel从同步逻辑来看,还是比较容易分析的,由于Servlet3支持异步特性,因此HttpChannel在支持异步这块加入了HttpChannelState这样复杂的状态机,从而导致HttpChannel的代码异常复杂,个人觉得Jetty这块的抽象不算非常简洁。感兴趣的读者可以下来分析下源码,相信也能收获不少知识。接下来的文章会继续分析HttpParser、HttpInput、HttpOutput等组件,欢迎大家持续关注~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot中使用Jetty作为Web容器时,配置X-Frame-Options HTTP头是为了防止网页被嵌入到其他网站(跨站嵌入,Clickjacking)的安全措施。默认情况下,如果你没有在Spring Boot应用的配置中显式设置这个头部,可能是因为Jetty的默认安全策略没有包含这一项。 要为Spring Boot应用配置X-Frame-Options,你需要在Spring Boot的`application.properties`或`application.yml`文件中添加相关的配置,或者直接在Java代码中进行配置。具体步骤如下: 1. **在`application.properties`中**: ```properties server.servlet.context-path=/ # 如果你的应用运行在根路径,不需此项 server.jetty.xframeoptions.enabled=true server.jetty.xframeoptions.value=SAMEORIGIN # 可选值有SAMEORIGIN, SAMEsite,或DENY ``` 2. **在`application.yml`中**: ```yaml server: servlet: context-path: / jetty: x-frame-options: enabled: true value: SAMEORIGIN ``` 3. **在Java代码中动态配置**: ```java @Configuration public class WebSecurityConfig { @Bean public ServerCustomizer jettyServerCustomizer() { return (server -> { server.setHandler(new HandlerWrapper() { @Override protected void doHandle(ServletRequest request, ServletResponse response, boolean containsError) throws IOException, ServletException { response.setHeader("X-Frame-Options", "SAMEORIGIN"); super.doHandle(request, response, containsError); } }); }); } } ``` 确保你重启应用后,`X-Frame-Options`头应该会被设置并生效。如果问题仍然存在,检查一下是否有其他中间件或配置冲突,或者检查日志以获取更多关于配置执行的详细信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值