使用HttpClient发送苹果VOIP推送,P12证书

前言

至于voip推送是啥,就不多说了,看到这的人,应该都知道了,实在不知道就去苹果开发者文档

两个核心,HTTP/2,TLSv1.2

HTTP/2,是HTTP/1.1的升级版,有啥好处,自行了解

TLS是SSL的升级版,目的是为了安全,有啥好处,自行了解

英语我也看不懂,转换成白话如下

推送地址:

沙盒:https://api.sandbox.push.apple.com/3/device/{token}
生产:https://api.push.apple.com/3/device/{token}

请求参数如下

参数名必选类型说明
apns-idNHeader通知id,一般用UUID,32位小写以 8-4-4-4-12 形式由连字符隔开的五组显示
apns-push-typeYHeaderalert,background,voip,complication,fileprovider,mdm 六选一,我用到的是voip
apns-expirationYHeader通知不再有效的日期。时间戳,如果传0则仅推送一次
apns-priorityYHeader通知的优先级。如果省略此标题,APN 将通知优先级设置为 10。10 指定立即发送通知 5 根据用户设备上的电源考虑指定发送通知
apns-topicYHeader通知主题,一般是bundle ID/app ID加上后缀,比如voip则加上.voip
Ybody需要传输的json数据

JDK9+的代码

因为从JDK9开始才支持HTTP/2,所以使用这种方式JDK必须大于等于JDK9,我在JDK11下测试无误

package cn.mengxw;

import org.apache.hc.core5.ssl.SSLContextBuilder;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import java.io.File;
import java.io.FileInputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.KeyStore;
import java.time.Duration;
import java.util.UUID;

public class HttpCli {
  public static void main(String[] args) throws Exception {
    long time1 = System.currentTimeMillis();
    System.setProperty("javax.net.debug", "all");

    // 证书文件路径
    String path = "";
    // 证书文件路径
    String password = "";
    // 需要推送到的toekn
    String token = "";
    // topic
    String topic = "".concat(".voip");
    // 如要传输的json字符串
    String jsonData = "";

    KeyStore keyStore = KeyStore.getInstance("PKCS12");
    keyStore.load(new FileInputStream(new File(path)), password.toCharArray());
    // 借用了SSLContextBuilder,自己构建也行,但是这个方便
    SSLContext sslContext =
        new SSLContextBuilder()
            .loadKeyMaterial(keyStore, password.toCharArray())
            .setKeyStoreType("PKCS12")
            .build();
    SSLParameters sslParameters = new SSLParameters();
    sslParameters.setProtocols(new String[] {"TLSv1.2"});

    HttpClient client =
        HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_2) // default
            .followRedirects(
                HttpClient.Redirect.ALWAYS) // /Always redirect, except from HTTPS URLs to HTTP URLs
            .connectTimeout(Duration.ofMillis(5000))
            .sslContext(sslContext)
            .sslParameters(sslParameters)
            //            .proxy(ProxySelector.of(new InetSocketAddress("localhost", 8888)))
            .build();

    HttpRequest request =
        HttpRequest.newBuilder()
            .uri(URI.create("https://api.sandbox.push.apple.com/3/device/" + token))
            .header("apns-id", UUID.randomUUID().toString().toLowerCase())
            .headers("apns-push-type", "voip")
            .header("apns-expiration", "0")
            .headers("apns-priority", "10")
            .headers("apns-topic", topic)
            .POST(HttpRequest.BodyPublishers.ofString(jsonData))
            .timeout(Duration.ofMillis(5009))
            .build();

    HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

    long time2 = System.currentTimeMillis();
    System.out.println("耗时:" + (time2 - time1) / 1000);

    System.out.println(response.statusCode());
    System.out.println(response);
    System.out.println(response.body());
  }
}

JDK7+

前面说了,JDK9才开始支持HTTP/2,但是我们生产环境用的是JDK8,那这个就很操蛋,不知道还有多少的小伙伴们也是。那这样子前面的代码就不能够使用了,继续翻阅文档后发现,httpcomponents-client在5.0开始支持HTTP/2了,并且支持从JDK1.7+运行,整合我意,我贴出官方地址,有意可自行前往

https://hc.apache.org/httpcomponents-core-5.1.x/examples.html

这个是官方例子

https://github.com/apache/httpcomponents-core/blob/5.1.x/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/examples/H2ConscriptRequestExecutionExample.java

截止目前,最新的maven坐标,官方为了和前一个版本区分,加了两个5

<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5 -->
<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
    <version>5.1</version>
</dependency>

httpclient5中对HTTP/2的处理是异步的,主要是通过 FutureCallback<Message<HttpResponse, String>>来处理响应
废话不多说,具体的请求代码如下

import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.http.*;
import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester;
import org.apache.hc.core5.http.nio.AsyncClientEndpoint;
import org.apache.hc.core5.http.nio.AsyncRequestProducer;
import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
import org.apache.hc.core5.http.nio.support.AsyncRequestBuilder;
import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
import org.apache.hc.core5.http.ssl.TLS;
import org.apache.hc.core5.http2.HttpVersionPolicy;
import org.apache.hc.core5.http2.config.H2Config;
import org.apache.hc.core5.http2.frame.RawFrame;
import org.apache.hc.core5.http2.impl.nio.H2StreamListener;
import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap;
import org.apache.hc.core5.io.CloseMode;
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.util.Timeout;




 /***
   *
   * @param url 请求地址
   * @param certificate 证书文件
   * @param keyStoreType 证书类型
   * @param password 证书密码
   * @param json 发送的数据
   * @param headers 请求头
   * @param callback 回调方法
   * @throws KeyStoreException
   * @throws IOException
   * @throws UnrecoverableKeyException
   * @throws NoSuchAlgorithmException
   * @throws CertificateException
   * @throws KeyManagementException
   * @throws URISyntaxException
   * @throws ExecutionException
   * @throws InterruptedException
   */
  public static void AsyncHttpsPostJson(
      String url,
      File certificate,
      String keyStoreType,
      String password,
      String json,
      Map<String, String> headers,
      FutureCallback<Message<HttpResponse, String>> callback)
      throws KeyStoreException, IOException, UnrecoverableKeyException, NoSuchAlgorithmException,
          CertificateException, KeyManagementException, URISyntaxException, ExecutionException,
          InterruptedException {

    KeyStore keyStore = KeyStore.getInstance(keyStoreType);
    keyStore.load(new FileInputStream(certificate), password.toCharArray());

    SSLContext sslContext =
        SSLContexts.custom()
            .loadKeyMaterial(keyStore, password.toCharArray())
            //            .setKeyStoreType("PKCS12")
            .build();

    H2Config h2Config = H2Config.custom().setPushEnabled(false).setCompressionEnabled(true).build();
    TlsStrategy tlsStrategy =
        new ClientTlsStrategyBuilder().setTlsVersions(TLS.V_1_2).setSslContext(sslContext).build();

    HttpAsyncRequester requester =
        H2RequesterBootstrap.bootstrap()
            .setH2Config(h2Config)
            .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2)
            .setTlsStrategy(tlsStrategy)
            .create();

    requester.start();

    URI uri = new URI(url);

    HttpHost target = new HttpHost("https", uri.getHost(), 443);

    Future<AsyncClientEndpoint> future = requester.connect(target, Timeout.ofSeconds(60));

    AsyncClientEndpoint clientEndpoint = future.get();

    AsyncRequestBuilder asyncRequestBuilder =
        AsyncRequestBuilder.post()
            .setHttpHost(target)
            .setPath(uri.getPath())
            .setCharset(Charset.forName("utf-8"));
    //    在请求超过1024字节的数据时,curl会先发送个请求头,询问服务器是否接受请求。甭搭理,如果加上这句,苹果服务器是不会响应的,会导致程序卡死
    asyncRequestBuilder.addHeader("expect", "");

    if (Objects.nonNull(headers)) {
      headers.forEach(
          (k, v) -> {
            asyncRequestBuilder.addHeader(k, v);
          });
    }
    if (StringUtils.isNotBlank(json)) {
      asyncRequestBuilder.setEntity(json);
    }

    AsyncRequestProducer requestProducer = asyncRequestBuilder.build();
    if (Objects.isNull(callback)) {
      callback = logCallBack(url);
    }

    clientEndpoint.execute(
        requestProducer, new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), callback);
    //    clientEndpoint.releaseAndReuse();
    //    requester.initiateShutdown();
    //    requester.close();
  }

关于Expect:100-continue

当要POST的数据大于1024字节的时候, curl并不会直接就发起POST请求, 而是会分为俩步,

  1. 发送一个请求, 包含一个Expect:100-continue, 询问Server使用愿意接受数据
  2. 接收到Server返回的100-continue应答以后, 才把数据POST给Server

这段文字具体参照这儿

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值