vertx Router no response - vert.x

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..

Related

Token mismatch exception when deploying in any VM

Here is my csrf and cors handler of my vertx application
#Log4j2
public class CsrfVerticle extends AbstractVerticle {
private final Set<HttpMethod> httpMethodSet =
new HashSet<>(Arrays.asList(GET, POST, OPTIONS, PUT, DELETE, HEAD));
private final Set<String> headerSet = new HashSet<>(
Arrays.asList("Content-Type", "Authorization", "Origin", "Accept", "X-Requested-With",
"Cookie", "X-XSRF-TOKEN"));
private Connection dbConnection;
private WebClient webClient;
private Vertx vertx;
public void start() throws Exception {
super.start();
HttpServer httpServer = TestService.vertx.createHttpServer();
Router router = Router.router(TestService.vertx);
SessionStore store = LocalSessionStore.create(vertx);
SessionHandler sessionHandler = SessionHandler.create(store)
.setCookieSameSite(CookieSameSite.STRICT)
.setCookieHttpOnlyFlag(false);
router.route().handler(LoggerHandler.create());
if (TestService.serviceConfiguration.isEnableCSRF()) {
router.route()
.handler(CorsHandler.create("*").allowedMethods(httpMethodSet).allowedHeaders(headerSet)
.allowCredentials(true).addOrigin(TestService.serviceConfiguration.getFrontendUrl()));
router.route().handler(
CSRFHandler.create(vertx, csrfSecret()).setCookieHttpOnly(false))
.handler(sessionHandler);
} else {
router.route()
.handler(CorsHandler.create("*").allowedMethods(httpMethodSet).allowedHeaders(headerSet)
.allowCredentials(true)).handler(sessionHandler);
}
dbConnection = createConnection(TestService.serviceConfiguration.getJdbcConfig());
TestAuth testAuth = new TestAuth(TestService.serviceConfiguration.getUsername(),
TestService.serviceConfiguration.getPassword());
AuthenticationHandler basicAuthHandler = BasicAuthHandler.create(testAuth);
router.route("/student/*").handler(basicAuthHandler);
router.route("/student/add").method(HttpMethod.POST).handler(this::handleAddUser);
router.route("/student/get").method(HttpMethod.GET).handler(this::handleGetUser);
router.route("/student/delete").method(HttpMethod.DELETE)
.handler(this::handleDeleteUser);
router.route("/student/update").method(HttpMethod.PUT).handler(this::handleUpdateUser);
httpServer.requestHandler(router).listen(TestService.serviceConfiguration.getPort());
log.info("Console Server Verticle Started Successfully. Listening to {} port",
TestService.serviceConfiguration.getPort());
}
I am able to receive cookies in browser and send it back along with updated X-XSRF-TOKEN attached to the header
Everything works fine in my local but when deploying in VM I get the below error for all post requests
ctx.fail(403, new IllegalArgumentException("Token signature does not match"));
from csrf handler of vertx.
Here are the frontend code to add x-xsrf-token when sending requests to backend
createXsrfHeader(headers: HttpHeaders) {
let xsrfToken = Cookies.get('XSRF-TOKEN')
let sessionToken = Cookies.get('vertx-web.session')
if(xsrfToken)
headers = headers.append('X-XSRF-TOKEN', xsrfToken);
// if(xsrfToken && sessionToken)
// headers = headers.append('Cookie', `XSRF-TOKEN=${xsrfToken}; vertx-web.session=${sessionToken}`);
return headers;
}
[Adding header to post request]
callPostRequest(subUrl: string,reqData: any) {
let headers = new HttpHeaders();
headers = this.createAuthorizationHeader(headers);
headers = this.createXsrfHeader(headers);
return this.http.post<any>(this.basicApiUrl+subUrl, reqData, {
headers: headers,
withCredentials : true
}).pipe(map(resData => {
// console.log(resData);
return resData;
}));
}
[Adding header to put request]
callPutRequest(subUrl: string,reqData: any) {
let headers = new HttpHeaders();
headers = this.createAuthorizationHeader(headers);
headers = this.createXsrfHeader(headers);
return this.http.put<any>(this.basicApiUrl+subUrl, reqData,{
headers: headers,
withCredentials : true
}).pipe(map(resData => {
// console.log(resData);
return resData;
}));
}
[Adding header to delete request]
callDeleteRequest(subUrl: string,reqData?: any) {
let headers = new HttpHeaders();
headers = this.createAuthorizationHeader(headers);
headers = this.createXsrfHeader(headers);
return this.http.delete<any>(this.basicApiUrl+subUrl, {
headers: headers,
withCredentials : true
}).pipe(map(resData => {
// console.log(resData);
return resData;
}));
}
Is there any ways to solve it.
I believe your problem is here:
router.route() // <--- HERE
.handler(
CSRFHandler.create(vertx, csrfSecret())
.setCookieHttpOnly(false))
.handler(sessionHandler);
You are telling the application to create a new CSRF token for each request that is happening, instead of being specific of which end points are really form-based endpoints.
Imagine the following, your form is on /student/form your browser may request:
/student/form (new CSRF token: OK)
/images/some-image-in-the-html.png (new CSRF token: probably Wrong)
/css/styles.css (new CSRF token: probably Wrong)
...
Now the issue is that the 1st call did correctly generated a token, but the following 2+ will generate new tokens too and these won't match the 1st so your tokens are always misaligned.
You probably need to be more specific with the resources you want to protect, from your code I am assuming that you probably want something like:
router.route(""/student/*") // <--- Froms are always here under
.handler(
CSRFHandler.create(vertx, csrfSecret())
.setCookieHttpOnly(false))
.handler(sessionHandler);
Be careful if calling other endpoints would affect the forms too. Note that you can add multiple handlers per route, so you can be more explicit with:
router.route(""/student/add")
// 1st always CSRF checks
.handler(
CSRFHandler.create(vertx, csrfSecret())
.setCookieHttpOnly(false))
// and now we the handler that will handle the form data
.handler(this::handleAddUser)

ReactiveFeignClient - how to propagate headers from controller to client without auth

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.

PUT request from Postman is not being received by Java Controller

I've written multiple REST Endpoints in my Controller [GET, POST and PUT].
GET and POST calls are working fine. But when I try to hit PUT request from Postman, my java controller is not able to receive that request. There is no error message. Response body is empty. Response code in Postman is 200, OK.
Here is my PUT Endpoint which is not able to get request from Postman:
#PutMapping(value = "/devRegistration")
public Object deviceRegistration(HttpServletRequest httpRequest,
#RequestBody(required = false) Map<String, Object> jsonBody ){
ResponseEntity<Object> response = null;
System.out.println("jsonBody = "+jsonBody);
devService.deviceRegistration(httpRequest, jsonBody);
return response;
}
Here is my GET Endpoint which is working fine:
#GetMapping(value = "/checkRegistration")
public void checkRegistration(HttpServletRequest httpRequest,
#RequestParam("appId") String appId, #RequestParam("offset") String offset ){
Map<String, Object> jsonBody = new HashMap<String, Object>();
jsonBody.put("appId", appId);
jsonBody.put("offset", offset);
service.checkRegistration(httpRequest, jsonBody);
}
Postman URL with Headers and Body for PUT request is :
https://localhost:8443/api/device/devRegistration
In Body:
app_Id : some value
offset : some value
Headers are:
RequestHeader
Content-Type
Authorization
MobileHeader
SessionToken
But, System.out.println() is not being executed in PUT Endpoint.
Let me know, if any other info. is required.
I found the answer.
Actually, I need to pass the params in the Body as raw form instead of form-data in Postman.
It worked now.

BodyCodec and pipe method forward status code

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();

RequestBuilder returns empty response

I am using RequestBuilder on the front end of GWT to send a HTTP GET request to a Restlet Web Service. However, the request can get into the web service and the web service return a String (in the format of JSON). The problem is no response is returned when I monitor the process through fireBug. Anybody knows why?
Here is the code:
String url = "http://localhost:8080/Books";
RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url);
try {
builder.sendRequest(null, new RequestCallback() {
public void onError(Request request, Throwable exception)
{
exception.printStackTrace();
Window.alert("fail - " + exception.getMessage());
}
public void onResponseReceived(Request request, Response response)
{
Window.alert("success - " + response.getText());
}
});
} catch (RequestException e)
{
e.printStackTrace();
}
response.getText() always return empty.
Thanks in advance!
Ike
Do you do your call to a Restlet server on the same host and port that served the webpage that makes the request ?
I am guessing you are running into http://en.wikipedia.org/wiki/Same_origin_policy
Your problem is the Same Origin Policy. The protocol, domain and port in all your requests must be the same as those where your GWT app is being served. If you're serving in Eclipse at port 8888 and your custom server is at port 8080, this won't be trivial.
Try configuring an apache server to proxy e.g. requests to http://localhost/gwt-app.html to http://localhost:8888/gwt-app.html and everything else to your server at http://localhost:8080/*