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 代理原理及實現(一)