简约之美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请求报文的格式:

调用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响应报文的格式:

/**
     * {@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

时间: 2024-12-28 05:10:49

简约之美Jodd-http--深入源码理解http协议的相关文章

型学习笔记5:C源码理解

型学习笔记5:C源码理解 http://jfsqhwhat2.eju.cn/ http://13660038501.i.sohu.com/v2/guestbook/index.htm http://15306736050.i.sohu.com/v2/guestbook/index.htm http://15090269366.i.sohu.com/v2/guestbook/index.htm http://13377896359.i.sohu.com/v2/guestbook/index.htm

HashMap源码理解

导语 HashMap是常用的数据结构,了解HashMap,对提高代码的效率有很大的帮助.HashMap在JDK1.8中对数据结构进行了优化:提高了查询和删除的效率.当然,这也导致了结构更加的复杂:但通过认真阅读源码,还是可以掌握其要领的. 读完本篇文章,你应该理解的内容 点击这里查看大图 说明:HashMap的数据结构是个Hash表(可以理解为数组),每个槽中存放着一些节点. 一般情况下,一个槽中存放一个节点: 数据量较大时,一个槽中可能存放多个节点,此时,各个节点以链表的方式连接在一起: 当一

spark 源码理解2 进一步窥探Master、Worker通信机制

上一篇文章 spark 源码理解1 从spark启动脚本开始 是分析执行start_all.sh时,集群中启动了哪些进程,下面我们再深入一点看看这些进程都是做什么用的,它们之间又是如何通信的? 一.Master进程的启动 Master进程,它主要负责对Worker.Driver.App等资源的管理并与它们进行通信,这篇文章中我打算着重讲一下它与Worker的通信,其它的部分放在以后的章节再加以描述. spark-daemon.sh start org.apache.spark.deploy.ma

Android IntentService源码理解 及 HandlerThread构建消息循环机制分析

前言:前面写了Handler的源码理解,关于Handler在我们Android开发中是到处能见到的异步通信方式.那么,在Android原生里,有那些也有到了Handler机制的呢?有很多,比如我们今天所要理解分析的IntentService就使用到了Handler.接下来,我们来深入了解一下. HandlerThread: IntentService使用到了Handler+HandlerThread构建的带有消息循环的异步任务处理机制,我们先简单看一下HandlerThread是如何工作的吧.

【春华秋实】深入源码理解.NET Core中Startup的注册及运行

原文:[春华秋实]深入源码理解.NET Core中Startup的注册及运行 写在前面 开发.NET Core应用,直接映入眼帘的就是Startup类和Program类,它们是.NET Core应用程序的起点.通过使用Startup,可以配置化处理所有向应用程序所做的请求的管道,同时也可以减少.NET应用程序对单一服务器的依赖性,使我们在更大程度上专注于面向多服务器为中心的开发模式. 目录: Startup讨论 Starup所承担的角色 Startup编写规范 ConfigureServices

.NET Core 3.0之深入源码理解Startup的注册及运行

原文:.NET Core 3.0之深入源码理解Startup的注册及运行 写在前面 开发.NET Core应用,直接映入眼帘的就是Startup类和Program类,它们是.NET Core应用程序的起点.通过使用Startup,可以配置化处理所有向应用程序所做的请求的管道,同时也可以减少.NET应用程序对单一服务器的依赖性,使我们在更大程度上专注于面向多服务器为中心的开发模式. 目录: Startup讨论 Starup所承担的角色 Startup编写规范 ConfigureServices C

.NET Core 3.0之深入源码理解Configuration(一)

原文:.NET Core 3.0之深入源码理解Configuration(一) Configuration总体介绍 微软在.NET Core里设计出了全新的配置体系,并以非常灵活.可扩展的方式实现.从其源码来看,其运行机制大致是,根据其Source,创建一个Builder实例,并会向其添加Provider,在我们使用配置信息的时候,会从内存中获取相应的Provider实例. .NET Core采用了统一的调用方式来加载不同类型的配置信息,并通过统一的抽象接口IConfigurationSourc

.NET Core 3.0之深入源码理解Configuration(二)

原文:.NET Core 3.0之深入源码理解Configuration(二) 文件型配置基本内容 上一篇文章讨论了Configuration的几个核心对象,本文继续讨论Configuration中关于文件型配置的相关内容.相比较而言,文件型配置的使用场景更加广泛,用户自定义配置扩展也可以基于文件型配置进行扩展.如果需要查看上一篇文章,可以点击移步. .NET Core文件型配置中我们提供了三种主要的实现,分别是JSON.XML.INI,请查看下图 由图可知,这三种配置的实现方式是一样的,当然了

多线程之美5一 AbstractQueuedSynchronizer源码分析&lt;一&gt;

AQS的源码分析 目录结构 1.什么是CAS ? 2.同步器类结构 3.CLH同步队列 4.AQS中静态内部类Node 5.方法分析 ? 5.1.acquire(int arg ) ? 5.2.release(int arg) 释放锁 6.总结 前言 在多线程环境下,我们一般会对临界区资源(共享资源)进行加锁,释放锁,保证同一时刻最多只有一个线程(独占模式),就如去公共厕所里,在使用一个小房间时会加锁避免自己在使用的时候,别人突然闯进来一样,引起不必要的麻烦,在使用完后,再打开锁,其他人才可使用