A Preliminary Exploration of OkHttp's Elegant Encapsulation of HttpUtils

I used to be unruly in the code, and now I work day and night in blog posts, just to share the results with you today. If you find this article useful, remember to follow me and I will bring you more.

introduce

HttpUtils is a recently open-sourced framework for lightweight encapsulation of OkHttp. Its original asynchronous preprocessor , unique tags , flexible upload and download progress monitoring and process control functions can easily solve many headaches. Also strive for purity and elegance.

  • Chain call, one point to the end
  • BaseURL, URL placeholder, JSON automatic packaging and parsing
  • Synchronous interceptors, asynchronous preprocessors, callback executors
  • File upload and download (process control, progress monitoring)
  • TCP connection pool, Http2

Project address Gitee: https://gitee.com/ejlchina-zhxu/httputils GitHub: https://github.com/ejlchina/httputils

Installation tutorial

Maven

<dependency>
     <groupId>com.ejlchina</groupId>
     <artifactId>httputils</artifactId>
     <version>2.2.0</version>
</dependency>

Gradle

compile 'com.ejlchina:httputils:2.2.0'

Instructions for use

1 Simple example

1.1 Building HTTP

HTTP http = HTTP.builder().build();

  The above code builds a simplest HTTPinstance, which has the following three methods:

  • async(String url)start an asynchronous request
  • sync(String url)start a sync request
  • cancel(String tag)Batch cancel requests based on tags

  For ease of use, we prefer to specify one at build time BaseUrl(see [5.1 Setting BaseUrl] below):

HTTP http = HTTP.builder()
        .baseUrl("http://api.demo.com")
        .build();

  In order to simplify the documentation, the following examples are httpset at build time .BaseUrlHTTP

1.2 Synchronization request

sync(String url)Start a synchronous request   with the method :

List<User> users = http.sync("/users") // http://api.demo.com/users
        .get()                         // GET请求
        .getBody()                     // 获取响应报文体
        .toList(User.class);           // 得到目标数据

  The method syncreturns a sync HttpTask, which can be chained.

1.3 Asynchronous requests

async(String url)Start an asynchronous request   with the method :

http.async("/users/1")                //  http://api.demo.com/users/1
        .setOnResponse((HttpResult result) -> {
            // 得到目标数据
            User user = result.getBody().toBean(User.class);
        })
        .get();                       // GET请求

  The method asyncreturns an async HttpTask, which can be chained.

2 Request method (GET|POST|PUT|DELETE)

  Both synchronous and asynchronous HttpTaskhave get, post, putand deletemethods. The difference is: the synchronous HttpTaskones return one HttpResult, and the asynchronous HttpTaskones return one HttpCall.

HttpResult res1 = http.sync("/users").get();     // 同步 GET
HttpResult res2 = http.sync("/users")post();     // 同步 POST
HttpResult res3 = http.sync("/users/1").put();   // 同步 PUT
HttpResult res4 = http.sync("/users/1").delete();// 同步 DELETE
HttpCall call1 = http.async("/users").get();     // 异步 GET
HttpCall call2 = http.async("/users").post();    // 异步 POST
HttpCall call3 = http.async("/users/1").put();   // 异步 PUT
HttpCall call4 = http.async("/users/1").delete();// 异步 DELETE

3 Parse the request result

3.1 Callback function

  The callback function can only be set for asynchronous requests:

http.async("/users/{id}")             // http://api.demo.com/users/1
        .addPathParam("id", 1)
        .setOnResponse((HttpResult result) -> {
            // 响应回调
        })
        .setOnException((Exception e) -> {
            // 异常回调
        })
        .setOnComplete((State state) -> {
            // 完成回调,无论成功失败都会执行
        })
        .get();

3.2 HttpResult

  HttpResultIt is the result after the HTTP request is executed. It is the return value of the synchronous request method ( get, post, put, delete) and the parameter of the asynchronous request response callback ( OnResponse). It defines the following methods:

  • getState()Get the request execution status enumeration, which has the following values:
    • State.CANCELEDrequest cancelled
    • State.RESPONSEDResponse received
    • State.TIMEOUTRequest timed out
    • State.NETWORK_ERRORNetwork Error
    • State.EXCEPTIONOther request exceptions
  • getStatus()get HTTP status code
  • isSuccessful()Whether the response is successful, the status code is between [200..300)
  • getHeaders()get HTTP response headers
  • getBody()Get the response message body Bodyinstance, which defines the following methods (for the same Bodyinstance, the following toXXX()class methods can only be used one and can only be called once):
    • toBytes()return byte array
    • toByteStream()return byte input stream
    • toCharStream()return character input stream
    • toString()return string
    • toJsonObject()return Json object
    • toJsonArray()return Json array
    • toBean(Class<T> type)Returns the JavaBean automatically parsed according to type json
    • toList(Class<T> type)Returns a list of JavaBeans automatically parsed according to type json
    • toFile(String filePath)Download to the specified path
    • toFile(File file)Download to the specified file
    • toFolder(String dirPath)Download to the specified directory
    • toFolder(File dir)Download to the specified directory
    • getContentType()Returns the media type of the message body
    • getContentLength()Returns the length in bytes of the message body
    • close()The message body is closed, and it is used when the message body is not consumed, for example, only the header of the message is read.
  • getError()Exceptions that occur during execution, automatically capture execution requests are network timeouts, network errors and other request exceptions that occur
  • close()Close the message and use it when the message body is not consumed, such as only reading the length

  For example, the request result is automatically transferred to Bean and List:

// 自动转Bean
Order order = http.sync("/orders/1")
        .get().getBody().toBean(Order.class);
        
// 自动转List
List<Order> orders = http.sync("/orders")
        .get().getBody().toList(Order.class);

  Example, download a file to the specified directory:

String path = "D:/reports/2020-03-01.xlsx";    // 文件保存目录

// 同步下载
http.sync("/reports/2020-03-01.xlsx")
        .get().getBody().toFile(path).start();

// 异步下载
http.async("/reports/2020-03-01.xlsx")
        .setOnResponse((HttpResult result) -> {
            result.getBody().toFile(path).start();
        })
        .get();

For a more detailed introduction to uploading and downloading, please see the following article: OkHttp elegantly encapsulates the uploading and downloading decryption of HttpUtils .

3.3 HttpCall

  HttpCallThe object is the return value of the asynchronous request method ( get, post, put, ), much like the interface of , it has the following methods:deletejavaFuture

  • cancel()Cancel this request and return the cancellation result
  • isCanceled()Returns whether the request was canceled
  • isDone()Returns whether the execution is complete, including cancellation and failure
  • getResult()Returns the execution result HttpResultobject. If the request is not completed, suspend the current thread until the execution is completed and then return

  Example of cancelling an asynchronous request:

HttpCall call = http.async("/users/1").get();

System.out.println(call.isCanceled());     // false

boolean success = call.cancel();           // 取消请求

System.out.println(success);               // true
System.out.println(call.isCanceled());     // true

4 Build HTTP tasks

  HTTPThe syncAND asyncmethod of an object returns an HttpTaskobject that provides a chainable addXXXAND setXXXsequence of methods for building the task itself.

  • addHeader(String name, String value)add request header

  • addHeader(Map<String, String> headers)add request header

  • addPathParam(String name, Object value)Add path parameters: replace the {name} placeholder in the URL

  • addPathParam(Map<String, ?> params)Add path parameters: replace the {name} placeholder in the URL

  • addUrlParam(String name, Object value)Add URL parameters: spliced ​​after the ? in the URL (query parameters)

  • addUrlParam(Map<String, ?> params)Add URL parameters: spliced ​​after the ? in the URL (query parameters)

  • addBodyParam(String name, Object value)Add Body parameter: put it in the message body in the form of form key=value& (form parameter)

  • addBodyParam(Map<String, ?> params)Add Body parameter: put it in the message body in the form of form key=value& (form parameter)

  • addJsonParam(String name, Object value)Add Json parameter: the request body is Json (supports multi-layer structure)

  • addJsonParam(Map<String, ?> params)Add Json parameter: the request body is Json (supports multi-layer structure)

  • setRequestJson(Object json)Set the Json string of the request body or the JavaBean to be converted to Json

  • setRequestJson(Object bean, String dateFormat)Set the Json string of the request body or the JavaBean to be converted to Json

  • addFileParam(String name, String filePath)upload files

  • addFileParam(String name, File file)upload files

  • addFileParam(String name, String type, InputStream inputStream)upload files

  • addFileParam(String name, String type, String fileName, InputStream input)upload files

  • addFileParam(String name, String type, byte[] content)upload files

  • addFileParam(String name, String type, String fileName, byte[] content)upload files

  • setTag(String tag)Add tags to HTTP tasks

  • setRange(long rangeStart)Set the Range header information for resuming the upload from a breakpoint

  • setRange(long rangeStart, long rangeEnd)Set Range header information for chunked download

5 Using labels

  Sometimes we want to classify HTTP tasks, in which case we can use the label function:

http.async("/users")    //(1)
        .setTag("A").get();
        
http.async("/users")    //(2)
        .setTag("A.B").get();
        
http.async("/users")    //(3)
        .setTag("B").get();
        
http.async("/users")    //(4)
        .setTag("B.C").get();
        
http.async("/users")    //(5)
        .setTag("C").get();

  After using tags, you can cancel HTTP tasks in batches by tags:

int count = http.cancel("B");              //(2)(3)(4)被取消(取消标签包含"B"的任务)
System.out.println(count);                 // 输出 3

  Likewise, only asynchronous HTTP tasks can be canceled. In addition to being used to cancel tasks, tags can also play a role in the preprocessor, see below [6.4 Parallel Preprocessor] and [6.5 Serial Preprocessor].

6 Configure HTTP

6.1 Setting BaseUrl

HTTP http = HTTP.builder()
        .baseUrl("http://api.demo.com")    // 设置 BaseUrl
        .build();

  After configuration BaseUrl, specific requests can be omitted BaseUrlto make the code more concise, for example:

http.sync("/users").get()                  // http://api.demo.com/users

http.sync("/auth/signin")                  // http://api.demo.com/auth/signin
        .addBodyParam("username", "Jackson")
        .addBodyParam("password", "xxxxxx")
        .post()                            // POST请求

  After configuration BaseUrl, if there is a special request, the full path method can still be used, which does not hinder at all:

http.sync("https://www.baidu.com").get()

6.2 Calling back the executor

  When you want to change the thread that executes the callback function, you can configure the callback executor. For example, in Android, if all callback functions are executed on the UI thread, you can HTTPconfigure the following at build time:

HTTP http = HTTP.builder()
        .callbackExecutor((Runnable run) -> {
            runOnUiThread(run);            // 在UI线程执行
        })
        .build();

  The callbacks affected by this configuration are: OnResponse, OnExceptionand OnComplete.

6.3 Configuring OkHttpClient

  Unlike other frameworks that encapsulate OkHttp, HttpUtils does not obscure the functions that OkHttp itself is very useful, as follows:

HTTP http = HTTP.builder()
    .config((Builder builder) -> {
        // 配置连接池 最小10个连接(不配置默认为 5)
        builder.connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES));
        // 配置连接超时时间
        builder.connectTimeout(20, TimeUnit.SECONDS);
        // 配置拦截器
        builder.addInterceptor((Chain chain) -> {
            Request request = chain.request();
            // 必须同步返回,拦截器内无法执行异步操作
            return chain.proceed(request);
        });
        // 其它配置: SSL、缓存、代理、事件监听...
    })
    .build();

6.4 Parallel Preprocessors

  The preprocessor ( Preprocessor) allows us to do some processing on the request itself according to the business before the request is sent, but unlike OkHttp's interceptor ( Interceptor): the preprocessor allows us to handle these issues asynchronously .

  For example, when we want to automatically add Tokenheader information for the request task, which Tokencan only be obtained through asynchronous methods requestToken, it is difficult to use at this time Interceptor, but it can be easily solved by using the preprocessor:

HTTP http = HTTP.builder()
        .addPreprocessor((PreChain chain) -> {
            HttpTask<?> task = chain.getTask();// 获得当前的HTTP任务
            if (!task.isTagged("Auth")) {      // 根据标签判断该任务是否需要Token
                return;
            }
            requestToken((String token) -> {   // 异步获取 Token
                task.addHeader("Token", token);// 为任务添加头信息
                chain.proceed();               // 继续当前的任务
            });
        })
        .build();

  And Interceptorlike, Preprocessoryou can also add more than one.

6.5 Serial Preprocessor

  Ordinary preprocessors can be processed in parallel, but sometimes we want a preprocessor to process only one task at a time. For example, when it Tokenexpires, we need to refresh to get a new one Token, and the refresh Tokenoperation can only be performed by one task, because if ntwo tasks are executed at the same time, then there must be n-1a task that has just been refreshed and Tokenmay be invalid immediately, and this is our undesired.

  To solve this problem, HttpUtils provides a serial preprocessor, which allows HTTP tasks to be queued to enter the preprocessor one by one:

HTTP http = HTTP.builder()
        .addSerialPreprocessor((PreChain chain) -> {
            HttpTask<?> task = chain.getTask();
            if (!task.isTagged("Auth")) {
                return;
            }
            // 检查过期,若需要则刷新Token
            requestTokenAndRefreshIfExpired((String token) -> {
                task.addHeader("Token", token);  
                chain.proceed();               // 调用此方法前,不会有其它任务进入该处理器
            });
        })
        .build();

  The serial preprocessor implements the ability to queue HTTP tasks for serial processing, but it's worth mentioning that it doesn't block any threads because of it!

7 Using the HttpUtils class

  The class HttpUtilsis the most important core class in the 1.x version. Because the HTTPinterface is abstracted in the 2.x version, its importance is not as important as it used to be. However, using it reasonably can still bring a lot of convenience, especially in environments without IOC containers, such as Android development and the development of some tool projects.

  The class HttpUtilsdefines four static methods:

  • async(String url)Start an asynchronous request (the content is implemented through a HTTPsingleton)
  • sync(String url)Start a synchronous request (the content is implemented through a HTTPsingleton)
  • cancel(String tag)Cancel request by tag (content is implemented through a HTTPsingleton)
  • of(HTTP http)Configure HttpUtilsthe held instance (a lazy instance HTTPwithout any configuration is used by default before calling this method )HTTP

  That is, classes httpcan be used wherever instances can be used HttpUtils, for example:

// 在配置HTTP实例之前,只能使用全路径方式
List<Role> roles = HttpUtils.sync("http://api.demo.com/roles")
        .get().getBody().toList(Role.class);

// 配置HTTP实例,全局生效
HttpUtils.of(HTTP.builder()
        .baseUrl("http://api.demo.com")
        .build());

// 内部使用新的HTTP实例
List<User> users = HttpUtils.sync("/users")
        .get().getBody().toList(User.class);

Next article: OkHttp elegantly encapsulates HttpUtils upload and download decryption


I used to be unruly in the code, and now I work day and night in blog posts, just to share the results with you today. If you find this article useful, remember to follow me and I will bring you more.

{{o.name}}
{{m.name}}

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=324080110&siteId=291194637