OkHttp 网络请求类型全解析

239 阅读6分钟

OkHttp 网络请求类型全解析

简介

OkHttp 是一个高效的 HTTP 客户端,支持多种网络通信协议和请求类型。它由 Square 公司开发,被广泛应用于 Android 和 Java 应用程序中。OkHttp 的设计理念是简单、高效、可扩展,能够自动处理常见的网络问题,如连接池、GZIP 压缩、响应缓存等。

本文档详细介绍 OkHttp 支持的各种请求类型,包括代码示例和使用场景对比,帮助开发者选择最适合自己应用场景的通信方式。

目录

  1. 标准 HTTP/HTTPS 请求
  2. WebSocket 请求
  3. Server-Sent Events (SSE) 请求
  4. HTTP/2 请求
  5. gRPC 请求
  6. 请求类型对比
  7. 如何选择合适的请求类型

1. 标准 HTTP/HTTPS 请求

特点

  • 支持 HTTP/1.1 和 HTTP/2 协议
  • 请求-响应模式
  • 支持 GET, POST, PUT, DELETE, HEAD, PATCH 等方法
  • 短连接或长连接(Keep-Alive)
  • 支持同步和异步调用
  • 支持拦截器链
  • 自动处理 Cookie
  • 支持响应缓存

适用场景

  • REST API 调用
  • 资源下载/上传
  • 表单提交
  • 一般的客户端-服务器通信

代码示例

同步请求
val client = OkHttpClient()

// 创建请求
val request = Request.Builder()
    .url("https://api.example.com/data")
    .header("User-Agent", "OkHttp Example")
    .build()

// 执行同步调用
client.newCall(request).execute().use { response ->
    if (!response.isSuccessful) throw IOException("Unexpected code $response")

    // 处理响应
    val responseBody = response.body?.string()
    println(responseBody)
}
异步请求
val client = OkHttpClient()

val request = Request.Builder()
    .url("https://api.example.com/data")
    .build()

// 执行异步调用
client.newCall(request).enqueue(object : Callback {
    override fun onFailure(call: Call, e: IOException) {
        e.printStackTrace()
    }

    override fun onResponse(call: Call, response: Response) {
        response.use {
            if (!response.isSuccessful) throw IOException("Unexpected code $response")

            val responseBody = response.body?.string()
            println(responseBody)
        }
    }
})
POST 请求
val client = OkHttpClient()

val json = """
    {
        "name": "John Doe",
        "email": "john@example.com"
    }
""".trimIndent()

val requestBody = json.toRequestBody("application/json; charset=utf-8".toMediaType())

val request = Request.Builder()
    .url("https://api.example.com/users")
    .post(requestBody)
    .build()

client.newCall(request).execute().use { response ->
    if (!response.isSuccessful) throw IOException("Unexpected code $response")
    println(response.body?.string())
}
文件上传
val client = OkHttpClient()

val file = File("document.pdf")
val requestBody = MultipartBody.Builder()
    .setType(MultipartBody.FORM)
    .addFormDataPart(
        "file",
        file.name,
        file.asRequestBody("application/pdf".toMediaType())
    )
    .addFormDataPart("description", "PDF document")
    .build()

val request = Request.Builder()
    .url("https://api.example.com/upload")
    .post(requestBody)
    .build()

client.newCall(request).execute().use { response ->
    if (!response.isSuccessful) throw IOException("Unexpected code $response")
    println(response.body?.string())
}

2. WebSocket 请求

特点

  • 全双工通信
  • 基于 TCP 的持久连接
  • 低延迟
  • 支持二进制和文本数据
  • 客户端和服务器都可以主动发送消息
  • 适合实时应用

适用场景

  • 实时聊天应用
  • 在线游戏
  • 协作编辑工具
  • 实时数据仪表板
  • 需要频繁双向通信的应用

代码示例

val client = OkHttpClient.Builder()
    .readTimeout(0, TimeUnit.MILLISECONDS)  // WebSocket 需要禁用读取超时
    .build()

val request = Request.Builder()
    .url("ws://echo.websocket.org")
    .build()

val listener = object : WebSocketListener() {
    override fun onOpen(webSocket: WebSocket, response: Response) {
        println("WebSocket connection opened")
        webSocket.send("Hello, WebSocket!")
    }

    override fun onMessage(webSocket: WebSocket, text: String) {
        println("Received text message: $text")
    }

    override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
        println("Received binary message: ${bytes.hex()}")
    }

    override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
        println("WebSocket closing: $code / $reason")
        webSocket.close(1000, null)
    }

    override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
        println("WebSocket closed: $code / $reason")
    }

    override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
        println("WebSocket failure: ${t.message}")
    }
}

val webSocket = client.newWebSocket(request, listener)

// 发送消息
webSocket.send("Another message")

// 发送二进制消息
val byteString = "Binary data".encodeUtf8()
webSocket.send(byteString)

// 关闭 WebSocket
// webSocket.close(1000, "Goodbye, WebSocket!")

3. Server-Sent Events (SSE) 请求

特点

  • 单向通信(服务器到客户端)
  • 基于 HTTP 的长连接
  • 自动重连机制
  • 纯文本格式
  • 支持事件类型和 ID
  • 适合服务器推送场景

适用场景

  • 实时通知
  • 股票行情更新
  • 社交媒体动态
  • 日志流
  • 进度更新

代码示例

val client = OkHttpClient.Builder()
    .connectTimeout(30, TimeUnit.SECONDS)
    .readTimeout(90, TimeUnit.SECONDS)
    .writeTimeout(30, TimeUnit.SECONDS)
    .build()

val request = Request.Builder()
    .url("https://example.com/sse-endpoint")
    .header("Accept", "text/event-stream")
    .build()

val factory = EventSources.createFactory(client)

val listener = object : EventSourceListener() {
    override fun onOpen(eventSource: EventSource, response: Response) {
        println("SSE connection opened: ${response.code}")
    }

    override fun onEvent(
        eventSource: EventSource,
        id: String?,
        type: String?,
        data: String
    ) {
        println("Event received:")
        println("  id: $id")
        println("  type: $type")
        println("  data: $data")

        // 处理不同类型的事件
        when (type) {
            "update" -> handleUpdate(data)
            "notification" -> showNotification(data)
            null -> handleDefaultEvent(data)
        }
    }

    override fun onClosed(eventSource: EventSource) {
        println("SSE connection closed normally")
    }

    override fun onFailure(eventSource: EventSource, t: Throwable?, response: Response?) {
        println("SSE connection failed: ${t?.message}")
        response?.let { println("Response code: ${it.code}") }
    }
}

val eventSource = factory.newEventSource(request, listener)

// 在适当的时候取消连接
// eventSource.cancel()

4. HTTP/2 请求

特点

  • 多路复用(单个连接上的并行请求)
  • 头部压缩
  • 服务器推送
  • 二进制格式
  • 流优先级
  • 与 HTTP/1.1 向后兼容

适用场景

  • 需要高性能的 Web 应用
  • 移动应用(减少电池消耗)
  • 资源密集型网站
  • 需要并行加载多个资源的场景

代码示例

// OkHttp 默认支持 HTTP/2,但可以明确指定
val client = OkHttpClient.Builder()
    .protocols(listOf(Protocol.HTTP_2, Protocol.HTTP_1_1))
    .build()

val request = Request.Builder()
    .url("https://example.com")
    .build()

client.newCall(request).execute().use { response ->
    println("Protocol: ${response.protocol}")

    if (!response.isSuccessful) throw IOException("Unexpected code $response")

    // 处理响应
    val responseBody = response.body?.string()
    println(responseBody)
}

查看 HTTP/2 推送的资源

val client = OkHttpClient.Builder()
    .protocols(listOf(Protocol.HTTP_2, Protocol.HTTP_1_1))
    .build()

val request = Request.Builder()
    .url("https://example.com")
    .build()

client.newCall(request).execute().use { response ->
    // 检查是否有服务器推送的资源
    val pushPromises = response.exchange?.connection?.protocol == Protocol.HTTP_2

    if (pushPromises) {
        // 注意:这只是概念性代码,OkHttp 目前没有直接暴露推送资源的 API
        // 实际上,OkHttp 会自动缓存推送的资源,并在后续请求中使用
        println("This connection supports HTTP/2 server push")
    }

    println(response.body?.string())
}

5. gRPC 请求

特点

  • 基于 HTTP/2
  • 使用 Protocol Buffers 序列化
  • 支持流式 RPC
  • 强类型接口
  • 代码生成
  • 支持多种语言

适用场景

  • 微服务架构
  • 高性能 API
  • 需要严格接口定义的系统
  • 多语言环境

代码示例

首先,在 build.gradle 文件中添加必要的依赖:

dependencies {
    // gRPC 核心依赖
    implementation 'io.grpc:grpc-okhttp:1.53.0'
    implementation 'io.grpc:grpc-protobuf:1.53.0'
    implementation 'io.grpc:grpc-stub:1.53.0'

    // 如果在 Android 上使用
    implementation 'io.grpc:grpc-android:1.53.0'
}

定义 Protocol Buffers 文件 (greeter.proto):

syntax = "proto3";

option java_package = "com.example.grpc";
option java_multiple_files = true;

package greeter;

// 定义服务
service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  rpc SayHelloStream (HelloRequest) returns (stream HelloReply) {}
}

// 请求消息
message HelloRequest {
  string name = 1;
}

// 响应消息
message HelloReply {
  string message = 1;
}

使用 OkHttp 作为 gRPC 传输层的客户端代码:

import io.grpc.ManagedChannel;
import io.grpc.okhttp.OkHttpChannelBuilder;
import io.grpc.stub.StreamObserver;
import com.example.grpc.GreeterGrpc;
import com.example.grpc.HelloRequest;
import com.example.grpc.HelloReply;
import java.util.concurrent.TimeUnit;

public class GrpcClient {
    private final ManagedChannel channel;
    private final GreeterGrpc.GreeterBlockingStub blockingStub;
    private final GreeterGrpc.GreeterStub asyncStub;

    public GrpcClient(String host, int port) {
        // 使用 OkHttpChannelBuilder 创建 gRPC 通道
        channel = OkHttpChannelBuilder.forAddress(host, port)
            .usePlaintext() // 用于非 TLS 连接,生产环境应使用 TLS
            .keepAliveTime(30, TimeUnit.SECONDS)
            .keepAliveTimeout(10, TimeUnit.SECONDS)
            .build();

        // 创建存根
        blockingStub = GreeterGrpc.newBlockingStub(channel);
        asyncStub = GreeterGrpc.newStub(channel);
    }

    // 同步调用示例
    public String sayHello(String name) {
        HelloRequest request = HelloRequest.newBuilder().setName(name).build();
        HelloReply response = blockingStub.sayHello(request);
        return response.getMessage();
    }

    // 流式调用示例
    public void sayHelloStream(String name, final StreamObserver<String> responseObserver) {
        HelloRequest request = HelloRequest.newBuilder().setName(name).build();

        asyncStub.sayHelloStream(request, new StreamObserver<HelloReply>() {
            @Override
            public void onNext(HelloReply reply) {
                responseObserver.onNext(reply.getMessage());
            }

            @Override
            public void onError(Throwable t) {
                responseObserver.onError(t);
            }

            @Override
            public void onCompleted() {
                responseObserver.onCompleted();
            }
        });
    }

    // 关闭通道
    public void shutdown() throws InterruptedException {
        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
    }

    // 使用示例
    public static void main(String[] args) {
        GrpcClient client = new GrpcClient("localhost", 50051);
        try {
            // 同步调用
            String response = client.sayHello("World");
            System.out.println("Response: " + response);

            // 流式调用
            client.sayHelloStream("Stream World", new StreamObserver<String>() {
                @Override
                public void onNext(String message) {
                    System.out.println("Stream response: " + message);
                }

                @Override
                public void onError(Throwable t) {
                    System.err.println("Stream error: " + t.getMessage());
                }

                @Override
                public void onCompleted() {
                    System.out.println("Stream completed");
                }
            });

            // 等待流式响应
            Thread.sleep(5000);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            client.shutdown();
        }
    }
}

6. 请求类型对比

下表对比了 OkHttp 支持的各种请求类型的关键特性:

特性HTTPWebSocketSSEHTTP/2gRPC
通信方向双向(请求-响应)全双工(双向同时)单向(服务器到客户端)双向双向(支持流式)
连接类型短连接/长连接持久连接长连接持久连接持久连接
实时性
数据格式任意文本/二进制文本二进制Protocol Buffers
自动重连需自行实现可配置
头部压缩HTTP/1.1 无,HTTP/2 有
多路复用HTTP/1.1 无,HTTP/2 有不适用不适用
资源消耗
浏览器支持全部全部现代浏览器大多数现代浏览器大多数现代浏览器需要特殊库
开发复杂度
适用场景通用 API 调用实时双向通信服务器推送更新高性能 API微服务通信

7. 如何选择合适的请求类型

选择合适的请求类型取决于你的应用需求。以下是一些选择指南:

使用标准 HTTP/HTTPS 请求,当你需要:

  • 简单的请求-响应交互
  • 与 RESTful API 通信
  • 上传或下载文件
  • 最广泛的兼容性
  • 简单的实现和维护

使用 WebSocket,当你需要:

  • 低延迟的双向通信
  • 客户端和服务器都能主动发送消息
  • 实时应用,如聊天、游戏或协作工具
  • 减少连接建立的开销
  • 支持二进制数据传输

使用 SSE,当你需要:

  • 服务器向客户端推送更新
  • 自动重连机制
  • 比 WebSocket 更简单的实现
  • 与现有 HTTP 基础设施更好的兼容性
  • 只需要文本数据传输

使用 HTTP/2,当你需要:

  • 提高性能和减少延迟
  • 在单个连接上并行发送多个请求
  • 减少头部数据的传输量
  • 服务器推送资源
  • 与 HTTP/1.1 向后兼容

使用 gRPC,当你需要:

  • 高性能的 RPC 通信
  • 强类型接口和代码生成
  • 支持流式数据传输
  • 多语言支持
  • 适合微服务架构

实际应用中的混合使用

在实际应用中,你可能需要混合使用多种请求类型:

  • 使用标准 HTTP 请求处理一般的 API 调用和资源获取
  • 使用 WebSocket 实现聊天功能或实时协作
  • 使用 SSE 推送通知或更新
  • 使用 HTTP/2 提高整体性能
  • 使用 gRPC 进行微服务间的高效通信

OkHttp 的灵活性使其成为处理各种网络通信需求的理想选择。通过了解每种请求类型的特点和适用场景,你可以为你的应用选择最合适的通信方式,提高性能和用户体验。