mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-28 15:23:14 +01:00
Update gcm-sender-async to use jdk11 httpclient
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*
|
||||
* Copyright (C) 2015 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -21,13 +21,11 @@ package org.whispersystems.gcm.server;
|
||||
*/
|
||||
public class Result {
|
||||
|
||||
private final Object context;
|
||||
private final String canonicalRegistrationId;
|
||||
private final String messageId;
|
||||
private final String error;
|
||||
|
||||
Result(Object context, String canonicalRegistrationId, String messageId, String error) {
|
||||
this.context = context;
|
||||
Result(String canonicalRegistrationId, String messageId, String error) {
|
||||
this.canonicalRegistrationId = canonicalRegistrationId;
|
||||
this.messageId = messageId;
|
||||
this.error = error;
|
||||
@@ -91,10 +89,4 @@ public class Result {
|
||||
return "InvalidRegistration".equals(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The context passed into Sender.send(), if any.
|
||||
*/
|
||||
public Object getContext() {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*
|
||||
* Copyright (C) 2015 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -16,33 +16,29 @@
|
||||
*/
|
||||
package org.whispersystems.gcm.server;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
import com.nurkiewicz.asyncretry.AsyncRetryExecutor;
|
||||
import com.nurkiewicz.asyncretry.RetryContext;
|
||||
import com.nurkiewicz.asyncretry.RetryExecutor;
|
||||
import com.nurkiewicz.asyncretry.function.RetryCallable;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.concurrent.FutureCallback;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
|
||||
import org.apache.http.impl.nio.client.HttpAsyncClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.whispersystems.gcm.server.internal.GcmResponseEntity;
|
||||
import org.whispersystems.gcm.server.internal.GcmResponseListEntity;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse.BodyHandlers;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import io.github.resilience4j.retry.IntervalFunction;
|
||||
import io.github.resilience4j.retry.Retry;
|
||||
import io.github.resilience4j.retry.RetryConfig;
|
||||
|
||||
/**
|
||||
* The main interface to sending GCM messages. Thread safe.
|
||||
*
|
||||
@@ -52,18 +48,20 @@ public class Sender {
|
||||
|
||||
private static final String PRODUCTION_URL = "https://fcm.googleapis.com/fcm/send";
|
||||
|
||||
private final CloseableHttpAsyncClient client;
|
||||
private final HttpClient client;
|
||||
private final String authorizationHeader;
|
||||
private final RetryExecutor executor;
|
||||
private final String url;
|
||||
private final URI uri;
|
||||
private final Retry retry;
|
||||
private final ObjectMapper mapper;
|
||||
private final ScheduledExecutorService executorService;
|
||||
|
||||
/**
|
||||
* Construct a Sender instance.
|
||||
*
|
||||
* @param apiKey Your application's GCM API key.
|
||||
*/
|
||||
public Sender(String apiKey) {
|
||||
this(apiKey, 10);
|
||||
public Sender(String apiKey, ObjectMapper mapper) {
|
||||
this(apiKey, mapper, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,31 +70,36 @@ public class Sender {
|
||||
* @param apiKey Your application's GCM API key.
|
||||
* @param retryCount The number of retries to attempt on a network error or 500 response.
|
||||
*/
|
||||
public Sender(String apiKey, int retryCount) {
|
||||
this(apiKey, retryCount, PRODUCTION_URL);
|
||||
public Sender(String apiKey, ObjectMapper mapper, int retryCount) {
|
||||
this(apiKey, mapper, retryCount, PRODUCTION_URL);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public Sender(String apiKey, int retryCount, String url) {
|
||||
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
this.url = url;
|
||||
public Sender(String apiKey, ObjectMapper mapper, int retryCount, String url) {
|
||||
this.mapper = mapper;
|
||||
this.executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
this.uri = URI.create(url);
|
||||
this.authorizationHeader = String.format("key=%s", apiKey);
|
||||
this.retry = Retry.of("fcm-sender", RetryConfig.custom()
|
||||
.maxAttempts(retryCount)
|
||||
.intervalFunction(IntervalFunction.ofExponentialRandomBackoff(Duration.ofMillis(100), 2.0))
|
||||
.retryOnException(this::isRetryableException)
|
||||
.build());
|
||||
|
||||
this.client = HttpAsyncClients.custom()
|
||||
.setMaxConnTotal(100)
|
||||
.setMaxConnPerRoute(10)
|
||||
.build();
|
||||
this.client = HttpClient.newBuilder()
|
||||
.version(HttpClient.Version.HTTP_2)
|
||||
.connectTimeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
}
|
||||
|
||||
this.executor = new AsyncRetryExecutor(scheduler).retryOn(ServerFailedException.class)
|
||||
.retryOn(TimeoutException.class)
|
||||
.retryOn(IOException.class)
|
||||
.withExponentialBackoff(100, 2.0)
|
||||
.withUniformJitter()
|
||||
.withMaxDelay(4000)
|
||||
.withMaxRetries(retryCount);
|
||||
private boolean isRetryableException(Throwable throwable) {
|
||||
while (throwable instanceof CompletionException) {
|
||||
throwable = throwable.getCause();
|
||||
}
|
||||
|
||||
this.client.start();
|
||||
return throwable instanceof ServerFailedException ||
|
||||
throwable instanceof TimeoutException ||
|
||||
throwable instanceof IOException;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,100 +108,47 @@ public class Sender {
|
||||
* @param message The message to send.
|
||||
* @return A future.
|
||||
*/
|
||||
public ListenableFuture<Result> send(Message message) {
|
||||
return send(message, null);
|
||||
}
|
||||
public CompletableFuture<Result> send(Message message) {
|
||||
try {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(uri)
|
||||
.header("Authorization", authorizationHeader)
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(message.serialize()))
|
||||
.timeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Asynchronously send a message with a context to be passed in the future result.
|
||||
*
|
||||
* @param message The message to send.
|
||||
* @param requestContext An opaque context to include the future result.
|
||||
* @return The future.
|
||||
*/
|
||||
public ListenableFuture<Result> send(final Message message, final Object requestContext) {
|
||||
return executor.getFutureWithRetry(new RetryCallable<ListenableFuture<Result>>() {
|
||||
@Override
|
||||
public ListenableFuture<Result> call(RetryContext context) throws Exception {
|
||||
SettableFuture<Result> future = SettableFuture.create();
|
||||
HttpPost request = new HttpPost(url);
|
||||
return retry.executeCompletionStage(executorService,
|
||||
() -> client.sendAsync(request, BodyHandlers.ofByteArray())
|
||||
.thenApply(response -> {
|
||||
switch (response.statusCode()) {
|
||||
case 400: throw new CompletionException(new InvalidRequestException());
|
||||
case 401: throw new CompletionException(new AuthenticationFailedException());
|
||||
case 204:
|
||||
case 200: return response.body();
|
||||
default: throw new CompletionException(new ServerFailedException("Bad status: " + response.statusCode()));
|
||||
}
|
||||
})
|
||||
.thenApply(responseBytes -> {
|
||||
try {
|
||||
List<GcmResponseEntity> responseList = mapper.readValue(responseBytes, GcmResponseListEntity.class).getResults();
|
||||
|
||||
request.setHeader("Authorization", authorizationHeader);
|
||||
request.setEntity(new StringEntity(message.serialize(),
|
||||
ContentType.parse("application/json")));
|
||||
if (responseList == null || responseList.size() == 0) {
|
||||
throw new CompletionException(new IOException("Empty response list!"));
|
||||
}
|
||||
|
||||
client.execute(request, new ResponseHandler(future, requestContext));
|
||||
GcmResponseEntity responseEntity = responseList.get(0);
|
||||
|
||||
return future;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shut down all existing HTTP connections.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void stop() throws IOException {
|
||||
this.client.close();
|
||||
}
|
||||
|
||||
private static final class ResponseHandler implements FutureCallback<HttpResponse> {
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
static {
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
|
||||
private final SettableFuture<Result> future;
|
||||
private final Object requestContext;
|
||||
|
||||
public ResponseHandler(SettableFuture<Result> future, Object requestContext) {
|
||||
this.future = future;
|
||||
this.requestContext = requestContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completed(HttpResponse result) {
|
||||
try {
|
||||
String responseBody = EntityUtils.toString(result.getEntity());
|
||||
|
||||
switch (result.getStatusLine().getStatusCode()) {
|
||||
case 400: future.setException(new InvalidRequestException()); break;
|
||||
case 401: future.setException(new AuthenticationFailedException()); break;
|
||||
case 204:
|
||||
case 200: future.set(parseResult(responseBody)); break;
|
||||
default: future.setException(new ServerFailedException("Bad status: " + result.getStatusLine().getStatusCode()));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
future.setException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Exception ex) {
|
||||
future.setException(ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
future.setException(new ServerFailedException("Canceled!"));
|
||||
}
|
||||
|
||||
private Result parseResult(String body) throws IOException {
|
||||
List<GcmResponseEntity> responseList = objectMapper.readValue(body, GcmResponseListEntity.class)
|
||||
.getResults();
|
||||
|
||||
if (responseList == null || responseList.size() == 0) {
|
||||
throw new IOException("Empty response list!");
|
||||
}
|
||||
|
||||
GcmResponseEntity responseEntity = responseList.get(0);
|
||||
|
||||
return new Result(this.requestContext,
|
||||
responseEntity.getCanonicalRegistrationId(),
|
||||
responseEntity.getMessageId(),
|
||||
responseEntity.getError());
|
||||
return new Result(responseEntity.getCanonicalRegistrationId(),
|
||||
responseEntity.getMessageId(),
|
||||
responseEntity.getError());
|
||||
} catch (IOException e) {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
})).toCompletableFuture();
|
||||
} catch (JsonProcessingException e) {
|
||||
return CompletableFuture.failedFuture(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
package org.whispersystems.gcm.server;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.squareup.okhttp.mockwebserver.MockResponse;
|
||||
import com.squareup.okhttp.mockwebserver.RecordedRequest;
|
||||
import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule;
|
||||
@@ -8,6 +11,7 @@ import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
@@ -22,25 +26,31 @@ public class SenderTest {
|
||||
@Rule
|
||||
public MockWebServerRule server = new MockWebServerRule();
|
||||
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
static {
|
||||
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
|
||||
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess() throws InterruptedException, ExecutionException, TimeoutException, IOException {
|
||||
MockResponse successResponse = new MockResponse().setResponseCode(200)
|
||||
.setBody(fixture("fixtures/response-success.json"));
|
||||
server.enqueue(successResponse);
|
||||
|
||||
String context = "my context";
|
||||
Sender sender = new Sender("foobarbaz", 10, server.getUrl("/gcm/send").toExternalForm());
|
||||
ListenableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build(), context);
|
||||
Sender sender = new Sender("foobarbaz", mapper, 10, server.getUrl("/gcm/send").toExternalForm());
|
||||
CompletableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
|
||||
|
||||
Result result = future.get(10, TimeUnit.SECONDS);
|
||||
|
||||
assertEquals(result.isSuccess(), true);
|
||||
assertEquals(result.isThrottled(), false);
|
||||
assertEquals(result.isUnregistered(), false);
|
||||
assertTrue(result.isSuccess());
|
||||
assertFalse(result.isThrottled());
|
||||
assertFalse(result.isUnregistered());
|
||||
assertEquals(result.getMessageId(), "1:08");
|
||||
assertNull(result.getError());
|
||||
assertNull(result.getCanonicalRegistrationId());
|
||||
assertEquals(context, result.getContext());
|
||||
|
||||
RecordedRequest request = server.takeRequest();
|
||||
assertEquals(request.getPath(), "/gcm/send");
|
||||
@@ -51,12 +61,12 @@ public class SenderTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadApiKey() throws ExecutionException, InterruptedException, TimeoutException {
|
||||
public void testBadApiKey() throws InterruptedException, TimeoutException {
|
||||
MockResponse unauthorizedResponse = new MockResponse().setResponseCode(401);
|
||||
server.enqueue(unauthorizedResponse);
|
||||
|
||||
Sender sender = new Sender("foobar", 10, server.getUrl("/gcm/send").toExternalForm());
|
||||
ListenableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
|
||||
Sender sender = new Sender("foobar", mapper, 10, server.getUrl("/gcm/send").toExternalForm());
|
||||
CompletableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
|
||||
|
||||
try {
|
||||
future.get(10, TimeUnit.SECONDS);
|
||||
@@ -73,8 +83,8 @@ public class SenderTest {
|
||||
MockResponse malformed = new MockResponse().setResponseCode(400);
|
||||
server.enqueue(malformed);
|
||||
|
||||
Sender sender = new Sender("foobarbaz", 10, server.getUrl("/gcm/send").toExternalForm());
|
||||
ListenableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
|
||||
Sender sender = new Sender("foobarbaz", mapper, 10, server.getUrl("/gcm/send").toExternalForm());
|
||||
CompletableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
|
||||
|
||||
try {
|
||||
future.get(10, TimeUnit.SECONDS);
|
||||
@@ -93,8 +103,8 @@ public class SenderTest {
|
||||
server.enqueue(error);
|
||||
server.enqueue(error);
|
||||
|
||||
Sender sender = new Sender("foobarbaz", 2, server.getUrl("/gcm/send").toExternalForm());
|
||||
ListenableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
|
||||
Sender sender = new Sender("foobarbaz", mapper, 3, server.getUrl("/gcm/send").toExternalForm());
|
||||
CompletableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
|
||||
|
||||
try {
|
||||
future.get(10, TimeUnit.SECONDS);
|
||||
@@ -118,15 +128,15 @@ public class SenderTest {
|
||||
server.enqueue(error);
|
||||
server.enqueue(success);
|
||||
|
||||
Sender sender = new Sender("foobarbaz", 3, server.getUrl("/gcm/send").toExternalForm());
|
||||
ListenableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
|
||||
Sender sender = new Sender("foobarbaz", mapper, 4, server.getUrl("/gcm/send").toExternalForm());
|
||||
CompletableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
|
||||
|
||||
Result result = future.get(10, TimeUnit.SECONDS);
|
||||
|
||||
assertEquals(server.getRequestCount(), 4);
|
||||
assertEquals(result.isSuccess(), true);
|
||||
assertEquals(result.isThrottled(), false);
|
||||
assertEquals(result.isUnregistered(), false);
|
||||
assertTrue(result.isSuccess());
|
||||
assertFalse(result.isThrottled());
|
||||
assertFalse(result.isUnregistered());
|
||||
assertEquals(result.getMessageId(), "1:08");
|
||||
assertNull(result.getError());
|
||||
assertNull(result.getCanonicalRegistrationId());
|
||||
@@ -141,11 +151,11 @@ public class SenderTest {
|
||||
server.enqueue(response);
|
||||
server.enqueue(response);
|
||||
|
||||
Sender sender = new Sender("foobarbaz", 2, server.getUrl("/gcm/send").toExternalForm());
|
||||
Sender sender = new Sender("foobarbaz", mapper ,2, server.getUrl("/gcm/send").toExternalForm());
|
||||
|
||||
server.get().shutdown();
|
||||
|
||||
ListenableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
|
||||
CompletableFuture<Result> future = sender.send(Message.newBuilder().withDestination("1").build());
|
||||
|
||||
try {
|
||||
future.get(10, TimeUnit.SECONDS);
|
||||
@@ -161,8 +171,8 @@ public class SenderTest {
|
||||
|
||||
server.enqueue(response);
|
||||
|
||||
Sender sender = new Sender("foobarbaz", 2, server.getUrl("/gcm/send").toExternalForm());
|
||||
ListenableFuture<Result> future = sender.send(Message.newBuilder()
|
||||
Sender sender = new Sender("foobarbaz", mapper,2, server.getUrl("/gcm/send").toExternalForm());
|
||||
CompletableFuture<Result> future = sender.send(Message.newBuilder()
|
||||
.withDestination("2")
|
||||
.withDataPart("message", "new message!")
|
||||
.build());
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
package org.whispersystems.gcm.server;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.tomakehurst.wiremock.junit.WireMockRule;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.squareup.okhttp.mockwebserver.MockResponse;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
||||
import static junit.framework.TestCase.assertEquals;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.whispersystems.gcm.server.util.FixtureHelpers.fixture;
|
||||
|
||||
@@ -24,6 +25,14 @@ public class SimultaneousSenderTest {
|
||||
@Rule
|
||||
public WireMockRule wireMock = new WireMockRule(8089);
|
||||
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
static {
|
||||
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
|
||||
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimultaneousSuccess() throws TimeoutException, InterruptedException, ExecutionException, JsonProcessingException {
|
||||
stubFor(post(urlPathEqualTo("/gcm/send"))
|
||||
@@ -31,15 +40,15 @@ public class SimultaneousSenderTest {
|
||||
.withStatus(200)
|
||||
.withBody(fixture("fixtures/response-success.json"))));
|
||||
|
||||
Sender sender = new Sender("foobarbaz", 2, "http://localhost:8089/gcm/send");
|
||||
List<ListenableFuture<Result>> results = new LinkedList<>();
|
||||
Sender sender = new Sender("foobarbaz", mapper, 2, "http://localhost:8089/gcm/send");
|
||||
List<CompletableFuture<Result>> results = new LinkedList<>();
|
||||
|
||||
for (int i=0;i<1000;i++) {
|
||||
results.add(sender.send(Message.newBuilder().withDestination("1").build()));
|
||||
}
|
||||
|
||||
int i=0;
|
||||
for (ListenableFuture<Result> future : results) {
|
||||
for (CompletableFuture<Result> future : results) {
|
||||
Result result = future.get(60, TimeUnit.SECONDS);
|
||||
System.out.println("Got " + (i++));
|
||||
|
||||
@@ -55,18 +64,18 @@ public class SimultaneousSenderTest {
|
||||
.willReturn(aResponse()
|
||||
.withStatus(503)));
|
||||
|
||||
Sender sender = new Sender("foobarbaz", 2, "http://localhost:8089/gcm/send");
|
||||
List<ListenableFuture<Result>> futures = new LinkedList<>();
|
||||
Sender sender = new Sender("foobarbaz", mapper, 2, "http://localhost:8089/gcm/send");
|
||||
List<CompletableFuture<Result>> futures = new LinkedList<>();
|
||||
|
||||
for (int i=0;i<1000;i++) {
|
||||
futures.add(sender.send(Message.newBuilder().withDestination("1").build()));
|
||||
}
|
||||
|
||||
for (ListenableFuture<Result> future : futures) {
|
||||
for (CompletableFuture<Result> future : futures) {
|
||||
try {
|
||||
Result result = future.get(60, TimeUnit.SECONDS);
|
||||
} catch (ExecutionException e) {
|
||||
assertTrue(e.getCause() instanceof ServerFailedException);
|
||||
assertTrue(e.getCause().toString(), e.getCause() instanceof ServerFailedException);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user