• 自研一个简易版本的OkHTTP


    一,背景

    为了彻底搞明白okhttp原理,仿照okhttp自研一个

    二,思路

    业务上没发出一个request,使用AsyncCall包装起来,然后在网络分发器的作用下,执行具体的每一个Call,这些具体的Call会经过层层的拦截器,最终会调用到CallServiceInterceptor,这个对象里面有一个ConnectionPool,保存着每一个url对应的socket,最终处理的结果返回交给具体的每一个call,然后在回调到业务层

    三,相关类图

    四,具体代码实现

    4.1 request

    1. public class Request {
    2. private Map<String, String> headers;
    3. private String method;
    4. private HttpUrl url;
    5. private RequestBody body;
    6. public Request(Builder builder) {
    7. this.url = builder.url;
    8. this.method = builder.method;
    9. this.headers = builder.headers;
    10. this.body = builder.body;
    11. }
    12. public String method() {
    13. return method;
    14. }
    15. public HttpUrl url() {
    16. return url;
    17. }
    18. public RequestBody body() {
    19. return body;
    20. }
    21. public Map<String, String> headers() {
    22. return headers;
    23. }
    24. public final static class Builder {
    25. HttpUrl url;
    26. Map<String, String> headers = new HashMap<>();
    27. String method;
    28. RequestBody body;
    29. public Builder url(String url) {
    30. try {
    31. this.url = new HttpUrl(url);
    32. return this;
    33. } catch (MalformedURLException e) {
    34. throw new IllegalStateException("Failed Http Url", e);
    35. }
    36. }
    37. public Builder addHeader(String name, String value) {
    38. headers.put(name, value);
    39. return this;
    40. }
    41. public Builder removeHeader(String name) {
    42. headers.remove(name);
    43. return this;
    44. }
    45. public Builder get() {
    46. method = "GET";
    47. return this;
    48. }
    49. public Builder post(RequestBody body) {
    50. this.body = body;
    51. method = "POST";
    52. return this;
    53. }
    54. public Request build() {
    55. if (url == null) {
    56. throw new IllegalStateException("url == null");
    57. }
    58. if (TextUtils.isEmpty(method)) {
    59. method = "GET";
    60. }
    61. return new Request(this);
    62. }
    63. }
    64. }

    4.2 Response 

    1. public class Response {
    2. int code;
    3. int contentLength = -1;
    4. Map<String, String> headers = new HashMap<>();
    5. String body;
    6. //保持连接
    7. boolean isKeepAlive;
    8. public Response() {
    9. }
    10. public Response(int code, int contentLength, Map<String, String> headers, String body,
    11. boolean isKeepAlive) {
    12. this.code = code;
    13. this.contentLength = contentLength;
    14. this.headers = headers;
    15. this.body = body;
    16. this.isKeepAlive = isKeepAlive;
    17. }
    18. public int getCode() {
    19. return code;
    20. }
    21. public int getContentLength() {
    22. return contentLength;
    23. }
    24. public Map<String, String> getHeaders() {
    25. return headers;
    26. }
    27. public String getBody() {
    28. return body;
    29. }
    30. public boolean isKeepAlive() {
    31. return isKeepAlive;
    32. }
    33. }

    4.3 HttpUrl

    1. public class HttpUrl {
    2. String protocol;
    3. String host;
    4. String file;
    5. int port;
    6. public HttpUrl(String url) throws MalformedURLException {
    7. URL url1 = new URL(url);
    8. host = url1.getHost();
    9. file = url1.getFile();
    10. file = TextUtils.isEmpty(file) ? "/" : file;
    11. protocol = url1.getProtocol();
    12. port = url1.getPort();
    13. port = port == -1 ? url1.getDefaultPort() : port;
    14. }
    15. public String getProtocol() {
    16. return protocol;
    17. }
    18. public String getHost() {
    19. return host;
    20. }
    21. public String getFile() {
    22. return file;
    23. }
    24. public int getPort() {
    25. return port;
    26. }
    27. }

    4.4 Call

    1. public class Call {
    2. Request request;
    3. HttpClient client;
    4. //是否执行过
    5. boolean executed;
    6. boolean cancel;
    7. public boolean isCancel() {
    8. return cancel;
    9. }
    10. public Request getRequest() {
    11. return request;
    12. }
    13. public Call(Request request, HttpClient client) {
    14. this.request = request;
    15. this.client = client;
    16. }
    17. public HttpClient getClient() {
    18. return client;
    19. }
    20. public void enqueue(Callback callback) {
    21. synchronized (this) {
    22. if (executed) {
    23. throw new IllegalStateException("已经执行过了,就不要执行");
    24. }
    25. executed = true;
    26. }
    27. //把任务交给调度器调度
    28. client.dispatcher().enqueue(new AsyncCall(callback));
    29. }
    30. /**
    31. * 是否取消
    32. */
    33. public void cancel() {
    34. cancel = true;
    35. }
    36. /**
    37. * 执行网络请求的线程
    38. */
    39. class AsyncCall implements Runnable {
    40. private Callback callback;
    41. public AsyncCall(Callback callback) {
    42. this.callback = callback;
    43. }
    44. @Override
    45. public void run() {
    46. //是否回调过
    47. boolean singaledCallbacked = false;
    48. try {
    49. //执行真正的请求
    50. Response response = getResponse();
    51. if (cancel) {
    52. singaledCallbacked = true;
    53. callback.onFailure(Call.this, new IOException("客户端主动执行了cancel"));
    54. } else {
    55. singaledCallbacked = true;
    56. callback.onResponse(Call.this, response);
    57. }
    58. } catch (Exception e) {
    59. // e.printStackTrace();
    60. if (!singaledCallbacked) {
    61. //如果没有回调过
    62. callback.onFailure(Call.this, e);
    63. }
    64. } finally {
    65. //将这个任务从调度器移除
    66. client.dispatcher().finished(this);
    67. }
    68. }
    69. public String host() {
    70. return request.url().getHost();
    71. }
    72. }
    73. /**
    74. * 这里是重点!!!
    75. * @return
    76. */
    77. private Response getResponse() throws Exception{
    78. //创建拦截器责任链
    79. List<Interceptor> interceptors = new ArrayList();
    80. //重试拦截器
    81. interceptors.add(new RetryInterceptor());
    82. //请求头拦截器
    83. interceptors.add(new HeaderInterceptor());
    84. //缓存拦截器
    85. interceptors.add(new CacheInterceptor());
    86. //连接拦截器
    87. interceptors.add(new ConnectionInterceptor());
    88. //通信拦截器
    89. interceptors.add(new CallServiceInterceptor());
    90. InterceptorChain chain = new InterceptorChain(interceptors, 0, this, null);
    91. return chain.process();
    92. }
    93. }

    4.5 InterceptorChain

    1. public class InterceptorChain {
    2. List<Interceptor> interceptors;
    3. int index;
    4. Call call;
    5. HttpConnection httpConnection;
    6. public InterceptorChain(List<Interceptor> interceptors, int index, Call call, HttpConnection connection) {
    7. this.interceptors = interceptors;
    8. this.index = index;
    9. this.call = call;
    10. this.httpConnection = connection;
    11. }
    12. public Response process(HttpConnection httpConnection) throws IOException{
    13. this.httpConnection = httpConnection;
    14. return process();
    15. }
    16. public Response process() throws IOException {
    17. if (index >= interceptors.size()) throw new IOException("Interceptor China index out max length");
    18. //获得拦截器 去执行
    19. Interceptor interceptor = interceptors.get(index);
    20. InterceptorChain next = new InterceptorChain(interceptors, index + 1, call, httpConnection);
    21. Response response = interceptor.intercept(next);
    22. return response;
    23. }
    24. }

    4.6 Interceptor

    1. public interface Interceptor {
    2. Response intercept(InterceptorChain chain) throws IOException;
    3. }

    4.7 ConnectionPool

    1. public class ConnectionPool {
    2. private static final String TAG = "ConnectionPool";
    3. private static final boolean DEBUG = BuildConfig.DEBUG;
    4. private final long keepAlive;
    5. private boolean cleanrunning;
    6. private Deque<HttpConnection> connections = new ArrayDeque<>();
    7. public ConnectionPool() {
    8. this(1, TimeUnit.MINUTES);
    9. }
    10. public ConnectionPool(long keepAlive, TimeUnit unit) {
    11. this.keepAlive = unit.toMillis(keepAlive);
    12. }
    13. private Runnable cleanupRunable = new Runnable() {
    14. @Override
    15. public void run() {
    16. while (true) {
    17. long waitDuration = cleanup(System.currentTimeMillis());
    18. if (waitDuration == -1) {
    19. return;
    20. }
    21. if (waitDuration > 0) {
    22. synchronized (ConnectionPool.this) {
    23. try {
    24. ConnectionPool.this.wait(waitDuration);
    25. } catch (InterruptedException e) {
    26. e.printStackTrace();
    27. }
    28. }
    29. }
    30. }
    31. }
    32. };
    33. private long cleanup(long currentTimeMillis) {
    34. long longgetIdleDuration = -1;
    35. synchronized (this) {
    36. Iterator<HttpConnection> iterator = connections.iterator();
    37. while (iterator.hasNext()) {
    38. HttpConnection connection = iterator.next();
    39. long idleDuration = currentTimeMillis - connection.getLastUseTime();
    40. //超过了最大允许闲置时间
    41. if (idleDuration > keepAlive) {
    42. if (DEBUG) Log.d(TAG, "ConnectionPool cleanup: " + "超出闲置时间,移除连接池");
    43. iterator.remove();
    44. connection.close();
    45. continue;
    46. }
    47. //没有超过闲置时间
    48. //记录 最长的闲置时间
    49. if (longgetIdleDuration < idleDuration) {
    50. longgetIdleDuration = idleDuration;
    51. }
    52. }
    53. //假如keepAlive是10s
    54. //
    55. if (longgetIdleDuration >= 0) {
    56. return keepAlive - longgetIdleDuration;
    57. }
    58. //连接池中没有连接
    59. cleanrunning = false;
    60. return longgetIdleDuration;
    61. }
    62. }
    63. private static final Executor executer = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
    64. 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new ThreadFactory() {
    65. @Override
    66. public Thread newThread(Runnable r) {
    67. Thread thread = new Thread(r, "Connection Pool");
    68. thread.setDaemon(true);//设置为守护线程,可以理解为跟进程同样的生命周期
    69. return thread;
    70. }
    71. });
    72. public void put(HttpConnection httpConnection) {
    73. //如果没有执行清理线程
    74. if (!cleanrunning) {
    75. cleanrunning = true;
    76. executer.execute(cleanupRunable);
    77. }
    78. connections.add(httpConnection);
    79. }
    80. public synchronized HttpConnection get(String host, int port) {
    81. Iterator<HttpConnection> iterator = connections.iterator();
    82. while (iterator.hasNext()) {
    83. HttpConnection connection = iterator.next();
    84. //如果查找到连接池始终在相同的host和port
    85. if (connection.isSameAddress(host, port)) {
    86. iterator.remove();
    87. return connection;
    88. }
    89. }
    90. return null;
    91. }
    92. }

    4.8  CallServiceInterceptor

    1. public class CallServiceInterceptor implements Interceptor {
    2. private static final String TAG = "CallServiceInterceptor";
    3. private static final boolean DEBUG = BuildConfig.DEBUG;
    4. @Override
    5. public Response intercept(InterceptorChain chain) throws IOException {
    6. if (DEBUG) Log.d(TAG, "CallServiceInterceptor intercept: " + "通信拦截器");
    7. HttpConnection connection = chain.httpConnection;
    8. HttpCode httpCode = new HttpCode();
    9. InputStream inputStream = connection.call(httpCode);
    10. String statusLine = httpCode.readLine(inputStream);
    11. Map<String, String> headers = httpCode.readHeaders(inputStream);
    12. int contentLength = -1;
    13. if (headers.containsKey("Content-Length")) {
    14. //如果有content-length,代表可以拿到响应体的字节长度
    15. contentLength = Integer.valueOf(headers.get("Content-Length"));
    16. }
    17. boolean isChunked = false;
    18. if (headers.containsKey("Transfer-Encoding")) {
    19. //如果有有Transfer-Encoding,表示是分块编码,此时没有响应体的长度
    20. isChunked = headers.get("Transfer-Encoding").equalsIgnoreCase("chunked");
    21. }
    22. String body = null;
    23. if (contentLength > 0) {
    24. byte[] bytes = httpCode.readBytes(inputStream, contentLength);
    25. body = new String(bytes);
    26. } else if (isChunked) {
    27. body = httpCode.readChunked(inputStream);
    28. }
    29. String[] status = statusLine.split(" ");
    30. boolean isKeepAlive = false;
    31. if (headers.containsKey("Connection")) {
    32. isKeepAlive = headers.get("Connection").equalsIgnoreCase("Keep-Alive");
    33. }
    34. connection.updateLastUseTime();
    35. return new Response(Integer.valueOf(status[1]), contentLength, headers, body,isKeepAlive);
    36. }
    37. }

  • 相关阅读:
    利用大模型反馈故障的解决方案
    Windows10 python3.11+pytorch2.01+cuda11.8安装与配置
    Viewport 源码解析
    BOM体系学习
    各大自动化测试框架对比
    1180:分数线划定/P1068 [NOIP2009 普及组] 分数线划定
    用于强化学习的置换不变神经网络
    设备安全:防火墙总结(1)
    Python批量裁剪图片
    HTML静态网页成品作业(HTML+CSS)——家乡泉州介绍网页(3个页面)(表格布局)
  • 原文地址:https://blog.csdn.net/qq_18757557/article/details/132882803