I am using HttpClient of Java 11 to post the request to an HTTP2 server. The HttpClient Object is created as a Singleton Spring bean as shown below.
#Bean
public HttpClient getClient() {
return HttpClient.newBuilder().version(Version.HTTP_2).executor(Executors.newFixedThreadPool(20)).followRedirects(Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20)).build();
}
I am using the sendAsync method to send the requests asynchronously.
When I try to hit the server continuously, I am receiving the error after certain time "java.io.IOException: too many concurrent streams". I used Fixed threadpool in the Client building to try to overcome this error, but it is still giving the same error.
The Exception stack is..
java.util.concurrent.CompletionException: java.io.IOException: too many concurrent streams
at java.base/java.util.concurrent.CompletableFuture.encodeRelay(CompletableFuture.java:367) ~[?:?]
at java.base/java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:1108) ~[?:?]
at java.base/java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2235) ~[?:?]
at java.net.http/jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:345) ~[java.net.http:?]
at java.net.http/jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(MultiExchange.java:250) ~[java.net.http:?]
at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1072) ~[?:?]
at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506) ~[?:?]
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1705) ~[?:?]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?]
at java.base/java.lang.Thread.run(Thread.java:834) [?:?]
Caused by: java.io.IOException: too many concurrent streams
at java.net.http/jdk.internal.net.http.Http2Connection.reserveStream(Http2Connection.java:440) ~[java.net.http:?]
at java.net.http/jdk.internal.net.http.Http2ClientImpl.getConnectionFor(Http2ClientImpl.java:103) ~[java.net.http:?]
at java.net.http/jdk.internal.net.http.ExchangeImpl.get(ExchangeImpl.java:88) ~[java.net.http:?]
at java.net.http/jdk.internal.net.http.Exchange.establishExchange(Exchange.java:293) ~[java.net.http:?]
at java.net.http/jdk.internal.net.http.Exchange.responseAsyncImpl0(Exchange.java:425) ~[java.net.http:?]
at java.net.http/jdk.internal.net.http.Exchange.responseAsyncImpl(Exchange.java:330) ~[java.net.http:?]
at java.net.http/jdk.internal.net.http.Exchange.responseAsync(Exchange.java:322) ~[java.net.http:?]
at java.net.http/jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:304) ~[java.net.http:?]
Can someone help me in fixing this issue?
The server is Tomcat9 and its max concurrent streams are the default.
When I try to hit the server continuously
The server has a setting for max_concurrent_streams that is communicated to the client during the initial establishment of a HTTP/2 connection.
If you blindly "hit the server continuously" using sendAsync you are not waiting for previous requests to finish and eventually you exceed the max_concurrent_streams value and receive the error above.
The solution is to send concurrently a number of requests that is less than max_concurrent_streams; after that, you only send a new request when a previous one completes.
This can easily implemented on the client using a Semaphore or something similar.
Unfortunately, the approach with Semaphore, suggested by #sbordet, didn't work for me. I tried this:
var semaphore = semaphores.computeIfAbsent(getRequestKey(request), k -> new Semaphore(MAX_CONCURRENT_REQUESTS_NUMBER));
CompletableFuture.runAsync(semaphore::acquireUninterruptibly, WAITING_POOL)
.thenComposeAsync(ignored -> httpClient.sendAsync(request, responseBodyHandler), ASYNC_POOL)
.whenComplete((response, e) -> semaphore.release());
There's no guarantee that a connection stream is released by the time the execution is passed to the next CompletableFuture, where the semaphore is released. For me the approach worked in case of normal execution, however if there're any exceptions, it seems that the connection stream may be closed after semaphore.release() is invoked.
Finally, I ended up by using OkHttp. It handles the problem (it just waits until some streams are freed up if the number of concurrent streams reaches max_concurrent_streams). It also handles the GOAWAY frame. In case of Java HttpClient I had to implement retry logic to handle this as it just throws IOException if the server sends GOAWAY frame.
I think #sbordet's answer is incorrect and this error does not occur because your requests per second is exceeding MAX_CONCURRENT_STREAMS, but because the number of open HTTP streams (per HTTP 2 connection?) exceeds that number.
For example, I have a server at work that has a MAX_CONCURRENT_STREAMS setting of 128:
$ curl -iv -H "Content-Type: application/json" https://example.local
...
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
But I seem to be able to hit it with up to ~1000 requests per second without getting any errors back:
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class TooManyConcurrentStreams1 {
private static final int CONCURRENCY = 1000;
public static void main(String[] args) {
final var counter = new AtomicInteger();
final var singletonHttpClient = newHttpClient();
final var singletonRequest = newRequest();
final var responses = new ArrayList<CompletableFuture<HttpResponse<Void>>>(CONCURRENCY);
for (int i = 0; i < CONCURRENCY; i++) {
responses.add(singletonHttpClient.sendAsync(singletonRequest, BodyHandlers.discarding()));
}
for (CompletableFuture<HttpResponse<Void>> response : responses) {
response.thenAccept(x -> {});
response.join();
System.out.println(counter.incrementAndGet());
}
singletonHttpClient.executor().ifPresent(executor -> {
if (executor instanceof ExecutorService executorService) {
executorService.shutdown();
}
});
}
public static HttpRequest newRequest() {
return HttpRequest.newBuilder()
.uri(Constants.TEST_URI)
.header("Content-Type", Constants.CONTENT_TYPE)
.header("Accept", Constants.CONTENT_TYPE)
.POST(HttpRequest.BodyPublishers.ofString(Constants.BODY))
.build();
}
public static HttpClient newHttpClient() {
return HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.executor(Executors.newFixedThreadPool(CONCURRENCY))
.build();
}
}
When I increase CONCURRENCY to an absurd number like 2000,
I get this error, and not java.io.IOException: too many concurrent streams:
Exception in thread "main" java.util.concurrent.CompletionException: java.net.SocketException: Connection reset
at java.base/java.util.concurrent.CompletableFuture.encodeRelay(CompletableFuture.java:368)
at java.base/java.util.concurrent.CompletableFuture.completeRelay(CompletableFuture.java:377)
at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1152)
at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
at java.base/java.util.concurrent.CompletableFuture.completeExceptionally(CompletableFuture.java:2162)
at java.net.http/jdk.internal.net.http.Stream.completeResponseExceptionally(Stream.java:1153)
at java.net.http/jdk.internal.net.http.Stream.cancelImpl(Stream.java:1238)
at java.net.http/jdk.internal.net.http.Stream.connectionClosing(Stream.java:1212)
at java.net.http/jdk.internal.net.http.Http2Connection.shutdown(Http2Connection.java:710)
at java.net.http/jdk.internal.net.http.Http2Connection$Http2TubeSubscriber.processQueue(Http2Connection.java:1323)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$LockingRestartableTask.run(SequentialScheduler.java:205)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:149)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:230)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:833)
However, I can reproduce your error with this code (I hit this error first, and then found your question here!)
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class TooManyConcurrentStreams2 {
public static void main(String[] args) {
final var singletonHttpClient = newHttpClient();
final var singletonRequest = newRequest();
final var counter = new AtomicInteger();
final var scheduler = Executors.newScheduledThreadPool(2);
scheduler.schedule(scheduler::shutdown, 1, TimeUnit.HOURS);
scheduler.scheduleAtFixedRate(() -> {
final var batchSize = counter.incrementAndGet();
final var responses = new ArrayList<CompletableFuture<HttpResponse<Void>>>(batchSize);
try {
for (int i = 0; i < batchSize; i++) {
responses.add(
singletonHttpClient.sendAsync(
singletonRequest,
BodyHandlers.discarding()
)
);
}
for (CompletableFuture<HttpResponse<Void>> response : responses) {
response.thenAccept(x -> {
});
response.join();
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("batchSize = " + batchSize);
}, 0, 500, TimeUnit.MILLISECONDS);
}
public static HttpRequest newRequest() {
return HttpRequest.newBuilder()
.uri(Constants.TEST_URI)
.header("Content-Type", Constants.CONTENT_TYPE)
.header("Accept", Constants.CONTENT_TYPE)
.POST(HttpRequest.BodyPublishers.ofString(Constants.BODY))
.build();
}
public static HttpClient newHttpClient() {
return HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.build();
}
}
This one fails on 128th (!) execution of my once per 500ms runnable:
java.util.concurrent.CompletionException: java.io.IOException: too many concurrent streams
at java.base/java.util.concurrent.CompletableFuture.encodeRelay(CompletableFuture.java:368)
at java.base/java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:1189)
at java.base/java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2309)
at java.net.http/jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:453)
at java.net.http/jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(MultiExchange.java:341)
at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150)
at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.io.IOException: too many concurrent streams
So the problem is not number of requests per second, but something else, which seems to be the number of concurrent open streams per http connection/client.
We can verify this by NOT sharing the same http client (and request) for all batch requests:
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class TooManyConcurrentStreams2 {
public static void main(String[] args) {
final var counter = new AtomicInteger();
final var scheduler = Executors.newScheduledThreadPool(2);
scheduler.schedule(scheduler::shutdown, 1, TimeUnit.HOURS);
scheduler.scheduleAtFixedRate(() -> {
final var httpClient = newHttpClient();
final var request = newRequest();
final var batchSize = counter.incrementAndGet();
final var responses = new ArrayList<CompletableFuture<HttpResponse<Void>>>(batchSize);
try {
for (int i = 0; i < batchSize; i++) {
responses.add(
httpClient.sendAsync(
request,
BodyHandlers.discarding()
)
);
}
for (CompletableFuture<HttpResponse<Void>> response : responses) {
response.thenAccept(x -> {
});
response.join();
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("batchSize = " + batchSize);
}, 0, 500, TimeUnit.MILLISECONDS);
}
public static HttpRequest newRequest() {
return HttpRequest.newBuilder()
.uri(Constants.TEST_URI)
.header("Content-Type", Constants.CONTENT_TYPE)
.header("Accept", Constants.CONTENT_TYPE)
.POST(HttpRequest.BodyPublishers.ofString(Constants.BODY))
.build();
}
public static HttpClient newHttpClient() {
return HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.build();
}
}
For me, this one fails at 143rd try with this error message:
java.util.concurrent.CompletionException: java.lang.InternalError: java.net.SocketException: Too many open files
at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:315)
at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:320)
at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1159)
at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.InternalError: java.net.SocketException: Too many open files
at java.net.http/jdk.internal.net.http.PlainHttpConnection.<init>(PlainHttpConnection.java:293)
at java.net.http/jdk.internal.net.http.AsyncSSLConnection.<init>(AsyncSSLConnection.java:49)
at java.net.http/jdk.internal.net.http.HttpConnection.getSSLConnection(HttpConnection.java:293)
at java.net.http/jdk.internal.net.http.HttpConnection.getConnection(HttpConnection.java:279)
at java.net.http/jdk.internal.net.http.Http2Connection.createAsync(Http2Connection.java:369)
at java.net.http/jdk.internal.net.http.Http2ClientImpl.getConnectionFor(Http2ClientImpl.java:128)
at java.net.http/jdk.internal.net.http.ExchangeImpl.get(ExchangeImpl.java:93)
at java.net.http/jdk.internal.net.http.Exchange.establishExchange(Exchange.java:343)
at java.net.http/jdk.internal.net.http.Exchange.responseAsyncImpl0(Exchange.java:475)
at java.net.http/jdk.internal.net.http.Exchange.responseAsyncImpl(Exchange.java:380)
at java.net.http/jdk.internal.net.http.Exchange.responseAsync(Exchange.java:372)
at java.net.http/jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:408)
at java.net.http/jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(MultiExchange.java:341)
at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150)
... 5 more
This one is most likely due to my laptop's relatively low ulimit of 12544.
Related
What is the best way to call a method continuously after a fixed interval?
I want to design a Poller that can pull messages from AWS SQS automatically after a defined time interval.
Any good suggestions are much appreciated.
There are two polling mechanisms short polling - if you are expecting data more frequently and long polling if less frequently.
You should use something mixed of the above i.e
pull recursively in the timeout of 10ms,
If pull contains any message(successful pull) continue polling with the same speed else
change a timeout to let say 5000ms.
Sample:
//timeout is in ms
timeout = 10;
function pullFromSQS() {
message = sqs.pull();
if (message.length) {
processMessage(message);
timeout = 10;
} else {
timeout = 5000;
}
wait(timeout);
pullFromSQS();
}
you can change the timeout as per your convenience for better optimization (both cost and performance)
You can use SDK provided by AWS to do polling of messages
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.amazonaws.AmazonClientException;
import com.amazonaws.auth.profile.ProfileCredentialsProvider;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.AmazonSQSClientBuilder;
import com.amazonaws.services.sqs.model.Message;
import com.amazonaws.services.sqs.model.ReceiveMessageRequest;
public class SQSRealtimePoller implements Runnable {
public static final int MAX_MESSAGES = 10;
public static final int DEFAULT_VISIBILITY_TIMEOUT = 15;
//Value greater that 0 makes it long polling, which will reduce SQS cost
public static final int WAIT_TIME = 20;
public static final int PROCESSORS = 2;
ExecutorService executor = Executors.newFixedThreadPool(1);
private String queueUrl;
private AmazonSQS amazonSqs;
ArrayBlockingQueue<Message> messageHoldingQueue = new ArrayBlockingQueue<Message>(
1);
public SQSRealtimePoller(String topic, String queueUrl
) {
this.queueUrl = queueUrl;
this.amazonSqs = getSQSClient();
messageHoldingQueue = new ArrayBlockingQueue<Message>(PROCESSORS);
//process more than 1 messages at a time.
executor = Executors.newFixedThreadPool(PROCESSORS);
}
#Override
public void run() {
ReceiveMessageRequest receiveMessageRequest = new ReceiveMessageRequest()
.withQueueUrl(queueUrl)
.withMaxNumberOfMessages(MAX_MESSAGES)
.withVisibilityTimeout(DEFAULT_VISIBILITY_TIMEOUT)
.withWaitTimeSeconds(WAIT_TIME);
while(true){
try {
List<Message> messages = amazonSqs
.receiveMessage(receiveMessageRequest).getMessages();
if (messages == null || messages.size() == 0) {
// If there were no messages during this poll period, SQS
// will return this list as null. Continue polling.
continue;
} else {
for (Message message : messages) {
try {
//will wait here till the queue has free space to add new messages. Read documentation
messageHoldingQueue.put(message);
} catch (InterruptedException e) {
}
Runnable run = new Runnable() {
#Override
public void run() {
try {
Message messageToProcess = messageHoldingQueue
.poll();
//Process your message here
System.out.println(messageToProcess);
//Delete the messages from queue
amazonSqs.deleteMessage(queueUrl,
messageToProcess
.getReceiptHandle());
} catch (Exception e) {
e.printStackTrace();
}
}
};
executor.execute(run);
}}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//Make this singleton
public static AmazonSQS getSQSClient(){
ProfileCredentialsProvider credentialsProvider = new ProfileCredentialsProvider();
try {
credentialsProvider.getCredentials();
} catch (Exception e) {
throw new AmazonClientException(
"Cannot load the credentials from the credential profiles file. " +
"Please make sure that your credentials file is at the correct " +
"location , and is in valid format.",
e);
}
AmazonSQS sqs = AmazonSQSClientBuilder.standard()
.withCredentials(credentialsProvider)
.withRegion(Regions.US_WEST_2)
.build();
return sqs;
}}
I'm trying to execute aws device farm example code that we can get below site.
https://docs.aws.amazon.com/devicefarm/latest/testgrid/getting-started-local.html
// Import the AWS SDK for Java 2.x Device Farm client:
...
// in your tests ...
public class MyTests {
// ... When you set up your test suite
private static RemoteWebDriver driver;
#Before
void setUp() {
String myProjectARN = "...";
DeviceFarmClient client = DeviceFarmClient.builder().region(Region.US_WEST_2).build();
CreateTestGridUrlRequest request = CreateTestGridUrlRequest.builder()
.expiresInSeconds(300)
.projectArn(myProjectARN)
.build();
CreateTestGridUrlResponse response = client.createTest.GridUrl(request);
URL testGridUrl = new URL(response.url());
// You can now pass this URL into RemoteWebDriver.
WebDriver driver = new RemoteWebDriver(testGridUrl, DesiredCapabilities.firefox());
}
#After
void tearDown() {
// make sure to close your WebDriver:
driver.quit();
}
}
After executing above codes, the error was occurred and the message is like this.
java.net.UnknownHostException: devicefarm.us-westt-2.amazonaws.com
I guess the code can't resolve host because of proxy server.
How can i resolve this problem?
Thanks.
Can you please confirm which line throws java.net.UnknownHostException: devicefarm.us-westt-2.amazonaws.com. Is it client.createTest.GridUrl(request) or WebDriver driver = new RemoteWebDriver(testGridUrl, DesiredCapabilities.firefox());
If it is the client.createTest.GridUrl(request), then please follow Proxy Configuration mentioned at https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/section-client-configuration.html
My current setUp method is like this.
#Before
public void setUp() {
try {
ProxyConfiguration.Builder proxyConfig = ProxyConfiguration.builder();
proxyConfig.endpoint(new URI("<YOUR PROXY URL>"));
proxyConfig.username("<YOUR USER ID>");
proxyConfig.password("YOUR PASSWORD");
ApacheHttpClient.Builder httpClientBuilder =
ApacheHttpClient.builder()
.proxyConfiguration(proxyConfig.build());
String myARN = "<YOUR ARN>";
DeviceFarmClient client = DeviceFarmClient.builder()
.credentialsProvider(DefaultCredentialsProvider.create())
.region(Region.US_WEST_2)
.httpClientBuilder(httpClientBuilder)
.overrideConfiguration(ClientOverrideConfiguration.builder().build())
.build();
CreateTestGridUrlRequest request = CreateTestGridUrlRequest.builder()
.expiresInSeconds(300) // 5 minutes
.projectArn(myARN)
.build();
URL testGridUrl = null;
CreateTestGridUrlResponse response = client.createTestGridUrl(request);
testGridUrl = new URL(response.url());
driver = new RemoteWebDriver(testGridUrl, DesiredCapabilities.chrome());
} catch (Exception e) {
e.printStackTrace();
}
}
Thank you again.
I'm trying to test a Dart REST app run on shelf_rest. Assuming a setup similar to the shelf_rest example, how can one test the configured routes without actually running an HTTP server?
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_rest/shelf_rest.dart';
void main() {
var myRouter = router()
..get('/accounts/{accountId}', (Request request) {
var account = new Account.build(accountId: getPathParameter(request, 'accountId'));
return new Response.ok(JSON.encode(account));
});
io.serve(myRouter.handler, 'localhost', 8080);
}
class Account {
final String accountId;
Account.build({this.accountId});
Account.fromJson(Map json) : this.accountId = json['accountId'];
Map toJson() => {'accountId': accountId};
}
class AccountResource {
#Get('{accountId}')
Account find(String accountId) => new Account.build(accountId: accountId);
}
Without getting into too much additional logic, how could the GET account endpoint be unit tested? Some basic tests I'd like to run would be:
GET /accounts/123 returns 200
GET /accounts/bogus returns 404
To create a unit test (i.e. without a running server) then you need to split myRouter outside of the main function and put it in a file in the lib dir. e.g
import 'dart:convert';
import 'package:shelf/shelf.dart';
import 'package:shelf_rest/shelf_rest.dart';
var myRouter = router()
..get('/accounts/{accountId}', (Request request) {
var account =
new Account.build(accountId: getPathParameter(request, 'accountId'));
return new Response.ok(JSON.encode(account));
});
class Account {
final String accountId;
Account.build({this.accountId});
Account.fromJson(Map json) : this.accountId = json['accountId'];
Map toJson() => {'accountId': accountId};
}
Then create a test file in the test directory and test like
import 'package:soQshelf_rest/my_router.dart';
import 'package:test/test.dart';
import 'package:shelf/shelf.dart';
import 'dart:convert';
main() {
test('/account/{accountId} should return expected response', () async {
final Handler handler = myRouter.handler;
final Response response = await handler(
new Request('GET', Uri.parse('http://localhost:9999/accounts/123')));
expect(response.statusCode, equals(200));
expect(JSON.decode(await response.readAsString()),
equals({"accountId": "123"}));
});
}
I've problem with initiation of JAXWSProperties in MessageContext of example webservice described on following blog
There is the helper class initiating HeaderList object in getHeaders() method:
import com.sun.xml.internal.ws.api.SOAPVersion;
import com.sun.xml.internal.ws.api.addressing.AddressingVersion;
import com.sun.xml.internal.ws.api.message.HeaderList;
import com.sun.xml.internal.ws.api.message.Headers;
import com.sun.xml.internal.ws.developer.JAXWSProperties;
import com.sun.xml.internal.ws.developer.WSBindingProvider;
import javax.xml.ws.EndpointReference;
import javax.xml.ws.Service;
import javax.xml.ws.WebServiceContext;
public final class CorrelationHelper<S extends Service> {
private WebServiceContext wsc;
private S service;
public CorrelationHelper(S service, WebServiceContext wsc) {
this.service = service;
this.wsc = wsc;
}
private HeaderList getHeaders() {
return (HeaderList)wsc.getMessageContext().get(JAXWSProperties.INBOUND_HEADER_LIST_PROPERTY);
}
public <P> P getCorrelatedPort(Class<P> portType) {
P port = service.getPort(getReplyTo(), portType);
((WSBindingProvider)port).setOutboundHeaders(Headers.create(AddressingVersion.W3C.relatesToTag,
getMessageId()));
return port;
}
private EndpointReference getReplyTo() {
return getHeaders().getReplyTo(AddressingVersion.W3C,
SOAPVersion.SOAP_11).toSpec();
}
private String getMessageId() {
return getHeaders().getMessageID(AddressingVersion.W3C,
SOAPVersion.SOAP_11);
}
}
this helper is called from service implementation as:
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.jws.HandlerChain;
import javax.jws.WebService;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.soap.Addressing;
import dev.home.examples.jobprocessor.client.JobProcessorNotify;
import dev.home.examples.jobprocessor.client.JobProcessorNotify_Service;
import dev.home.examples.jobprocessor.client.JobReplyType;
import dev.home.examples.jobprocessor.handlers.CorrelationHelper;
import dev.home.examples.jobprocessor.types.JobType;
#WebService(serviceName = "JobProcessor",
targetNamespace = "http://examples.home.dev/jobprocessor",
portName = "jobProcessor",
endpointInterface = "dev.home.examples.jobprocessor.ws.JobProcessor")
#HandlerChain(file = "JobProcessor-HandlerChain.xml")
#Addressing(required = true)
public class JobProcessorImpl {
//...
public void processJob(JobType job) {
// do processing
int seconds = doJob();
// prepare reply message
JobReplyType jobReply = new JobReplyType();
jobReply.setJobId(job.getJobId());
jobReply.setResult(String.format("Job payload %s processed in %d seconds!",
job.getPayload(), seconds));
// do correlation and perform the callback
JobProcessorNotify jobProcessorNotify =
correlationHelper.getCorrelatedPort(JobProcessorNotify.class);
jobProcessorNotify.replyFinishedJob(jobReply);
}
}
HeaderList is not initiated during getReplyTo() and than is returned as null:
(HeaderList)wsc.getMessageContext().get(JAXWSProperties.INBOUND_HEADER_LIST_PROPERTY)
Caused by: java.lang.NullPointerException
at dev.home.examples.jobprocessor.handlers.CorrelationHelper.getReplyTo(CorrelationHelper.java:67)
at dev.home.examples.jobprocessor.handlers.CorrelationHelper.getCorrelatedPort(CorrelationHelper.java:56)
at dev.home.examples.jobprocessor.ws.JobProcessorImpl.processJob(JobProcessorImpl.java:61)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.apache.cxf.service.invoker.AbstractInvoker.performInvocation(AbstractInvoker.java:180)
at org.apache.cxf.jaxws.JAXWSMethodInvoker.performInvocation(JAXWSMethodInvoker.java:66)
at org.apache.cxf.service.invoker.AbstractInvoker.invoke(AbstractInvoker.java:96)
... 17 more
although the SOAP data Header contains all data:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:typ="http://examples.home.dev/jobprocessor/types">
<soapenv:Header xmlns:wsa="http://www.w3.org/2005/08/addressing"><wsa:Action>http://examples.home.dev/jobprocessor/processJob</wsa:Action><wsa:ReplyTo><wsa:Address>http://CZ407032:8088/mockJobProcessorNotify</wsa:Address></wsa:ReplyTo><wsa:MessageID>uuid:96dd09e2-c448-47c0-902f-7eb95421e232</wsa:MessageID><wsa:To>http://CZ407032:8088/JobProcessor</wsa:To></soapenv:Header>
<soapenv:Body>
...
</soapenv:Body></soapenv:Envelope>
You are using the property INBOUND_HEADER_LIST_PROPERTY. Reading JAX-WS documentation, shows an unpleasant warning
THIS PROPERTY IS EXPERIMENTAL AND IS SUBJECT TO CHANGE WITHOUT NOTICE IN FUTURE.
HeaderList and JAXWSProperties are classes in com.sun.xml.internal.ws.* package. Do you really want to use this? The blog is dated in 2012, may be the behaviour has changed
Check if this simple code return a not null object (after injecting messageContext as #Resource in your service)
HeaderList hl = (HeaderList) messageContext.get(JAXWSProperties.INBOUND_HEADER_LIST_PROPERTY);
Getting Error while consuming webservices in java through SOAP approach. Plz suggest, i am stuck in this for last 10 days. I am using this server for webservices "http://www.webservicex.net/globalweather.asmx"
Error:
Exception in thread "main" [SOAPException: faultCode=SOAP-ENV:Client; msg=Error
opening socket: Connection timed out: connect;
targetException=java.lang.IllegalArgumentException: Error opening
socket: Connection timed out: connect]
at org.apache.soap.transport.http.SOAPHTTPConnection.se(SOAPHTTPConnection.java:324)
at org.apache.soap.rpc.Call.invoke(Call.java:205)
at com.check.ClientNet.main(ClientNet.java:47)
My java code is :
package com.check;
import java.net.*;
import java.util.*;
import org.apache.soap.*;
import org.apache.soap.encoding.SOAPMappingRegistry;
import org.apache.soap.rpc.*;
import org.apache.soap.encoding.soapenc.StringDeserializer;
import org.apache.soap.util.xml.QName;
import com.check.ProxyAuthenticator;
public class ClientNet {
public static void main (String[] args)
throws Exception {
Properties properties = System.getProperties();
properties.put("http.proxyHost", "10.136.236.30");
properties.put("http.proxyPort", "8080");
properties.put("http.proxyUser", "bnkishore");
properties.put("http.proxyPassword","XXXX");
Properties newprops = new Properties(properties);
System.setProperties(newprops);
String username = System.getProperty("http.proxyUser");
String password = System.getProperty("http.proxyPassword");
if (username != null && !username.equals("")) {
Authenticator.setDefault(new ProxyAuthenticator(username, password));
}
System.out.println("\n\nCalling the SOAP Server:\n\n");
//http://www.webservicex.net/globalweather.asmx
URL url = new URL ("http://www.webservicex.net/globalweather.asmx");
String CountryName = "India";
Call call = new Call();
SOAPMappingRegistry soapMappingRegistry = new SOAPMappingRegistry();
soapMappingRegistry.mapTypes(Constants.NS_URI_SOAP_ENC, new QName("http://www.webserviceX.NET", "globalweather"),null,null, new StringDeserializer());
call.setTargetObjectURI("http://www.webserviceX.NET");
call.setMethodName("GetCitiesByCountry");
call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
Vector<Parameter> params = new Vector<Parameter>();
params.addElement(new Parameter("CountryName", String.class, CountryName, null));
call.setParams (params);
System.out.print("The SOAP Server says: ");
Response resp = call.invoke(url, " ");
if (resp.generatedFault()) {
Fault fault = resp.getFault();
System.out.println("\nOuch, the call failed: ");
System.out.println(" Fault Code = " + fault.getFaultCode());
System.out.println(" Fault String = " + fault.getFaultString());
} else {
Parameter result = resp.getReturnValue();
System.out.print(result.getValue());
System.out.println();
}
}
}
And ProxyAuthencator code is :
package com.check;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
public class ProxyAuthenticator extends Authenticator {
private String userName, passWord;
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(userName,passWord.toCharArray());
}
public ProxyAuthenticator(String userName, String password) {
this.userName = userName;
this.passWord = password;
getPasswordAuthentication();
}
}
Thanks.
There is a timeout, that means you client is not reaching the server. Check connectivity. For dealing with a proxy, additionally to your ProxyAuthenticator you need to add few system properties: proxySet, proxyHost and proxyPort. If you are using maven, you can do it in this way:
mvn jetty:run -DproxySet=true -DproxyHost=proxy.indra.es -DproxyPort=8080