Source code series -- OkHttp

Keywords: JSON OkHttp github socket

OkHttp official website address: https://square.github.io/okhttp/

We talked about get request earlier. Let's take a look at post request

package okhttp3.guide;

import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class PostExample {
  public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");

  OkHttpClient client = new OkHttpClient();

  String post(String url, String json) throws IOException {
    RequestBody body = RequestBody.create(json, JSON);
    Request request = new Request.Builder()
        .url(url)
        .post(body)
        .build();
    try (Response response = client.newCall(request).execute()) {
      return response.body().string();
    }
  }

  String bowlingJson(String player1, String player2) {
    return "{'winCondition':'HIGH_SCORE',"
        + "'name':'Bowling',"
        + "'round':4,"
        + "'lastSaved':1367702411696,"
        + "'dateStarted':1367702378785,"
        + "'players':["
        + "{'name':'" + player1 + "','history':[10,8,6,7,8],'color':-13388315,'total':39},"
        + "{'name':'" + player2 + "','history':[6,10,5,10,10],'color':-48060,'total':41}"
        + "]}";
  }

  public static void main(String[] args) throws IOException {
    PostExample example = new PostExample();
    String json = example.bowlingJson("Jesse", "Jake");
    String response = example.post("http://www.roundsapp.com/post", json);
    System.out.println(response);
  }
}

Github download source address https://github.com/square/okhttp

Step one. Constructor

OkHttpClient client = new OkHttpClient();

Source code:

public OkHttpClient() {
  this(new Builder());
}

In the Builder() method is the initialization of a bunch of variables and objects

Step two. Construct request body

RequestBody body = RequestBody.create(json, JSON);

There are multiple overloaded methods create in the RequestBody class. Let's look at the method with the second parameter string

public static RequestBody create(@Nullable MediaType contentType, String content) {
  Charset charset = UTF_8;
  if (contentType != null) {
    charset = contentType.charset();
    if (charset == null) {
      charset = UTF_8;
      contentType = MediaType.parse(contentType + "; charset=utf-8");
    }
  }
  byte[] bytes = content.getBytes(charset);
  return create(contentType, bytes);
}

The front is to set the character type, then turn String to byte array, and finally call another create method.

The second create method calls the third create method. OK, the daily operation of the source code

public static RequestBody create(final @Nullable MediaType contentType, final byte[] content,
    final int offset, final int byteCount) {
  if (content == null) throw new NullPointerException("content == null");
  Util.checkOffsetAndCount(content.length, offset, byteCount);
  return new RequestBody() {
    @Override public @Nullable MediaType contentType() {
      return contentType;
    }

    @Override public long contentLength() {
      return byteCount;
    }

    @Override public void writeTo(BufferedSink sink) throws IOException {
      sink.write(content, offset, byteCount);
    }
  };
}

Finally, return a RequestBody and override three internal methods. BufferedSink Baidu next is a cache string in Okio, which is to store the constructed json string in a cache, and then use it again

Step three. Construct request

Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();

1) Internal class Builder of Request

public Builder() {
  this.method = "GET";
  this.headers = new Headers.Builder();
}

The default is GET request mode, which will be fixed later; the second initializes an internal class Builder of Headers, which does not do any substantive operation

2) The url method of the internal class Builder of Request

public Builder url(String url) {
  if (url == null) throw new NullPointerException("url == null");

  // Silently replace web socket URLs with HTTP URLs.
  if (url.regionMatches(true, 0, "ws:", 0, 3)) {
    url = "http:" + url.substring(3);
  } else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
    url = "https:" + url.substring(4);
  }

  return url(HttpUrl.get(url));
}

Go to HttpUrl class

public static HttpUrl get(String url) {
  return new Builder().parse(null, url).build();
}

In this case, there is an ArrayList in Builder(): encodedPathSegments; in the parse() method, there is a series of parameter parsing; the build() method is as follows

public HttpUrl build() {
  if (scheme == null) throw new IllegalStateException("scheme == null");
  if (host == null) throw new IllegalStateException("host == null");
  return new HttpUrl(this);
}

In the constructor of HttpUrl is the initialization of some objects and variables

3) The post method of the internal class Builder of Request

public Builder post(RequestBody body) {
  return method("POST", body);
}

The previous request method is GET by default, which is modified to POST here

public Builder method(String method, @Nullable RequestBody body) {
  if (method == null) throw new NullPointerException("method == null");
  if (method.length() == 0) throw new IllegalArgumentException("method.length() == 0");
  if (body != null && !HttpMethod.permitsRequestBody(method)) {
    throw new IllegalArgumentException("method " + method + " must not have a request body.");
  }
  if (body == null && HttpMethod.requiresRequestBody(method)) {
    throw new IllegalArgumentException("method " + method + " must have a request body.");
  }
  this.method = method;
  this.body = body;
  return this;
}

The first four IFS are all exception judgments, followed by method and body assignments

4) The post method of the internal class Builder of Request

public Request build() {
  if (url == null) throw new IllegalStateException("url == null");
  return new Request(this);
}

Constructor for Requst

Request(Builder builder) {
  this.url = builder.url;
  this.method = builder.method;
  this.headers = builder.headers.build();
  this.body = builder.body;
  this.tags = Util.immutableMap(builder.tags);
}

Step 4:

Response response = client.newCall(request).execute()

newCall method of OkHttpClient

@Override public Call newCall(Request request) {
  return RealCall.newRealCall(this, request, false /* for web socket */);
}

Go to RealCall class

static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
  // Safely publish the Call instance to the EventListener.
  RealCall call = new RealCall(client, originalRequest, forWebSocket);
  call.transmitter = new Transmitter(client, call);
  return call;
}

The Internal.instance.realConnectionPool(client.connectionPool()) in the Transmitter constructor can find the static code block of OkHttpClient and return a RealConnectionPool after execution

private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
    Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
    new SynchronousQueue<>(), Util.threadFactory("OkHttp ConnectionPool", true));

At first glance, RealConnectionPool is a thread pool with 0 core threads and a maximum of non core threads. Here, there is a Deque dual end queue, that is, the upgraded version of the queue. Both ports can access elements, making it more flexible

Finally, execute the request execute method. The actual execution is the RealCall execute method

@Override public Response execute() throws IOException {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  transmitter.timeoutEnter();
  transmitter.callStart();
  try {
    client.dispatcher().executed(this);
    return getResponseWithInterceptorChain();
  } finally {
    client.dispatcher().finished(this);
  }
}

Here's what other bloggers call chain blockers

Response getResponseWithInterceptorChain() throws IOException {
  // Build a full stack of interceptors.
  List<Interceptor> interceptors = new ArrayList<>();
  interceptors.addAll(client.interceptors());
  interceptors.add(new RetryAndFollowUpInterceptor(client));
  interceptors.add(new BridgeInterceptor(client.cookieJar()));
  interceptors.add(new CacheInterceptor(client.internalCache()));
  interceptors.add(new ConnectInterceptor(client));
  if (!forWebSocket) {
    interceptors.addAll(client.networkInterceptors());
  }
  interceptors.add(new CallServerInterceptor(forWebSocket));

  Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
      originalRequest, this, client.connectTimeoutMillis(),
      client.readTimeoutMillis(), client.writeTimeoutMillis());

  boolean calledNoMoreExchanges = false;
  try {
    Response response = chain.proceed(originalRequest);
    if (transmitter.isCanceled()) {
      closeQuietly(response);
      throw new IOException("Canceled");
    }
    return response;
  } catch (IOException e) {
    calledNoMoreExchanges = true;
    throw transmitter.noMoreExchanges(e);
  } finally {
    if (!calledNoMoreExchanges) {
      transmitter.noMoreExchanges(null);
    }
  }
}

Chain.processed is called in each interceptor, so that all interceptors form a chain

Five. In addition, there is an asynchronous request

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
  Request request = new Request.Builder()
      .url("http://publicobject.com/helloworld.txt")
      .build();

  client.newCall(request).enqueue(new Callback() {
    @Override public void onFailure(Call call, IOException e) {
      e.printStackTrace();
    }

    @Override public void onResponse(Call call, Response response) throws IOException {
      try (ResponseBody responseBody = response.body()) {
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

        Headers responseHeaders = response.headers();
        for (int i = 0, size = responseHeaders.size(); i < size; i++) {
          System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
        }

        System.out.println(responseBody.string());
      }
    }
  });
}

enqueue method of RealCall

@Override public void enqueue(Callback responseCallback) {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  transmitter.callStart();
  client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

client.dispatcher().enqueue(new AsyncCall(responseCallback))

enqueue method into Dispatcher class

void enqueue(AsyncCall call) {
  synchronized (this) {
    readyAsyncCalls.add(call);

    // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
    // the same host.
    if (!call.get().forWebSocket) {
      AsyncCall existingCall = findExistingCallWithHost(call.host());
      if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
    }
  }
  promoteAndExecute();
}

readyAsyncCalls is a Deque two terminal queue

@Nullable private AsyncCall findExistingCallWithHost(String host) {
  for (AsyncCall existingCall : runningAsyncCalls) {
    if (existingCall.host().equals(host)) return existingCall;
  }
  for (AsyncCall existingCall : readyAsyncCalls) {
    if (existingCall.host().equals(host)) return existingCall;
  }
  return null;
}

There is a line of code in the last promoteAndExecute method

asyncCall.executeOn(executorService())
public synchronized ExecutorService executorService() {
  if (executorService == null) {
    executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
        new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
  }
  return executorService;
}

executorService is also a thread pool with 0 core threads and the maximum number of non core threads

Finally, go to the executeOn method of the inner class AsyncCall of RealCall

void executeOn(ExecutorService executorService) {
  assert (!Thread.holdsLock(client.dispatcher()));
  boolean success = false;
  try {
    executorService.execute(this);
    success = true;
  } catch (RejectedExecutionException e) {
    InterruptedIOException ioException = new InterruptedIOException("executor rejected");
    ioException.initCause(e);
    transmitter.noMoreExchanges(ioException);
    responseCallback.onFailure(RealCall.this, ioException);
  } finally {
    if (!success) {
      client.dispatcher().finished(this); // This call is no longer running!
    }
  }
}

Mainly executorService.execute method

It can be seen that the GET request is similar to the POST request. The key is the implementation of each interceptor

Welcome to my WeChat official account: Android circle.

Posted by md7dani on Tue, 28 Apr 2020 20:00:40 -0700