OkHttp的基本使用 实现GET/POST请求 authenticator自动认证 Cookie管理 请求头设置

基本概念

相关文档:https://github.com/square/okhttp

PS:基于okhttp库实现,OkHttpClient中实现了get 、 post客户端请求方法。

OkHttp是一个高效、灵活、易于使用的HTTP客户端库。在网络请求处理上采用了异步模型,并将连接池、压缩、网络协议等多种技术应用到其中,从而提高了网络请求的效率和处理速度。OkHttp支持HTTP/2协议,可以进行数据流复用以及服务器推送。同时,OkHttp还支持GZIP压缩、连接超时设置、缓存、重试等功能,提供了非常丰富的API接口

引入pom.xml依赖:

复制代码
   <dependency>
       <groupId>com.squareup.okhttp3</groupId>
       <artifactId>okhttp</artifactId>
       <version>4.9.0</version>
</dependency>

Java.net URL & URI区别:

URL url = new URL("http://localhost:80");

System.out.println(url.getHost()+" , "+ url.getPort() +" , " +url.getProtocol());//localhost , 80 , http

URI uri = URI.create("http://localhost:80");

System.out.println(uri.getHost() +" , "+ uri.getPort() );//localhost , 80

1. 实例化OkHttpClient

//方式一:创建OkHttpClient实例,使用默认构造函数,创建默认配置OkHttpClient(官方建议全局只有一个实例)

OkHttpClient okHttpClient = new OkHttpClient();

//方式二:使用静态内部类

OkHttpClient httpClient = new OkHttpClient.Builder().connectTimeout(13, TimeUnit.SECONDS).build();

//方式三:使用实例方法newBuilder(), 其实也是使用的静态内部类

OkHttpClient client = new OkHttpClient().newBuilder().build();

1-2.超时、连接池、缓存等OkHttpClient实例初始化参数设置

复制代码
private static TrustManager[] buildTrustManagers() {
    return new TrustManager[]{
            new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) { }
                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) { }
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[]{};
                }
            }
    };
}
/** 生成安全套接字工厂,用于https请求的证书跳过 */
private static SSLSocketFactory createSSLSocketFactory(TrustManager[] trustAllCerts) {
    SSLSocketFactory ssfFactory = null;
    try {
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new SecureRandom());
        ssfFactory = sc.getSocketFactory();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return ssfFactory;
}
TrustManager[] trustManagers = buildTrustManagers(); 
OkHttpClient okHttpClient = new OkHttpClient.Builder()
         .connectTimeout(60, TimeUnit.SECONDS) //超时
         .writeTimeout(60, TimeUnit.SECONDS)
         .readTimeout(60, TimeUnit.SECONDS)
         // 跳过ssl认证(https) ,下面hostname校验也需要跳过
         .sslSocketFactory(createSSLSocketFactory(trustManagers), (X509TrustManager) trustManagers[0])
         .hostnameVerifier((hostName, session) -> true) //跳过hostname校验. 
         .retryOnConnectionFailure(true)
        //设置连接池最大连接数量:持续存活的连接。最大连接数、连接存活的时间、连接存活的时间单位
         .connectionPool(new ConnectionPool(10, 10, TimeUnit.MINUTES))
         // 参数1:缓存文件 参数2:缓存大小
         //.cache(new Cache(new File("/home/work/cache.tmp"), 10 * 1024 * 1024))
         .build();

2-1.同步发送get请求

复制代码
try {
OkHttpClient client = new OkHttpClient();
//HttpUrl.Builder builder = HttpUrl.get(url).newBuilder();
//builder.addQueryParameter((String)key, (String) value);
//HttpUrl url = builder.build(); //也可以通过api方式添加queryParameter参数生成url
    Request request = new Request.Builder().url("https://www.baidu.com?a=a").get().build();
    Response response = client.newCall(request).execute();
    // 响应状态码
    int code = response.code();
    // 响应body
    ResponseBody body = response.body();
    // 响应body1: 字符串, 当响应内容比较小的时候使用, 响应内容太大最好使用后面的方式, 因为该方法会将内容全部加载到内容
    String str = body.string();
    // 响应body2: 字节数组
    byte[] bytes = body.bytes();
    // 响应body3: 字符流
    Reader reader = body.charStream();
    // 响应body4: 字节流
    InputStream stream = body.byteStream();
} catch (IOException e) {
    e.printStackTrace();
}

2-2. 异步发送get请求

复制代码
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("https://www.baidu.com").get().build();
client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) { }
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        String result = response.body().string();
        System.out.println(result);
    }
});

3.发送post请求

3-1. 参数为类型为 application/json ( RequestBody )

复制代码
try {
    OkHttpClient client = new OkHttpClient();
    /**
     * 构建RequestBody常用的三种方法
     * public static RequestBody create(@Nullable MediaType contentType, String content)
     * public static RequestBody create(@Nullable MediaType contentType, byte[] content)
     * public static RequestBody create(@Nullable final MediaType contentType, final File file)
     */
    // 构建请求参数
    HashMap<String, String> param = new HashMap<>();
    param.put("name", "zhangsan");
    // 设置参数类型
    MediaType mediaType = MediaType.parse("application/json; charset=utf-8");
    RequestBody requestBody = RequestBody.create(mediaType, JSON.toJSONString(param));
    Request request = new Request.Builder().url("https://xxxx").post(requestBody).build();
    Response response = client.newCall(request).execute();
    System.out.println(response.body().string());
} catch (IOException e) {
    e.printStackTrace();
}

3-2.参数类型为application/x-www-form-urlencoded 普通表单(FormBody

复制代码
try {
    OkHttpClient client = new OkHttpClient();
    // 构建表单body
    FormBody.Builder builder  = new FormBody.Builder();
    builder.add("name", "zhangsan");
    FormBody formBody = builder.build();

    Request request = new Request.Builder().url("https://xxxx").post(formBody).build();
    Response response = client.newCall(request).execute();
    System.out.println(response.body().string());
} catch (IOException e) {
    e.printStackTrace();
}

3-3.参数类型为multipart/form-data ,数据里有文件(MultipartBody)

复制代码
try {
    OkHttpClient client = new OkHttpClient();
    // 构建带文件的body
    MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
    // 键值参数
    builder.addFormDataPart("name", "zhangsan");
    // 文件, 二进制流数据 application/octet-stream, 不关注文件的扩展类型
    RequestBody requestBody = 
RequestBody.create(MediaType.parse("application/octet-stream"), new File("/home/work/1.txt"));  //new byte[]{1, 2, 3};  也可以是将文件内容读取转成byte[]
    builder.addFormDataPart("file", "1.txt", requestBody);
    MultipartBody multipartBody = builder.build();
    Request request = new Request.Builder().url("https://xxxx").post(multipartBody).build();
    Response response = client.newCall(request).execute();
    System.out.println(response.body().string());
} catch (IOException e) {
    e.printStackTrace();
}

5.authenticator自动认证修正验证器

authenticator 401(Unauthorized)服务器失效认证

authenticator自动认证修正验证器。比如 token 失效的时候返回 401(Unauthorized),在这里重新获取刷新 token。

当 HTTP 响应的状态代码是 401 时,OkHttp 会从设置的 authenticator 对象中获取到新的 Request 对象并再次尝试发出请求。Authenticator 接口中的 authenticate 方法用来提供进行认证的 Request 对象。

Authorization的授权认证方式在我接触中有两种Basic、Bearer

(1)Basic:HTTP基本认证,在请求的时候加上以下请求头:

Authorization : basic base64encode(username+":"+password))

将用户名和密码用英文冒号(:)拼接起来,并进行一次Base64编码。服务端拿到basic码,然后自己查询相关信息再按照base64encode(username+":"+password))的方式得出当前用户的basic进行对比。

(2)Bearer: 就是常用的token验证。在使用的时候需要加上以下请求头:

Authorization: Bearer xxxxx

双token机制 refresh_token(刷新token,时间一般较长) 和 access_token(认证token时间一般较短)可保证,用户操作过程中避免token过期。

Authenticator返回null的时候,OkHttp会停止重试。为了避免Authentication不起效时还反复重试, 可以再重试过一次之后,返回null退出执行

复制代码
try {
OkHttpClient okHttpClient =new OkHttpClient.Builder()
.authenticator(new Authenticator() {//401(Unauthorized)无权限触发重新鉴权并构建新request重试
      public Request authenticate(Route route, Response response) throws IOException {
        System.out.println(response.challenges().toString());
        String credential = Credentials.basic("jesse", "password1");//重新doLogin授权认证
        if (responseCount(response) >= 1) {//重新尝试调用此接口一次后,放弃尝试
          return null;//返回null则停止重试
        }
//返回一个request对象则okhttp使用该request对象再次尝试发出请求
        return response.request().newBuilder().addHeader("Authorization", credential).build();
      }
    }).build();
    Request request = new Request.Builder().url("http://xxxx").build();
    Response response = okHttpClient.newCall(request).execute();
    if (response.isSuccessful() && response.body() != null) {
        System.out.println(response.body().string());
    }
} catch (IOException e) {
    e.printStackTrace();
}
    private int responseCount(Response response) {
        int result = 0;
        while ((response = response.priorResponse()) != null) { //如果此响应不是由自动重试触发的,则返回null
            result++;
        }
        return result;
}

自动触发authenticator原理:

1.通过Response对象的challenges()方法可以得到第一次请求失败的授权相关的信息.

Authenticating for response: Response{protocol=http/1.1, code=401, message=Unauthorized, url=https://publicobject.com/secrets/hellosecret.txt}

Challenges: [Basic realm="OkHttp Secrets"]

2.如果响应码是401 unauthorized,那么会返回"WWW-Authenticate"相关信息,这种情况下,要执行OkHttpClient.Builder的authenticator()方法在Authenticator对象的authenticate()中 对新的Request对象调用header("Authorization", credential)方法,设置其Authorization请求头;

3.如果Response的响应码是407 proxy unauthorized,那么会返回"Proxy-Authenticate"相关信息,表示不是最终的服务器要求客户端登录授权信息,而是客户端和服务器之间的代理服务器要求客户端登录授权信息,这时候要执行OkHttpClient.Builder的proxyAuthenticator()方法,在Authenticator对象的authenticate()中 对新的Request对象调用header("Proxy-Authorization", credential)方法,设置其Proxy-Authorization请求头。

proxyAuthenticator 407(Proxy Authentication Required)针对代理服务器的认证

authenticateProxy 方法用来提供对代理服务器进行认证的 Request 对象。

当 HTTP 响应的状态代码返回 407 时,OkHttp 会从设置的 authenticateProxy 对象中获取到新的 Request 对象并再次尝试发出请求。Authenticator 接口中的 authenticate 方法用来提供进行认证的 Request 对象。

复制代码
OkHttpClient okHttpClient =new OkHttpClient.Builder()
.proxyAuthenticator(new Authenticator() {//407(roxy Authentication Required)无权限触发重新鉴权并构建新request重试
      public Request authenticate(Route route, Response response) throws IOException {
        System.out.println(response.challenges().toString());
        String credential = Credentials.basic("jesse", "password1");//重新doLogin授权认证
        if (responseCount(response) >= 1) {//重新尝试调用此接口一次后,放弃尝试
          return null;//返回null则停止重试
        }
//返回一个request对象则okhttp使用该request对象再次尝试发出请求
        return response.request().newBuilder().addHeader("Authorization", credential).build();
      }
}).build();
    Request request = new Request.Builder().url("http://xxxx").build();
    Response response = okHttpClient.newCall(request).execute();
    if (response.isSuccessful() && response.body() != null) {
        System.out.println(response.body().string());
    }

6. 自动存储和携带cookie

复制代码
static Map<String, List<Cookie>> cookieStore = new HashMap<>();
try {
    OkHttpClient okHttpClient =
            new OkHttpClient
                    .Builder()
                    .cookieJar(new CookieJar() {
//当返回响应时,okHttp通过saveFromResponse 方法解析响应报文的setCookie字段,获取cookie相关信息存入map
                        @Override
                        public void saveFromResponse(HttpUrl httpUrl, List<Cookie> list) {
                            cookieStore.put(httpUrl.host(), list);
                        }
//当网络请求时okHttp通过loadForRequest方法返回一个List 并会解析到下一次请求报文的cookie首部,okhttp最终是以 builder.addHeader("Cookie", cookie)的方式添加到请求头中
//目前做得不是很严格,若要和浏览器特性一致,这里还需要做domain 和 path校验过滤一下(子路径 子域名可添加进去)
                        @Override
                        public List<Cookie> loadForRequest(HttpUrl httpUrl) {
                            List<Cookie> cookies = cookieStore.get(httpUrl.host());
                            return cookies != null ? cookies : Collections.emptyList();
                        }
                    }).build();
    Request request = new Request.Builder().url("http://xxxx").build();
    Response response = okHttpClient.newCall(request).execute();
    if (response.isSuccessful() && response.body() != null) {
        System.out.println(response.body().string());
    }
} catch (IOException e) {
    e.printStackTrace();
}

CookieJar底层源码:

复制代码
  private String cookieHeader(List<Cookie> cookies) {
    StringBuilder cookieHeader = new StringBuilder();
    for (int i = 0, size = cookies.size(); i < size; i++) {
      if (i > 0) {
        cookieHeader.append("; ");
      }
      Cookie cookie = cookies.get(i);
      cookieHeader.append(cookie.name()).append('=').append(cookie.value());
    }
    return cookieHeader.toString();
  }

7.为request添加请求头

复制代码
 Request request = new Request.Builder()
    .url("https://api.github.com/repos/square/okhttp/issues")
    .header("User-Agent", "OkHttp Headers.java")
    .addHeader("Accept", "application/json; q=0.5")
    .addHeader("Accept", "application/vnd.github.v3+json")
.build();
相关推荐
Predestination王瀞潞2 小时前
Java EE3-我独自整合(第三章:Spring DI 入门案例)
java·spring·java-ee
Ttang232 小时前
Java爬虫:Jsoup+OkHttp实战指南
java·爬虫·okhttp
Chan162 小时前
SpringAI:MCP 协议介绍与接入方法
java·人工智能·spring boot·spring·java-ee·intellij-idea·mcp
dllxhcjla2 小时前
苍穹外卖2
java
迷藏4942 小时前
**发散创新:Go语言中基于上下文的优雅错误处理机制设计与实战**在现代后端开发中,**错误处理**早已不是简单
java·开发语言·后端·python·golang
杰克尼2 小时前
知识点总结--day10(Spring-Cloud框架)
java·开发语言
gelald2 小时前
Spring - AOP 原理
java·后端·spring
zwqwyq2 小时前
springboot与springcloud对应版本
java·spring boot·spring cloud
以太浮标3 小时前
华为eNSP模拟器综合实验之- NATServer 实践配置解析
网络·网络协议·华为·智能路由器·信息与通信