移动架构10-手写Okhttp框架
官方地址:https://github.com/square/okhttp
为了学习Okhttp框架,手写一个框架。用法和Okhttp框架一样。
HttpClient client = new HttpClient.Builder().build();
Request request = new Request.Builder()
.url("http://www.kuaidi100.com/query?type=yuantong&postid=11111111111")
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, Throwable throwable) {
showReult(showResult, throwable.getMessage());
}
@Override
public void onResponse(Call call, Response response) {
showReult(showResult, response.getBody());
}
});
下面按照框架的使用步骤,来讲解手写Okhttp的思路。
一、创建HttpClient
HttpClient是框架的入口,使用建造者模式来创建。
主要作用是创建调度器、创建Http连接池、添加拦截器。
/**
* 创建HttpClient
*/
public HttpClient build() {
if (null == dispatcher) {
dispatcher = new Dispatcher();
}
if (null == httpConnectionPool) {
httpConnectionPool = new HttpConnectionPool();
}
return new HttpClient(this);
}
1.Dispatcher
Dispatcher:调度器,管理所有的请求任务
Dispatcher执行请求任务需要先检查最大请求数和相同的host的最大请求数,如果没超过就加入运行队列,使用ExecutorService来执行;如果超过了,就加入等待队列。
/**
* 执行请求任务
*
* @param call
*/
public void enqueue(Call.AsyncCall call) {
//不能超过最大请求数,同时执行相同的host请求不能超过最大host数
Log.e("Dispatcher", "同时有:" + runningAsyncCalls.size());
Log.e("Dispatcher", "host同时有:" + runningCallsForHost(call));
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
Log.e("Dispatcher", "提交执行");
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
Log.e("Dispatcher", "等待执行");
readyAsyncCalls.add(call);
}
}
当一个请求完成时,就执行等待队列中的任务。
/**
* 执行等待队列中的请求
*/
private void promoteCalls() {
//同时请求达到上限
if (runningAsyncCalls.size() >= maxRequests) {
return;
}
//没有等待执行请求
if (readyAsyncCalls.isEmpty()) {
return;
}
for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) {
Call.AsyncCall call = i.next();
//同一host同时请求为达上限
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
//到达同时请求上限
if (runningAsyncCalls.size() >= maxRequests) {
return;
}
}
}
2.HttpConnectionPool
HttpConnectionPool:连接池,用来缓存、复用Http连接。
每次添加Http连接时,都检查清理无效的连接。
/**
* 检查需要移除的连接返回下次检查时间
*/
long cleanup(long now) {
long longestIdleDuration = -1;
synchronized (this) {
for (Iterator i = connections.iterator(); i.hasNext(); ) {
HttpConnection connection = i.next();
//获得闲置时间 多长时间没使用这个了
long idleDuration = now - connection.lastUsetime;
//如果闲置时间超过允许
if (idleDuration > keepAliveDuration) {
connection.closeQuietly();
i.remove();
Log.e("Pool", "移出连接池");
continue;
}
//获得最大闲置时间
if (longestIdleDuration < idleDuration) {
longestIdleDuration = idleDuration;
}
}
//下次检查时间
if (longestIdleDuration >= 0) {
return keepAliveDuration - longestIdleDuration;
} else {
//连接池没有连接 可以退出
cleanupRunning = false;
return longestIdleDuration;
}
}
}
二、创建Request
Request是请求信息,主要作用是保存请求头、请求方法、请求地址、请求体。
/**
* 请求信息
*/
public class Request {
//请求头
private Map headers;
//请求方法
private String method;
//请求地址
private HttpUrl url;
//请求体
private RequestBody body;
...
}
HttpUrl:请求地址,用来保存请求协议、主机名、请求路径和端口号;
RequestBody:请求体,用来指定字符编码,拼接请求参数。
//拼接请求参数
public String body() {
StringBuffer sb = new StringBuffer();
for (Map.Entry entry : encodedBodys.entrySet()) {
sb.append(entry.getKey())
.append("=")
.append(entry.getValue())
.append("&");
}
if (sb.length() != 0) {
sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
}
三、创建Call
Call:请求任务。用来保存Request、HttpClient和请求状态(是否执行过)。
public Call(HttpClient client, Request request) {
this.client = client;
this.request = request;
}
四、调用Call
1.调用过程
Call.enqueue(),实际是调度器在调用AsyncCall。
//异步调用
public Call enqueue(Callback callback) {
//同一请求执行一次
synchronized (this) {
if (executed) {
throw new IllegalStateException("Already Executed");
}
executed = true;
}
client.dispatcher().enqueue(new AsyncCall(callback));
return this;
}
AsyncCall是一个Runnable,用来获取响应,执行回调函数。
@Override
public void run() {
//是否已经通知过callback
boolean signalledCallback = false;
try {
//获取响应
Response response = getResponse();
if (canceled) {
signalledCallback = true;
callback.onFailure(Call.this, new IOException("Canceled"));
} else {
signalledCallback = true;
callback.onResponse(Call.this, response);
}
} catch (IOException e) {
if (!signalledCallback) {
callback.onFailure(Call.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
getResponse():获取响应,是使用责任链的模式层层调动的。可以理解为递归。
Response getResponse() throws IOException {
ArrayList interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(new InterceptorRetry());
interceptors.add(new InterceptorHeaders());
interceptors.add(new InterceptorConnection());
interceptors.add(new InterceptorCallService());
InterceptorChain interceptorChain = new InterceptorChain(interceptors, 0, this, null);
return interceptorChain.proceed();
}
2.责任链
InterceptorChain:责任链,是框架的核心。责任链会调用拦截器,而每个拦截器都会调用下一个拦截器获取Response,调用之前做请求初始化操作,调用之后做响应处理操作。
/**
* 调用拦截器,获取Response
*/
public Response proceed(HttpConnection connection) throws IOException {
Interceptor interceptor = interceptors.get(index);
InterceptorChain next = new InterceptorChain(interceptors, index + 1, call, connection);
Response response = interceptor.intercept(next);
return response;
}
InterceptorRetry:请求失败时,重新请求
public Response intercept(InterceptorChain chain) throws IOException {
Log.e("interceprot", "重试拦截器....");
Call call = chain.call;
IOException exception = null;
for (int i = 0; i < chain.call.client().retrys(); i++) {
if (call.isCanceled()) {
throw new IOException("Canceled");
}
try {
//调用下一个拦截器
Response response = chain.proceed();
return response;
} catch (IOException e) {
exception = e;
}
}
throw exception;
}
InterceptorHeaders:添加请求头信息
public Response intercept(InterceptorChain chain) throws IOException {
Log.e("interceprot","Http头拦截器....");
Request request = chain.call.request();
Map headers = request.headers();
headers.put(HttpCodec.HEAD_HOST, request.url().getHost());
headers.put(HttpCodec.HEAD_CONNECTION, HttpCodec.HEAD_VALUE_KEEP_ALIVE);
if (null != request.body()) {
String contentType = request.body().contentType();
if (contentType != null) {
headers.put(HttpCodec.HEAD_CONTENT_TYPE, contentType);
}
long contentLength = request.body().contentLength();
if (contentLength != -1) {
headers.put(HttpCodec.HEAD_CONTENT_LENGTH, Long.toString(contentLength));
}
}
//调用下一个拦截器
return chain.proceed();
}
InterceptorConnection:通过Socket创建Http连接,使用连接池来获取Http连接
public Response intercept(InterceptorChain chain) throws IOException {
Log.e("interceprot","连接拦截器....");
Request request = chain.call.request();
HttpClient client = chain.call.client();
HttpUrl url = request.url();
String host = url.getHost();
int port = url.getPort();
HttpConnection connection = client.connectionPool().get(host, port);
if (null == connection) {
connection = new HttpConnection();
} else {
Log.e("call", "使用连接池......");
}
connection.setRequest(request);
try {
//调用下一个拦截器
Response response = chain.proceed(connection);
if (response.isKeepAlive()) {
client.connectionPool().put(connection);
}
return response;
} catch (IOException e) {
throw e;
}
}
InterceptorCallService:发送Http请求
public Response intercept(InterceptorChain chain) throws IOException {
Log.e("interceprot", "通信拦截器....");
HttpCodec httpCodec = chain.httpCodec;
HttpConnection connection = chain.connection;
InputStream is = connection.call(httpCodec);
//HTTP/1.1 200 OK 空格隔开的响应状态
String statusLine = httpCodec.readLine(is);
//读取响应头
Map headers = httpCodec.readHeaders(is);
//是否保持连接
boolean isKeepAlive = false;
if (headers.containsKey(HttpCodec.HEAD_CONNECTION)) {
isKeepAlive = headers.get(HttpCodec.HEAD_CONNECTION).equalsIgnoreCase(HttpCodec
.HEAD_VALUE_KEEP_ALIVE);
}
int contentLength = -1;
if (headers.containsKey(HttpCodec.HEAD_CONTENT_LENGTH)) {
contentLength = Integer.valueOf(headers.get(HttpCodec.HEAD_CONTENT_LENGTH));
}
//分块编码数据
boolean isChunked = false;
if (headers.containsKey(HttpCodec.HEAD_TRANSFER_ENCODING)) {
isChunked = headers.get(HttpCodec.HEAD_TRANSFER_ENCODING).equalsIgnoreCase(HttpCodec
.HEAD_VALUE_CHUNKED);
}
String body = null;
if (contentLength > 0) {
byte[] bytes = httpCodec.readBytes(is, contentLength);
body = new String(bytes);
} else if (isChunked) {
body = httpCodec.readChunked(is);
}
String[] status = statusLine.split(" ");
connection.updateLastUseTime();
//返回响应结果
return new Response(Integer.valueOf(status[1]), contentLength, headers, body, isKeepAlive);
}
3.创建Http连接
HttpConnection:使用Socket建立http连接,支持Https协议
/**
* 创建socket,支持Https协议
*/
private void createSocket() throws IOException {
if (null == socket || socket.isClosed()) {
HttpUrl url = request.url();
//需要sslsocket
if (url.getProtocol().equalsIgnoreCase(HTTPS)) {
socket = SSLSocketFactory.getDefault().createSocket();
} else {
socket = new Socket();
}
socket.connect(new InetSocketAddress(url.getHost(), url.getPort()));
os = socket.getOutputStream();
is = socket.getInputStream();
}
}
HttpCodec:封装http协议,读写http请求
/**
* 封装http协议,并发送http请求
*/
public void writeRequest(OutputStream os, Request request) throws IOException {
StringBuffer protocol = new StringBuffer();
//请求行
protocol.append(request.method());
protocol.append(SPACE);
protocol.append(request.url().getFile());
protocol.append(SPACE);
protocol.append(VERSION);
protocol.append(CRLF);
//http请求头
Map headers = request.headers();
for (Map.Entry entry : headers.entrySet()) {
protocol.append(entry.getKey());
protocol.append(COLON);
protocol.append(SPACE);
protocol.append(entry.getValue());
protocol.append(CRLF);
}
protocol.append(CRLF);
//http请求体 如果存在
RequestBody body = request.body();
if (null != body) {
protocol.append(body.body());
}
//发送http请求
os.write(protocol.toString().getBytes());
os.flush();
}
/**
* 读取响应头
*/
public Map readHeaders(InputStream is) throws IOException {
HashMap headers = new HashMap<>();
while (true) {
String line = readLine(is);
//读取到空行 则下面的为body
if (isEmptyLine(line)) {
break;
}
int index = line.indexOf(":");
if (index > 0) {
String name = line.substring(0, index);
// ": "移动两位到 总长度减去两个("\r\n")
String value = line.substring(index + 2, line.length() - 2);
headers.put(name, value);
}
}
return headers;
}
/**
* 按行读取http响应数据
*/
public String readLine(InputStream is) throws IOException {
try {
byte b;
boolean isMabeyEofLine = false;
//标记
byteBuffer.clear();
byteBuffer.mark();
while ((b = (byte) is.read()) != -1) {
byteBuffer.put(b);
// 读取到/r则记录,判断下一个字节是否为/n
if (b == CR) {
isMabeyEofLine = true;
} else if (isMabeyEofLine) {
//上一个字节是/r 并且本次读取到/n
if (b == LF) {
//获得目前读取的所有字节
byte[] lineBytes = new byte[byteBuffer.position()];
//返回标记位置
byteBuffer.reset();
byteBuffer.get(lineBytes);
//清空所有index并重新标记
byteBuffer.clear();
byteBuffer.mark();
String line = new String(lineBytes);
return line;
}
isMabeyEofLine = false;
}
}
} catch (Exception e) {
throw new IOException(e);
}
throw new IOException("Response Read Line.");
}
/**
* 按字节数组读取http响应数据
*/
public byte[] readBytes(InputStream is, int len) throws IOException {
byte[] bytes = new byte[len];
int readNum = 0;
while (true) {
readNum += is.read(bytes, readNum, len - readNum);
//读取完毕
if (readNum == len) {
return bytes;
}
}
}
/**
* 按块读取http响应数据
* 分块传输编码(Chunked transfer encoding)是超文本传输协议(HTTP)中的一种数据传输机制,
* 允许HTTP由应用服务器发送给客户端应用( 通常是网页浏览器)的数据可以分成多个部分。
* 分块传输编码只在HTTP协议1.1版本(HTTP/1.1)中提供。
*/
public String readChunked(InputStream is) throws IOException {
int len = -1;
boolean isEmptyData = false;
StringBuffer chunked = new StringBuffer();
while (true) {
//解析下一个chunk长度
if (len < 0) {
String line = readLine(is);
line = line.substring(0, line.length() - 2);
len = Integer.valueOf(line, 16);
//chunk编码的数据最后一段为 0\r\n\r\n
isEmptyData = len == 0;
} else {
//块长度不包括\r\n 所以+2将 \r\n 读走
byte[] bytes = readBytes(is, len + 2);
chunked.append(new String(bytes));
len = -1;
if (isEmptyData) {
return chunked.toString();
}
}
}
}
最后
代码:https://gitee.com/yanhuo2008/AndroidCommon/tree/master/ToolHttp