I would like to ask about error handling for streaming with body codec and pipe method.
I am having GateWay and Storage services communicating via HTTP.
A user can send GET request to GateWay which forwards the request to Storage service which responses with the required file.
The problem is when the file is not found. In this case, the user receives a response with status 200 and status message OK.
It seems that response with status 404 is never received by the GateWay Service (or at least not before the response is already sent) but the HttpServerRequest response is ended with status 200.
Is there any way that the GateWay service forwards the status code from the Storage service response to user ????
Thank you
GateWay Service:
httpClientRequest.as(BodyCodec.pipe(request.response().setChunked(true))).rxSend()
.subscribe(
response -> {
if (response.statusCode() != OK_STATUS) {
routingContext.response().setStatusCode(response.statusCode()).end();
}
},
error -> LOG.error("KO")
);
Storage Service:
var options = new OpenOptions().setRead(true).setCreate(false).setWrite(false);
request.pause();
fileSystem
.rxOpen("pathToFile", options)
.flatMapCompletable(asyncFile -> asyncFile.rxPipeTo(request.response()))
.subscribe(
() -> LOG.info("OK"),
error -> {
request.resume();
routingContext.response()
.setStatusCode(404)
.setStatusMessage("File not found")
.end();
}
);
Communication schema
The WebClient pushes response bytes to the body codec before the Single returned by rxSend completes. This is by design, as the WebClient only operates by buffering content before handing it over to the user.
For your use case, it would be better to work with the raw HttpClient.
request.response().setChunked(true);
HttpClientRequest httpClientRequest = httpClient.get(port, host, requestURI);
httpClientRequest
.handler(httpClientResponse -> {
if (httpClientResponse.statusCode() != OK_STATUS) {
routingContext.response().setStatusCode(httpClientResponse.statusCode()).end();
} else {
httpClientResponse.pipeTo(request.response(), ar -> {
if (ar.failed()) {
LOG.error("KO");
}
});
}
}).exceptionHandler(t -> handleConnectionFailure(t)).end();
Related
I wrote a dispatcher which routing a request to backend server, and response from backend is encrypted. When I decrypt the response body and write to RoutingContext response. Client can't receive response.
code like below
Router router;
router.routeWithRegex(patter).failureHandler(this::onFailure).handler(this::onRequest);
private void onRequest(Routing context){
...
HttpClient client = vertx.createHttpClient(new HttpClientOptions());
HttpClientRequest requestToBackend =
client.request(method, port, backendHost, uri, backendRsp -> onBackendResponse(context, backendRsp));
context.request().handler(body -> handleReq(requestToBackend));
context.request().endHandler((v) -> requestToBackend.end());
}
private void onBackendResponse(RoutingContext context, io.vertx.core.http.HttpClientResponse backendRsp) {
....
backendRsp.handler(data -> {
byte[] decrypt = decrypt(data);
context.request().response().write(data); // this works fine
// context.request().response().write(Buffer.buffer(decrypt)); // change to this, client can't receive response then
});
backendRsp.endHandler((v) -> context.request().response().end());
}
finally worked out, I changed the response body (shorter than origin), but not modify the header "content-length". so client keep waiting..
I received X-Forwarded-Host and X-Forwarded-Proto in my controller endpoints, and the endpoint has a reactive pipeline to call a ReactiveFeignClient class.
These headers should be propagated to my client requests, but as I see it, it has not. I have no Principal in this pipeline, because the endpoints needs no auth, so I cannot use ReactiveSecurityContextHolder.withAuthentication(user)
I already added a WebFilter to read headers from request:
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange).subscriberContext((context) -> {
ServerHttpRequest request = exchange.getRequest();
Map<String, String> headers = (Map)request.getHeaders().toSingleValueMap().entrySet().stream().filter((entry) -> {
return ((String)entry.getKey()).equalsIgnoreCase(this.authLibConfig.getXForwardedHostHeader()) || ((String)entry.getKey()).equalsIgnoreCase(this.authLibConfig.getXForwardedProtoHeader());
}).collect(Collectors.toMap(Entry::getKey, Entry::getValue));
System.out.println("Adding all headers now: ");
context.put("headers_to_propagate", headers);
return context;
});
}
But I don't know where in the config of client can I retrieve them from the Context and put into requests in client.
Now I do this:(
#Bean
public ReactiveHttpRequestInterceptor forwardingHeadersInterceptor(ReactiveFeignUtils reactiveFeignUtils) {
return reactiveFeignUtils::mutateRequestHeadersForNoAuthRequests;
}
And:
public Mono<ReactiveHttpRequest> mutateRequestHeadersForNoAuthRequests(ReactiveHttpRequest reactiveHttpRequest) {
return Mono.subscriberContext().doOnNext((context) -> {
System.out.println("Current context: " + context.toString());
if (context.hasKey("headers_to_propagate")) {
System.out.println("Getting all host headers: ");
reactiveHttpRequest.headers().putAll((Map)context.get("headers_to_propagate"));
}
}).thenReturn(reactiveHttpRequest);
}
But no headers are forwarded.
I ended up creating a customized class implementing Authentication and add these fields as metadata property to it; because even though this endpoint requires no auth, headers related to member id and other auth info are received, so I can construct an Authentication principal.
Actually as I see, working with this object is the only way.
I am trying to connect to AWS API gateway using the Vertx Webclient:
HttpRequest<Buffer> request = webClient.postAbs(targetHost);
request.putHeader("Authorization", auth);
request.putHeader("Content-Type", contentType);
request.putHeader("Host", hostName);
request.sendJson(new JsonObject(jsonData), response -> {
if (response.succeeded()) {
final JsonObject result = response.result().bodyAsJsonObject();
logger.info(result.toString());
routingContext.response()
.setStatusCode(200)
.putHeader("Content-Type", "application/json")
.end(result.toString());
} else {
logger.error(response.cause().getMessage());
routingContext.fail(new Exception(response.cause().getMessage()));
}
});
and always am receiving the same error response i.e. The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method.
But if I use a javax.ws.rs.client.WebTarget,
Client client = ClientBuilder.newClient();
WebTarget target = client.target(targetHost);
response=target.request()
.header("Authorization", auth)
.header("Content-Type", contentType)
.header("Host", hostName)
.post(jsonData,Response.class);
I am able to receive a proper response.
Any idea what Vertx web client is doing differently?
So we figured out the issue, turns out the json data that we were using was not being passed properly in the Vertx implementation.
So we can just create a new JSONObject, create the data and use this object to eveluate the aws signature as well as pass the same object in the actual API call.
Thanks!
With OkHTTP I have two methods for configuring redirects -
followRedirects(boolean followRedirects) -> Configures whether to allow redirection at all.
followSslRedirects(boolean followProtocolRedirects) -> Configures whether to allow HTTP -> HTTPS and HTTPS -> HTTP redirects.
Though what I am looking to achieve is to allow HTTP -> HTTPS redirects while simultaneously blocking HTTPS -> HTTP redirection. The only way I can think of is to add an OkHTTP Interceptor as shown in the code below to check for request and response url protocols. Though I am not sure if it's the most optimum/correct way to achieve this as we are still making the calls to redirected url while doing chain.proceed(request). Can anyone points me toward a better approach here. Thanks.
new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
if (!isValidRedirect(request.url(), response.request().url())) {
throw new IOException("Invalid redirect from secure server to insecure server");
}
return response;
}
})
.build();
private static boolean isValidRedirect(HttpUrl url, HttpUrl newUrl) {
//unless it's https, don't worry about it
if (!url.url().getProtocol().equals("https")) {
return true;
}
// If https, verify that we're on the same server.
// Not being so means we got redirected from a secure link to a
// different link, which isn't acceptable.
return url.url().getHost().equals(newUrl.url().getHost());
}
I'm a GWT beginner. I debug my program in GWT development mode. The url is http://127.0.0.1:8888/Replayer.html?gwt.codesvr=127.0.0.1:9997.
I want to get data from existing server which provided data in json format. My code is:
String url = "http://i.abc.com?sid=" + mSessionId + "&action=info";
RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, URL.encode(url));
try {
Request request = builder.sendRequest(null, new RequestCallback() {
public void onError(Request request, Throwable exception) {
// Couldn't connect to server (could be timeout, SOP
// violation, etc.)
Window.alert("Get fudao info error");
mPrepare = false;
}
#Override
public void onResponseReceived(Request request, Response response) {
GWT.log("statuscodeļ¼"+response.getStatusCode());
if (200 == response.getStatusCode()) {
// Process the response in response.getText()
Window.alert(response.getText());
mPrepare = true;
} else {
// Handle the error. Can get the status text from
// response.getStatusText()
Window.alert("Get fudao info wrong");
mPrepare = false;
}
}
});
} catch (RequestException e) {
// Couldn't connect to server
}
When run the application, the request failed and its status was "canceled". Is it the reason that I cannot request remote server address from localhost for SOP restrictions?
How to fetch data of remote server in GWT development mode?
Normally can't fetch data from another server form GWT client code. But your local server can serve as proxy, e.g. you sending request to your local server, it will send request to remote server, than it will get response from remote server and give it to the GWT client code. This is basically the easiest solution.