Okhttp介绍与实践


一、基本介绍

1.介绍

okhttp 是一款常用的网络请求三方库,许多项目产品或多或少都会用到它,如常用的 retrofit 工具底层即通过 okhttp 实现。

2.依赖导入

Maven 工程中导入 okhttp3 依赖,为了方便对象序列化同时引入 Jackson 工具。

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.2</version>
</dependency>
        
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.3</version>
</dependency>

3.初始化

okhttp 初始化示例如下,在初始化时可指定超时等配置:

public void init() {
    OkHttpClient okHttpClient = new OkHttpClient.Builder()
            // 请求超时时间
            .readTimeout(3, TimeUnit.MINUTES)
            // 连接超时时间
            .connectTimeout(3, TimeUnit.MINUTES)
            .build();
}

二、请求头

设置 HTTP 请求头一共有 3 中方式,下面分别进行介绍,需要注意当请求头中无法正常传输中文,需要通过 URLEncode 类库编码后传输。

1. header

通过 header() 添加若 key 值已存在则会覆盖值内容。

public void demo1() {
    Request request1 = new Request.Builder()
            .url("http://127.0.0.1:8000")
            // 重复覆盖
            .header("Content-Type", "type-1.1")
            .header("Content-Type", "type-1.2")
            .build();

    // [{Content-Type:type-1.2}]
    System.out.println("request1: " + request1.headers());
}

2. addHeader

通过 addHeader() 添加若 key 值已存在则会添加多个 key 相同的键值对。

public void demo2() {
    Request request2 = new Request.Builder()
            .url("http://127.0.0.1:8000")
            // 重复添加多个
            .addHeader("Content-Type", "type-2.1")
            .addHeader("Content-Type", "type-2.2")
            .build();

    // [{Content-Type:type-2.1}, {Content-Type:type-2.2}]
    System.out.println("request2: " + request2.headers());
}

3. headers

通过 headers() 作用效果等价 addHeader(),对于重复的 key 不会覆盖,在存入多个请求头时其操作更方便。

public void demo3() {
    okhttp3.Headers.Builder headers = new okhttp3.Headers.Builder();
    builder.add("Content-Type", "type-3.1");
    builder.add("Content-Type", "type-3.2");
    Request request3 = new Request.Builder()
            .url("http://127.0.0.1:8000")
            .headers(headers.build())
            .build();

    // [{Content-Type:type-3.1}, {Content-Type:type-3.2}]
    System.out.println("request3: " + request3.headers());
}

三、数据类型

1. 请求体

HTTP 中不同请求类型对应的请求体类型值参考下表:

类型 属性值
纯文本 text/plain;
Json对象 application/json;
表单数据 application/x-www-form-urlencoded;
二进制流数据(文件下载) application/octet-stream;
表单中文件上传 multipart/form-data;

2. Json对象

Spring Boot 接口开发中当传输 Java bean 对象时常用到 @RequestBody 注解,此类接口使用 okhttp 发送请求时需设置媒体类型为 Json

其中 type 用于指定网络传输格式,内容参考上一点。

public void demo1() {
    User user = new User("Alex");
    String data = new ObjectMapper().writeValueAsString(user);
    // 构建请求体
    String type = "application/json;";
    MediaType mediaType = MediaType.parse(type);
    RequestBody body = RequestBody.create(data, mediaType);
}

3. 表单对象

Json 对象相对应的即表单对账,在 Spring Boot 接口中通常使用 @RequestParam 注解标识。

传输表单数据一共有两种方式,一种是类似上面设置对应的媒体类型,另一种则通过 FormBody 实现。

/**
 * 方式一:指定媒体类型为表单
 */
public void demo2() {
    User user = new User("Alex");
    String data = new ObjectMapper().writeValueAsString(user);
    // 构建请求体
    String type = "application/x-www-form-urlencoded;";
    MediaType mediaType = MediaType.parse(type);
    RequestBody body = RequestBody.create(data, mediaType);
}

/**
 * 方式二:通过 FormBody 构建
 */
public void demo3() {
    // 测试数据
    Map<String, String> paramsMap = new HashMap<>();
    paramsMap.put("name", "Alex");
    // 构建请求体
    FormBody.Builder builder = new FormBody.Builder();
    paramsMap.keySet()
        .forEach(it -> {
            builder.add(it, paramsMap.get(it));
        });
    RequestBody body = builder.build();
}

四、网络请求

1. GET请求

使用 Get 方式请求将参数直接拼接在 url 中即可。

public void demo() {
    // 构建请求
    Request request = new Request.Builder()
                .url("http://127.0.0.1:8000?id=1&name=Alex")
                .get()
                .build();
}

2. POST请求

使用 Post 请求方式则根据数据类型按照上述中先构建请求头再传入 post() 中。

public void demo() {
    // 构建对象
    User user = new User("Alex");
    String data = new ObjectMapper().writeValueAsString(user);
    String type = "application/x-www-form-urlencoded;";
    MediaType mediaType = MediaType.parse(type);
    RequestBody body = RequestBody.create(data, mediaType);
    // 构建请求
    Request request = new Request.Builder()
                .url("http://127.0.0.1:8000")
                // 传入请求体
                .post(body)
                .build();
}

3. 同步请求

通过 newCall().execute() 发送同步请求,此时线程进入阻塞,当接收到请求响应后才会继续后续内容。

public String callRequest(Request request) {
    String answer = null;
    try {
        // 发送请求并获取响应
        Response response = okHttpClient.newCall(request).execute();
        if (response.isSuccessful()) {
            // 获取响应体内容
            ResponseBody body = response.body();
            if (body == null) {
                throw new RuntimeException("Response body is null");
            }

            answer = body.string();
        } else {
            System.out.println(response.code() + ": " + response.message());
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    return answer;
}

4. 异步请求

通过 enqueue() 方法的异步回调实现异步网络请求,可配合 volatile 等关键字设置状态实现异步转同步监控。

public void callAsyncRequest(Request request) {
    okHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            throw new RuntimeException(e);
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            if (!response.isSuccessful()) {
                throw new IOException("Unexpected code: " + response);
            }
            // 获取请求结果 
            ResponseBody body = response.body();
            if (body == null) {
                throw new RuntimeException("Response body is null");
            }

            String result = body.string();
            System.out.println("Result: " + result);
        }
    });
}

文章作者: 烽火戏诸诸诸侯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 烽火戏诸诸诸侯 !
  目录