CXF JAXRS client not reusing TCP connections - rest

I'm using the JAX-RS support in CXF 2.2.5 to invoke REST webservices. I'm creating a single org.apache.cxf.jaxrs.client.WebClient instance for each endpoint I need to communicate with (typically one or two endpoints for any given deployment) and re-using this client for each web-service invocation.
The problem I face is that the client is creating new TCP connections to the server for each request, despite using the keep-alive setting. At high traffic levels, this is causing problems. An excerpt from my client code is below.
I'm trying to dig through the CXF source to identify the problem but getting hopelessly lost at present. Any thoughts greatly appreciated.
Thanks,
FB
ConcurrentMap<String, WebClient> webclients = new ConcurrentHashMap<String, WebClient>();
public void dispatchRequest(MyRequestClass request, String hostAddress) {
// Fetch or create the web client if we don't already have one for this hostAddress
// NOTE: WebClient is only thread-safe if not changing the URI or headers between calls!
// http://cxf.apache.org/docs/jax-rs-client-api.html#JAX-RSClientAPI-ThreadSafety
WebClient client = webclients.get(hostAddress);
if (client == null) {
String serviceUrl = APP_HTTP_PROTOCOL + "://" + hostAddress + ":" + APP_PORT + "/" + APP_REQUEST_PATH;
WebClient newClient = WebClient.create(serviceUrl).accept(MediaType.TEXT_PLAIN);
client = webclients.putIfAbsent(hostAddress, newClient);
if (client == null) {
client = newClient;
} // Else, another thread must have added the client in the meantime - that's fine if so.
}
XStream marshaller = MyCollection.getMarshaller();
String requestXML = marshaller.toXML(request);
Response response = null;
try {
// Send it!
response = client.post(requestXML);
}
catch (Exception e) {
}
...
}

In your sample code you get a JAX-RS Response, which getEntity() method will return an InputStream by default. Therefore, being CXF not responsible for consuming the stream, this is obviously left open.
If you don't explicitly close that, it would be closed during a Garbage Collection phase.
But even so, under high traffic rates, this little latency prevents the underlying HTTP connection to be reinserted into the internal pool of persistent connections exploited by HttpURLConnection (that CXF is using under the bonnet). So it cannot be reused on time.
If you take care of closing the InputStream, you should not see a large number of TIME_WAIT sockets anymore.

I would definitely try updating to a newer and supported version of CXF. There have been a LOT of updates to the JAX-RS stuff in the newer versions of CXF and this issue may already be fixed.

Related

Close RESTEasy client after a certain delay

I'm trying to close a RESTEasy client after a certain delay (e.g 5 seconds) and it seems the current configuration I'm using is not working at all.
HttpClient httpClient = HttpClientBuilder.create()
.setConnectionTimeToLive(5, TimeUnit.SECONDS)
.setDefaultRequestConfig(RequestConfig.custom()
.setConnectionRequestTimeout(5 * 1000)
.setConnectTimeout(5 * 1000)
.setSocketTimeout(5 * 1000).build())
.build();
ApacheHttpClient43Engine engine = new ApacheHttpClient43Engine(httpClient, localContext);
ResteasyClient client = new ResteasyClientBuilder().httpEngine(engine).build();
according to the documentation the ConnectionTimeToLive should close the connection no matter if there's payload or not.
please find attached the link
https://access.redhat.com/documentation/en-us/red_hat_jboss_enterprise_application_platform/7.3/html-single/developing_web_services_applications/index#jax_rs_client
In my specific case, there sometimes is some latency and the payload is sent in chunks (below the socketTimeout interval hence the connection is kept alive and it could happen that the client is active for hours)
My main goal is to kill the client and release the connection but I feel there is something I'm missing in the configuration.
I'm using wiremock to replicate this specific scenario by sending the payload in chucks.
.withChunkedDribbleDelay
any clue about the configuration?
You may try using .withFixedDelay(60000) instead of .withChunkedDribbleDelay().

Vertx request does not end on sendFile throwing

I'm new to vert.x and I'm trying to create a simple download service.
I used Request#sendFile(fileName) and it works well, but if I pass a directory path to Request#sendFile(fileName) it throws an exception, which is totally fine.
The problem is that, even if I catch that exception with an handler, I can't send any data nor end the request, an that leaves the http client (the browser) stuck on an endless spinning progress.
That is an example that reproduces the problem:
VertxOptions options = new VertxOptions();
options.setBlockedThreadCheckInterval(1000*60*60);
Vertx vertx = Vertx.vertx(options);
HttpServer server = vertx.createHttpServer();
Router router = Router.router(vertx);
router
.route(HttpMethod.GET,"/foo")
.handler(ctx->{
// this path exist but is not a file, is a directory.
ctx.response().sendFile("docs/pdf",asr->{
if(asr.failed()) {
ctx.response()
.setStatusCode(404)
// I can't end the connection the only thing I can do is close it
// I've commented out this lambda because is not what I want to happen.
// It's just an hack to end the request all the same.
.end("File not found: "+"docs/pdf" /*, (x)->{ctx.response().close();}*/ );
}
});
});
server
.requestHandler(router)
.listen(3000);
I can this problem by checking first if the path references to a file which both exsist and is not a directory (which in fact I did in the real code), but that leaves me with doubt about what would happen if the IOException was something different (like reading a broken file, or an unauthorized file ...).
When this error happens no data is sent through the wire, I've both checked form the browser and sniffing packets TCP packets (0 bytes send from the server to the browser).
The only things that works is closing the connection with Response#close(), which at least closes the keep-alive http connection, and ends the browser request.
What I want to achieve is to send some information back to the client to tell something went wrong, possibly setting the status code to an appropriate 4** error and possibly adding some details to it (either in status text or in the response body).
You should add failureHandler to your router:
route.failureHandler(frc-> {
frc.response().setStatusCode( 400 ).end("Sorry! Not today");
});
see https://vertx.io/docs/vertx-web/java/#_error_handling

NETTY 4.1.4: TCP Socket Server which replies back towards clients after processing requests

i'm new to Netty and intend to create a tcp socket server which reads the info of each client and replies back towards client before processing requests immediately ,i.e. sort of an acknowledgement towards client as and when the message enters overriden channelRead method of ChannelInboundHandlerAdapter class.
Please guide me in the above specified objective.
i'm currently trying the basic netty 4.1.4 echo server example however i wanted server to send back acknowledgement to the client so i updated channelread method as follows :
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ctx.write(msg);
ChannelFuture cf = ctx.channel().write("FROM SERVER");
System.out.println("Channelfuture is "+cf);
}
and the output obtained was as follows:
Channelfuture is DefaultChannelPromise#3f4ee9dd(failure: java.lang.UnsupportedOperationException: unsupported message type: String (expected: ByteBuf, FileRegion))
I understand the error that it is expecting bytebuf but how do i achieve it? also, whether this method would be able to send out acknowledgement towards client
You can use String.getBytes(Charset) and Unpooled.wrappedBuffer(byte[]) to convert to ByteBuf.
ChannelFuture cf = ctx.channel()
.write(Unpooled.wrappedBuffer("FROM SERVER".getBytes(CharsetUtil.UTF_8)));
Also note that ctx.channel().write(...); may not be what you want. Consider ctx.write(...); instead. The difference is that if your handler is a ChannelDuplexHandler it would receive a write event when you do channel().write(). Using ctx instead of channel will send the write out from your handlers point in the pipeline instead of from the end of the pipeline, which is usually what you want.

SOAP header for authentication in Notes/Domino

I need to consume a WS that requires client (machine) certificate. More precisely it uses WS-Security : SOAP Message Security, WS-Security : X.509 Certificate Token Profile as defined by http://docs.oasis-open.org/wss-m/wss/v1.1.1/os/wss-SOAPMessageSecurity-v1.1.1-os.html
Using the native consumer, Domino don't add (magically) the authentication in the SOAP header. Now how I'm supposed to do add the security in header? Ideally in LotusScript...
I don't see anyway to embed the Consumer in my own header or enrich the existing consumer. I join the IBM response on this.
So my question:
Is there a work around to do this in Lotusscript ?
did some of you done something like this in java (and I will probably make an LS2J since a lot of LotusScript code already exists when I will get (hopefully) the response from the WS
IBM response:
We understand that you are attempting to use SOAP header for authentication. Unfortunately this is currently not supported.
For your reference, we have at least two Enhancement Requests (that I could find in this area) that are related to this topic:
SPR # SODY9H6BTM: Creating Your Own Soap Objects Does Not Support Client Certificate Authentication In A Web Agent.
SPR # JSHN7A3MLP: Authentication data in the Header element of WS Consumer SOAP envelopes
Unfortunately there is nothing further we can do in Support at this time.
If I understood your problem correctly you do not know how to deal with SOAP header and if so there are 2 things you may want to know:
1) Passing session using native Domino consumer approach. See example below
TestServiceLocator service = new TestServiceLocator();
TestPort port = service.getTestPort();
// that would tell to save cookie session between calls
((javax.xml.rpc.Stub)port)._setProperty(javax.xml.rpc.Stub.SESSION_MAINTAIN_PROPERTY, Boolean.TRUE);
2) If it does not work for you, you may try to use native SOAP approach. I've blogged about that recently: SOAP and passing session
// Create SOAP Connection
SOAPConnectionFactory soapConnectionFactory = SOAPConnectionFactory.newInstance();
SOAPConnection soapConnection = soapConnectionFactory.createConnection();
// connect to webserivce
SOAPMessage soapResponse = soapConnection.call(connect(username, password), url);
// read cookie from response and use it when send another requests
MimeHeaders session = soapResponse.getMimeHeaders();
String sesisonCookie = session.getHeader("Set-Cookie")[0];
SOAPMessage soapResponse2 = soapConnection.call(customerGetAll(sesisonCookie), url);
soapConnection.close();
and than imagine you are in customGetAll method
SOAPBody soapBody = envelope.getBody();
SOAPElement soapBodyElem = soapBody.addChildElement("Customer_GetAll", "m");
soapMessage.getMimeHeaders().addHeader("Cookie", sesisonCookie);
soapMessage.saveChanges();
Hope it will help.

Read from Half Open Socket

I am trying to connect to Apple Push Notification Service which uses a simple binary protocol over TCP protected with TLS (or SSL). The protocol indicates that when an error is encountered (there are about 10 well defined error conditions) APNS will send back an error response and then close the connection. This results in a half closed socket because the remote peer closed the socket. I can see its a graceful shutdown because APNS sends a FIN and RST using tcpdump.
Out of all the error conditions, I can deal with most before sending with validation. The situation in which this fails is when a notification is sent to an invalid device token which cannot be dealt with that easily because the tokens could be malformed. Tokens are opaque 32 byte values that are provided by APNS to a device and then registered with me. I have no way of knowing if it is valid when submitted to my service. Presumably APNS checksums the tokens in some way that they can do quick validation on the token fast.
Anyway,
I did what I thought was the right thing:-
a. open socket
b. try writing
c. if write failed, read the error response
Unfortunately, this doesn't seem to work. I figure APNS is sending an error response and I am not reading it back right or I am not setting the socket up right. I have tried the following techniques:-
Use a separate thread per socket to try-read the response if any every 5ms or so.
Use a blocking read after write failure.
Use a final read after remote disconnect.
I have tried this with C# + .NET 4.5 on Windows and Java 1.7 on Linux. In either case, I never seem to get the error response and the socket indicates that no data is available to read.
Are half-closed sockets supported on these operating systems and/or frameworks? There isn't anything that seems to indicate either way.
I know that the way I am setting things up works correctly because if I use a valid token with a valid notification, those do get delivered.
In response to one of the comments, I am using the enhanced notification format so a response should arrive from APNS.
Here is the code I have for C#:-
X509Certificate certificate =
new X509Certificate(#"Foo.cer", "password");
X509CertificateCollection collection = new X509CertificateCollection();
collection.Add(certificate);
Socket socket =
new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect("gateway.sandbox.push.apple.com", 2195);
NetworkStream stream =
new NetworkStream(socket, System.IO.FileAccess.ReadWrite, false);
stream.ReadTimeout = 1000;
stream.WriteTimeout = 1000;
sslStream =
new SslStream(stream, true,
new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
sslStream.AuthenticateAsClient("gateway.sandbox.push.apple.com", collection,
SslProtocols.Default, false);
sslStream.ReadTimeout = 10000;
sslStream.WriteTimeout = 1000;
// Task rdr = Task.Factory.StartNew(this.reader);
// rdr is used for parallel read of socket sleeping 5ms between each read.
// Not used now but another alternative that was tried.
Random r = new Random(DateTime.Now.Second);
byte[] buffer = new byte[32];
r.NextBytes(buffer);
byte[] resp = new byte[6];
String erroneousToken = toHex(buffer);
TimeSpan t = (DateTime.UtcNow - new DateTime(1970, 1, 1));
int timestamp = (int) t.TotalSeconds;
try
{
for (int i = 0; i < 1000; ++i)
{
// build the notification; format is published in APNS docs.
var not = new ApplicationNotificationBuilder().withToken(buffer).withPayload(
#'{"aps": {"alert":"foo","sound":"default","badge":1}}').withExpiration(
timestamp).withIdentifier(i+1).build();
sslStream.Write(buffer);
sslStream.Flush();
Console.Out.WriteLine("Sent message # " + i);
int rd = sslStream.Read(resp, 0, 6);
if (rd > 0)
{
Console.Out.WriteLine("Found response: " + rd);
break;
}
// doesn't really matter how fast or how slow we send
Thread.Sleep(500);
}
}
catch (Exception ex)
{
Console.Out.WriteLine("Failed to write ...");
int rd = sslStream.Read(resp, 0, 6);
if (rd > 0)
{
Console.Out.WriteLine("Found response: " + rd); ;
}
}
// rdr.Wait(); change to non-infinite timeout to allow error reader to terminate
I implemented server side for APNS in Java and have problems reading the error responses reliably (meaning - never miss any error response), but I do manage to get error responses.
You can see this related question, though it has no adequate answer.
If you never manage to read the error response, there must be something wrong with your code.
Using a separate thread for reading worked for me, though not 100% reliable.
Use a blocking read after write fail - that's what Apple suggest to do, but it doesn't always work. It's possible that you send 100 messages, and the first has an invalid token, and only after the 100th message you get a write failure. At this point it is sometimes too late to read the error response from the socket.
I'm not sure what you mean there.
If you want to guarantee that the reading of the error responses will work, you should try to read after each write, with a sufficient timeout. This, of course, is not practical for using in production (since it's incredibly slow), but you can use it to verify that your code of reading and parsing the error response is correct. You can also use it to iterate over all the device tokens you have, and find all the invalid ones, in order to clean your DB.
You didn't post any code, so I don't know what binary format you are using to send messages to APNS. If you are using the simple format (that starts with a 0 byte and has no message ID), you won't get any responses from Apple.