chttp服务器源码
Ⅰ OkHttp源码分析:五大拦截器详解
主要完成两件事: 重试与重定向
重试与重定向拦截器主要处理Response,可以看到RouteException和IOException都是调用了recover,返回true表示允许重试。允许重试—>continue—> while (true)—>realChain.proceed,这就完成了重试的过程。
接着看重定向
重定向总结
另附HTTP响应状态码分类:
小结: RetryAndFollowUpInterceptor是整个责任链中的第一个,首次接触到Request和最后接收Response的角色,它的主要功能是判断是否需要重试与重定向。
重试的前提是出现了RouteException或IOException,会通过recover方法进行判断是否进行重试。
重定向是发生在重试判定后,不满足重试的条件,会进一步调用followUpRequest根据Response的响应码进行重定向操作。
补全请求头:
小结: BridgeInterceptor是连接应用程序和服务器的桥梁,它为我们补全请求头,将请求转化为符合网络规范的Request。得到响应后:1.保存Cookie,在下次请求会读取对应的cookie数据设置进请求头,默认cookieJar不提供的实现 2.如果使用gzip返回的数据,则使用 GzipSource 包装便于解析。
缓存拦截器顾名思义处理缓存的,但是要建立在get请求的基础上,我们可以去通过okHttpClient.cache(cache)去设置。缓存拦截器的处理流程:
1.从缓存中取出对应请求的响应缓存
2.通过CacheStrategy判断使用缓存或发起网络请求,此对象中的networkRequest代表需要发起网络请求,cacheResponse表示直接使用缓存。
即: networkRequest存在则优先发起网络请求,否则使用cacheResponse缓存,若都不存在则请求失败。
如果最终判定不能使用缓存,需要发起网络请求,则来到下一个拦截器ConnectInterceptor
StreamAllocation对象是在第一个拦截器RetryAndFollowUpInterceptor中初始化完成的(设置了连接池、url路径等),当一个请求发出,需要建立连接,建立连接之后需要使用流来读取数据,这个StreamAllocation就是协调请求、连接与数据流三者之前的关系,它负责为一次请求寻找连接,然后获得流来实现网络通信。
StreamAllocation对象有两个关键角色:
真正的连接是在RealConnection中实现的,连接由ConnectionPool管理。
接着我们看下RealConnection的创建和连接的建立:
streamAllocation.newStream—>findHealthyConnection—>findConnection
findConnection:
①StreamAllocation的connection如果可以复用则复用
②如果connection不能复用,则从连接池中获取RealConnection对象,获取成功则返回
③如果连接池里没有,则new一个RealConnection对象
④调用RealConnection的connect()方法发起请求
⑤将RealConnection对象存进连接池中,以便下次复用
⑥返回RealConnection对象
小结:
ConnectInterceptor拦截器从拦截器链中获取StreamAllocation对象,这个对象在第一个拦截器中创建,在ConnectInterceptor中才用到。
执行StreamAllocation对象的newStream方法创建HttpCodec对象,用来编码HTTP request和解码HTTP response。
newStream方法里面通过findConnection方法返回了一个RealConnection对象。
StreamAllocation对象的connect方法拿到上面返回的RealConnection对象,这个RealConnection对象是用来进行实际的网络IO传输的。
writeRequestHeaders和readResponseHeaders(以Http2Codec为例)
小结: CallServerInterceptor完成HTTP协议报文的封装和解析。
①获取拦截器链中的HttpCodec、StreamAllocation、RealConnection对象
②调用httpCodec.writeRequestHeaders(request)将请求头写入缓存
③判断是否有请求体,如果有,请求头通过携带特殊字段 Expect:100-continue来询问服务器是否愿意接受请求体。(一般用于上传大容量请求体或者需要验证)
④通过httpCodec.finishRequest()结束请求
⑤通过responseBuilder构建Response
⑥返回Response
Ⅱ C++实现http简易代理服务器
我正在做与你同样的工作,我选择了mongoose,一款开源的http服务器。
http://code.google.com/p/mongoose/
这是它的网站,可以下载源码,并且协议非常自由。
有不明白之处可以联系我。我正在基于mongoose进行开发。
=========================================
源码在这里可以下载到,http://code.google.com/p/mongoose/downloads/list
我也只是一个c程序员,不太会网络编程。但用mongoose的确非常简单,因为就是c++写的,你应该看起来不费劲。
Ⅲ 易语言如何获取网页源码 HTTP读文件太慢而且读出来好多网页是乱码 有没有其他办法
网页乱码是因为需要进行编码转换,详见模块 彗星Http模块 ,至于慢,其它命令都一样,这个和网络有关
Ⅳ 深入理解 HttpSecurity【源码篇】
HttpSecurity 也是 Spring Security 中的重要一环。我们平时所做的大部分 Spring Security 配置也都是基于 HttpSecurity 来配置的。因此我们有必要从源码的角度来理解下 HttpSecurity 到底干了啥?
首先我们来看下 HttpSecurity 的继承关系图:
可以看到,HttpSecurity 继承自 ,同时实现了 SecurityBuilder 和 HttpSecurityBuilder 两个接口。
我们来看下 HttpSecurity 的定义:
这里每一个类都带有泛型,看得人有点眼花缭乱。
我把这个泛型类拿出来和大家讲一下,小伙伴们就明白了。
泛型主要是两个,DefaultSecurityFilterChain 和 HttpSecurity,HttpSecurity 就不用说了,这是我们今天的主角,那么 DefaultSecurityFilterChain 是干嘛的?
这我们就得从 SecurityFilterChain 说起了。
先来看定义:
SecurityFilterChain 其实就是我们平时所说的 Spring Security 中的过滤器链,它里边定义了两个方法,一个是 matches 方法用来匹配请求,另外一个 getFilters 方法返回一个 List 集合,集合中放着 Filter 对象,当一个请求到来时,用 matches 方法去比较请求是否和当前链吻合,如果吻合,就返回 getFilters 方法中的过滤器,那么当前请求会逐个经过 List 集合中的过滤器。这一点,小伙伴们可以回忆前面【深入理解 FilterChainProxy【源码篇】】一文。
SecurityFilterChain 接口只有一个实现类,那就是 DefaultSecurityFilterChain:
DefaultSecurityFilterChain 只是对 SecurityFilterChain 中的方法进行了实现,并没有特别值得说的地方,松哥也就不啰嗦了。
那么从上面的介绍中,大家可以看到,DefaultSecurityFilterChain 其实就相当于是 Spring Security 中的过滤器链,一个 DefaultSecurityFilterChain 代表一个过滤器链,如果系统中存在多个过滤器链,则会存在多个 DefaultSecurityFilterChain 对象。
接下来我们把 HttpSecurity 的这几个父类捋一捋。
SecurityBuilder 就是用来构建过滤器链的,在 HttpSecurity 实现 SecurityBuilder 时,传入的泛型就是 DefaultSecurityFilterChain,所以 SecurityBuilder#build 方法的功能很明确,就是用来构建一个过滤器链出来。
HttpSecurityBuilder 看名字就是用来构建 HttpSecurity 的。不过它也只是一个接口,具体的实现在 HttpSecurity 中,接口定义如下:
这里的方法比较简单:
这便是 HttpSecurityBuilder 中的功能,这些接口在 HttpSecurity 中都将得到实现。
AbstractSecurityBuilder 类实现了 SecurityBuilder 接口,该类中主要做了一件事,就是确保整个构建只被构建一次。
可以看到,这里重新定义了 build 方法,并设置 build 方法为 final 类型,无法被重写,在 build 方法中,通过 AtomicBoolean 实现该方法只被调用一次。具体的构建逻辑则定义了新的抽象方法 doBuild,将来在实现类中通过 doBuild 方法定义构建逻辑。
AbstractSecurityBuilder 方法的实现类就是 。
中所做的事情就比较多了,我们分别来看。
首先 中定义了一个枚举类,将整个构建过程分为 5 种状态,也可以理解为构建过程生命周期的五个阶段,如下:
五种状态分别是 UNBUILT、INITIALIZING、CONFIGURING、BUILDING 以及 BUILT。另外还提供了两个判断方法,isInitializing 判断是否正在初始化,isConfigured 表示是否已经配置完毕。
中的方法比较多,松哥在这里列出来两个关键的方法和大家分析:
第一个就是这个 add 方法,这相当于是在收集所有的配置类。将所有的 xxxConfigure 收集起来存储到 configurers 中,将来再统一初始化并配置,configurers 本身是一个 LinkedHashMap ,key 是配置类的 class,value 是一个集合,集合里边放着 xxxConfigure 配置类。当需要对这些配置类进行集中配置的时候,会通过 getConfigurers 方法获取配置类,这个获取过程就是把 LinkedHashMap 中的 value 拿出来,放到一个集合中返回。
另一个方法就是 doBuild 方法。
在 AbstractSecurityBuilder 类中,过滤器的构建被转移到 doBuild 方法上面了,不过在 AbstractSecurityBuilder 中只是定义了抽象的 doBuild 方法,具体的实现在 。
doBuild 方法就是一边更新状态,进行进行初始化。
beforeInit 是一个预留方法,没有任何实现。
init 方法就是找到所有的 xxxConfigure,挨个调用其 init 方法进行初始化。
beforeConfigure 是一个预留方法,没有任何实现。
configure 方法就是找到所有的 xxxConfigure,挨个调用其 configure 方法进行配置。
最后则是 performBuild 方法,是真正的过滤器链构建方法,但是在 中 performBuild 方法只是一个抽象方法,具体的实现在 HttpSecurity 中。
这便是 HttpSecurity 所有父类、父接口的功能。
看完了父辈,接下来回到我们今天文章的主题,HttpSecurity。
HttpSecurity 做的事情,就是进行各种各样的 xxxConfigurer 配置。
随便举几例:
HttpSecurity 中有大量类似的方法,过滤器链中的过滤器就是这样一个一个配置的。我就不一一介绍了。
每个配置方法的结尾都会来一句 getOrApply,这个是干嘛的?
getConfigurer 方法是在它的父类 中定义的,目的就是去查看当前这个 xxxConfigurer 是否已经配置过了。
如果当前 xxxConfigurer 已经配置过了,则直接返回,否则调用 apply 方法,这个 apply 方法最终会调用到 #add 方法,将当前配置 configurer 收集起来。
HttpSecurity 中还有一个 addFilter 方法:
这个 addFilter 方法的作用,主要是在各个 xxxConfigurer 进行配置的时候,会调用到这个方法,(xxxConfigurer 就是用来配置过滤器的),把 Filter 都添加到 fitlers 变量中。
最终在 HttpSecurity 的 performBuild 方法中,构建出来一个过滤器链:
先给过滤器排序,然后构造 DefaultSecurityFilterChain 对象。
好啦,这就是 HttpSecurity 的一个大致工作流程。把握住了这个工作流程,剩下的就只是一些简单的重复的 xxxConfigurer 配置了,松哥就不再啰嗦啦。
如果小伙伴们觉得有收获,记得点个在看鼓励下松哥哦~
Ⅳ 如何编写一个简单的HTTP代理服务器,最好提供VB源码
VERSION 5.00
Object = "{248DD890-BB45-11CF-9ABC-0080C7E7B78D}#1.0#0"; "MSWINSCK.OCX"
Begin VB.Form Form1
ClientHeight = 3090
ClientLeft = 60
ClientTop = 450
ClientWidth = 4680
LinkTopic = "Form1"
ScaleHeight = 3090
ScaleWidth = 4680
StartUpPosition = 3 '窗口缺省
Begin VB.ListBox List1
Height = 1500
Left = 120
TabIndex = 0
Top = 720
Width = 3615
End
Begin MSWinsockLib.Winsock SckGET
Index = 0
Left = 2520
Top = 360
_ExtentX = 741
_ExtentY = 741
_Version = 393216
End
Begin MSWinsockLib.Winsock Winsock1
Left = 2040
Top = 360
_ExtentX = 741
_ExtentY = 741
_Version = 393216
End
End
Attribute VB_Name = "Form1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Dim PathName As String
Sub Begin()
'启动Winsock1,使用listen方法,听254端口
DoEvents
Winsock1.Close
Winsock1.Protocol = sckTCPProtocol
Winsock1.LocalPort = 254
Winsock1.Listen
End Sub
Private Sub Form_Load()
'开始进行十个监听
For i = 1 To 9
Load SckGET(i)
SckGET(i).Close
SckGET(i).Protocol = sckTCPProtocol
Next
PathName = "E:\http\" '初始化目录
Begin
End Sub
Private Sub SckGET_DataArrival(Index As Integer, ByVal bytesTotal As Long)
Dim DataReceived As String
Dim pos1 As Long, pos2 As Long
Dim QuestFile As String
Dim TData As Byte
pos1 = 0: pos2 = 0
SckGET(Index).GetData DataReceived, vbString
'处理报文
For i = 1 To Len(DataReceived)
If Mid(DataReceived, i, 1) = " " Then
If pos1 = 0 Then pos1 = i + 1 Else pos2 = i - 1: Exit For
End If
Next
'对请求的文件进行处理
On Error GoTo ExitThisSub
If pos1 <> 0 And pos2 <> 0 Then
QuestFile = Mid(DataReceived, pos1 + 1, pos2 - pos1) '得到主文件名
If QuestFile = "" Then QuestFile = "index.htm" '主页
QuestFile = PathName & QuestFile '得到路径
List1.AddItem SckGET(Index).LocalIP & " : GET " & QuestFile & " HTTP/1.1"
If Dir(QuestFile) = "" Then QuestFile = PathName & "404.htm" '未找到
End If
'开始发送文件信息
Open QuestFile For Binary As #1
SckGET(Index).SendData "HTTP/1.0 200 OK" + vbCrLf
SckGET(Index).SendData "MIME_version:1.0" + vbCrLf
SckGET(Index).SendData "Content_Length:" + CStr(LOF(1)) + vbCrLf
SckGET(Index).SendData "" + vbCrLf
Do While Not EOF(1)
Get #1, , TData
SckGET(Index).SendData TData
Loop
ExitThisSub:
Close #1
Begin
SckGET(Index).Close
SckGET(Index).LocalPort = 254
End Sub
Private Sub Winsock1_ConnectionRequest(ByVal requestID As Long)
Con:
For i = 0 To 9
If SckGET(i).State <> 7 Then SckGET(i).Accept requestID: Exit For '查找未用的SCK
If i = 9 And SckGET(9).State <> 7 Then Delay (1): GoTo Con '如果没有则继续等待
Next
End Sub
Sub Delay(Seconds&)
t& = Timer
Delay: DoEvents
If Timer < t + Seconds Then GoTo Delay
End Sub
Ⅵ 常见的服务器状态代码有哪些
一些常见的状态码为:
一、1开头
1xx(临时响应)表示临时响应并需要请求者继续执行操作的状态代码。代码 说明
100 (继续) 请求者应当继续提出请求。 服务器返回此代码表示已收到请求的第一部分,正在等待其余部分。
101 (切换协议) 请求者已要求服务器切换协议,服务器已确认并准备切换。
二、2开头
2xx (成功)表示成功处理了请求的状态代码。代码 说明
200 (成功) 服务器已成功处理了请求。 通常,这表示服务器提供了请求的网页。
201 (已创建) 请求成功并且服务器创建了新的资源。
202 (已接受) 服务器已接受请求,但尚未处理。
203 (非授权信息) 服务器已成功处理了请求,但返回的信息可能来自另一来源。
204 (无内容) 服务器成功处理了请求,但没有返回任何内容。
205 (重置内容) 服务器成功处理了请求,但没有返回任何内容。
206 (部分内容) 服务器成功处理了部分 GET 请求。
三、3开头
3xx (重定向) 表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向。代码 说明
300 (多种选择) 针对请求,服务器可执行多种操作。 服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择。
301 (永久移动) 请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。
302 (临时移动) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
303 (查看其他位置) 请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码。
304 (未修改) 自从上次请求后,请求的网页未修改过。 服务器返回此响应时,不会返回网页内容。
305 (使用代理) 请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理。
307 (临时重定向) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
四、4开头
4xx(请求错误) 这些状态代码表示请求可能出错,妨碍了服务器的处理。代码 说明
400 (错误请求) 服务器不理解请求的语法。
401 (未授权) 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。
403 (禁止) 服务器拒绝请求。
404 (未找到) 服务器找不到请求的网页。
405 (方法禁用) 禁用请求中指定的方法。
406 (不接受) 无法使用请求的内容特性响应请求的网页。
407 (需要代理授权) 此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理。
408 (请求超时) 服务器等候请求时发生超时。
409 (冲突) 服务器在完成请求时发生冲突。 服务器必须在响应中包含有关冲突的信息。
410 (已删除) 如果请求的资源已永久删除,服务器就会返回此响应。
411 (需要有效长度) 服务器不接受不含有效内容长度标头字段的请求。
412 (未满足前提条件) 服务器未满足请求者在请求中设置的其中一个前提条件。
413 (请求实体过大) 服务器无法处理请求,因为请求实体过大,超出服务器的处理能力。
414 (请求的 URI 过长) 请求的 URI(通常为网址)过长,服务器无法处理。
415 (不支持的媒体类型) 请求的格式不受请求页面的支持。
416 (请求范围不符合要求) 如果页面无法提供请求的范围,则服务器会返回此状态代码。
417 (未满足期望值) 服务器未满足"期望"请求标头字段的要求。
五、5开头
5xx(服务器错误)这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错。代码 说明
500 (服务器内部错误) 服务器遇到错误,无法完成请求。
501 (尚未实施) 服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码。
502 (错误网关) 服务器作为网关或代理,从上游服务器收到无效响应。
503 (服务不可用) 服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态。
504 (网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求。
505 (HTTP 版本不受支持) 服务器不支持请求中所用的 HTTP 协议版本。
Ⅶ 如何开发自己的HttpServer-NanoHttpd源码解读
现在作为一个开发人员,http server相关的内容已经是无论如何都要了解的知识了。用curl发一个请求,配置一下apache,部署一个web server对我们来说都不是很难,但要想搞清楚这些背后都发生了什么技术细节还真不是很简单的。所以新的系列将是分享我学习Http Server的过程。
NanoHttpd是Github上的一个开源项目,号称只用一个java文件就能创建一个http server,我将通过分析NanoHttpd的源码解析如何开发自己的HttpServer。Github 地址:https://github.com/NanoHttpd/nanohttpd
在开始前首先简单说明HttpServer的基本要素:
1.能接受HttpRequest并返回HttpResponse
2.满足一个Server的基本特征,能够长时间运行
关于Http协议一般HttpServer都会声明支持Http协议的哪些特性,nanohttpd作为一个轻量级的httpserver只实现了最简单、最常用的功能,不过我们依然可以从中学习很多。
首先看下NanoHttpd类的start函数
[java]view plain
publicvoidstart()throwsIOException{
myServerSocket=newServerSocket();
myServerSocket.bind((hostname!=null)?newInetSocketAddress(hostname,myPort):newInetSocketAddress(myPort));
myThread=newThread(newRunnable(){
@Override
publicvoidrun(){
do{
try{
finalSocketfinalAccept=myServerSocket.accept();
registerConnection(finalAccept);
finalAccept.setSoTimeout(SOCKET_READ_TIMEOUT);
finalInputStreaminputStream=finalAccept.getInputStream();
asyncRunner.exec(newRunnable(){
@Override
publicvoidrun(){
OutputStreamoutputStream=null;
try{
outputStream=finalAccept.getOutputStream();
=tempFileManagerFactory.create();
HTTPSessionsession=newHTTPSession(tempFileManager,inputStream,outputStream,finalAccept.getInetAddress());
while(!finalAccept.isClosed()){
session.execute();
}
}catch(Exceptione){
//,wethrowourownSocketException
//tobreakthe"keepalive"loopabove.
if(!(einstanceofSocketException&&"NanoHttpdShutdown".equals(e.getMessage()))){
e.printStackTrace();
}
}finally{
safeClose(outputStream);
safeClose(inputStream);
safeClose(finalAccept);
unRegisterConnection(finalAccept);
}
}
});
}catch(IOExceptione){
}
}while(!myServerSocket.isClosed());
}
});
myThread.setDaemon(true);
myThread.setName("NanoHttpdMainListener");
myThread.start();
}
1.创建ServerSocket,bind制定端口
2.创建主线程,主线程负责和client建立连接
3.建立连接后会生成一个runnable对象放入asyncRunner中,asyncRunner.exec会创建一个线程来处理新生成的连接。
4.新线程首先创建了一个HttpSession,然后while(true)的执行httpSession.exec。
这里介绍下HttpSession的概念,HttpSession是java里Session概念的实现,简单来说一个Session就是一次httpClient->httpServer的连接,当连接close后session就结束了,如果没结束则session会一直存在。这点从这里的代码也能看到:如果socket不close或者exec没有抛出异常(异常有可能是client段断开连接)session会一直执行exec方法。
一个HttpSession中存储了一次网络连接中server应该保存的信息,比如:URI,METHOD,PARAMS,HEADERS,COOKIES等。
5.这里accept一个client的socket就创建一个独立线程的server模型是ThreadServer模型,特点是一个connection就会创建一个thread,是比较简单、常见的socket server实现。缺点是在同时处理大量连接时线程切换需要消耗大量的资源,如果有兴趣可以了解更加高效的NIO实现方式。
当获得client的socket后自然要开始处理client发送的httprequest。
Http Request Header的parse:
[plain]view plain
//Readthefirst8192bytes.
//Thefullheadershouldfitinhere.
//Apache'sdefaultheaderlimitis8KB.
//!
byte[]buf=newbyte[BUFSIZE];
splitbyte=0;
rlen=0;
{
intread=-1;
try{
read=inputStream.read(buf,0,BUFSIZE);
}catch(Exceptione){
safeClose(inputStream);
safeClose(outputStream);
thrownewSocketException("NanoHttpdShutdown");
}
if(read==-1){
//socketwasbeenclosed
safeClose(inputStream);
safeClose(outputStream);
thrownewSocketException("NanoHttpdShutdown");
}
while(read>0){
rlen+=read;
splitbyte=findHeaderEnd(buf,rlen);
if(splitbyte>0)
break;
read=inputStream.read(buf,rlen,BUFSIZE-rlen);
}
}
1.读取socket数据流的前8192个字节,因为http协议中头部最长为8192
2.通过findHeaderEnd函数找到header数据的截止位置,并把位置保存到splitbyte内。
[java]view plain
if(splitbyte<rlen){
inputStream.unread(buf,splitbyte,rlen-splitbyte);
}
parms=newHashMap<String,String>();
if(null==headers){
headers=newHashMap<String,String>();
}
//.
BufferedReaderhin=newBufferedReader(newInputStreamReader(newByteArrayInputStream(buf,0,rlen)));
//
Map<String,String>pre=newHashMap<String,String>();
decodeHeader(hin,pre,parms,headers);
1.使用unread函数将之前读出来的body pushback回去,这里使用了pushbackstream,用法比较巧妙,因为一旦读到了header的尾部就需要进入下面的逻辑来判断是否需要再读下去了,而不应该一直读,读到没有数据为止
2.decodeHeader,将byte的header转换为java对象
1.Http协议第一行是Method URI HTTP_VERSION
2.后面每行都是KEY:VALUE格式的header
3.uri需要经过URIDecode处理后才能使用
4.uri中如果包含?则表示有param,httprequest的param一般表现为:/index.jsp?username=xiaoming&id=2
下面是处理cookie,不过这里cookie的实现较为简单,所以跳过。之后是serve方法,serve方法提供了用户自己实现httpserver具体逻辑的很好接口。在NanoHttpd中的serve方法实现了一个默认的简单处理功能。
这个默认的方法处理了PUT和POST方法,如果不是就返回默认的返回值。
parseBody方法中使用了tmpFile的方法保存httpRequest的content信息,然后处理,具体逻辑就不细说了,不是一个典型的实现。
最后看一下发response的逻辑:
sendAsFixedLength(outputStream,pending);
}
outputStream.flush();
safeClose(data);
}catch(IOExceptionioe){
//Couldn'twrite?Nocando.
}
}
发送response的步骤如下:
1.设置mimeType和Time等内容。
2.创建一个PrintWriter,按照HTTP协议依次开始写入内容
3.第一行是HTTP的返回码
4.然后是content-Type
5.然后是Date时间
6.之后是其他的HTTP Header
7.设置Keep-Alive的Header,Keep-Alive是Http1.1的新特性,作用是让客户端和服务器端之间保持一个长链接。
8.如果客户端指定了ChunkedEncoding则分块发送response,Chunked Encoding是Http1.1的又一新特性。一般在response的body比较大的时候使用,server端会首先发送response的HEADER,然后分块发送response的body,每个分块都由chunk length 和chunk data 组成,最后由一个0 结束。
9.如果没指定ChunkedEncoding则需要指定Content-Length来让客户端指定response的body的size,然后再一直写body直到写完为止。
最后总结下实现HttpServer最重要的几个部分:
1.能够accept tcp连接并从socket中读取request数据
2.把request的比特流转换成request对象中的对象数据
3.根据http协议的规范处理http request
4.产生http response再写回到socket中传给client。
Ⅷ OkHttp源码解析 (三)——代理和路由
初看OkHttp源码,由于对Address、Route、Proxy、ProxySelector、RouteSelector等理解不够,读源码非常吃力,看了几遍依然对于寻找复用连接、创建连接、连接服务器、连接代理服务器、创建隧道连接等逻辑似懂非懂,本篇决定梳理一遍相关的概念及基本原理。
● HTTP/1.1(HTTPS)
● HTTP/2
● SPDY
一个http请求的流程(直连):
1、输入url及参数;
2、如果是url是域名则解析ip地址,可能对应多个ip,如果没有指定端口,则用默认端口,http请求用80;
3、创建socket,根据ip和端口连接服务器(socket内部会完成3次TCP握手);
4、socket成功连接后,发送http报文数据。
一个https请求的流程(直连):
1、输入url及参数;
2、如果是url是域名则解析ip地址,可能对应多个ip,如果没有指定端口,则用默认端口,https请求用443;
3、创建socket,根据ip和端口连接服务器(socket内部会完成3次TCP握手);
4、socket成功连接后进行TLS握手,可通过java标准款提供的SSLSocket完成;
5、握手成功后,发送https报文数据。
1、分类
● HTTP代理:普通代理、隧道代理
● SOCKS代理:SOCKS4、SOCKS5
2、HTTP代理分类及说明
普通代理
HTTP/1.1 协议的第一部分。其代理过程为:
● client 请求 proxy
● proxy 解析请求获取 origin server 地址
● proxy 向 origin server 转发请求
● proxy 接收 origin server 的响应
● proxy 向 client 转发响应
其中proxy获取目的服务器地址的标准方法是解析 request line 里的 request-URL。因为proxy需要解析报文,因此普通代理无法适用于https,因为报文都是加密的。
隧道代理
通过 Web 代理服务器用隧道方式传输基于 TCP 的协议。
请求包括两个阶段,一是连接(隧道)建立阶段,二是数据通信(请求响应)阶段,数据通信是基于 TCP packet ,代理服务器不会对请求及响应的报文作任何的处理,都是原封不动的转发,因此可以代理 HTTPS请求和响应。
代理过程为:
● client 向 proxy 发送 CONNET 请求(包含了 origin server 的地址)
● proxy 与 origin server 建立 TCP 连接
● proxy 向 client 发送响应
● client 向 proxy 发送请求,proxy 原封不动向 origin server 转发请求,请求数据不做任何封装,为原生 TCP packet.
3、SOCKS代理分类及说明
● SOCKS4:只支持TCP协议(即传输控制协议)
● SOCKS5: 既支持TCP协议又支持UDP协议(即用户数据包协议),还支持各种身份验证机制、服务器端域名解析等。
SOCK4能做到的SOCKS5都可得到,但反过来却不行,比如我们常用的聊天工具QQ在使用代理时就要求用SOCKS5代理,因为它需要使用UDP协议来传输数据。
有了上面的基础知识,下面分析结合源码分析OkHttp路由相关的逻辑。OkHttp用Address来描述与目标服务器建立连接的配置信息,但请求输入的可能是域名,一个域名可能对于多个ip,真正建立连接是其中一个ip,另外,如果设置了代理,客户端是与代理服务器建立直接连接,而不是目标服务器,代理又可能是域名,可能对应多个ip。因此,这里用Route来描述最终选择的路由,即客户端与哪个ip建立连接,是代理还是直连。下面对比下Address及Route的属性,及路由选择器RouteSelector。
描述与目标服务器建立连接所需要的配置信息,包括目标主机名、端口、dns,SocketFactory,如果是https请求,包括TLS相关的SSLSocketFactory 、HostnameVerifier 、CertificatePinner,代理服务器信息Proxy 、ProxySelector 。
Route提供了真正连接服务器所需要的动态信息,明确需要连接的服务器IP地址及代理服务器,一个Address可能会有很多个路由Route供选择(一个DNS对应对个IP)。
Address和Route都是数据对象,没有提供操作方法,OkHttp另外定义了RouteSelector来完成选择的路由的操作。
1、读取代理配置信息:resetNextProxy()
读取代理配置:
● 如果有指定代理(不读取系统配置,在OkHttpClient实例中指定),则只用1个该指定代理;
● 如果没有指定,则读取系统配置的,可能有多个。
2、获取需要尝试的socket地址(目标服务器或者代理服务器):resetNextInetSocketAddress()
结合Address的host和代理,解析要尝试的套接字地址(ip+端口)列表:
● 直连或者SOCK代理, 则用目标服务器的主机名和端口,如果是HTTP代理,则用代理服务器的主机名和端口;
● 如果是SOCK代理,根据目标服务器主机名和端口号创建未解析的套接字地址,列表只有1个地址;
● 如果是直连或HTTP代理,先DNS解析,得到InetAddress列表(没有端口),再创建InetSocketAddress列表(带上端口),InetSocketAddress与InetAddress的区别是前者带端口信息。
3、获取路由列表:next()
选择路由的流程解析:
● 遍历每个代理对象,可能多个,直连的代理对象为Proxy.DIRECT(实际是没有中间代理的);
● 对每个代理获取套接字地址列表;
● 遍历地址列表,创建Route,判断Route如果在路由黑名单中,则添加到失败路由列表,不在黑名单中则添加到待返回的Route列表;
● 如果最后待返回的Route列表为空,即可能所有路由都在黑名单中,实在没有新路由了,则将失败的路由集合返回;
● 传入Route列表创建Selection对象,对象比较简单,就是一个目标路由集合,及读取方法。
为了避免不必要的尝试,OkHttp会把连接失败的路由加入到黑名单中,由RouteDatabase管理,该类比较简单,就是一个失败路由集合。
1、创建Address
Address的创建在RetryAndFollowUpInteceptor里,每次请求会声明一个新的Address及StreamAllocation对象,而StreamAllocation使用Address创建RouteSelector对象,在连接时RouteSelector确定请求的路由。
每个Requst都会构造一个Address对象,构造好了Address对象只是有了与服务器连接的配置信息,但没有确定最终服务器的ip,也没有确定连接的路由。
2、创建RouteSelector
在StreamAllocation声明的同时会声明路由选择器RouteSelector,为一次请求寻找路由。
3、选择可用的路由Route
下面在测试过程跟踪实例对象来理解,分别测试直连和HTTP代理HTTP2请求路由的选择过程:
● 直连请求流程
● HTTP代理HTTPS流程
请求url: https://www.jianshu.com/p/63ba15d8877a
1、构造address对象
2、读取代理配置:resetNextProxy
3、解析目标服务器套接字地址:resetNextInetSocketAddress
4、选择Route创建RealConnection
5、确定协议
测试方法:
● 在PC端打开Charles,设置端口,如何设置代理,网上有教程,比较简单;
● 手机打开WIFI,选择连接的WIFI修改网络,在高级选项中设置中指定了代理服务器,ip为PC的ip,端口是Charles刚设置的端口;
● OkHttpClient不指定代理,发起请求。
1、构造address对象
2、读取代理配置:resetNextProxy
3、解析目标服务器套接字地址:resetNextInetSocketAddress
4、选择Route创建RealConnection
5、创建隧道
由于是代理https请求,需要用到隧道代理。
从图可以看出,建立隧道其实是发送CONNECT请求,header包括字段Proxy-Connection,目标主机名,请求内容类似:
6、确定协议,SSL握手
1、代理可分为HTTP代理和SOCK代理;
2、HTTP代理又分为普通代理和隧道代理;普通代理适合明文传输,即http请求;隧道代理仅转发TCP包,适合加密传输,即https/http2;
3、SOCK代理又分为SOCK4和SOCK5,区别是后者支持UDP传输,适合代理聊天工具如QQ;
4、没有设置代理(OkHttpClient没有指定同时系统也没有设置),客户端直接与目标服务器建立TCP连接;
5、设置了代理,代理http请求时,客户端与代理服务器建立TCP连接,如果代理服务器是域名,则解释代理服务器域名,而目标服务器的域名由代理服务器解析;
6、设置了代理,代理https/http2请求时,客户端与代理服务器建立TCP连接,发送CONNECT请求与代理服务器建立隧道,并进行SSL握手,代理服务器不解析数据,仅转发TCP数据包。
如何正确使用 HTTP proxy
OkHttp3中的代理与路由
HTTP 代理原理及实现(一)