Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”
This article also participated in the “Digitalstar Project” to win a creative gift package and creative incentive money
preface
In the first two articles mainly explained OkHttp source code parsing, in this article, will combine all the knowledge of the first two articles, handwritten from scratch a castrated version of OkHttp framework. As a result, readers can also recreate the OkHttp framework from scratch, step by step, as in this chapter.
Before we begin, let’s take a look at the steps we need to follow to make a fake emasculated version of OkHttp.
- Follow the pattern, create the body, duplicate it, then inject the soul
- Create a Request object, recreate a Response object
- Flow chart: distributor, chain of responsibility, interceptor
- Dispatcher: execution queue, wait queue, thread pool, logical judgment, thread termination
- Interceptor: What to do with the interceptor’s responsibilities (exclusive, only doing its own thing)
- Chain of responsibility pattern: There must be a chain interface and other implementation classes, following the hidden to class, exposed to the interface
- Auxiliary class completion
1. Create a corresponding prototype first
Let’s take a look at how OkHttp was originally used
Request request = new Request.Builder().url(PATH).build();
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {}@Override
public void onResponse(Call call, Response response) {
content = response.body().toString();
mHandler.sendEmptyMessage(1); }});Copy the code
Ok, the way OkHttp was originally used, we’ll make a copy of it, and then we’ll add our own suffix to each variable to make it our own, and there we go.
Request2 request2 = new Request2.Builder().url(PATH).build();
OkHttpClient2 okHttpClient2 = new OkHttpClient2.Builder().build();
Call2 call2 = okHttpClient2.newCall(request2);
call2.enqueue(new Callback2() {
@Override
public void onFailure(Call2 call, IOException e) {}@Override
public void onResponse(Call2 call, Response2 response) {
content = response.body().toString();
mHandler.sendEmptyMessage(1); }});Copy the code
Now sure code all report red, because there is no corresponding class, since there is no that we create one by one, code report red also solved one by one. Create a new class Request2 that does not write any logic, so that the corresponding Request2 is:
Request2.class
public class Request2 {
public static class Builder {
public Builder(a) {}public Builder url(String url) {
return this;
}
public Request2 build(a) {
return newRequest2(); }}}Copy the code
I don’t have to say much about this, the typical builder model. The OkHttpClient2 class is almost identical to Request2, but without the URL method.
OkHttpClient2.class
public class OkHttpClient2 {
public Call2 newCall(Request2 request2) {
return new RealCall2();
}
public static class Builder {
public Builder(a) {}public OkHttpClient2 build(a) {
return newOkHttpClient2(); }}}Copy the code
Next comes the Call2 to be implemented and its corresponding implementation class, RealCall2.
Call2.class
public interface Call2 {
void enqueue(Callback2 callback2);
}
Copy the code
RealCall2.class
public class RealCall2 implements Call2{
@Override
public void enqueue(Callback2 callback2) {}}Copy the code
Now Callback2 doesn’t have it, so let’s go ahead and create it
Callback2.class
public interface Callback2 {
public void onFailure(Call2 call, IOException e);
public void onResponse(Call2 call, Response2 response);
}
Copy the code
Now only Response2 error reported, continue to create
Response2.class
public class Response2 {
public int getStatusCode(a) {
return statusCode;
}
public void setStatusCode(int statusCode) {
this.statusCode = statusCode;
}
public ResponseBody body(a) {
return body;
}
public void setBody(ResponseBody body) {
this.body = body;
}
/ / 1, the code
//2
private int statusCode;
private ResponseBody body;
}
Copy the code
So here we need to create the ResponseBody.
ResponseBody.class
public class ResponseBody {
private InputStream inputStream;
private String bodyString;
private long contentLength;
private byte[] bytes;
public InputStream getInputStream(a) {
return inputStream;
}
public void setInputStream(InputStream inputStream) {
this.inputStream = inputStream;
}
public String string(a) {
return bodyString;
}
public ResponseBody setBodyString(String bodyString) {
this.bodyString = bodyString;
return this;
}
public long getContentLength(a) {
return contentLength;
}
public void setContentLength(long contentLength) {
this.contentLength = contentLength;
}
public byte[] getBytes() {
return bytes;
}
public void setBytes(byte[] bytes) {
this.bytes = bytes; }}Copy the code
At this point, the handwritten OkHttp shell has been written, and the code is not red. Now it’s time to inject your soul.
Inject soul into request2.class
Again, let’s inject souls from top to bottom. Request2 is a class that encapsulates information about a request. It is a class that encapsulates information about a request. It is a class that encapsulates information about a request.
- RequestMethod String requestMethod
- Request address String URL
- Request header Map
,>
mHeaderList
- Request body RequestBody2 RequestBody2
- Redirection address String redictUrl
- Caching and so on
RequestBody2 is not available here, so.
RequestBody2.class
public class RequestBody2 {
// Form submission Type Application /x-www-form-urlencoded
public static final String TYPE = "application/x-www-form-urlencoded";
private final String ENC = "utf-8";
// Request body set A =123&b=666
Map<String, String> bodys = new HashMap<>();
/** * Add request body information *@param key
* @param value
*/
public void addBody(String key, String value) {
// URL encoding is required
try {
bodys.put(URLEncoder.encode(key, ENC), URLEncoder.encode(value, ENC));
} catch(UnsupportedEncodingException e) { e.printStackTrace(); }}/** * get the request body information */
public String getBody(a) {
StringBuffer stringBuffer = new StringBuffer();
for (Map.Entry<String, String> stringStringEntry : bodys.entrySet()) {
// a=123&b=666&
stringBuffer.append(stringStringEntry.getKey())
.append("=")
.append(stringStringEntry.getValue())
.append("&");
}
// a=123&b= 666&delete &
if(stringBuffer.length() ! =0 ) {
stringBuffer.deleteCharAt(stringBuffer.length() -1);
}
returnstringBuffer.toString(); }}Copy the code
This is nothing to say, just a very simple way to add fetch, so the injected soul of Request2 is:
Request2.class
public class Request2 {
public static final String GET = "GET";
public static final String POST = "POST";
private String url;
private String requestMethod = GET; // The default request is GET
private Map<String, String> mHeaderList = new HashMap<>(); // The request set of the request header
private RequestBody2 requestBody2;
private Builder builder;
private boolean isCache;
private String redictUrl;// Redirect the URL
public boolean isCache(a) {
return isCache;
}
public void setCache(boolean cache) {
isCache = cache;
}
public String getRedictUrl(a) {
return redictUrl;
}
public void setRedictUrl(String redictUrl) {
this.redictUrl = redictUrl;
}
public String getUrl(a) {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getRequestMethod(a) {
return requestMethod;
}
public Map<String, String> getmHeaderList(a) {
return mHeaderList;
}
public RequestBody2 getRequestBody2(a) {
return requestBody2;
}
public Request2(a) {
this(new Builder());
}
public Builder builder(a){
return builder;
}
public Request2(Builder builder) {
this.builder = builder;
this.url = builder.url;
this.requestMethod = builder.requestMethod;
this.mHeaderList = builder.mHeaderList;
this.requestBody2 = builder.requestBody2;
}
public static class Builder {
private String url;
private String requestMethod = GET; // The default request is GET
private Map<String, String> mHeaderList = new HashMap<>();
private RequestBody2 requestBody2;
public Builder url(String url) {
this.url = url;
return this;
}
public Builder get(a) {
requestMethod = GET;
return this;
}
public Builder post(RequestBody2 requestBody2) {
requestMethod = POST;
this.requestBody2 = requestBody2;
return this;
}
/**
* Connection: keep-alive
* Host: restapi.amap.com
* @return* /
public Builder addRequestHeader(String key, String value) {
mHeaderList.put(key, value);
return this;
}
public Builder removeRequestHeader(String key) {
if(mHeaderList! =null) {
mHeaderList.remove(key);
}
return this;
}
public Request2 build(a) {
return new Request2(this); }}}Copy the code
At this point, the body request2.class is almost done, no more difficulty, and it’s OkHttpClient2’s turn
Inject soul into okHttpClient2.class
Before we start, take a look at the flowchart to see what OkHttpClient2 does.
As shown in the figure, OkHttpClient2 uses the newCall method to return RealCall, the implementation object of the Call, and then calls the Execute and enQueue methods of the Dispatcher object through RealCall. The corresponding interceptors are then called in turn to generate a Response object. In the last article on interceptors, in addition to the system’s five interceptors, developers can customize interceptors, and post the source code here.
Response getResponseWithInterceptorChain(a) throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
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, null.null.null.0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
Copy the code
From the source code, the developer’s custom interceptor is added through the OkHttpClient object, that is, our hand-written counterpart to OkHttpClient2 should also have a developer’s custom interceptor capability.
OkHttpClient2.class
public class OkHttpClient2 {
//1
//2
//3. A custom array of interceptors
// Number of retries
int recount;
// The dispenser is initialized
Dispatcher2 dispatcher;
List<Interceptor2> myInterceptors=new ArrayList<>();
public OkHttpClient2(Builder builder) {
this.recount = builder.recount;
this.dispatcher = builder.dispatcher;
this.myInterceptors = builder.myInterceptors;
}
public Dispatcher2 dispatcher(a){
return dispatcher;
}
public int getRecount(a) {
return recount;
}
public Call2 newCall(Request2 request){
return new RealCall2(this,request);
}
public static class Builder{
List<Interceptor2> myInterceptors=new ArrayList<>();
int recount = 3; // Number of retries
// The dispenser is initialized
Dispatcher2 dispatcher;
/** * constructor */
public Builder(a){
dispatcher = new Dispatcher2();
}
public Builder addInterceptor(Interceptor2 interceptor2){
myInterceptors.add(interceptor2);
return this;
}
public void setRecount(int recount) {
this.recount = recount;
}
public OkHttpClient2 build(a){
return new OkHttpClient2(this); }}public List<Interceptor2> getMyInterceptors(a) {
returnmyInterceptors; }}Copy the code
The Dispatcher2 dispatcher has synchronous and asynchronous methods, but only asynchronous methods are implemented here
Dispatcher2.class
public class Dispatcher2 {
public void enqueue(RealCall2.AsyncCall2 call){}}Copy the code
The latest RealCall2. Class
public class RealCall2 implements Call2{
public RealCall2(OkHttpClient2 okHttpClient2, Request2 request) {}@Override
public void enqueue(Callback2 callback2) {}class AsyncCall2 implements Runnable{
@Override
public void run(a) {}}}Copy the code
Interceptor2.class
public interface Interceptor2 {
Response2 doNext(Chain2 chain2);
}
Copy the code
Since there are multiple interceptors, and each function point is different, and each interceptor is related to the next, there is a chain of responsibility here. As we saw in the last article, the function of all chain of responsibility interceptors is to turn a Request into a getResponse, so we define an implementation class Chain2.
Chain2.class
public interface Chain2 {
// The responsibility chain encapsulates the Request and returns a Response
Request2 getRequest(a);
Response2 getResponse(Request2 request2);
}
Copy the code
Chain2 implements the class ChainManager.class
public class ChainManager implements Chain2{
@Override
public Request2 getRequest(a) {
return null;
}
@Override
public Response2 getResponse(Request2 request2) {
return null; }}Copy the code
Once the okHttpClient2.class spirit has been injected, it’s time to write the high-concurrency dispenser.
4. High concurrency distributor implementation
In the first article on OkHttp, I mainly explained the dispensers and how the two-pipe queue is implemented. I won’t go into the details here.
As shown in the figure
In the Dispatcher, there are two queues, so.
public class Dispatcher2 {
private int maxRequests = 64;// Access tasks at the same time. The maximum number of different domain names is 64
private int maxRequestsPerHost = 5;// A maximum of five DNS servers can be accessed simultaneously
// The actual enforcer is call, which then hands the interceptor the specific task
//RealCall2
private Deque<RealCall2.AsyncCall2> runningAsyncCalls = new ArrayDeque<>();
// Waiting queue
private Deque<RealCall2.AsyncCall2> readyAsyncCalls = new ArrayDeque<>();
// Synchronization scheme
// Asynchronous scheme
public void enqueue(RealCall2.AsyncCall2 call){
The number of requests for the same domain name is less than 5
if(runningAsyncCalls.size()<maxRequests && runningCallsForHost(call)<maxRequestsPerHost){
runningAsyncCalls.add(call);
// Create a thread from the pool
executorService().execute(call);
}else{ readyAsyncCalls.add(call); }}// Calculate whether the current domain name exceeds 5
private int runningCallsForHost(RealCall2.AsyncCall2 call) {
int count = 0;
if(runningAsyncCalls.isEmpty()){
return 0;
}
SocketRequestServer srs = new SocketRequestServer();
for(RealCall2.AsyncCall2 runningAsyncCall:runningAsyncCalls){
if(srs.getHost(runningAsyncCall.getRequest()).equals(call.getRequest())){ count++; }}return count;
}
// Get the thread from the thread pool
public ExecutorService executorService(a){
ExecutorService service = ThreadPoolManager.getInstance().getExecutor();
return service;
}
/* //1 okHTTP request end * 1. Remove completed task * 2. AsyncCall2. Run finished * @param call2 */
public void finished(RealCall2.AsyncCall2 call2){
runningAsyncCalls.remove(call2);
// If the task in preparation is empty, return directly
if(readyAsyncCalls.isEmpty()){
return;
}
for(RealCall2.AsyncCall2 readyAsyncCall:readyAsyncCalls){
readyAsyncCalls.remove(readyAsyncCall);
runningAsyncCalls.add(readyAsyncCall);
// Start the taskexecutorService().execute(readyAsyncCall); }}}Copy the code
Here use two auxiliary classes, there is no logic, directly posted
SocketRequestServer.class
public class SocketRequestServer {
private final String K = "";
private final String VIERSION = "HTTP / 1.1";
private final String GRGN = "\r\n";
/** * todo uses the Request object to find the domain name HOST *@param request2
* @return* /
public String getHost(Request2 request2) {
try {
// http://restapi.amap.com/v3/weather/weatherInfo?city=110101&key=13cb58f5884f9749287abbead9c658f2
URL url = new URL(request2.getUrl());
return url.getHost(); // restapi.amap.com
} catch (MalformedURLException e) {
e.printStackTrace();
}
return null;
}
/** * todo port *@param request2
* @return* /
public int getPort(Request2 request2) {
try {
URL url = new URL(request2.getUrl());
int port = url.getPort();
return port == -1 ? url.getDefaultPort() : port;
} catch (MalformedURLException e) {
e.printStackTrace();
}
return -1;
}
/** * todo gets all the information in the request header *@param request2
* @return* /
public String getRequestHeaderAll(Request2 request2) {
// Get the request mode
URL url = null;
try {
url = new URL(request2.getUrl());
} catch (MalformedURLException e) {
e.printStackTrace();
}
String file = url.getFile();
GET /v3/weather/weatherInfo? City = 110101 & key = 13 cb58f5884f9749287abbead9c658f2 HTTP / 1.1 \ r \ n
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(request2.getRequestMethod()) // GET or POST
.append(K)
.append(file)
.append(K)
.append(VIERSION)
.append(GRGN);
// TODO gets the request set for concatenation
/** * Content-Length: 48\r\n * Host: restapi.amap.com\r\n * Content-Type: application/x-www-form-urlencoded\r\n */
if(! request2.getmHeaderList().isEmpty()) { Map<String,String> mapList = request2.getmHeaderList();for (Map.Entry<String, String> entry: mapList.entrySet()) {
stringBuffer.append(entry.getKey())
.append(":").append(K)
.append(entry.getValue())
.append(GRGN);
}
// Concatenate empty lines to represent the following POST request body
stringBuffer.append(GRGN);
}
// TODO POST requests have request body concatenation
if ("POST".equalsIgnoreCase(request2.getRequestMethod())) {
stringBuffer.append(request2.getRequestBody2().getBody()).append(GRGN);
}
returnstringBuffer.toString(); }}Copy the code
Here is the concatenation of the request body, which is easy to understand at a glance.
ThreadPoolManager.class
public class ThreadPoolManager {
/** * Singleton design pattern (hungry) * singleton first privates the constructor, then hungry starts to create and provides the get method */
private static ThreadPoolManager mInstance = new ThreadPoolManager();
public static ThreadPoolManager getInstance(a) {
return mInstance;
}
private int corePoolSize;// The number of core thread pools, and the number of threads that can execute at the same time
private int maximumPoolSize;// Maximum number of thread pools, which represents the number of waiting tasks that can continue to hold when the buffer queue is full
private long keepAliveTime = 1;// Survival time
private TimeUnit unit = TimeUnit.HOURS;
private ThreadPoolExecutor executor;
private ThreadPoolManager(a) {
/** * Assign corePoolSize to the number of processor cores currently available to the device *2 + 1 to maximize CPU efficiency */
corePoolSize = Runtime.getRuntime().availableProcessors() * 2 + 1;
maximumPoolSize = corePoolSize; // If maximumPoolSize is not used, it must be assigned or an error will be reported
executor = new ThreadPoolExecutor(
0.// The number of core threads
Integer.MAX_VALUE, // corePoolSize is first followed by new LinkedBlockingQueue
() and maximumPoolSize, but its number includes corePoolSize
60L.// Indicates the lifetime of the waiting task in maximumPoolSize
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(), // Buffer queue, used to hold waiting tasks, Linked first in first out
Executors.defaultThreadFactory(), // Create a factory for the thread
new ThreadPoolExecutor.AbortPolicy() // The processing policy used for tasks that exceed maximumPoolSize
);
}
public ThreadPoolExecutor getExecutor(a) {
return executor;
}
/** * Perform the task */
public void execute(Runnable runnable) {
if (runnable == null) return;
executor.execute(runnable);
}
/** * Removes the task */ from the thread pool
public void remove(Runnable runnable) {
if (runnable == null) return; executor.remove(runnable); }}Copy the code
This class is the thread pool management class.
The latest RealCall2. Class
public class RealCall2 implements Call2{
private OkHttpClient2 okHttpClient2;
private Request2 request2;
// The contractor executes the process
public OkHttpClient2 getOkHttpClient2(a){
return okHttpClient2;
}
public RealCall2(OkHttpClient2 okHttpClient2, Request2 request2) {
this.okHttpClient2 = okHttpClient2;
this.request2 = request2;
}
@Override
public void enqueue(Callback2 callback2) {
// A place to get things done
// Give it away
okHttpClient2.dispatcher().enqueue(new AsyncCall2(callback2));
}
class AsyncCall2 implements Runnable{
private Callback2 callback2;
public AsyncCall2(Callback2 callback) {
this.callback2 = callback;
}
Request2 getRequest(a){
return RealCall2.this.request2;
}
@Override
public void run(a) {}}}Copy the code
Now that the dispenser is done, it’s time for the chain of responsibility.
5. Realization of responsibility chain
We implemented the Chain2 interface through ChainManager, but we didn’t do anything about it. Now it’s time to inject soul into the chain of responsibility.
** Latest ChainManager **
public class ChainManager implements Chain2 {
/ / chain nodes
// One is to designate the next leader, index
private int index;// Represents the horn of the currently executing chain node
private Call2 call;// who is responsible for the entire chain of responsibility
private Request2 request2;// Each node is assembling Request2
private List<Interceptor2> interceptors;// My node must implement Interceptor2 to be a member of the responsibility chain
public Call2 getCall(a){
return call;
}
public ChainManager(int index, Call2 call, Request2 request2, List<Interceptor2> interceptors) {
this.index = index;
this.call = call;
this.request2 = request2;
this.interceptors = interceptors;
}
@Override
public Request2 getRequest(a) {
return request2;
}
@Override
public Response2 getResponse(Request2 request2) {
// Return a Response to Request
if(interceptors==null || interceptors.isEmpty()){
return new Response2();
}
if(index>=interceptors.size()){
// If index exceeds size, it returns directly without going to the next node
return new Response2();
}
// The current responsibility chain
Interceptor2 interceptor2 = interceptors.get(index);
// Initialize the next responsibility chain manager
ChainManager manager = new ChainManager(index+1,call,request2,interceptors);
// The current responsibility chain calls the next responsibility chain
Response2 response2 = interceptor2.doNext(manager);
// The chain of responsibility is finished
returnresponse2; }}Copy the code
The so-called chain of responsibility, you can think of it as something like an iron chain, where the top link links the next link, and where the last link ends represents the beginning of the next link. Chain of responsibility implementation is that simple. Next comes the implementation of the interceptor.
6. Implementation of interceptors
As discussed in OkHttp 2, the role of each interceptor will be drawn from zero one by one. I’m ready
- RetryAndFollowInterceptor redirect interceptors
- BridgeInterceptor BridgeInterceptor
- CacheInterceptor a CacheInterceptor
- CallServerInteceptor connection + read/write interceptor
Now we are ready to implement them in turn
6.1 Implement the redirection interceptor
RetryAndFollowInterceptor.class
public class RetryAndFollowInterceptor implements Interceptor2 {
@Override
public Response2 doNext(Chain2 chain2){
// get Response from network interceptor
System.out.println("I'm redirecting the interceptor again, executed.");
ChainManager chainManager = (ChainManager) chain2;
RealCall2 realCall2 = (RealCall2) chainManager.getCall();
OkHttpClient2 client2 = realCall2.getOkHttpClient2();
IOException ioException = null;
// Number of retries
if(client2.getRecount() ! =0) {
for (int i = 0; i < client2.getRecount(); i++) { / / 3
try {
// log. d(TAG, "I'm a retry interceptor, I'm going to Return Response2 ");
System.out.println("I'm a retry interceptor. I'm going to Return Response2.");
// If there is no exception, the loop ends
Response2 response2 = chain2.getResponse(chainManager.getRequest()); // Execute the next interceptor (task node)
Request2 request2 = chainManager.getRequest();// This is the result of receiving the network interceptor
// If RedictUrl has data, it is redirected
if(! TextUtils.isEmpty(request2.getRedictUrl())){// Replace the url
request2.setUrl(request2.getRedictUrl());
return chain2.getResponse(request2);
}
return response2;
} catch(Exception e) { e.printStackTrace(); }}}return newResponse2(); }}Copy the code
This is just the logic for redirection, as long as you’re in the retry range, you can redirect.
6.2 Implement bridge interceptors
BridgeInterceptor.class
public class BridgeInterceptor implements Interceptor2 {
@Override
public Response2 doNext(Chain2 chain2){
/** * Request encapsulates */
ChainManager chainManager = (ChainManager) chain2;
Request2 request2 = chain2.getRequest();
Map<String,String> mHeadList = request2.getmHeaderList();
mHeadList.put("Host".new SocketRequestServer().getHost(chainManager.getRequest()));
if("POST".equalsIgnoreCase(request2.getRequestMethod())){
// Request body type LAN
/** * Content-Length: 48 * Content-Type: application/x-www-form-urlencoded */
mHeadList.put("Content-Length", request2.getRequestBody2().getBody().length()+"");
mHeadList.put("Content-Type", RequestBody2.TYPE);
}
returnchain2.getResponse(request2); }}Copy the code
There is also very simple request header wrapping
6.3 Implement cache interceptors
CacheInterceptor.class
public class CacheInterceptor implements Interceptor2 {
@Override
public Response2 doNext(Chain2 chain){
Request2 request = chain.getRequest();
// HTTP 1.0 only has pragma
// Cache-control version 1.1 does
// Set the response Cache time to 60 seconds, that is, set the cache-control header,
// And remove the pragma header, because the pragma is also a header property that controls the cache
Pragma:no-cache, same as cache-control :no-cache.
Pragma: no-cache is compatible with HTTP 1.0, cache-control: no-cache is provided by HTTP 1.1.
Pragma: no-cache can be applied to HTTP 1.0 and HTTP 1.1,
Cache-control: no-cache applies only to HTTP 1.1.
// Lists page, pin-duo, 60s,
request = request.builder()
.removeRequestHeader("pragma")
.addRequestHeader("Cache-Control"."max-age=60")
.build();
String json = CacheTemp.cacheMap.get(request.getUrl());
if(! TextUtils.isEmpty(json)){ Response2 response2 =new Response2();
ResponseBody body = new ResponseBody();
body.setBodyString(json);
response2.setBody(body);
return response2;
}
// Only Get requests can fetch cached data
if(request.getRequestMethod().equals("GET")) {
CacheTemp.isCache = false;
}else{
CacheTemp.isCache = false;
}
returnchain.getResponse(request); }}Copy the code
The CacheTemp helper class is used here
CacheTemp.class
public class CacheTemp {
public static boolean isCache;
public static Map<String,String> cacheMap=new HashMap<>();
}
Copy the code
I didn’t write an SD card stream cache like OkHttp. After all, castrated version, of course, readers here can be disk cache, soft cache are used.
6.4 Connection + Read/write interceptor
CallServerInteceptor.class
public class CallServerInteceptor implements Interceptor2 {
@Override
public Response2 doNext(Chain2 chain2){
Response2 response2 = new Response2();
try {
SocketRequestServer srs = new SocketRequestServer();
Request2 request2 = chain2.getRequest();
Socket socket = new Socket(srs.getHost(request2), srs.getPort(request2));
/ / todo request
// output
OutputStream os = socket.getOutputStream();
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(os));
String requestAll = srs.getRequestHeaderAll(request2);
// Log.d(TAG, "requestAll:" + requestAll);
System.out.println("requestAll:" + requestAll);
bufferedWriter.write(requestAll); // Send a request to the server - request header information all
bufferedWriter.flush(); // Write it out...
/ / todo response
//final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
InputStream is = socket.getInputStream();
HttpCodec httpCodec = new HttpCodec();
// Read a response line
String responseLine = httpCodec.readLine(is);
System.out.println("Response line:" + responseLine);
// Read the response header
Map<String, String> headers = httpCodec.readHeaders(is);
for (Map.Entry<String, String> entry : headers.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
if (headers.containsKey("Location")) {//Location indicates that the request is redirected
request2.setRedictUrl(headers.get("Location"));
}
// Read the response body? You need to distinguish between content-Length and Chunked encoding
if (headers.containsKey("Content-Length")) {
int length = Integer.valueOf(headers.get("Content-Length"));
byte[] bytes = httpCodec.readBytes(is, length);
String content = new String(bytes);
System.out.println("Response." + content);
if (request2.isCache()) {
CacheTemp.cacheMap.put(request2.getUrl(), content);
}
ResponseBody responseBody = new ResponseBody();
//responseBody.setInputStream(is);
responseBody.setContentLength(length);
responseBody.setBytes(bytes);
response2.setBody(responseBody);
//response2.setBody(new ResponseBody().setBodyString(content.replaceAll("\\r\\n", "")));
//mHandler.sendEmptyMessage(1);
} else {
// Chunk code Chunk returns data in chunks. Links that take time to return can be returned quickly
String response = httpCodec.readChunked(is);
if (CacheTemp.isCache) {
CacheTemp.cacheMap.put(request2.getUrl(), response);
}
response2.setBody(new ResponseBody().setBodyString(response.replaceAll("\\r\\n"."")));
System.out.println("Response." + response);
}
is.close();
socket.close();
//response2 = chain2.getResponse(request2); // Execute the next interceptor (task node)
}catch (Exception e){
e.printStackTrace();
}
// response2.setBody(" process through....") );
returnresponse2; }}Copy the code
I’m not going to do exactly what OkHttp does, but I’m going to be a little bit lazy and write the connection interceptor and the read/write interceptor together. All interceptor implementations have been written by this point. The next step is to assemble the interceptor.
7. Use interceptors
Using the same flow chart, we can see that interceptors are used after Dispatcher, while real synchronous asynchro is triggered in RealCall, that is, interceptors are assembled in RealCall. So the final RealCall code.
RealCall2.class
public class RealCall2 implements Call2{
private OkHttpClient2 okHttpClient2;
private Request2 request2;
// The contractor executes the process
public OkHttpClient2 getOkHttpClient2(a){
return okHttpClient2;
}
public RealCall2(OkHttpClient2 okHttpClient2, Request2 request2) {
this.okHttpClient2 = okHttpClient2;
this.request2 = request2;
}
@Override
public void enqueue(Callback2 callback2) {
// A place to get things done
// Give it away
okHttpClient2.dispatcher().enqueue(new AsyncCall2(callback2));
}
class AsyncCall2 implements Runnable{
private Callback2 callback2;
public AsyncCall2(Callback2 callback) {
this.callback2 = callback;
}
Request2 getRequest(a){
return RealCall2.this.request2;
}
@Override
public void run(a) {
// This is where the real work begins. The chain of responsibility is suspended
//callback2 returns a result, either success or failure
try {
// get the chain of responsibility
Response2 response2 = getResponseChain();
callback2.onResponse(RealCall2.this, response2);
}catch (Exception e){
callback2.onFailure(RealCall2.this.new IOException("OKHTTP getResponseWithInterceptorChain error... e:" + e.toString()));
}finally {
okHttpClient2.dispatcher().finished(this); }}private Response2 getResponseChain(a) {
// add elements to the chain, i.e. interceptors
List<Interceptor2> interceptor2s = new ArrayList<>();
interceptor2s.add(new BridgeInterceptor());
interceptor2s.add(new RetryAndFollowInterceptor());
interceptor2s.add(new CacheInterceptor());
// Add your own interceptor here
interceptor2s.addAll(okHttpClient2.getMyInterceptors());
interceptor2s.add(new CallServerInteceptor());
ChainManager chainManager = new ChainManager(0,RealCall2.this,request2,interceptor2s);
returnchainManager.getResponse(request2); }}}Copy the code
Now that we’re nearing the end of the handwritten OkHttp framework, let’s run it through to see how it looks.
The response header Location also has the corresponding attribute. This is nothing more than a typical redirect. But on closer inspection, the redirected address is exactly the same as the requested address. This is a little weird, so take this address to the browser and try it out.
In the browser, there is no redirection at all. So the root cause is not a redirection problem at all. Could that be a network request with no Https adaptation? We all know that there is a layer of SSL certificates between Http and Https, and our connection interceptor does not handle the response at all, but connects directly to the Socket. As a result
// HTTP creates the socket directly new, whereas our HTTPS socket
// Socket socket = new Socket(srs.getHost(request2), srs.getPort(request2));
/ / HTTPS and HTTP
Socket socket = null;
if(request2.getUrl().startsWith("https://")) {// Get an SSL context
SSLContext sslContext = SSLContext.getInstance("TLS");
// Trust all certificates on this machine
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
// Initialize the certificate
trustManagerFactory.init((KeyStore) null);
// Trust certificate Settings
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
// The certificate manager is initialized
sslContext.init(null, trustManagers, null);
// Get the SSLSocket factory from sslContext
SSLSocketFactory socketFactory = sslContext.getSocketFactory();
/ / create a socket
socket = socketFactory.createSocket();
socket.connect(new InetSocketAddress(srs.getHost(request2), srs.getPort(request2)));
}else {
socket = new Socket(srs.getHost(request2), srs.getPort(request2));
}
Copy the code
I added this judgment to the connection interceptor so that if the request is HTTPS, it automatically adds certificate authentication, and if the request is HTTP, it initializes the socket directly. Let’s try it out on the run.
At this point, a neutered version of the OkHttp framework ends. I believe that you will be able to fully understand OkHttp source code.