1. 程式人生 > >簡約之美Jodd-http--深入原始碼理解http協議

簡約之美Jodd-http--深入原始碼理解http協議

Jodd 是一個開源的 Java 工具集, 包含一些實用的工具類和小型框架。簡單,卻很強大!

jodd-http是一個輕巧的HTTP客戶端。現在我們以一個簡單的示例從原始碼層看看是如何實現的?

   HttpRequest httpRequest = HttpRequest.get("http://jodd.org"); //1. 構建一個get請求
    HttpResponse response = httpRequest.send(); //2.傳送請求並接受響應資訊

    System.out.println(response);//3.列印響應資訊

構建一個get請求

先複習一下http請求報文的格式:

wKioL1MpX-qwK1-PAAExXPRpR8M814.jpg

下圖展示一般請求所帶有的屬性

wKiom1MphduAsu6XAAM_loPLbc0713.jpg

呼叫get方法構建http請求:

    /**
     * Builds a GET request.
     */
    public static HttpRequest get(String destination) {
        return new HttpRequest()
                .method("GET")
                .set(destination);
    }

method方法如下:

    /**
     * Specifies request method. It will be converted into uppercase.
     
*/ public HttpRequest method(String method) { this.method = method.toUpperCase(); return this; }

set方法如下:

/**
     * Sets the destination (method, host, port... ) at once.
     */
    public HttpRequest set(String destination) {
        destination = destination.trim();

        
// http method int ndx = destination.indexOf(' '); if (ndx != -1) { method = destination.substring(0, ndx).toUpperCase(); destination = destination.substring(ndx + 1); } // protocol ndx = destination.indexOf("://"); if (ndx != -1) { protocol = destination.substring(0, ndx); destination = destination.substring(ndx + 3); } // host ndx = destination.indexOf('/'); if (ndx == -1) { ndx = destination.length(); } if (ndx != 0) { host = destination.substring(0, ndx); destination = destination.substring(ndx); // port ndx = host.indexOf(':'); if (ndx == -1) { port = DEFAULT_PORT; } else { port = Integer.parseInt(host.substring(ndx + 1)); host = host.substring(0, ndx); } } // path + query path(destination); return this; }

上述方法,根據destination解析出一下幾個部分:

1. 方法:HTTP1.1支援7種請求方法:GET、POST、HEAD、OPTIONS、PUT、DELETE和TARCE。

2. 協議:http或者https

3. 主機:請求的伺服器地址

4. 埠:請求的伺服器埠

5. 路徑+查詢引數,其中引數以“?”開頭,使用“&”連線

    /**
     * Sets request path. Query string is allowed.
     * Adds a slash if path doesn't start with one.
     * Query will be stripped out from the path.
     * Previous query is discarded.
     * @see #query()
     */
    public HttpRequest path(String path) {
        // this must be the only place that sets the path

        if (path.startsWith(StringPool.SLASH) == false) {
            path = StringPool.SLASH + path;
        }

        int ndx = path.indexOf('?');

        if (ndx != -1) {
            String queryString = path.substring(ndx + 1);

            path = path.substring(0, ndx);

            query = HttpUtil.parseQuery(queryString, true);
        } else {
            query = HttpValuesMap.ofObjects();
        }

        this.path = path;

        return this;
    }

傳送請求

先熟悉一下http響應報文的格式:

wKiom1MpmHWALc2UAADu14JLceA655.jpg

響應首部一般包含如下內容:

wKiom1MprnXiYF18AALhmNtc3OE334.jpg

/**
     * {@link #open() Opens connection} if not already open, sends request,
     * reads response and closes the request. If keep-alive mode is enabled
     * connection will not be closed.
     */
    public HttpResponse send() {
        if (httpConnection == null) {
            open();
        }

        // prepare http connection

        if (timeout != -1) {
            httpConnection.setTimeout(timeout);
        }

        // sends data
        HttpResponse httpResponse;
        try {
            OutputStream outputStream = httpConnection.getOutputStream();

            sendTo(outputStream);

            InputStream inputStream = httpConnection.getInputStream();

            httpResponse = HttpResponse.readFrom(inputStream);

            httpResponse.assignHttpRequest(this);
        } catch (IOException ioex) {
            throw new HttpException(ioex);
        }

        boolean keepAlive = httpResponse.isConnectionPersistent();

        if (keepAlive == false) {
            // closes connection if keep alive is false, or if counter reached 0
            httpConnection.close();
            httpConnection = null;
        }

        return httpResponse;
    }

1. 開啟HttpConnection

    /**
     * Opens a new {@link HttpConnection connection} using
     * {@link JoddHttp#httpConnectionProvider default connection provider}.
     */
    public HttpRequest open() {
        return open(JoddHttp.httpConnectionProvider);
    }

    /**
     * Opens a new {@link jodd.http.HttpConnection connection}
     * using given {@link jodd.http.HttpConnectionProvider}.
     */
    public HttpRequest open(HttpConnectionProvider httpConnectionProvider) {
        if (this.httpConnection != null) {
            throw new HttpException("Connection already opened");
        }
        try {
            this.httpConnectionProvider = httpConnectionProvider;
            this.httpConnection = httpConnectionProvider.createHttpConnection(this);
        } catch (IOException ioex) {
            throw new HttpException(ioex);
        }

        return this;
    }

判斷是否有連線,若沒有連線則建立一個新的連線。

2. 建立連線實現

    /**
     * Creates new connection from current {@link jodd.http.HttpRequest request}.
     *
     * @see #createSocket(String, int)
     */
    public HttpConnection createHttpConnection(HttpRequest httpRequest) throws IOException {
        Socket socket;

        if (httpRequest.protocol().equalsIgnoreCase("https")) {
            SSLSocket sslSocket = createSSLSocket(httpRequest.host(), httpRequest.port());

            sslSocket.startHandshake();

            socket = sslSocket;
        } else {
            socket = createSocket(httpRequest.host(), httpRequest.port());
        }

        return new SocketHttpConnection(socket);
    }

3. 建立socket

  根據協議的不同,http使用SocketFactory建立socket,https使用SSLSocketFactory建立SSLSocket。最終使用SocketHttpConnection進行包裝。

SocketHttpConnection繼承自HttpConnection,實現了socket的輸入輸出流連線。注意:https建立完SSLSocket時需要進行握手。

public class SocketHttpConnection implements HttpConnection {

    protected final Socket socket;

    public SocketHttpConnection(Socket socket) {
        this.socket = socket;
    }

    public OutputStream getOutputStream() throws IOException {
        return socket.getOutputStream();
    }

    public InputStream getInputStream() throws IOException {
        return socket.getInputStream();
    }

    public void close() {
        try {
            socket.close();
        } catch (IOException ignore) {
        }
    }

    public void setTimeout(int milliseconds) {
        try {
            socket.setSoTimeout(milliseconds);
        } catch (SocketException sex) {
            throw new HttpException(sex);
        }
    }

    /**
     * Returns <code>Socket</code> used by this connection.
     */
    public Socket getSocket() {
        return socket;
    }
}

 開啟Connection的輸出流傳送資訊,開啟connection的輸入流接受返回資訊。

            OutputStream outputStream = httpConnection.getOutputStream();

            sendTo(outputStream);

            InputStream inputStream = httpConnection.getInputStream();

傳送過程:

    protected HttpProgressListener httpProgressListener;

    /**
     * Sends request or response to output stream.
     */
    public void sendTo(OutputStream out) throws IOException {
        Buffer buffer = buffer(true);

        if (httpProgressListener == null) {
            buffer.writeTo(out);
        }
        else {
            buffer.writeTo(out, httpProgressListener);
        }

        out.flush();
    }

將緩衝區的資料寫入輸出流,併發送。

接受資料並讀取報文內容:

/**
     * Reads response input stream and returns {@link HttpResponse response}.
     * Supports both streamed and chunked response.
     */
    public static HttpResponse readFrom(InputStream in) {
        InputStreamReader inputStreamReader;
        try {
            inputStreamReader = new InputStreamReader(in, StringPool.ISO_8859_1);
        } catch (UnsupportedEncodingException ignore) {
            return null;
        }
        BufferedReader reader = new BufferedReader(inputStreamReader);

        HttpResponse httpResponse = new HttpResponse();

        // the first line
        String line;
        try {
            line = reader.readLine();
        } catch (IOException ioex) {
            throw new HttpException(ioex);
        }

        if (line != null) {

            line = line.trim();

            int ndx = line.indexOf(' ');
            httpResponse.httpVersion(line.substring(0, ndx));

            int ndx2 = line.indexOf(' ', ndx + 1);
            if (ndx2 == -1) {
                ndx2 = line.length();
            }
            httpResponse.statusCode(Integer.parseInt(line.substring(ndx, ndx2).trim()));

            httpResponse.statusPhrase(line.substring(ndx2).trim());
        }

        httpResponse.readHeaders(reader);
        httpResponse.readBody(reader);

        return httpResponse;
    }

小結

  從上面的程式碼,我們可以看出http使用socket來建立和destination的連線,然後通過連線的輸出流和輸入流來進行通訊。

參考文獻:

【1】http://www.it165.net/admin/html/201403/2541.html

【2】http://jodd.org/doc/http.html

相關推薦

簡約Jodd-http--深入原始碼理解http協議

Jodd 是一個開源的 Java 工具集, 包含一些實用的工具類和小型框架。簡單,卻很強大! jodd-http是一個輕巧的HTTP客戶端。現在我們以一個簡單的示例從原始碼層看看是如何實現的?  HttpRequest httpRequest = HttpRequest.get("http://j

.NET Core 3.0深入原始碼理解Configuration(二)

檔案型配置基本內容 上一篇文章討論了Configuration的幾個核心物件,本文繼續討論Configuration中關於檔案型配置的相關內容。相比較而言,檔案型配置的使用場景更加廣泛,使用者自定義配置擴充套件也可以基於檔案型配置進行擴充套件。如果需要檢視上一篇文章,可以點選移步。 .NET Core檔案型配

.NET Core 3.0深入原始碼理解Configuration(三)

寫在前面 上一篇文章討論了檔案型配置的基本內容,本篇內容討論JSON型配置的實現方式,理解了這一種配置型別的實現方式,那麼其他型別的配置實現方式基本可以觸類旁通。看過了上一篇文章的朋友,應該看得出來似曾相識。此圖主要表達了檔案型配置的實現,當然其他配置,包括自定義配置,都會按照這樣的方式去實現。 JSON

.NET Core 3.0深入原始碼理解Kestrel的整合與應用(一)

  寫在前面 ASP.NET Core 的 Web 伺服器預設採用Kestrel,這是一個基於libuv(一個跨平臺的基於Node.js非同步I/O庫)的跨平臺、輕量級的Web伺服器。 在開始之前,先回顧一下.NET Core 3.0預設的main()方法模板中,我們會呼叫Host.CreateDe

.NET Core 3.0深入原始碼理解Kestrel的整合與應用(二)

  前言 前一篇文章主要介紹了.NET Core繼承Kestrel的目的、執行方式以及相關的使用,接下來將進一步從原始碼角度探討.NET Core 3.0中關於Kestrel的其他內容,該部分內容,我們無需掌握,依然可以用好Kestrel,本文只是將一些內部的技術點揭露出來,供自己及大家有一個較

.NET Core 3.0深入原始碼理解HttpClientFactory(一)

寫在前面 建立HttpClient例項的時候,在內部會建立HttpMessageHandler鏈,我們知道HttpMessageHandler是負責建立連線的抽象處理程式,所以HttpClient的維護實際上就是維護HttpMessageHandler的使用,釋放HttpClient並不會及時釋放連線,而通

.NET Core 3.0深入原始碼理解HttpClientFactory(二)

  寫在前面 上一篇文章討論了通過在ConfigureServices中呼叫services.AddHttpClient()方法,並基於此進一步探討了DefaultHttpClientFactory是如何建立HttpClient例項和HttpMessageHandler例項的,並瞭解了Defau

.NET Core 3.0深入原始碼理解HttpClientFactory實戰

  寫在前面 前面兩篇文章透過原始碼角度,理解了HttpClientFactory的內部實現,當我們在專案中使用時,總會涉及以下幾個問題: HttpClient超時處理以及重試機制 HttpClient熔斷器模式的實現 HttpClient日誌記錄與追蹤鏈 接下來我們將從使用角度對上述問題作出說

.NET Core 3.0深入原始碼理解Host(二)

  寫在前面 停了近一個月的技術部落格,隨著正式脫離996的魔窟,接下來也正式恢復了。本文從原始碼角度進一步討論.NET Core 3.0 中關於Host擴充套件的一些技術點,主要討論Long Run Program的建立與守護。 關於Host,我們最容易想到的就是程式的啟動與停止,而其中隱藏著非常

.NET Core 3.0深入原始碼理解ObjectPool(一)

寫在前面 物件池是一種比較常用的提高系統性能的軟體設計模式,它維護了一系列相關物件列表的容器物件,這些物件可以隨時重複使用,物件池節省了頻繁建立物件的開銷。 它使用取用/歸還-重複取用的操作模式,如下圖所示: 本文將主要介紹物件池的基本概念、物件池的優勢及其工作機制,下一篇文件將從原始碼角度介紹.NET

.NET Core 3.0深入原始碼理解ObjectPool(二)

寫在前面 前文主要介紹了ObjectPool的一些理論基礎,本文主要從原始碼角度理解Microsoft.Extensions.ObjectPool是如何實現的。下圖為其三大核心元件圖: 核心元件 ObjectPool ObjectPool是一個泛型抽象類,裡面只有兩個抽象方法,Get和Return。它從底

.NET Core 3.0深入原始碼理解HealthCheck(一)

寫在前面 我們的系統可能因為正在部署、服務異常終止或者其他問題導致系統處於非健康狀態,這個時候我們需要知道系統的健康狀況,而健康檢查可以幫助我們快速確定系統是否處於正常狀態。一般情況下,我們會提供公開的HTTP介面,用於專門化健康檢查。 NET Core提供的健康檢查庫包括Microsoft.Extensio

.NET Core 3.1深入原始碼理解HealthCheck(二)

寫在前面前文討論了HealthCheck的理論部分,本文將討論有關HealthCheck的應用內容。可以監視記憶體、磁碟和其他物理伺服器資源的使用情況來了解是否處於正常狀態。執行狀況檢查可以測試應用的依賴項(如資料庫和外部服務終結點)以確認是否可用和正常工作。執行狀況探測可以由容器業務流程協調程式和負載均衡器

簡約Kotlin(五)Kotlin自定義可清除內容EditText

前言 前面四篇文章已經介紹了Kotlin的一些基礎內容。這篇將使用Kotlin實現一個簡單的自定義View,可清除內容的輸入框AutoClearEditText。 實現思路 1.自定義AutoClearEditText繼承EditText

簡約:軟體設計道》- 讀書筆記

本篇讀書筆記的內容大部分是來自我剛參與的掘金的讀書打卡活動。我在這次讀書打卡活動選擇了《簡約之美:軟體設計之道》這本書。我把這幾次打卡的內容作了整合,也加了一點沒有在打卡活動裡面寫的內容構成了這篇讀書筆記,希望讀者可以有些收穫。 追求卓越還是得過且過 “程式中複雜的部分必須以某種簡

多執行緒5一 AbstractQueuedSynchronizer原始碼分析<一>

AQS的原始碼分析 <一> 目錄結構 1、什麼是CAS ? 2、同步器類結構 3、CLH同步佇列 4、AQS中靜態內部類Node 5、方法分析 ​ 5.1、acquire(int arg ) ​ 5.2、release(int arg) 釋放鎖 6、總結 前言 在多執行緒環境下,我們一般會對臨界區

多執行緒7一ReentrantReadWriteLock原始碼分析

目錄 前言 在多執行緒環境下,為了保證執行緒安全, 我們通常會對共享資源加鎖操作,我們常用Synchronized關鍵字或者ReentrantLock 來實現,這兩者加鎖方式都是排他鎖,即同一時刻最多允許一個執行緒操作,然而大多數場景中對共享資源讀多於寫,那麼存線上程安全問題的是寫操作(修改,新增,刪除)

多執行緒8一 AbstractQueuedSynchronizer原始碼分析<二>

目錄 AQS的原始碼分析 <二> 該篇主要分析AQS的ConditionObject,是AQS的內部類,實現等待通知機制。 1、條件佇列 條件佇列與AQS中的同步佇列有所不同,結構圖如下: 兩者區別: 1、連結串列結構不同,條件佇列是單向連結串列,同步佇列是雙向連結串列。 2、兩個佇列中

併發程式設計,帶你深入理解java多執行緒原理

1.什麼是多執行緒? 多執行緒是為了使得多個執行緒並行的工作以完成多項任務,以提高系統的效率。執行緒是在同一時間需要完成多項任務的時候被實現的。 2.瞭解多執行緒 瞭解多執行緒之前我們先搞清楚幾個重要的概念! 如上圖所示:對我們的專案有一個主記憶體,這個主記憶體裡面存放了我們的共享變數、方法區、堆中的物件等

深入理解HTTP協議及原理分析快取

3.2 快取的實現原理 3.2.1什麼是Web快取 WEB快取(cache)位於Web伺服器和客戶端之間。 快取會根據請求儲存輸出內容的副本,例如html頁面,圖片,檔案,當下一個請求來到的時候:如果是相同的URL,快取直接使用副本響應訪問請求,而不是向源伺服器再次傳送請求