簡約之美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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM