All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.yawenok.fcm.client.impl.FcmHttpClient Maven / Gradle / Ivy

The newest version!
package io.github.yawenok.fcm.client.impl;

import com.alibaba.fastjson.JSON;
import io.github.yawenok.fcm.client.FcmClient;
import io.github.yawenok.fcm.client.concurrent.FutureCallback;
import io.github.yawenok.fcm.client.response.FcmMessageResponse;
import io.github.yawenok.fcm.client.response.FcmMessageResultInfo;
import org.apache.http.*;
import org.apache.http.auth.AuthSchemeProvider;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.Lookup;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.*;
import org.apache.http.impl.client.*;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.nio.conn.NoopIOSessionStrategy;
import org.apache.http.nio.conn.SchemeIOSessionStrategy;
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.github.yawenok.fcm.client.FcmConfig;
import io.github.yawenok.fcm.client.request.FcmMessage;
import io.github.yawenok.fcm.client.exceptions.ConnectionException;
import io.github.yawenok.fcm.client.FcmConstant;
import io.github.yawenok.fcm.client.proxy.ProxyConfig;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.*;

@SuppressWarnings("unchecked")
public class FcmHttpClient implements FcmClient {
    private final Logger logger = LoggerFactory.getLogger(FcmHttpClient.class);

    private FcmConfig fcmConfig;

    private CloseableHttpAsyncClient asyncHttpClient;

    @Override
    public void init(final FcmConfig config) throws ConnectionException {
        if (config == null) {
            throw new IllegalArgumentException("fcmConfig is null!");
        }
        fcmConfig = config;

        try {
            // 配置io线程
            IOReactorConfig ioReactorConfig = IOReactorConfig.custom()
                    .setIoThreadCount(fcmConfig.getIoThreadCount())
                    .setTcpNoDelay(true)
                    .setSoReuseAddress(true)
                    .setSoKeepAlive(true)
                    .build();

            // 设置协议http和https对应的处理socket链接工厂
            Registry strategyRegistry = RegistryBuilder
                    .create()
                    .register("http", new NoopIOSessionStrategy())
                    .register("https", new SSLIOSessionStrategy(createIgnoreVerifySSL()))
                    .build();

            // 连接池设置
            PoolingNHttpClientConnectionManager connectionManager = new PoolingNHttpClientConnectionManager(new DefaultConnectingIOReactor(ioReactorConfig), strategyRegistry);
            connectionManager.setDefaultMaxPerRoute(config.getMaxPerRoute());
            connectionManager.setMaxTotal(config.getMaxTotal());

            // 连接相关配置
            ConnectionConfig defaultConnectionConfig = ConnectionConfig.custom()
                    .setMalformedInputAction(CodingErrorAction.IGNORE)
                    .setUnmappableInputAction(CodingErrorAction.IGNORE)
                    .setCharset(Consts.UTF_8).build();

            // 超时设置
            RequestConfig defaultRequestConfig = RequestConfig.custom()
                    .setConnectTimeout(config.getConnectionTimeout())
                    .setSocketTimeout(config.getSocketTimeout())
                    .setConnectionRequestTimeout(config.getConnectionRequestTimeout())
                    .build();

            // 验证方式
            Lookup authSchemeRegistry = RegistryBuilder
                    .create()
                    .register(AuthSchemes.BASIC, new BasicSchemeFactory())
                    .register(AuthSchemes.DIGEST, new DigestSchemeFactory())
                    .register(AuthSchemes.NTLM, new NTLMSchemeFactory())
                    .register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory())
                    .register(AuthSchemes.KERBEROS, new KerberosSchemeFactory())
                    .build();

            HttpAsyncClientBuilder httpAsyncClientBuilder = HttpAsyncClients.custom()
                    .setConnectionManager(connectionManager)
                    .setDefaultRequestConfig(defaultRequestConfig)
                    .setDefaultConnectionConfig(defaultConnectionConfig)
                    .setDefaultAuthSchemeRegistry(authSchemeRegistry);

            // 设置代理
            ProxyConfig proxyConfig = config.getProxyConfig();
            if (proxyConfig != null) {
                if (proxyConfig.getUsername() != null && proxyConfig.getPassword() != null) {
                    // Define the Credentials to be used:
                    CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
                    credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(proxyConfig.getUsername(), proxyConfig.getPassword()));

                    // Set the Authentication Strategy:
                    httpAsyncClientBuilder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy());
                    httpAsyncClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
                }
                httpAsyncClientBuilder.setProxy(new HttpHost(proxyConfig.getHost(), proxyConfig.getPort()));
            }

            asyncHttpClient = httpAsyncClientBuilder.build();
            asyncHttpClient.start();
        } catch (Exception e) {
            throw new ConnectionException("FcmClient init error!", e);
        }
    }

    @Override
    public void destroy() {
        if (asyncHttpClient != null) {
            try {
                asyncHttpClient.close();
                asyncHttpClient = null;
            } catch (IOException e) {
                logger.error("Destroy client error!", e);
            }
        }
    }

    @Override
    public FcmMessageResponse sendMessageSync(final FcmMessage message) throws InterruptedException, ExecutionException, ConnectionException {
        if (asyncHttpClient == null) {
            throw new ConnectionException("Client is not init!");
        }

        Future responseFuture = this.sendMessageAsync(message);
        return responseFuture.get();
    }

    @Override
    public Future sendMessageAsync(final FcmMessage message) throws ConnectionException {
        if (asyncHttpClient == null) {
            throw new ConnectionException("Client is not init!");
        }

        HttpUriRequest request = buildRequest(message);
        Future responseFuture = asyncHttpClient.execute(request, null);

        return new ResponseFuture(responseFuture);
    }

    @Override
    public void sendMessageAsync(final FcmMessage message, final FutureCallback callback) throws ConnectionException {
        if (asyncHttpClient == null) {
            throw new ConnectionException("Client is not init!");
        }

        HttpUriRequest request = buildRequest(message);
        asyncHttpClient.execute(request, new ResponseFutureCallback(message, callback));

        return;
    }

    private HttpUriRequest buildRequest(final FcmMessage message) {
        return RequestBuilder.post(FcmConstant.FCM_HOST_PRODUCTION)
                .addHeader(HttpHeaders.AUTHORIZATION, String.format("key=%s", fcmConfig.getApiKey()))
                .setHeader(HttpHeaders.CONTENT_TYPE, "application/json")
                .setEntity(new StringEntity(message.build(), StandardCharsets.UTF_8))
                .build();
    }

    /**
     * 忽略自定义证书验证
     *
     * @return
     */
    private SSLContext createIgnoreVerifySSL() {
        try {
            SSLContext sslContext = SSLContext.getInstance("SSLv3");
            X509TrustManager trustManager = new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                }

                @Override
                public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
            };

            sslContext.init(null, new TrustManager[]{trustManager}, null);
            return sslContext;
        } catch (Exception e) {
            logger.error("Ignore verify SSL error!", e);
        }
        return null;
    }

    private FcmMessageResponse parseFCMMessageResponse(HttpResponse httpResponse) throws InterruptedException, ExecutionException {
        FcmMessageResponse fcmMessageResponse = new FcmMessageResponse();
        fcmMessageResponse.setCode(httpResponse.getStatusLine().getStatusCode());
        try {
            HttpEntity entity = httpResponse.getEntity();
            if (entity != null) {
                String responseBody = EntityUtils.toString(entity);
                if (fcmMessageResponse.getCode() == HttpStatus.SC_OK) {
                    fcmMessageResponse.setFcmMessageResultInfo(JSON.parseObject(responseBody, FcmMessageResultInfo.class));
                }
            }
        } catch (IOException e) {
            logger.error("Parse entity error!", e);
        }
        return fcmMessageResponse;
    }

    private final class ResponseFutureCallback implements org.apache.http.concurrent.FutureCallback {
        private final FcmMessage message;
        private final FutureCallback futureCallback;

        private ResponseFutureCallback(FcmMessage message, FutureCallback futureCallback) {
            this.message = message;
            this.futureCallback = futureCallback;
        }

        @Override
        public void completed(HttpResponse httpResponse) {
            if (futureCallback != null) {
                try {
                    FcmMessageResponse fcmMessageResponse = parseFCMMessageResponse(httpResponse);
                    futureCallback.completed(message, fcmMessageResponse);
                } catch (Exception e) {
                    futureCallback.failed(message, e);
                }
            }
        }

        @Override
        public void failed(Exception e) {
            if (futureCallback != null) {
                futureCallback.failed(message, e);
            }
        }

        @Override
        public void cancelled() {
            if (futureCallback != null) {
                futureCallback.cancelled(message);
            }
        }
    }

    private final class ResponseFuture implements Future {
        private final Future httpResponseFuture;

        private FcmMessageResponse fcmMessageResponse = null;

        public ResponseFuture(Future httpResponseFuture) {
            this.httpResponseFuture = httpResponseFuture;
        }

        @Override
        public boolean cancel(boolean mayInterruptIfRunning) {
            return httpResponseFuture.cancel(mayInterruptIfRunning);
        }

        @Override
        public boolean isCancelled() {
            return httpResponseFuture.isCancelled();
        }

        @Override
        public boolean isDone() {
            return httpResponseFuture.isDone();
        }

        @Override
        public FcmMessageResponse get() throws InterruptedException, ExecutionException {
            if (fcmMessageResponse == null) {
                fcmMessageResponse = parseFCMMessageResponse(httpResponseFuture.get());
            }
            return fcmMessageResponse;
        }

        @Override
        public FcmMessageResponse get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
            if (fcmMessageResponse == null) {
                fcmMessageResponse = parseFCMMessageResponse(httpResponseFuture.get(timeout, unit));
            }
            return fcmMessageResponse;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy