I am developing a Microservice application in SpringBoot. I am using Spring Cloud gateway there,now since Spring Cloud Gateway uses WebFlux module so,I want to extract username and password inside ServerAuthenticationConverter. But unfortunately flow is getting stuck on subscribe() method.
#Component
public class MyConverter implements ServerAuthenticationConverter {
#Override
public Mono<Authentication> convert(ServerWebExchange exchange) {
String token = exchange.getRequest().getHeaders().getFirst("token");
Map<String,String> credentialMap = new HashMap<>();
if(StringUtils.containsIgnoreCase(exchange.getRequest().getPath().toString(),"/login")){
exchange.getFormData().subscribe(data -> {
for(Map.Entry<String,List<String>> mapEntry : data.entrySet()) {
for (String value : mapEntry.getValue()) {
credentialMap.put(mapEntry.getKey(),value);
log.info("key=" + mapEntry.getKey() + "|value=" + mapEntry.getValue());
}
}
});
User user = new User(credentialMap.get("username"),credentialMap.get("password"));
return Mono.justOrEmpty(new UsernamePasswordAuthenticationToken(user,credentialMap.get("password"), List.of(new SimpleGrantedAuthority("ADMIN"))));
}
else{
if(StringUtils.isNotBlank(token)){
if(StringUtils.contains(token,"Bearer")){
return Mono.justOrEmpty(new MyToken(AuthorityUtils.NO_AUTHORITIES,token.substring(7)));
}else{
return Mono.justOrEmpty(new MyToken(AuthorityUtils.NO_AUTHORITIES,token));
}
}
}
}
throw new IllegalArgumentException("Invalid Access");
}
}
But after printing log statement within subscribe method program flow is getting halted,no exception.
I think subscribe() method is causing some thread level issue.Can someone figureout the problem????
Related
Hello I am new to Webflux I follow a tutorial for building reactive microservices. In my project I faced the following problem.
I want to create a crud api for the product service and the following is the Create method
#Override
public Product createProduct(Product product) {
Optional<ProductEntity> productEntity = Optional.ofNullable(repository.findByProductId(product.getProductId()).block());
productEntity.ifPresent((prod -> {
throw new InvalidInputException("Duplicate key, Product Id: " + product.getProductId());
}));
ProductEntity entity = mapper.apiToEntity(product);
Mono<Product> newProduct = repository.save(entity)
.log()
.map(mapper::entityToApi);
return newProduct.block();
}
The problem is that when I call this method from postman I get the error
"block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-3" but when I use a StreamListener this call works ok. The stream Listener gets events from a rabbit-mq channel
StreamListener
#EnableBinding(Sink.class)
public class MessageProcessor {
private final ProductService productService;
public MessageProcessor(ProductService productService) {
this.productService = productService;
}
#StreamListener(target = Sink.INPUT)
public void process(Event<Integer, Product> event) {
switch (event.getEventType()) {
case CREATE:
Product product = event.getData();
LOG.info("Create product with ID: {}", product.getProductId());
productService.createProduct(product);
break;
default:
String errorMessage = "Incorrect event type: " + event.getEventType() + ", expected a CREATE or DELETE event";
LOG.warn(errorMessage);
throw new EventProcessingException(errorMessage);
}
}
}
I Have two questions.
Why this works with The StreamListener and not with a simple request?
Is there a proper way in webflux to return the object of the Mono or we always have to return a Mono?
Your create method would want to look more like this and you would want to return a Mono<Product> from your controller rather than the object alone.
public Mono<Product> createProduct(Product product) {
return repository.findByProductId(product.getProductId())
.switchIfEmpty(Mono.just(mapper.apiToEntity(product)))
.flatMap(repository::save)
.map(mapper::entityToApi);
}
As #Thomas commented you are breaking some of the fundamentals of reactive coding and not getting the benefits by using block() and should read up on it more. For example the reactive mongo repository you are using will be returning a Mono which has its own methods for handling if it is empty without needing to use an Optional as shown above.
EDIT to map to error if entity already exists otherwise save
public Mono<Product> createProduct(Product product) {
return repository.findByProductId(product.getProductId())
.hasElement()
.filter(exists -> exists)
.flatMap(exists -> Mono.error(new Exception("my exception")))
.then(Mono.just(mapper.apiToEntity(product)))
.flatMap(repository::save)
.map(mapper::entityToApi);
}
I am using Spring Boot 2.1.14.RELEASE, Java8, Spring Boot.
I have a client from which I have to access another rest service.
I need to retry an Http404 and HTTP500 2 times whereas not retry any other exceptions.
I am using RestTemplate to invoke the rest service like this:
restTemplate.postForEntity(restUrl, requestEntity, String.class);
I looked into using Retryable as well as RetryTemplate and implemented the retry functionality using RetryTemplate.
I have implemented this in 2 ways:
OPTION1:
The RetryTemplate bean is:
#Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(retryProperties.getDelayForCall());
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
retryTemplate.setRetryPolicy(exceptionClassifierRetryPolicy);
return retryTemplate;
}
ClassifierRetryPolicy is:
#Component
public class ExceptionClassifierRetryPolicy1 extends ExceptionClassifierRetryPolicy {
#Inject
private RetryProperties retryProperties;
public ExceptionClassifierRetryPolicy1(){
final SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
simpleRetryPolicy.setMaxAttempts(2);
this.setExceptionClassifier(new Classifier<Throwable, RetryPolicy>() {
#Override
public RetryPolicy classify(Throwable classifiable) {
if (classifiable instanceof HttpServerErrorException) {
// For specifically 500
if (((HttpServerErrorException) classifiable).getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR) {
return simpleRetryPolicy;
}
return new NeverRetryPolicy();
}
else if (classifiable instanceof HttpClientErrorException) {
// For specifically 404
if (((HttpClientErrorException) classifiable).getStatusCode() == HttpStatus.NOT_FOUND) {
return simpleRetryPolicy;
}
return new NeverRetryPolicy();
}
return new NeverRetryPolicy();
}
});
}
}
In my client class, I am using retryTemplate like this:
public void postToRestService(...,...){
...
retryTemplate.execute(context -> {
logger.info("Processing request...");
responseEntity[0] = restTemplate.postForEntity(restURL, requestEntity, String.class);
return null;
}, context -> recoveryCallback(context));
...
}
The rest service being invoked is throwing HTTP404 on every request.
My expectation is: The client should submit one request, receive HTTP404, and perform 2 retries. So a total of 3 requests submitted to rest service before invoking recovery callback method.
My observation is: The client is submitting 2 requests to rest service.
Above observation makes sense from what I have read about RetryTemplate.
So the questions are:
Is the above implementation of retryTemplate correct? If not, how to implement and invoke it? Another option that I tried implementing (but didn't get any far) was using a RetryListenerSupport on the client method and invoking the retryTemplate inside the onError method.
Are we supposed to bump up the retry count by 1 to achieve what is desired? I have tried this and it gets me what I need but the RetryTemplate isn't created with this purpose in mind.
OPTION2: Code implementing option mentioned in #1 above:
Client method:
#Retryable(listeners = "RestClientListener")
public void postToRestService(...,...){
...
responseEntity[0] = restTemplate.postForEntity(restURL, requestEntity, String.class);
...
}
Listener:
public class RestClientListener extends RetryListenerSupport {
private static final Logger logger = LoggerFactory.getLogger(RestClientListener.class);
#Inject
RestTemplate restTemplate;
#Inject
RetryTemplate retryTemplate;
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
logger.info("Retrying count for RestClientListener "+context.getRetryCount());
...
final ResponseEntity<String>[] responseEntity = new ResponseEntity[]{null};
if( context.getLastThrowable().getCause() != null &&
(context.getLastThrowable().getCause() instanceof RestClientResponseException &&
((RestClientResponseException) context.getLastThrowable().getCause()).getRawStatusCode() == HttpStatus.NOT_FOUND.value()))
{
logger.info("Retrying now: ", context.getLastThrowable().toString());
retryTemplate.execute(context2 -> {
logger.info("Processing request...: {}", context2);
responseEntity[0] = restTemplate.postForEntity(restURL, requestEntity, String.class);
return responseEntity;
}, context2 -> recoveryCallback(context2));
}
else {
// Only retry for the above if condition
context.setExhaustedOnly();
}
}
}
The problem with this approach is that I cannot find a way to share objects between my client and clientListener classes. These objects are required in order to create requestEntity and header objects. How can this be achieved?
simpleRetryPolicy.setMaxAttempts(2);
Means 2 attempts total, not 2 retries.
Thanks for reading ahead of time. In my main method I have a PublishSubscribeChannel
#Bean(name = "feeSchedule")
public SubscribableChannel getMessageChannel() {
return new PublishSubscribeChannel();
}
In a service that does a long running process it creates a fee schedule that I inject the channel into
#Service
public class FeeScheduleCompareServiceImpl implements FeeScheduleCompareService {
#Autowired
MessageChannel outChannel;
public List<FeeScheduleUpdate> compareFeeSchedules(String oldStudyId) {
List<FeeScheduleUpdate> sortedResultList = longMethod(oldStudyId);
outChannel.send(MessageBuilder.withPayload(sortedResultList).build());
return sortedResultList;
}
}
Now this is the part I'm struggling with. I want to use completable future and get the payload of the event in the future A in another spring bean. I need future A to return the payload from the message. I think want to create a ServiceActivator to be the message end point but like I said, I need it to return the payload for future A.
#org.springframework.stereotype.Service
public class SFCCCompareServiceImpl implements SFCCCompareService {
#Autowired
private SubscribableChannel outChannel;
#Override
public List<SFCCCompareDTO> compareSFCC(String state, int service){
ArrayList<SFCCCompareDTO> returnList = new ArrayList<SFCCCompareDTO>();
CompletableFuture<List<FeeScheduleUpdate>> fa = CompletableFuture.supplyAsync( () ->
{ //block A WHAT GOES HERE?!?!
outChannel.subscribe()
}
);
CompletableFuture<List<StateFeeCodeClassification>> fb = CompletableFuture.supplyAsync( () ->
{
return this.stateFeeCodeClassificationRepository.findAll();
}
);
CompletableFuture<List<SFCCCompareDTO>> fc = fa.thenCombine(fb,(a,b) ->{
//block C
//get in this block when both A & B are complete
Object theList = b.stream().forEach(new Consumer<StateFeeCodeClassification>() {
#Override
public void accept(StateFeeCodeClassification stateFeeCodeClassification) {
a.stream().forEach(new Consumer<FeeScheduleUpdate>() {
#Override
public void accept(FeeScheduleUpdate feeScheduleUpdate) {
returnList new SFCCCompareDTO();
}
});
}
}).collect(Collectors.toList());
return theList;
});
fc.join();
return returnList;
}
}
Was thinking there would be a service activator like:
#MessageEndpoint
public class UpdatesHandler implements MessageHandler{
#ServiceActivator(requiresReply = "true")
public List<FeeScheduleUpdate> getUpdates(Message m){
return (List<FeeScheduleUpdate>) m.getPayload();
}
}
Your question isn't clear, but I'll try to help you with some info.
Spring Integration doesn't provide CompletableFuture support, but it does provide an async handling and replies.
See Asynchronous Gateway for more information. And also see Asynchronous Service Activator.
outChannel.subscribe() should come with the MessageHandler callback, by the way.
I am not able to figure out a way to attach a WebSocketHandlerAdapter to a reactor netty server.
Requirements:
I want to start a reactor netty server and attach http (REST) endpoints and websocket endpoints to the same server. I have gone through the documentation and some sample demo application mentioned in the documentation. They show how to attach a HttpHandlerAdapter to the the HttpServer using newHandler() function. But when it comes to websockets they switch back to using spring boot and annotation examples. I am not able to find how to attach websockets using functional endpoints.
Please point me in the right direction on how to implement this.
1. how do I attach the websocket adapter to the netty server?
2. Should I use HttpServer or TcpServer?
Note:
1. I am not using spring boot.
2. I am not using annotations.
3. Trying to achieve this only using functional webflux end points.
Sample code:
public HandlerMapping webSocketMapping()
{
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/echo", new EchoTestingWebSocketHandler());
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setUrlMap(map);
mapping.setOrder(-1);
return mapping;
}
public WebSocketHandlerAdapter wsAdapter()
{
HandshakeWebSocketService wsService = new HandshakeWebSocketService(new ReactorNettyRequestUpgradeStrategy());
return new WebSocketHandlerAdapter(wsService);
}
protected void startServer(String host, int port)
{
HttpServer server = HttpServer.create(host, port);
server.newHandler(wsAdapter()).block(); //how do I attach the websocket adapter to the netty server
}
Unfortunately, there is no easy way to do that without running up whole SpringBootApplication. Otherwise, you will be required to write whole Spring WebFlux handlers hierarchy by your self. Consider to compose your functional routing with SpringBootApplication:
#SpringBootApplication
public class WebSocketApplication {
public static void main(String[] args) {
SpringApplication.run(WebSocketApplication.class, args);
}
#Bean
public RouterFunction<ServerResponse> routing() {
return route(
POST("/api/orders"),
r -> ok().build()
);
}
#Bean
public HandlerMapping wsHandlerMapping() {
HashMap<String, WebSocketHandler> map = new HashMap<>();
map.put("/ws", new WebSocketHandler() {
#Override
public Mono<Void> handle(WebSocketSession session) {
return session.send(
session.receive()
.map(WebSocketMessage::getPayloadAsText)
.map(tMessage -> "Response From Server: " + tMessage)
.map(session::textMessage)
);
}
});
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setUrlMap(map);
mapping.setOrder(-1);
return mapping;
}
#Bean
HandlerAdapter wsHandlerAdapter() {
return new WebSocketHandlerAdapter();
}
}
Incase if SpringBoot infra is not the case
try to consider direct interaction with ReactorNetty instead. Reactor Netty Provides pritty good abstraction around native Netty and you may interacti with it in the same functional maner:
ReactorHttpHandlerAdapter handler =
new ReactorHttpHandlerAdapter(yourHttpHandlers);
HttpServer.create()
.startRouterAndAwait(routes -> {
routes.ws("/pathToWs", (in, out) -> out.send(in.receive()))
.file("/static/**", ...)
.get("**", handler)
.post("**", handler)
.put("**", handler)
.delete("**", handler);
}
);
I deal with it this way. and use native reactor-netty
routes.get(rootPath, (req, resp)->{
// doFilter check the error
return this.doFilter(request, response, new RequestAttribute())
.flatMap(requestAttribute -> {
WebSocketServerHandle handleObject = injector.getInstance(GameWsHandle.class);
return response
.header("content-type", "text/plain")
.sendWebsocket((in, out) ->
this.websocketPublisher3(in, out, handleObject, requestAttribute)
);
});
})
private Publisher<Void> websocketPublisher3(WebsocketInbound in, WebsocketOutbound out, WebSocketServerHandle handleObject, RequestAttribute requestAttribute) {
return out
.withConnection(conn -> {
// on connect
handleObject.onConnect(conn.channel());
conn.channel().attr(AttributeKey.valueOf("request-attribute")).set(requestAttribute);
conn.onDispose().subscribe(null, null, () -> {
conn.channel().close();
handleObject.disconnect(conn.channel());
// System.out.println("context.onClose() completed");
}
);
// get message
in.aggregateFrames()
.receiveFrames()
.map(frame -> {
if (frame instanceof TextWebSocketFrame) {
handleObject.onTextMessage((TextWebSocketFrame) frame, conn.channel());
} else if (frame instanceof BinaryWebSocketFrame) {
handleObject.onBinaryMessage((BinaryWebSocketFrame) frame, conn.channel());
} else if (frame instanceof PingWebSocketFrame) {
handleObject.onPingMessage((PingWebSocketFrame) frame, conn.channel());
} else if (frame instanceof PongWebSocketFrame) {
handleObject.onPongMessage((PongWebSocketFrame) frame, conn.channel());
} else if (frame instanceof CloseWebSocketFrame) {
conn.channel().close();
handleObject.disconnect(conn.channel());
}
return "";
})
.blockLast();
});
}
I have normal #Controller and #RestController in one App.
How it possible to handle erros in Json for REST, and redirect to error page for normal #Controller?
You can make use of the Spring Annotation #ExceptionHandler in your controller and throw Exceptions in your controller logic. For an short example I will use Java's RuntimeException. You could define your own exception and throw them
// your controller classes
#Controller
public class MyController {
#ExceptionHanlder(RuntimeException.class)
public String errorInController(){
// for your custom page
return "yourDefineErrorTemplatePage";
// if you want to redirect to the default spring page
// return "redirect:/error";
}
#RequestMapping("yourFirstEndpoint")
public String getPage(){
if(yourLogicHere){
throw new RuntimeException("Display error page");
}
return "myPage";
}
}
Your #RestController could look like the following:
#RestController
public class RestControllerClass{
#ExceptionHandler(RuntimeException.class)
public ResponseEntity<Error> errorOccured(){
// you can return just a String or define your own 'error object'
Error error = new Error("Some error occured");
return new ResponseEntity<Error>(error, Http.Status.NOT_FOUND);
}
#RequestMapping("yourSecondEndpoint")
public ResponseEntity<YourEntity> getPage(){
// the entity you want do return as json
YourEntity yourEntity = new YourEntity();
if(yourLogicHere){
throw new RuntimeException("Display error page");
}
return new ResponseEntity<YourEntity>(yourEntity, HttpStatus.OK);
}
}
Example for the Error Object:
public class Error{
private String errorMessage;
public Error(String errorMessage){
this.errorMessage = errorMessage;
}
}
I hope this small example can solve your Problem.
For more details visit: https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc