SpringBoot WebFlux WebClient: Mono and Flux usage - java

So, I am just getting into WebClient since RestTemplate got a deprecated note.
Things is, I am a bit lost when it comes to requesting and consuming the responses.
At the moment, I am playing around with the Riot Games API for League of Legends. If you check their documentation, you need to send a API key either as a param or a header to consume their services.
As I am trying the WebClient, I am not worrying about abstraction right now, so the current test I have is:
A Builder class, right now I only set the baseUrl and api key:
#Service
public class WebClientService {
#Autowired
private Environment env;
public WebClient get(String url) {
return WebClient.builder()
.baseUrl(url)
.defaultHeader("X-Riot-Token", env.getProperty("app.riot.api.key"))
.build();
}
}
To test, I am making a call to the /summoners endpoint using the below code:
#Service
public class SummonerServices {
#Autowired
private WebClientService web;
private String riotEndPoint = "https://br1.api.riotgames.com/lol/summoner/v4/summoners/by-name/";
public Flux<Summoners> getSummoner(String summonerName) {
return web.get(riotEndPoint + summonerName).get()
.exchange().flatMapMany(e -> e.bodyToMono(Summoners.class));
}
}
I have a controller (omitted because it's not relevant), I call it, and it gets the result as expected.
But I noticed that instead of a Flux, I can use a Mono like this:
public Mono<Summoners> getSummoner(String summonerName) {
return web.get(riotEndPoint + summonerName).get()
.exchange().flatMap(e -> e.bodyToMono(Summoners.class));
}
What I am failing to understand, by reading the docs and looking at examples on the internet, is when or why to choose one over another?
It seems that for both I can have access to the header response, I have access to the same doOnSucess / doOnError and others methods to treat responses and errors.
They seems pretty much the same, but I they wouldn't just create two exact ways to do the same thing with different names, right? So if anyone could point me to a doc explaining Fux and Mono I would be very glad. Google returned to me pretty much the same tutorials, and they don't explain, they just make use of them.

Related

Can I use a graphQL client to communicate to a graphql server class in the same application?

I am currently creating a POC. We have a proxy lambda linked up to an API gateway. This Lambda access some data and returns films. These items are very large, so we would like to give customers the ability to filter which attributes they get from the items. We would like to do this using graphql. I am also using quarkus and java following this tutorial. https://quarkus.io/guides/smallrye-graphql
I was thinking that by using graphql I could make a resource like
#GraphQLApi
public class FilmResource {
#Inject
GalaxyService service;
#Query("allFilms")
#Description("Get all Films from a galaxy far far away")
public List<Film> getAllFilms() {
return service.getAllFilms();
}
#Query
#Description("Get a Films from a galaxy far far away")
public Film getFilm(#Name("filmId") int id) {
return service.getFilm(id);
}
}
An aws proxy lambda accepts a type of APIGatewayProxyRequestEvent which has a String body. I was hoping I could wrap FilmResource.java in a graphql client and directly apply the body of an APIGatewayProxyEvent to the Film Resource.
This would look something like this
#Named("test")
public class TestLambda implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
#Inject
FilmResource resource;
#Override
public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent awsLambdaEvent, Context context) {
DynamicGraphQLClient client = DynamicGraphQLClientBuilder.newBuilder().client(resource).build();
JsonObject data = client.executeSync(awsLambdaEvent.getBody()).getData();
return new APIGatewayProxyResponseEvent().withBody(data.toString()).withStatusCode(200);
}
}
So Ideally, the customer would be able to pass the below query to the lambda, and it would receive the correct response.
query allFilms {
allFilms {
title
releaseDate
}
}
However, it seems like all the clients only work with remote graphql servers. Is there a client that can wrap an internal graphql server class?
Source: https://quarkus.io/guides/smallrye-graphql-client
There's no way to specifically have the client run over some kind of 'local' transport, but you should still be able to use the HTTP transport even though the target is inside the same JVM. You just need to configure the client to run against an appropriate URL, that could be something like
quarkus.smallrye-graphql-client.MY-CLIENT-KEY.url=http://127.0.0.1:${quarkus.http.port:8080}/graphql
And inject a dynamic client instance using
#Inject
#GraphQLClient("MY-CLIENT-KEY")
DynamicGraphQLClient client;

In spring-data-elasticsearch with High Level Rest Client, when does the RestClient closed?

I have a question about the relationships between High Level REST Client and spring-data-elasticsearch.
In my spring-boot project, I created the RestClientConfig for the connection,
// Config file
#Configuration
#EnableElasticsearchAuditing
public class RestClientConfig extends AbstractElasticsearchConfiguration {
#Value("${spring.data.elasticsearch.client.reactive.endpoints}")
private String endpoint;
#Value("${spring.data.elasticsearch.client.reactive.username}")
private String username;
#Value("${spring.data.elasticsearch.client.reactive.password}")
private String pwd;
#Override
#Bean
public RestHighLevelClient elasticsearchClient() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo(endpoint)
.withBasicAuth(username, pwd)
.build();
return RestClients.create(clientConfiguration).rest();
}
}
and using ElasticSearchRepository to send some requests to the cluster.
// repository
public interface TestRepository extends ElasticsearchRepository<Test, String> {
}
// I use the repository for whole request to the elasticsearch cluster
testRepo.save(/* object */)
Of course it works well, however, I'm really curious about when the client closed. (As I know, I should close the rest-client when I finished the job. do I have to close it by myself?(if so, when?) or the spring-data-elasticsearch do it for me?)
Could you please let me know how it works? Any links for codes or documents will be tremendously thankful because I couldn't find any.
Thank you!
Spring Data Elasticsearch never closes the client as it does not create it, you create it in your configuraiton.
And like the linked document states:
you should close the high-level client when you are well and truly done with it
Spring Data Elasticsearch cannot know if and when your application does not need the connection to Elasticsearch any more.
So you might keep a reference to the client and close it if you think you really don't need it. But any calls to ElasticsearchOperations or repositories will fail after that.
Actually I never heard of a problem because of wasted resources of a single instance of this class in an application, I'd say don't bother with closing it.

How to make endpoints initialization asynchronous in Spring Boot?

In my Spring Boot project, a couple of REST API endpoints require a class whose initialization takes several minutes. Because of this, it takes several minutes to start the REST API.
Is it possible (using Spring Boot) to make so that these few endpoints are initialized asynchronously i.e. all other endpoints are initialized right away and REST API starts working and these endpoints are initialized whenever the class that they need is initialized and are simply not available to the user before that?
I tried looking into #Async and other ways to make things asynchronous in Spring Boot, but that did not help.
I would really appreciate some help.
Try #Lazy annotation. When it's applied to the spring component, it will be initialized on the first call.
Some resources:
https://www.baeldung.com/spring-lazy-annotation
Java Doc
There's nothing built into Spring to do what you want, but you could implement it yourself by returning a 404 Not Found response while the service is initializing and a 200 OK once it's available. The following is one way to implement that:
#RestController
class ExampleController {
private final Future<SlowInitializationService> service;
ExampleController() {
this.service = ForkJoinPool.commonPool().submit(SlowInitializationService::new);
}
#GetMapping
ResponseEntity<Result> example() throws InterruptedException, ExecutionException {
if (this.service.isDone()) {
return new ResponseEntity<>(this.service.get().perform(), HttpStatus.OK);
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
Rather than using the common pool, you may want to inject an Executor or similar. It'll depend on the rest of your app and how it handles threading.

Is there a way to get a two-way connection from client-browser to server application simulating a shell?

Problem
Very short: I want to create Spring Shell, but as a web application.
I want to create a web-application (preferably using Spring Boot), where the frontend (ReactJS) looks like a terminal (or shell), and the backend processes inputted commands. Look at https://codepen.io/AndrewBarfield/pen/qEqWMq. I want to build a full web app for something that looks like that.
I want to build a framework, so that I can develop backend commands without knowing anything about the frontend/web application structure. I basically want to instantiate a "Terminal" object, where I give some kind of input-stream and output-stream. This way I can program this Terminal based on my given interfaces and structure, without the need of setting up all kind of front-end stuff.
A good summary of the question would be: how to send all keyboard inputs to the backend, and how to send all output to the frontend?
The reason I want to create a web application, is because I want it to be available online.
What I tried
I think the way of reaching this is using websockets. I have created a small web application using this (https://developer.okta.com/blog/2018/09/25/spring-webflux-websockets-react) tutorial, without the security part. The websocket part is almost suitable, I just cannot get an "input" and "output" stream-like object.
#Controller
public class WebSocketController {
private SimpMessagingTemplate simpMessagingTemplate;
#Autowired
public WebSocketController(SimpMessagingTemplate simpMessagingTemplate) {
this.simpMessagingTemplate = simpMessagingTemplate;
}
#MessageMapping("/queue")
#SendToUser("/topic/greetings")
public Greeting greeting(HelloMessage message, #Header(name = "simpSessionId") String sessionId) throws Exception {
System.out.println(sessionId);
// Do some command parsing or whatever.
String output = "You inputted:" + HtmlUtils.htmlEscape(message.getName());
return new Greeting(output);
}
private MessageHeaders createHeaders(String sessionId) {
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(sessionId);
return headerAccessor.getMessageHeaders();
}
Now with this code, you can parse a command. However, it doesn't keep any "state". I don't know how it works with states and websockets.
I saw you had this Spring Sessions + WebSockets (https://docs.spring.io/spring-session/docs/current/reference/html5/guides/boot-websocket.html), but this is not really what I want.
I can send a message from the backend to the frontend by using this code:
simpMessagingTemplate.convertAndSendToUser(sessionId, "/topic/greetings", "hey", createHeaders(sessionId));
However, I want my terminal to be able to wait for input commands from the user. Seems like a stretch, but does anybody know how to achieve this?
What I sort of want
I basically want other people to program to this interface:
public interface ITerminal {
void setInputStream(Object someKindOfWrapperForTheInput);
void setOutputStream(Object someWrapperOfSimpMessagingTemplate);
void start();
}
When somebody opens the web application, they get a dedicated terminal object (so a single connection per user). Whever somebody enters a command in the frontend application, I want it to be received by the terminal object, processed, and response outputted to the frontend.
Reasons for doing this
I really like creating command-line applications, and I don't like building frontend stuff. I work as a software engineer for a company where we build a web application, where I mostly program backend stuff. All the frontend part is done by other people (lucky for me!). However, I like doing some projects at home, and this seemed cool.
If you have any thoughts or ideas on how to approach this, just give an answer! I am interested in the solution, using the SpringBoot framework is not a requirement. I ask this question using Spring Boot and ReactJS, because I have already built applications with that. A lot has been figured out already, and I think this probably exists as well.
The only requirement is that I can achieve this with Java on a tomcat-server. The rest is optional :)
Unclear?
I tried my best to make my story clear, but I am not sure if my purpose of what I want to achieve is clear. However, I don't know how to formulate it in such a way you understand. If you have any suggestions or questions, dont hesitate to comment!
If the only thing you want is a Live Spring shell that shows up in the browser it's fairly simple, all you need is to expose a standard WebSocket via the WebSocketConfigurer, then add a WebSocketHandler that executes the command and then returns the resulting String as a TextMessage.
Firstly the Socket configuration that allows clients to connect to the 'cli' endpoint
#Configuration
#EnableWebSocket
public class WebSocketConfiguration implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(cliHandler(), "/cli").setAllowedOrigins("*");
}
#Bean
public CLIWebSocketHandler cliHandler() {
return new CLIWebSocketHandler();
}
}
Then the WebSocketHandler that executes the command. I recommend that for every #ShellMethod you specify the return type as String, don't use logging or System writes as they won't be returned during the evaluation.
#Component
public class CLIWebSocketHandler extends TextWebSocketHandler {
#Autowired
private Shell shell;
#Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String result = shell.evaluate(() -> message.getPayload()).toString();
session.sendMessage(new TextMessage(result));
}
}
You can use an extension like Simple WebSocket Client to test it, by going to ws://localhost:port/cli
This is the most basic solution, adding features like security should be easy after this. Notice that I don't use STOMP, because you probably want to isolate users. But it can work alongside STOMP based endpoints, so you can have pub-sub functionality for other parts of the project.
From the question I sense that answer you'd like is something that involved Input and OutputStreams. You could possibly look into redirecting the output of Spring Shell to a different stream then have them forwarded to the sessions but it's probably much more complicated and has other trade-offs. It's simpler to just return a String as the result, it looks better in print outs anyway.

How to build a rest service accepting any request?

I want to test my integrated services. Therefore I need a rest-service, that takes any request to any url and responds with HTTP 200 - OK. (Later on the answer shall be configurable, based on the url.)
How can I build such a service with spring-boot?
I tried using a custom HandlerInterceptor, but this will only work, if the url is exposed:
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AllAccessInterceptor());
}
private static class AllAccessInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle() {
response.setStatus(HttpServletResponse.SC_OK);
return true;
}
}
}
I think the spring boot in a whole and spring mvc in particular are not intended to do this in a nutshell, so any solution won't be straightforward and good in general.
So, yes, the interceptors technically can be a solution but then, how will you configure the actual answer (besides the 200 status there should be some data sent back to the caller part). What is the request to be checked is a post request and you expect to check a body of a very specific form.
Based on your comment
I want to spin up my acceptor-service locally. Than configure it as remote service for a service I want to dev-test manually on my machine.
Consider using Wiremock as a mock server. It would work pretty much like mockito:
You'll be able to specify the expectations like "if I call the remote service with the following params -> return that answer" and so forth. Technically it answers your question because you indeed won't need to implement the enpoint for each expectation specification that's exactly what wiremock does.
You can even run it with test containers in docker during the test so that it will start up at the beginning of the test and stop when the test is over but its a different topic.
I found the answer myself.
Instead of returning true in the preHandle method, I need to return false. This will not allow any further execution of Interceptors.

Categories

Resources