Vertx client gets clientabort exception - https

I am geting clientabort sockettimeout read exception at server side while invoking a rest service through https client inside vertx application. If i invoke a http setvice, it works fine though.
I get 200 ok in vertx and do not get any data back. And also i get connection was closed error in vertx.
Any idea why it happens. Help appreciated.
Code:
final HttpClient httpClient1 = vertx.createHttpClient(
new HttpClientOptions()
.setDefaultHost("localhost")
.setDefaultPo‌​rt(8443)
.setSsl(true‌​)
.setKeepAlive(true)‌
​.setMaxPoolSize(100)‌​
.setTrustAll(true)
);
‌​HttpClientRequest req = httpClient1.request(HttpMethod.POST, "/api/test/");
req.headers()
.set("Content-Length","10000000")‌​
.set("Content-Type",‌​"application/json")
.‌​set("Cache-Control", "no-transform, max-age=0");
Buffer body=Buffer.buffer("Hello World");
req.write(body);

That's not the way to send a request.
Hope working example may help you.
Server:
final Vertx vertx = Vertx.vertx();
vertx.createHttpServer().requestHandler((c) -> {
c.bodyHandler(b -> {
System.out.println(b.toString());
});
c.response().end("ok");
}).listen(8443);
Client:
final HttpClient client = vertx.createHttpClient(
new HttpClientOptions()
.setDefaultHost("localhost")
.setDefaultPort(8443));
client.request(HttpMethod.POST, "/", (r) -> {
System.out.println("Got response");
}).putHeader("Content-Length", Integer.toString("Hello".length()))
.write("Hello").end();
Two important notes: you must end .request() with .end() and you must set Content-Length correctly.

Related

Stream response to downstream clients in SpringBoot

I have a controller proxy api endpoint where it receives different request payloads which are intended to different services. This controller validates payload and adds few headers based on certain rules. In this current context, i do not want to parse the received response from upstream services. proxy method should simply stream response to downstream clients so that it can scale well without going into any memory issues when dealing with large response payloads.
I have implemented method like this:
suspend fun proxyRequest(
url: String,
request: ServerHttpRequest,
customHeaders: HttpHeaders = HttpHeaders.EMPTY,
): ResponseEntity<String>? {
val modifiedReqHeaders = getHeadersWithoutOrigin(request, customHeaders)
val uri = URI.create(url)
val webClient = proxyClient.method(request.method!!)
.uri(uri)
.body(request.body)
modifiedReqHeaders.forEach {
val list = it.value.iterator().asSequence().toList()
val ar: Array<String> = list.toTypedArray()
#Suppress("SpreadOperator")
webClient.header(it.key, *ar)
}
return webClient.exchangeToMono { res ->
res.bodyToMono(String::class.java).map { b -> ResponseEntity.status(res.statusCode()).body(b) }
}.awaitFirstOrNull()
}
But this doesn't seems to be streaming. When i try to download large file, it is complaining failed to hold large data buffer.
Can someone help me in writing reactive streamed approach?
This is what i have done finally.
suspend fun proxyRequest(
url: String,
request: ServerHttpRequest,
response: ServerHttpResponse,
customHeaders: HttpHeaders = HttpHeaders.EMPTY,
): Void? {
val modifiedReqHeaders = getHeadersWithoutOrigin(request, customHeaders)
val uri = URI.create(url)
val webClient = proxyClient.method(request.method!!)
.uri(uri)
.body(request.body)
modifiedReqHeaders.forEach {
val list = it.value.iterator().asSequence().toList()
val ar: Array<String> = list.toTypedArray()
#Suppress("SpreadOperator")
webClient.header(it.key, *ar)
}
val respEntity = webClient
.retrieve()
.toEntityFlux<DataBuffer>()
.awaitSingle()
response.apply {
headers.putAll(respEntity.headers)
statusCode = respEntity.statusCode
}
return response.writeWith(respEntity.body ?: Flux.empty()).awaitFirstOrNull()
}
Let me know if this is truly sending data downstream and flushing?
Your first code snippet fails with memory issues because it is buffering in memory the whole response body as a String and forwards it after. If the response is quite large, you might fill the entire available memory.
The second approach also fails because instead of returning the entire Flux<DataBuffer> (so the entire response as buffers), you're only returning the first one. This fails because the response is incomplete.
Even if you manage to fix this particular issue, there are many other things to pay attention to:
it seems you're not returning the original response headers, effectively changing the response content type
you should not forward all the incoming response headers, as some of them are really up to the server (like transfer encoding)
what happens with security-related request/response headers?
how are you handling tracing and metrics?
You could take a look at the Spring Cloud Gateway project, which handles a lot of those subtleties and let you manipulate requests/responses.

Webflux streaming Nginx exception

I have Spring Cloud SSE #webflux streaming service, and my front connect with it via Nginx. Some times I have this:
Failed to load resource: net::ERR_CONTENT_LENGTH_MISMATCH
the server responded with a status of 504 (Gateway Timeout)
Event source with retry again reconnect and it works.
Front code looks like this
this.source = new EventSource("/comment/stream", { });
this.source.addEventListener("message", function (event)
backend side
#GetMapping(path = "/comment/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Comment> feed() {
return this.commentRepository.findAll();
}
Configuring netty with timeout does not help.
it was from real timout 60s from spring cloud gateway in front of this streaming service.

Angular 4 httpd post call fails in iOS

I am having a weird problem in a POST call in iOS using Nativescript and Angular 4. I have tested the same code in Android and works perfect. The server does not register any error but the call fails. This is the code I am using right now:
...
import { Http, Headers, Response, RequestOptions } from "#angular/http";
...
let headers = new Headers();
headers.append("Content-Type", "application/x-www-form-urlencoded");
let options = new RequestOptions({ headers: headers });
let body =
"Username=" + this.username
+"&Password=" + this.password;
this.http.post("http://myserver.com/api/login", body, options)
.map(result => result.json())
.subscribe(
result => {
this.loader.hide();
if (result.Authenticated) {
// Do something...
}
else {
Dialogs.alert({title: 'Error', message: "Username and/or password are incorrect.", okButtonText: "Close"});
}
},
error => {
Dialogs.alert({title: 'Error', message: "There was an error trying to send the request, please try again later.", okButtonText: "Close"});
}
)
The app shows me There was an error trying to send the request, please try again later. message, but there is no error in the server and the curious thing is that Android works fine.
I have enabled the enableProdMode() in Angular and no luck.
Any help please?
Thanks!
We were having crashing and hanging issues on Nativescript iOS + http connections until yesterday. We switched to https connections for our servers' API and everything is working perfectly now.
Maybe it has something to do with the secure / non secure connections on iOS?
Try switching to a secure connection and see what happens.
If you can't do that, try adding this to your info.plist file to allow http connections:
<key>NSAppTransportSecurity</key>
<dict>
<!--Include to allow all connections (DANGER)-->
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>

Enable authenticator manually

Currently my client authenticates request only on case of 401 response:
this.client.authenticator(new okhttp3.Authenticator() {
public Request authenticate(Route route, Response response) throws IOException {
String credentials = authenticator.getCredentials();
if (credentials.equals(response.request().header("Authorization"))) {
throw new TraversonException(401, "Unauthorized", response.request().url().toString());
} else {
defaultHeader("Authorization", credentials);
Request.Builder newRequest = response.request().newBuilder()
.headers(Headers.of(defaultHeaders));
return newRequest.build();
}
});
But I'd like to change this behavior and be able to call it either manually or auto per first call? Is it possible somehow?
If the authentication is predictably required and not related to a proxy, then the solution is to implement an Interceptor instead of Authenticator.
OkHttpClient.Builder clientBuilder = ...;
clientBuilder.networkInterceptors().add(0, myInterceptor);
client = clientBuilder.build();
Example Interceptor https://github.com/yschimke/oksocial/blob/48e0ca53b85e608443eab614829cb0361c79aa47/src/main/java/com/baulsupp/oksocial/uber/UberAuthInterceptor.java
n.b. There is discussion around possible support for this usecase in https://github.com/square/okhttp/pull/2458. One issue with current Authenticator API is that it assumes a Response from the failed (401) request.

ALM 12 REST using SpringFramework RestTemplate: "401 QCSession cookie missing"

In ALM 12 we have to explicity call "qcbin/rest/site-session" to get session.
When I GET call "/qcbin/rest/site-session" I receive the following:
"Set-Cookie=[BIGipServerALMAST330P-QC=rd100o00000000000000000000ffff0fe0dd74o8080; path=/, ]""
I extract the cookie as described here:
HP ALM 12 REST not returning QCSession cookie.
Instead of this RestConnector, our project is using RestTemplate from SpringFramework, so I did:
private HashMap getQCSession() throws Exception {
URL url = new URL("https://almxxxx.saas.hp.com/qcbin/rest/site-session?login-form-required=y");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/xml");
conn.setRequestProperty("Accept", "application/xml");
conn.connect();
HashMap cookies = updateCookies(conn);
return cookies;
}
public HashMap updateCookies(HttpURLConnection conn) {
String cookie2 = conn.getHeaderField("Set-Cookie");
int equalIndex = cookie2.indexOf('=');
int semicolonIndex = cookie2.indexOf(';');
String cookieKey = cookie2.substring(0, equalIndex);
String cookieValue = cookie2.substring(equalIndex + 1, semicolonIndex);
HashMap cookies = new HashMap();
cookies.put(cookieKey, cookieValue);
System.out.println(cookies.toString());
return cookies;
}
To send the cookie in the GET call using the RestTemplate I followed the instructions from http://springinpractice.com/2012/04/08/sending-cookies-with-resttemplate/, so I did:
public <U extends Object> ResponseEntity<U> getFromURL(URI url, Class<U> responseBodyClass) throws Exception {
logger.info("GET na URL: {} esperando classe: {} no response", url, responseBodyClass);
HashMap cookie = this.getQCSession();
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add("Cookie", this.getQCSession().toString());
this.httpEntity = new HttpEntity(null, requestHeaders);
return super.exchange(url, HttpMethod.GET, this.httpEntity, responseBodyClass);
}
The requestHeaders content added to the HttpEntity (SpringFramework) is:
{Cookie=[{BIGipServerALMAST330P-QC=rd100o00000000000000000000ffff0fe0dd74o8080}]}
However I'm still getting "401 QCSession cookie missing".
I already tried to send in the GET call the JSESSIONID, with no success as well.
I appreciate any help.
Any clue?
I ran into this. As of ALM12 you need to create a session also.
I POST some XML or JSON to here "/authentication-point/alm-authenticate" to authenticate
Then collect the Set-Cookie header
I then POST to "/rest/site-session" with the cookie from the previous response.
I collect the session cookies from that response to use in my subsequent requests.
Hope that helps
I don't know, if it can help you but you are sending it with query param for UI authentication.
"POST .../rest/site-session?login-form-required=y"
I would suggest to POST it without query param
"POST .../rest/site-session"
Also the order of actions you should do before asking for QCSession token is:
1.Check whether you are authenticated
"GET .../rest/is-authenticated"
2.If not you'll get reference where to authenticate: WWW-Authenticate: LWSSO realm=".../authentication-point"
3.Send basic auth header to authentication point with added alm-authenticate at the end. Which returns you LWSSO_COOKIE_KEY.
"POST .../authentication-point/alm-authenticate"
Authentication: Basic BASE64{username:password}
4.Then you'll need to POST that LWSSO_COOKIE_KEY to site-session and ALM will return you QCSession key.
"POST .../rest/site-session"
"Cookie: LWSSO_COOKIE_KEY={cookie}; Path=/"
Hopefully I was able to help you.
If you still have problems, feel free to contact me.

Resources