尝试使用 Jetty 异步 HTTP 客户端时出现 NPE

NPE when trying to use Jetty async HTTP client

尝试使用我在 GitHub -

准备的 Firebase Cloud Messaging by Google with the help of non-blocking Jetty HTTP client in a simple test case
private static final HttpClient sHttpClient = new HttpClient();
private static final Response.ContentListener sFcmListener = new Response.ContentListener() {
    @Override
    public void onContent(Response response, ByteBuffer content) {
        if (response.getStatus() != 200) {
            return;
        }

        String body = StandardCharsets.UTF_8.decode(content).toString();
        System.out.printf("onContent: %s\n", body);
        Map<String, Object> resp = (Map<String, Object>) JSON.parse(body);

        try {
            Object[] results = (Object[]) resp.get(FCM_RESULTS);
            Map result = (Map) results[0];
            String error = (String) result.get(FCM_ERROR);
            if (FCM_NOT_REGISTERED.equals(error)) {
                // TODO delete invalid FCM token from the database
            }
        } catch (Exception ignore) {
        }
    }
};

public static void main(String[] args) throws Exception {
    sHttpClient.start();
    sHttpClient.POST(FCM_URL)
        .header(HttpHeader.AUTHORIZATION, FCM_KEY)
        .header(HttpHeader.CONTENT_TYPE, "application/json")
        .content(new StringContentProvider(JSON.toString(REQUEST)))
        .onResponseContent(sFcmListener)
        .send();
}

但不幸的是执行立即失败并出现 NPE:

2017-06-30 10:46:41.312:INFO::main: Logging initialized @168ms to org.eclipse.jetty.util.log.StdErrLog
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.NullPointerException
    at org.eclipse.jetty.client.util.FutureResponseListener.getResult(FutureResponseListener.java:118)
    at org.eclipse.jetty.client.util.FutureResponseListener.get(FutureResponseListener.java:101)
    at org.eclipse.jetty.client.HttpRequest.send(HttpRequest.java:682)
    at de.afarber.fcmnotregistered.Main.main(Main.java:68)
Caused by: java.lang.NullPointerException
    at org.eclipse.jetty.io.ssl.SslClientConnectionFactory.newConnection(SslClientConnectionFactory.java:59)
    at org.eclipse.jetty.client.AbstractHttpClientTransport$ClientSelectorManager.newConnection(AbstractHttpClientTransport.java:191)
    at org.eclipse.jetty.io.ManagedSelector.createEndPoint(ManagedSelector.java:420)
    at org.eclipse.jetty.io.ManagedSelector.access00(ManagedSelector.java:61)
    at org.eclipse.jetty.io.ManagedSelector$CreateEndPoint.run(ManagedSelector.java:599)
    at org.eclipse.jetty.util.thread.Invocable.invokePreferred(Invocable.java:128)
    at org.eclipse.jetty.util.thread.Invocable$InvocableExecutor.invoke(Invocable.java:222)
    at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:294)
    at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:199)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:672)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.run(QueuedThreadPool.java:590)
    at java.lang.Thread.run(Thread.java:745)

请问为什么会这样?

更新:

我已经切换到使用 BufferingResponseListener 并且 NPE 消失了,但是现在程序打印 java.net.NoRouteToHostException: No route to host 即使 the Google FCM endpoint 是一个众所周知的主机:

private static final HttpClient sHttpClient = new HttpClient();
private static final BufferingResponseListener sFcmListener = new BufferingResponseListener() {
    @Override
    public void onComplete(Result result) {
        if (!result.isSucceeded()) {
            System.err.println(result.getFailure()); // No route to host
            return;
        }

        String body = getContentAsString(StandardCharsets.UTF_8);
        System.out.printf("onContent: %s\n", body);
        Map<String, Object> resp = (Map<String, Object>) JSON.parse(body);

        try {
            Object[] results = (Object[]) resp.get(FCM_RESULTS);
            Map map = (Map) results[0];
            String error = (String) map.get(FCM_ERROR);
            if (FCM_NOT_REGISTERED.equals(error)) {
                // TODO delete invalid FCM token from the database
            }
        } catch (Exception ex) {
            System.err.println(ex);
        }
    }
};

public static void main(String[] args) throws Exception {
    sHttpClient.start();
    sHttpClient.POST(FCM_URL)
        .header(HttpHeader.AUTHORIZATION, FCM_KEY)
        .header(HttpHeader.CONTENT_TYPE, "application/json")
        .content(new StringContentProvider(JSON.toString(REQUEST)))
        .send(sFcmListener);
}

我尝试任何 FCM_URL 值都得到 No route to host,为什么?

添加 SslContextFactory 对我有帮助:

private static final SslContextFactory sFactory = new SslContextFactory();
private static final HttpClient sHttpClient = new HttpClient(sFactory);
private static final BufferingResponseListener sFcmListener = new BufferingResponseListener() {
    @Override
    public void onComplete(Result result) {
        if (!result.isSucceeded()) {
            System.err.println(result.getFailure());
            return;
        }

        String body = getContentAsString(StandardCharsets.UTF_8);
        System.out.printf("onComplete: %s\n", body);

        try {
            Map<String, Object> resp = (Map<String, Object>) JSON.parse(body);
            Object[] results = (Object[]) resp.get(FCM_RESULTS);
            Map map = (Map) results[0];
            String error = (String) map.get(FCM_ERROR);
            System.out.printf("error: %s\n", error);
            if (FCM_NOT_REGISTERED.equals(error) ||
                FCM_MISSING_REGISTRATION.equals(error) ||
                FCM_INVALID_REGISTRATION.equals(error)) {
                // TODO delete invalid FCM token from the database
            }
        } catch (Exception ex) {
            System.err.println(ex);
        }
    }
};

public static void main(String[] args) throws Exception {
    sHttpClient.start();
    sHttpClient.POST(FCM_URL)
        .header(HttpHeader.AUTHORIZATION, FCM_KEY)
        .header(HttpHeader.CONTENT_TYPE, "application/json")
        .content(new StringContentProvider(JSON.toString(REQUEST)))
        .send(sFcmListener);
}

我还有一个悬而未决的问题是 ,这样我就可以在回复时从我的数据库中删除它...