私以为,阅读开源项目是与世界级技术大牛直接对话的最好方式。
此次来分享下 OkHttp 源码的分析。
一、开源项目 OkHttp
在Android、Java开发领域中,相信大家都听过或者在使用Square家大名鼎鼎的网络请求库:OkHttp ,当前多数著名的开源项目如 Fresco、Glide、 Picasso、 Retrofit都在使用OkHttp,这足以说明其质量,而且该项目仍处在不断维护中。
二、问题
在分析okhttp源码之前,我想先提出一个问题,如果我们自己来设计一个网络请求库,这个库应该长什么样子?大致是什么结构呢?
下面我和大家一起来构建一个网络请求库,并在其中融入okhttp中核心的设计思想,希望借此让读者感受并学习到okhttp中的精华之处,而非仅限于了解其实现。
笔者相信,如果你能耐心阅读完本篇,不仅能对http协议有进一步理解,更能够学习到世界级项目的思维精华,提高自身思维方式。
三、思考
首先,我们假设要构建的的网络请求库叫做WingjayHttpClient
,那么,作为一个网络请求库,它最基本功能是什么呢?
在我看来应该是:接收用户的请求 -> 发出请求 -> 接收响应结果并返回给用户。
那么从使用者角度而言,需要做的事是:
- 创建一个
Request
:在里面设置好目标URL;请求method如GET/POST等;一些header如Host、User-Agent等;如果你在POST上传一个表单,那么还需要body。
- 将创建好的
Request
传递给WingjayHttpClient
。
WingjayHttpClient
去执行Request
,并把返回结果封装成一个Response
给用户。而一个Response
里应该包括statusCode如200,一些header如content-type等,可能还有body
到此即为一次完整请求的雏形。那么下面我们来具体实现这三步。
四、雏形实现
下面我们先来实现一个httpClient的雏形,只具备最基本的功能。
1. 创建Request
类
首先,我们要建立一个Request
类,利用Request
类用户可以把自己需要的参数传入进去,基本形式如下:
class Request {
String url;
String method;
Headers headers;
Body requestBody;
public Request(String url, String method, @Nullable Headers headers, @Nullable Body body) {
this.url = url;
...
}
}
2. 将Request
对象传递给WingjayHttpClient
我们可以设计WingjayHttpClient
如下:
class WingjayHttpClient {
public Response sendRequest(Request request) {
return executeRequest(request);
}
}
3. 执行Request
,并把返回结果封装成一个Response
返回
class WingjayHttpClient {
...
private Response executeRequest(Request request) {
//使用socket来进行访问
Socket socket = new Socket(request.getUrl(), 80);
ResponseData data = socket.connect().getResponseData();
return new Response(data);
}
...
}
class Response {
int statusCode;
Headers headers;
Body responseBody
...
}
五、功能扩展
利用上面的雏形,可以得到其使用方法如下:
Request request = new Request("http://wingjay.com");
WingjayHttpClient client = new WingjayHttpClient();
Response response = client.sendRequest(request);
handle(response);
然而,上面的雏形是远远不能胜任常规的应用需求的,因此,下面再来对它添加一些常用的功能模块。
1. 重新把简陋的user Request组装成一个规范的http request
一般的request中,往往用户只会指定一个URL和method,这个简单的user request是不足以成为一个http request,我们还需要为它添加一些header,如Content-Length, Transfer-Encoding, User-Agent, Host, Connection, 和 Content-Type,如果这个request使用了cookie,那我们还要将cookie添加到这个request中。
我们可以扩展上面的sendRequest(request)
方法:
[class WingjayHttpClient]
public Response sendRequest(Request userRequest) {
Request httpRequest = expandHeaders(userRequest);
return executeRequest(httpRequest);
}
private Request expandHeaders(Request userRequest) {
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
...
}
2. 支持自动重定向
有时我们请求的URL已经被移走了,此时server会返回301状态码和一个重定向的新URL,此时我们要能够支持自动访问新URL而不是向用户报错。
对于重定向这里有一个测试性URL:http://www.publicobject.com/helloworld.txt ,通过访问并抓包,可以看到如下信息:

因此,我们在接收到Response后要根据status_code是否为重定向,如果是,则要从Response Header里解析出新的URL-Location
并自动请求新URL。那么,我们可以继续改写sendRequest(request)
方法:
[class WingjayHttpClient]
private boolean allowRedirect = true;
// user can set redirect status when building WingjayHttpClient
public void setAllowRedirect(boolean allowRedirect) {
this.allowRedirect = allowRedirect;
}
public Response sendRequest(Request userRequest) {
Request httpRequest = expandHeaders(userRequest);
Response response = executeRequest(httpRequest);
switch (response.statusCode()) {
// 300: multi choice; 301: moven permanently;
// 302: moved temporarily; 303: see other;
// 307: redirect temporarily; 308: redirect permanently
case 300:
case 301:
case 302:
case 303:
case 307:
case 308:
return handleRedirect(response);
default:
return response;
}
}
// the max times of followup request
private static final int MAX_FOLLOW_UPS = 20;
private int followupCount = 0;
private Response handleRedirect(Response response) {
// Does the WingjayHttpClient allow redirect?
if (!client.allowRedirect()) {
return null;
}
// Get the redirecting url
String nextUrl = response.header("Location");
// Construct a redirecting request
Request followup = new Request(nextUrl);
// check the max followupCount
if (++followupCount > MAX_FOLLOW_UPS) {
throw new Exception("Too many follow-up requests: " + followUpCount);
}
// not reach the max followup times, send followup request then.
return sendRequest(followup);
}
利用上面的代码,我们通过获取原始userRequest
的返回结果,判断结果是否为重定向,并做出自动followup处理。
一些常用的状态码
100~199:指示信息,表示请求已接收,继续处理
200~299:请求成功,表示请求已被成功接收、理解、接受
300~399:重定向,要完成请求必须进行更进一步的操作
400~499:客户端错误,请求有语法错误或请求无法实现
500~599:服务器端错误,服务器未能实现合法的请求
3. 支持重试机制
所谓重试,和重定向非常类似,即通过判断Response
状态,如果连接服务器失败等,那么可以尝试获取一个新的路径进行重新连接,大致的实现和重定向非常类似,此不赘述。
4. Request & Response 拦截机制
这是非常核心的部分。
通过上面的重新组装request
和重定向机制,我们可以感受的,一个request
从user创建出来后,会经过层层处理后,才真正发出去,而一个response
,也会经过各种处理,最终返回给用户。
笔者认为这和网络协议栈非常相似,用户在应用层发出简单的数据,然后经过传输层、网络层等,层层封装后真正把请求从物理层发出去,当请求结果回来后又层层解析,最终把最直接的结果返回给用户使用。
最重要的是,每一层都是抽象的,互不相关的!
因此在我们设计时,也可以借鉴这个思想,通过设置拦截器Interceptor
,每个拦截器会做两件事情:
- 接收上一层拦截器封装后的request,然后自身对这个request进行处理,例如添加一些header,处理后向下传递;
- 接收下一层拦截器传递回来的response,然后自身对response进行处理,例如判断返回的statusCode,然后进一步处理。
那么,我们可以为拦截器定义一个抽象接口,然后去实现具体的拦截器。
interface Interceptor {
Response intercept(Request request);
}
大家可以看下上面这个拦截器设计是否有问题?

我们想象这个拦截器能够接收一个request,进行拦截处理,并返回结果。
但实际上,它无法返回结果,而且它在处理request后,并不能继续向下传递,因为它并不知道下一个Interceptor
在哪里,也就无法继续向下传递。
那么,如何解决才能把所有Interceptor
串在一起,并能够依次传递下去。
public interface Interceptor {
Response intercept(Chain chain);
interface Chain {
Request request();
Response proceed(Request request);
}
}
使用方法如下:假如我们现在有三个Interceptor
需要依次拦截:
```java
// Build a full stack of interceptors.
List interceptors = new ArrayList<>();
interceptors.add(new MyInterceptor1());
interceptors.add(new MyInterceptor2());
interceptors.add(new MyInterceptor3());
Interceptor.Chain chain = new RealInterceptorChain(
私以为,阅读开源项目是与世界级技术大牛直接对话的最好方式。
此次来分享下 OkHttp 源码的分析。
一、开源项目 OkHttp
在Android、Java开发领域中,相信大家都听过或者在使用Square家大名鼎鼎的网络请求库:OkHttp ,当前多数著名的开源项目如 Fresco、Glide、 Picasso、 Retrofit都在使用OkHttp,这足以说明其质量,而且该项目仍处在不断维护中。
二、问题
在分析okhttp源码之前,我想先提出一个问题,如果我们自己来设计一个网络请求库,这个库应该长什么样子?大致是什么结构呢?
下面我和大家一起来构建一个网络请求库,并在其中融入okhttp中核心的设计思想,希望借此让读者感受并学习到okhttp中的精华之处,而非仅限于了解其实现。
笔者相信,如果你能耐心阅读完本篇,不仅能对http协议有进一步理解,更能够学习到世界级项目的思维精华,提高自身思维方式。
三、思考
首先,我们假设要构建的的网络请求库叫做WingjayHttpClient
,那么,作为一个网络请求库,它最基本功能是什么呢?
在我看来应该是:接收用户的请求 -> 发出请求 -> 接收响应结果并返回给用户。
那么从使用者角度而言,需要做的事是:
- 创建一个
Request
:在里面设置好目标URL;请求method如GET/POST等;一些header如Host、User-Agent等;如果你在POST上传一个表单,那么还需要body。
- 将创建好的
Request
传递给WingjayHttpClient
。
WingjayHttpClient
去执行Request
,并把返回结果封装成一个Response
给用户。而一个Response
里应该包括statusCode如200,一些header如content-type等,可能还有body
到此即为一次完整请求的雏形。那么下面我们来具体实现这三步。
四、雏形实现
下面我们先来实现一个httpClient的雏形,只具备最基本的功能。
1. 创建Request
类
首先,我们要建立一个Request
类,利用Request
类用户可以把自己需要的参数传入进去,基本形式如下:
class Request {
String url;
String method;
Headers headers;
Body requestBody;
public Request(String url, String method, @Nullable Headers headers, @Nullable Body body) {
this.url = url;
...
}
}
2. 将Request
对象传递给WingjayHttpClient
我们可以设计WingjayHttpClient
如下:
class WingjayHttpClient {
public Response sendRequest(Request request) {
return executeRequest(request);
}
}
3. 执行Request
,并把返回结果封装成一个Response
返回
class WingjayHttpClient {
...
private Response executeRequest(Request request) {
//使用socket来进行访问
Socket socket = new Socket(request.getUrl(), 80);
ResponseData data = socket.connect().getResponseData();
return new Response(data);
}
...
}
class Response {
int statusCode;
Headers headers;
Body responseBody
...
}
五、功能扩展
利用上面的雏形,可以得到其使用方法如下:
Request request = new Request("http://wingjay.com");
WingjayHttpClient client = new WingjayHttpClient();
Response response = client.sendRequest(request);
handle(response);
然而,上面的雏形是远远不能胜任常规的应用需求的,因此,下面再来对它添加一些常用的功能模块。
1. 重新把简陋的user Request组装成一个规范的http request
一般的request中,往往用户只会指定一个URL和method,这个简单的user request是不足以成为一个http request,我们还需要为它添加一些header,如Content-Length, Transfer-Encoding, User-Agent, Host, Connection, 和 Content-Type,如果这个request使用了cookie,那我们还要将cookie添加到这个request中。
我们可以扩展上面的sendRequest(request)
方法:
[class WingjayHttpClient]
public Response sendRequest(Request userRequest) {
Request httpRequest = expandHeaders(userRequest);
return executeRequest(httpRequest);
}
private Request expandHeaders(Request userRequest) {
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
...
}
2. 支持自动重定向
有时我们请求的URL已经被移走了,此时server会返回301状态码和一个重定向的新URL,此时我们要能够支持自动访问新URL而不是向用户报错。
对于重定向这里有一个测试性URL:http://www.publicobject.com/helloworld.txt ,通过访问并抓包,可以看到如下信息:

因此,我们在接收到Response后要根据status_code是否为重定向,如果是,则要从Response Header里解析出新的URL-Location
并自动请求新URL。那么,我们可以继续改写sendRequest(request)
方法:
[class WingjayHttpClient]
private boolean allowRedirect = true;
// user can set redirect status when building WingjayHttpClient
public void setAllowRedirect(boolean allowRedirect) {
this.allowRedirect = allowRedirect;
}
public Response sendRequest(Request userRequest) {
Request httpRequest = expandHeaders(userRequest);
Response response = executeRequest(httpRequest);
switch (response.statusCode()) {
// 300: multi choice; 301: moven permanently;
// 302: moved temporarily; 303: see other;
// 307: redirect temporarily; 308: redirect permanently
case 300:
case 301:
case 302:
case 303:
case 307:
case 308:
return handleRedirect(response);
default:
return response;
}
}
// the max times of followup request
private static final int MAX_FOLLOW_UPS = 20;
private int followupCount = 0;
private Response handleRedirect(Response response) {
// Does the WingjayHttpClient allow redirect?
if (!client.allowRedirect()) {
return null;
}
// Get the redirecting url
String nextUrl = response.header("Location");
// Construct a redirecting request
Request followup = new Request(nextUrl);
// check the max followupCount
if (++followupCount > MAX_FOLLOW_UPS) {
throw new Exception("Too many follow-up requests: " + followUpCount);
}
// not reach the max followup times, send followup request then.
return sendRequest(followup);
}
利用上面的代码,我们通过获取原始userRequest
的返回结果,判断结果是否为重定向,并做出自动followup处理。
一些常用的状态码
100~199:指示信息,表示请求已接收,继续处理
200~299:请求成功,表示请求已被成功接收、理解、接受
300~399:重定向,要完成请求必须进行更进一步的操作
400~499:客户端错误,请求有语法错误或请求无法实现
500~599:服务器端错误,服务器未能实现合法的请求
3. 支持重试机制
所谓重试,和重定向非常类似,即通过判断Response
状态,如果连接服务器失败等,那么可以尝试获取一个新的路径进行重新连接,大致的实现和重定向非常类似,此不赘述。
4. Request & Response 拦截机制
这是非常核心的部分。
通过上面的重新组装request
和重定向机制,我们可以感受的,一个request
从user创建出来后,会经过层层处理后,才真正发出去,而一个response
,也会经过各种处理,最终返回给用户。
笔者认为这和网络协议栈非常相似,用户在应用层发出简单的数据,然后经过传输层、网络层等,层层封装后真正把请求从物理层发出去,当请求结果回来后又层层解析,最终把最直接的结果返回给用户使用。
最重要的是,每一层都是抽象的,互不相关的!
因此在我们设计时,也可以借鉴这个思想,通过设置拦截器Interceptor
,每个拦截器会做两件事情:
- 接收上一层拦截器封装后的request,然后自身对这个request进行处理,例如添加一些header,处理后向下传递;
- 接收下一层拦截器传递回来的response,然后自身对response进行处理,例如判断返回的statusCode,然后进一步处理。
那么,我们可以为拦截器定义一个抽象接口,然后去实现具体的拦截器。
interface Interceptor {
Response intercept(Request request);
}
大家可以看下上面这个拦截器设计是否有问题?

我们想象这个拦截器能够接收一个request,进行拦截处理,并返回结果。
但实际上,它无法返回结果,而且它在处理request后,并不能继续向下传递,因为它并不知道下一个Interceptor
在哪里,也就无法继续向下传递。
那么,如何解决才能把所有Interceptor
串在一起,并能够依次传递下去。
public interface Interceptor {
Response intercept(Chain chain);
interface Chain {
Request request();
Response proceed(Request request);
}
}
使用方法如下:假如我们现在有三个Interceptor
需要依次拦截:
```java
// Build a full stack of interceptors.
List interceptors = new ArrayList<>();
interceptors.add(new MyInterceptor1());
interceptors.add(new MyInterceptor2());
interceptors.add(new MyInterceptor3());
Interceptor.Chain chain = new RealInterceptorChain(
私以为,阅读开源项目是与世界级技术大牛直接对话的最好方式。
此次来分享下 OkHttp 源码的分析。
一、开源项目 OkHttp
在Android、Java开发领域中,相信大家都听过或者在使用Square家大名鼎鼎的网络请求库:OkHttp ,当前多数著名的开源项目如 Fresco、Glide、 Picasso、 Retrofit都在使用OkHttp,这足以说明其质量,而且该项目仍处在不断维护中。
二、问题
在分析okhttp源码之前,我想先提出一个问题,如果我们自己来设计一个网络请求库,这个库应该长什么样子?大致是什么结构呢?
下面我和大家一起来构建一个网络请求库,并在其中融入okhttp中核心的设计思想,希望借此让读者感受并学习到okhttp中的精华之处,而非仅限于了解其实现。
笔者相信,如果你能耐心阅读完本篇,不仅能对http协议有进一步理解,更能够学习到世界级项目的思维精华,提高自身思维方式。
三、思考
首先,我们假设要构建的的网络请求库叫做WingjayHttpClient
,那么,作为一个网络请求库,它最基本功能是什么呢?
在我看来应该是:接收用户的请求 -> 发出请求 -> 接收响应结果并返回给用户。
那么从使用者角度而言,需要做的事是:
- 创建一个
Request
:在里面设置好目标URL;请求method如GET/POST等;一些header如Host、User-Agent等;如果你在POST上传一个表单,那么还需要body。
- 将创建好的
Request
传递给WingjayHttpClient
。
WingjayHttpClient
去执行Request
,并把返回结果封装成一个Response
给用户。而一个Response
里应该包括statusCode如200,一些header如content-type等,可能还有body
到此即为一次完整请求的雏形。那么下面我们来具体实现这三步。
四、雏形实现
下面我们先来实现一个httpClient的雏形,只具备最基本的功能。
1. 创建Request
类
首先,我们要建立一个Request
类,利用Request
类用户可以把自己需要的参数传入进去,基本形式如下:
class Request {
String url;
String method;
Headers headers;
Body requestBody;
public Request(String url, String method, @Nullable Headers headers, @Nullable Body body) {
this.url = url;
...
}
}
2. 将Request
对象传递给WingjayHttpClient
我们可以设计WingjayHttpClient
如下:
class WingjayHttpClient {
public Response sendRequest(Request request) {
return executeRequest(request);
}
}
3. 执行Request
,并把返回结果封装成一个Response
返回
class WingjayHttpClient {
...
private Response executeRequest(Request request) {
//使用socket来进行访问
Socket socket = new Socket(request.getUrl(), 80);
ResponseData data = socket.connect().getResponseData();
return new Response(data);
}
...
}
class Response {
int statusCode;
Headers headers;
Body responseBody
...
}
五、功能扩展
利用上面的雏形,可以得到其使用方法如下:
Request request = new Request("http://wingjay.com");
WingjayHttpClient client = new WingjayHttpClient();
Response response = client.sendRequest(request);
handle(response);
然而,上面的雏形是远远不能胜任常规的应用需求的,因此,下面再来对它添加一些常用的功能模块。
1. 重新把简陋的user Request组装成一个规范的http request
一般的request中,往往用户只会指定一个URL和method,这个简单的user request是不足以成为一个http request,我们还需要为它添加一些header,如Content-Length, Transfer-Encoding, User-Agent, Host, Connection, 和 Content-Type,如果这个request使用了cookie,那我们还要将cookie添加到这个request中。
我们可以扩展上面的sendRequest(request)
方法:
[class WingjayHttpClient]
public Response sendRequest(Request userRequest) {
Request httpRequest = expandHeaders(userRequest);
return executeRequest(httpRequest);
}
private Request expandHeaders(Request userRequest) {
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
...
}
2. 支持自动重定向
有时我们请求的URL已经被移走了,此时server会返回301状态码和一个重定向的新URL,此时我们要能够支持自动访问新URL而不是向用户报错。
对于重定向这里有一个测试性URL:http://www.publicobject.com/helloworld.txt ,通过访问并抓包,可以看到如下信息:

因此,我们在接收到Response后要根据status_code是否为重定向,如果是,则要从Response Header里解析出新的URL-Location
并自动请求新URL。那么,我们可以继续改写sendRequest(request)
方法:
[class WingjayHttpClient]
private boolean allowRedirect = true;
// user can set redirect status when building WingjayHttpClient
public void setAllowRedirect(boolean allowRedirect) {
this.allowRedirect = allowRedirect;
}
public Response sendRequest(Request userRequest) {
Request httpRequest = expandHeaders(userRequest);
Response response = executeRequest(httpRequest);
switch (response.statusCode()) {
// 300: multi choice; 301: moven permanently;
// 302: moved temporarily; 303: see other;
// 307: redirect temporarily; 308: redirect permanently
case 300:
case 301:
case 302:
case 303:
case 307:
case 308:
return handleRedirect(response);
default:
return response;
}
}
// the max times of followup request
private static final int MAX_FOLLOW_UPS = 20;
private int followupCount = 0;
private Response handleRedirect(Response response) {
// Does the WingjayHttpClient allow redirect?
if (!client.allowRedirect()) {
return null;
}
// Get the redirecting url
String nextUrl = response.header("Location");
// Construct a redirecting request
Request followup = new Request(nextUrl);
// check the max followupCount
if (++followupCount > MAX_FOLLOW_UPS) {
throw new Exception("Too many follow-up requests: " + followUpCount);
}
// not reach the max followup times, send followup request then.
return sendRequest(followup);
}
利用上面的代码,我们通过获取原始userRequest
的返回结果,判断结果是否为重定向,并做出自动followup处理。
一些常用的状态码
100~199:指示信息,表示请求已接收,继续处理
200~299:请求成功,表示请求已被成功接收、理解、接受
300~399:重定向,要完成请求必须进行更进一步的操作
400~499:客户端错误,请求有语法错误或请求无法实现
500~599:服务器端错误,服务器未能实现合法的请求
3. 支持重试机制
所谓重试,和重定向非常类似,即通过判断Response
状态,如果连接服务器失败等,那么可以尝试获取一个新的路径进行重新连接,大致的实现和重定向非常类似,此不赘述。
4. Request & Response 拦截机制
这是非常核心的部分。
通过上面的重新组装request
和重定向机制,我们可以感受的,一个request
从user创建出来后,会经过层层处理后,才真正发出去,而一个response
,也会经过各种处理,最终返回给用户。
笔者认为这和网络协议栈非常相似,用户在应用层发出简单的数据,然后经过传输层、网络层等,层层封装后真正把请求从物理层发出去,当请求结果回来后又层层解析,最终把最直接的结果返回给用户使用。
最重要的是,每一层都是抽象的,互不相关的!
因此在我们设计时,也可以借鉴这个思想,通过设置拦截器Interceptor
,每个拦截器会做两件事情:
- 接收上一层拦截器封装后的request,然后自身对这个request进行处理,例如添加一些header,处理后向下传递;
- 接收下一层拦截器传递回来的response,然后自身对response进行处理,例如判断返回的statusCode,然后进一步处理。
那么,我们可以为拦截器定义一个抽象接口,然后去实现具体的拦截器。
interface Interceptor {
Response intercept(Request request);
}
大家可以看下上面这个拦截器设计是否有问题?

我们想象这个拦截器能够接收一个request,进行拦截处理,并返回结果。
但实际上,它无法返回结果,而且它在处理request后,并不能继续向下传递,因为它并不知道下一个Interceptor
在哪里,也就无法继续向下传递。
那么,如何解决才能把所有Interceptor
串在一起,并能够依次传递下去。
public interface Interceptor {
Response intercept(Chain chain);
interface Chain {
Request request();
Response proceed(Request request);
}
}
使用方法如下:假如我们现在有三个Interceptor
需要依次拦截:
```java
// Build a full stack of interceptors.
List interceptors = new ArrayList<>();
interceptors.add(new MyInterceptor1());
interceptors.add(new MyInterceptor2());
interceptors.add(new MyInterceptor3());
Interceptor.Chain chain = new RealInterceptorChain(