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

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

  1. Caffe源码理解2:SyncedMemory CPU和GPU间的数据同步

    目录 写在前面 成员变量的含义及作用 构造与析构 内存同步管理 参考 博客:blog.shinelee.me | 博客园 | CSDN 写在前面 在Caffe源码理解1中介绍了Blob类,其中的数据成 ...

  2. 基于SpringBoot的Environment源码理解实现分散配置

    前提 org.springframework.core.env.Environment是当前应用运行环境的公开接口,主要包括应用程序运行环境的两个关键方面:配置文件(profiles)和属性.Envi ...

  3. jedis的源码理解-基础篇

    [jedis的源码理解-基础篇][http://my.oschina.net/u/944165/blog/127998] (关注实现关键功能的类)   基于jedis 2.2.0-SNAPSHOT   ...

  4. VUEJS2.0源码理解--优

    VUEJS2.0源码理解 http://jiongks.name/blog/vue-code-review/#pingback-112428

  5. AdvanceEast源码理解

    目录 文章思路 源码理解 一. 标签点形式 按顺序排列四个点,逆时针旋转,且第一个点为左上角点(刚开始选择最左边的点, 二. 标签切边 三. loss计算 四. NMS 最后说明 文章思路 大神的gi ...

  6. 物联网防火墙himqtt源码之MQTT协议分析

    物联网防火墙himqtt源码之MQTT协议分析 himqtt是首款完整源码的高性能MQTT物联网防火墙 - MQTT Application FireWall,C语言编写,采用epoll模式支持数十万 ...

  7. Pytorch学习之源码理解:pytorch/examples/mnists

    Pytorch学习之源码理解:pytorch/examples/mnists from __future__ import print_function import argparse import ...

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

    原文:.NET Core 3.0之深入源码理解Startup的注册及运行   写在前面 开发.NET Core应用,直接映入眼帘的就是Startup类和Program类,它们是.NET Core应用程 ...

  9. 深入源码理解Spring整合MyBatis原理

    写在前面 聊一聊MyBatis的核心概念.Spring相关的核心内容,主要结合源码理解Spring是如何整合MyBatis的.(结合右侧目录了解吧) MyBatis相关核心概念粗略回顾 SqlSess ...

随机推荐

  1. 通俗易懂的来讲讲DOM

    DOM是所有前端开发每天打交道的东西,但是随着jQuery等库的出现,大大简化了DOM操作,导致大家慢慢的“遗忘”了它的本来面貌.不过,要想深入学习前端知识,对DOM的了解是不可或缺的,所以本文力图系 ...

  2. 随手记_C#验证码

    前言 最近在网上偶然看见一个验证码,觉得很有意思,于是搜了下,是使用第三方实现的,先看效果: 总体来说效果还是可以的,官方提供的SDK也比较详细,可配置性很高.在这里在简单啰嗦几句使用方式: 使用步骤 ...

  3. MySQL数据库和InnoDB存储引擎文件

    参数文件 当MySQL示例启动时,数据库会先去读一个配置参数文件,用来寻找数据库的各种文件所在位置以及指定某些初始化参数,这些参数通常定义了某种内存结构有多大等.在默认情况下,MySQL实例会按照一定 ...

  4. 如何进行python性能分析?

    在分析python代码性能瓶颈,但又不想修改源代码的时候,ipython shell以及第三方库提供了很多扩展工具,可以不用在代码里面加上统计性能的装饰器,也能很方便直观的分析代码性能.下面以我自己实 ...

  5. 【基于WinForm+Access局域网共享数据库的项目总结】之篇一:WinForm开发总体概述与技术实现

    篇一:WinForm开发总体概述与技术实现 篇二:WinForm开发扇形图统计和Excel数据导出 篇三:Access远程连接数据库和窗体打包部署 [小记]:最近基于WinForm+Access数据库 ...

  6. 【基于WPF+OneNote+Oracle的中文图片识别系统阶段总结】之篇一:WPF常用知识以及本项目设计总结

    篇一:WPF常用知识以及本项目设计总结:http://www.cnblogs.com/baiboy/p/wpf.html 篇二:基于OneNote难点突破和批量识别:http://www.cnblog ...

  7. bash字符串操作

    参考 http://www.cnblogs.com/chengmo/archive/2010/10/02/1841355.html 问题:bash怎么提取字符串的最后一位?例如python中strin ...

  8. Java实现Excel中的NORMSDIST函数和NORMSINV函数

    由于工作中需要将Excel中的此两种函数转换成java函数,从而计算内部评级的资本占用率和资本占用金额.经过多方查阅资料和整理,总结出如下两个转换方法 标准正态分布累计函数NORMSDIST: pub ...

  9. 每天一个设计模式-7 生成器模式(Builder)

    每天一个设计模式-7 生成器模式(Builder) 一.实际问题 在讨论工厂方法模式的时候,提到了一个导出数据的应用框架,但是并没有涉及到导出数据的具体实现,这次通过生成器模式来简单实现导出成文本,X ...

  10. 微信小程序教程汇总

    目前市面上在内测期间出来的一些实战类教程还是很不错的,主要还是去快速学习小程序开发的整体流程,一个组件一个组件的讲的很可能微信小程序一升级,这个组件就变了,事实本就如此,谁让现在是内测呢.我们不怕,下 ...