How do I add a delay with data from the Mono? - java

I have a service that is returning a value which contains delay information.
public Mono<R> authenticate(
A authenticationRequest,
#RequestHeader Map<String, String> headers,
ServerHttpResponse serverHttpResponse) {
final AuthServiceResponse<R> authenticationResponse = authService.authenticate(authenticationRequest, headers);
serverHttpResponse.setStatusCode(authenticationResponse.getStatusCode());
return Mono.just(authenticationResponse.getOperationResponse())
.delayElement(authenticationResponse.getDelay());
}
I'd like to try to convert it so it is reactive I got this far...
public Mono<R> authenticate(
A authenticationRequest,
#RequestHeader Map<String, String> headers,
ServerHttpResponse serverHttpResponse) {
return authService.authenticate(authenticationRequest, headers)
.map(authenticationResponse->{
serverHttpResponse.setStatusCode(authenticationResponse.getStatusCode());
return authenticationResponse.getOperationResponse()
});
...
but I wasn't sure how to add the "delayElement" capability.

You can use Mono.fromCallable + delayElement within a flatMap like this:
return authService.authenticate(authenticationRequest, headers)
.flatMap(authenticationResponse -> {
return Mono.fromCallable(() -> authenticationResponse.getOperationResponse())
.delayElement(authenticationResponse.getDelay())
});
One thing to note... you cannot pass ServerHttpResponse in this situation as a parameter, but you have ServerWebExchange which has the request and response along with the headers. The complete solution is
public Mono<R> authenticate(
#RequestBody SimpleAuthenticationRequest authenticationRequest,
ServerWebExchange serverWebExchange) {
return authService
.authenticate(authenticationRequest, serverWebExchange.getRequest().getHeaders())
.doOnNext(
serviceResponse ->
serverWebExchange.getResponse().setStatusCode(serviceResponse.getStatusCode()))
.flatMap(
serviceResponse ->
Mono.fromCallable(serviceResponse::getOperationResponse)
.delayElement(serviceResponse.getDelay()));
}

Try this to add delay based on your authenticationResponse.getDelay() value
public Mono<Object> authenticate(Object authenticationRequest,#RequestHeader Object headers,
Object serverHttpResponse) {
return authenticate(authenticationRequest,headers)
.flatMap(authenticationResponse -> {
Mono<String> delayElement = Mono.just("add delay")
.delayElement(Duration.ofSeconds(authenticationResponse.getDelay()));
Mono<Object> actualResponse =Mono.just(authenticationResponse.getOperationResponse());
return Mono.zip(delayElement,actualResponse).map(tupleT2 -> tupleT2.getT2());
});
}
let me know if it doesn't work. i will try to find other way.

Related

In Spring Cloud gateway API, how add dynamic headers or query parameter?

I am working on application in which I am using Spring Cloud gateway and in front-end Angular 2, for login I am using Keyclock SSO.
However, after getting logged in I need to send some user information in front-end in some encrypted format, either via headers or query parameters. For that in Spring Cloud gateway I have written below code but it's not working.
This is my custom GlobalFilter where I try to add it headers and parameters but in front-end I am not getting it.
#Component
public class InterceptorFilterGatewayFilterFactory extends AbstractGatewayFilterFactory<InterceptorFilterGatewayFilterFactory.Config> {
public InterceptorFilterGatewayFilterFactory() {
super(Config.class);
}
#Override
public Config newConfig() {
return new Config();
}
public static class Config {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
#Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest sr = exchange.getRequest();
Mono var10000 = exchange.getPrincipal().filter((principal) -> principal instanceof OAuth2AuthenticationToken)
.map(p -> p).map((p) -> {
LinkedHashSet<URI> attr = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
ServerHttpRequest request = exchange.getRequest();
// Here I try to add query parameter
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.put("e", Collections.singletonList(toHexString(ja.toString().getBytes())));
URI newUri = UriComponentsBuilder.fromUri(request.getURI())
.replaceQueryParams(unmodifiableMultiValueMap(queryParams))
.build(true).toUri();
ServerHttpRequest updatedRequest = exchange.getRequest().mutate().uri(newUri)
.build();
// Here I add header
updatedRequest.mutate().header("e", toHexString(ja.toString().getBytes())).build();
return exchange.mutate().request(updatedRequest).build();
}).defaultIfEmpty(exchange).flatMap(chain::filter);
}
return var10000;
};
}
And in application yml file I am providing route as follow:
- id: appDepartmentWise
predicates:
- Path=/app/*/sso_login
- Method=GET,POST
uri: http://localhost:9000/app/
filters:
- PreserveHostHeader
- RewritePath=/.*, /app/index.html
- InterceptorFilter
- AddRequestParameter=e,*
What configuration is missing, and how to add dynamic Header or Query Parameters?
In case if it helps any one to add dynamic query parameter and redirect page you can do it using below code:
#GetMapping("/user")
public Mono<Void> sendRedirect(ServerWebExchange exchange, Principal p) {
// Here we can can create string which can have dynamic parameters
String responseData = "e="+p..getName();
return Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.FOUND);
// this used to get the base URL.
UriComponents uriComponents = UriComponentsBuilder.fromUri(exchange.getRequest().getURI()).replacePath(exchange.getRequest().getPath().contextPath().value()).replaceQuery((String)null).fragment((String)null).build();
// As below we can re-generate the URL and redirect it.
response.getHeaders().setLocation(URI.create(uriComponents.toUriString()+"/app/Dept"+"/sso_login?"+responseData));
});
}

au.com.dius.pact.consumer.PactMismatchesException: The following requests were not received:

This test case is for mocking the health check contract.
TestClass
#Pact(consumer = "Consumer")
public RequestResponsePact getHealthCheck(PactDslWithProvider builder) {
Map<String, String> headers = new HashMap<String, String>();
headers.put("Content-Type", "text/plain");
headers.put("callchainid", "a4275861-f60a-44ab-85a6-c0c2c9df5e27");
return builder
.given("get health check")
.uponReceiving("get health data")
.path("/health")
.method("GET")
.headers(headers )
.willRespondWith()
.status(200)
.body("{\"status\":\"UP\",\"components\":{\"db\":{\"status\":\"UP\",\"details\":{\"database\":\"PostgreSQL\",\"validationQuery\":\"isValid()\"}}}}")
.toPact();
}
#Test
#PactTestFor(pactMethod = "getHealthCheck")
void getHealthData(MockServer mockServer) {
WebClient webClient=WebClient.builder().baseUrl(mockServer.getUrl()).build();
final String callChainId="a4275861-f60a-44ab-85a6-c0c2c9df5e27";
ThreadContext.put(CallChainIdService.HEADER_NAME, callChainId);
AsyncClient asyncClient=new AsyncClient(webClient);
Mono<ClientResponse> responseMono=asyncClient.getHealthCheck();
System.out.println(responseMono);
}
Here the webclient end point code which i am trying to hit,
AsyncClient Class
private final WebClient CLIENT;
#Override
public Mono<ClientResponse> getHealthCheck() {
return get(MediaType.TEXT_PLAIN, "/health");
}
private Mono<ClientResponse> get(MediaType contentType, String uri, Object... params) {
return CLIENT
.mutate()
.defaultHeader(CallChainIdService.HEADER_NAME, ThreadContext.get(CallChainIdService.HEADER_NAME))
.build()
.get()
.uri(uri, params)
.accept(contentType)
.exchange();
}
When i run the test , i got PactMismatchesException: The following requests were not received.
au.com.dius.pact.consumer.PactMismatchesException: The following requests were not received:
method: GET
path: /health
query: {}
headers: {callchainid=[a4275861-f60a-44ab-85a6-c0c2c9df5e27], Content-Type=[text/plain]}
matchers: MatchingRules(rules={})
generators: Generators(categories={})
body: MISSING
I am not sure what i am doing wrong here. Appreciate your inputs and help
It looks like you have content-type as the expected header you send in the GET call, but in fact you send only an accept header (both with the media type text/plain).
I think your test should be updated to use accept.

Spring Webclient decode custom application/multipart-related,application/dicom (Wado-RS)

I'm trying to decode a multipart-related request that is just a simple multi files download but with a specific content type by part (application/dicom and not application/octet-stream).
Since the structure of the response body might be identical, I could just tell the "multipart codec" to treat that content type as an octet-stream.
public Flux<FilePart> getDicoms(String seriesUri) {
return webClient.get()
.uri(seriesUri)
.accept(MediaType.ALL)
.retrieve()
.bodyToFlux(FilePart.class);
}
How can I do that?
An easier way of reading a multipart response:
private Mono<ResponseEntity<Flux<Part>>> queryForFiles(String uri)
final var partReader = new DefaultPartHttpMessageReader();
partReader.setStreaming(true);
return WebClient.builder()
.build()
.get()
.uri(wadoUri)
.accept(MediaType.ALL)
.retrieve()
.toEntityFlux((inputMessage, context) -> partReader.read(ResolvableType.forType(DataBuffer.class), inputMessage, Map.of())))
This is what I've done to make it work. I used directly the DefaultPartHttpMessageReader class to do it cleanly (spring 5.3).
public Flux<Part> getDicoms(String wadoUri) {
final var partReader = new DefaultPartHttpMessageReader();
partReader.setStreaming(true);
return WebClient.builder()
.build()
.get()
.uri(wadoUri)
.accept(MediaType.ALL)
//.attributes(clientRegistrationId("keycloak"))
.exchange()
.flatMapMany(clientResponse -> {
var message = new ReactiveHttpInputMessage() {
#Override
public Flux<DataBuffer> getBody() {
return clientResponse.bodyToFlux(DataBuffer.class);
}
#Override
public HttpHeaders getHeaders() {
return clientResponse.headers().asHttpHeaders();
}
};
return partReader.read(ResolvableType.forType(DataBuffer.class), message, Map.of());
});
}

render http response in callback

I have been reading the Micronaut documentation but I cannot find the way to render the http response in a callback as I can do it for instance with Jax-Rs Jersey.
Here what I want to achieve
#Get("/scalaFuture")
public void getScalaFuture() {
Futures.successful(new SpringBootEntityDaoDTO())
.onComplete(result -> {
if (result.isSuccess()) {
return HttpResponse.ok(result.get());
} else {
return HttpResponse.serverError(result.failed().get());
}
}, ExecutorContextUtil.defaultExecutionContext());
}
Basically render the response in the callback of the future.
Something similar as I do with JaxRS in the Observable callback using AsyncResponse
#POST
#Path("/bla")
public void foo(#Suspended final AsyncResponse asyncResponse) {
Observable<EntityDaoDTO> observable = observableFosConnectorManager.execute("EntityAggregateRoot", "database", getEntityDaoDTO(), null, MethodDTO.CREATE);
observable
.subscribeOn(Schedulers.computation())
.subscribe(result -> {
EntityPayLoad entityPayLoad = new EntityPayLoad();
entityPayLoad.setTitle(result.getTitle());
entityPayLoad.setDescription(result.getDescription());
asyncResponse.resume(Response.status(Response.Status.OK.getStatusCode()).entity(entityPayLoad).build());
}, t -> asyncResponse.resume(Response.status(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()).build()),
() -> getLogger().info(null, "Subscription done"));
}
Regards
Micronaut allows different return types including reactive responses.
For example, you can return a CompletableFuture:
#Controller("/people")
public class PersonController {
Map<String, Person> inMemoryDatastore = new ConcurrentHashMap<>();
#Post("/saveFuture")
public CompletableFuture<HttpResponse<Person>> save(#Body CompletableFuture<Person> person) {
return person.thenApply(p -> {
inMemoryDatastore.put(p.getFirstName(), p);
return HttpResponse.created(p);
}
);
}
}
Convert your scala future to a Java completable future: https://stackoverflow.com/a/46695386/2534803
https://docs.micronaut.io/latest/guide/index.html#_binding_using_completablefuture

springframework get all request headers

I know that spring3 has #RequestHeader to get a single request header in a controller. I'm wondering if there is an easy way to get ALL the request headers? I'm hoping for something like this:
#RequestMapping(value="/some/url",RequestMethod.GET)
public void endpoint(RequestParams params, BindingResult result, #RequestHeader MultiValueMap<String,String> headers, HttpServletRequest request, ModelMap model) {
}
Currently I'm doing something like this:
MultiValueMap<String,String> headers = new HttpHeaders();
for (Enumeration names = request.getHeaderNames(); names.hasMoreElements();) {
String name = (String)names.nextElement();
for (Enumeration values = request.getHeaders(name); values.hasMoreElements();) {
String value = (String)values.nextElement();
headers.add(name,value);
}
}
From the Javadocs:
#RequestHeader can be used on a Map, MultiValueMap, or HttpHeaders method parameter to gain access to all request headers.
More info is available online here and there.
if you don't want to read doc:
mappingMethodName(#RequestHeader Map<String, String> headers) {
headers.forEach((key, value) -> {
System.out.printf("Header '%s' = %s%n", key, value);
});
}

Categories

Resources