Vert.x - RxJava - zip observables - event-handling

I am trying to zip to observables using Vert.x and RxJava. I don't know if I am misunderstanding something or this is just some kind of bug. Here is the code.
public class BusVerticle extends Verticle {
public void start() {
final RxVertx rxVertx = new RxVertx(vertx);
Observable<RxMessage<JsonObject>> bus = rxVertx.eventBus().registerHandler("busName");
Observable<RxHttpClientResponse> httpResponse = bus.mapMany(new Func1<RxMessage<JsonObject>, Observable<RxHttpClientResponse>>() {
public Observable<RxHttpClientResponse> call(RxMessage<JsonObject> rxMessage) {
RxHttpClient rxHttpClient = rxVertx.createHttpClient();
rxHttpClient.coreHttpClient().setHost("localhost").setPort(80);
return rxHttpClient.getNow("/uri");
}
});
Observable<RxMessage<JsonObject>> zipObservable = Observable.zip(bus, httpResponse, new Func2<RxMessage<JsonObject>, RxHttpClientResponse, RxMessage<JsonObject>>() {
public RxMessage<JsonObject> call(RxMessage<JsonObject> rxMessage, RxHttpClientResponse rxHttpClientResponse) {
return rxMessage;
}
});
zipObservable.subscribe(new Action1<RxMessage<JsonObject>>() {
public void call(RxMessage<JsonObject> rxMessage) {
rxMessage.reply();
}
});
}
}
I want to make an HTTP request using information from the received message and then zip both observables, the event bus and the HTTP response, in order to reply to the message with information from the HTTP response.
I am not getting any response for the message where I am sending it.
Thanks in advance!

I have solved it with a workaround. Some kind of mixed solution.
public class BusVerticle extends Verticle {
public void start() {
final RxVertx rxVertx = new RxVertx(vertx);
vertx.eventBus().registerHandler("busName", new Handler<Message<JsonObject>>() {
public void handle(final Message<JsonObject> message) {
RxHttpClient rxHttpClient = rxVertx.createHttpClient();
rxHttpClient.coreHttpClient().setHost("localhost").setPort(80);
Observable<RxHttpClientResponse> httpRequest = rxHttpClient.getNow("/uri");
httpRequest.subscribe(new Action1<RxHttpClientResponse>() {
public void call(RxHttpClientResponse response) {
container.logger().error(response.statusCode());
message.reply(new JsonObject().putString("status", "ok"));
}
});
}
});
}
}

Related

How to send response before actions in quarkus/Vert.X

Following similar question in spring. I want to be able to get in the router the response. and do a response flush, so I could continue work on the server without extending the RTT
meaning, do something like the answer in spring:
public void doSomething(#RequestBody List<Message> messages, HttpServletResponse response) {
int code = (messages!=null && !messages.isEmpty()) ? HttpServletResponse.SC_OK
: HttpServletResponse.SC_NOT_FOUND;
if (code != HttpServletResponse.SC_OK) {
response.sendError(code, res);
return;
}
java.io.PrintWriter wr = response.getWriter();
response.setStatus(code);
wr.print(res);
wr.flush();
wr.close();
// Now it it time to do the long processing
...
}
This is my quarkus code today:
#Path("/events")
class EventsRouter {
val logger: Logger = Logger.getLogger(EventsRouter::class.java)
#POST
#Consumes(MediaType.APPLICATION_JSON)
fun handleEvent(
#HeaderParam("User-Agent") userAgent: String?,
eventPayload: EventPayload,
): Response {
val time = LocalDateTime.now()
...
return Response.ok().build()
}
}
You can use Vert.x executeBlocking to run a blocking code asynchronously.
Here's an example (not tested, so please regard this as Pseudo):
#Path("/events")
class EventsRouter {
val logger: Logger = Logger.getLogger(EventsRouter::class.java);
private final Vertx vertx;
#Inject
public EventsRouter(Vertx vertx) {
this.vertx = vertx;
}
#POST
#Consumes(MediaType.APPLICATION_JSON)
fun handleEvent(
#HeaderParam("User-Agent") userAgent: String?,
eventPayload: EventPayload,
): Response {
val time = LocalDateTime.now()
vertx.executeBlocking(promise -> {
//Do something...
promise.complete();
}, res -> {
System.out.println("Something was done");
});
return Response.ok().build();
}
}

masstransit - consumers are no registered and activated

I'm trying to register consumers but no success using mass transit.
I registered MT using Autofac using module approach.
Firstly - I created some simple message:
public class SimpleMessage
{
public string msg { get; set; }
}
and I've managed to send them into queue:
var endpointTest = await _busControl.GetSendEndpoint(new Uri("queue:queueTest"));
await endpointTest.Send(new SimpleMessage
{
msg = "test"
});
Then I created a consumer:
public class SimpleMessageConsumer : IConsumer<SimpleMessage>
{
private readonly ILogger _logger;
public SimpleMessageConsumer(ILogger logger)
{
_logger = logger;
}
public async Task Consume(ConsumeContext<SimpleMessage> context)
{
_logger.Info($"got msg from queue: {context.Message}");
}
}
But it won't run when the message appeared in the queue. My configuration is:
public class BusModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<BusSettings>().As<IBusSettings>();
builder.AddMassTransit(cfg =>
{
cfg.AddConsumer<SimpleMessageConsumer, SimpleMessageConsumerDefinition>();
cfg.Builder.Register(context =>
{
var busSettings = context.Resolve<IBusSettings>();
var logger = context.Resolve < ILogger >();
var busControl = Bus.Factory.CreateUsingRabbitMq(bus =>
{
bus.AutoDelete = busSettings.AutoDelete;
bus.Durable = busSettings.Durable;
bus.Exclusive = busSettings.Exclusive;
bus.ExchangeType = busSettings.Type;
//bus.UseNServiceBusJsonSerializer();
bus.Host(busSettings.HostAddress, busSettings.Port, busSettings.VirtualHost, null, h =>
{
h.Username(busSettings.Username);
h.Password(busSettings.Password);
});
bus.ReceiveEndpoint("queueTest", ec =>
{
ec.Consumer(() => new SimpleMessageConsumer(logger));
});
});
return busControl;
}).SingleInstance().As<IBusControl>().As<IBus>();
});
}
}
in program.cs
I have:
services.AddMassTransitHostedService();
and
containerBuilder.RegisterModule<BusModule>();
Such I mentioned - sending a msg to queue works but consumer wasn't running.
Can you help me what did I do wrong? how should I fix the configuration? in order to activate the consumer?
I've updated your configuration to work properly, using the actual bus configuration methods instead of mixing the two solutions:
public class BusModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<BusSettings>().As<IBusSettings>();
builder.AddMassTransit(cfg =>
{
cfg.AddConsumer<SimpleMessageConsumer, SimpleMessageConsumerDefinition>();
cfg.UsingRabbitMq((context,cfg) =>
{
var busSettings = context.GetRequiredService<IBusSettings>();
var logger = context.GetRequiredService<ILogger>();
//bus.UseNServiceBusJsonSerializer();
bus.Host(busSettings.HostAddress, busSettings.Port, busSettings.VirtualHost, null, h =>
{
h.Username(busSettings.Username);
h.Password(busSettings.Password);
});
bus.ReceiveEndpoint("queueTest", ec =>
{
// i'm guessing these apply to the receive endpoint, not the bus endpoint
ec.AutoDelete = busSettings.AutoDelete;
ec.Durable = busSettings.Durable;
ec.Exclusive = busSettings.Exclusive;
ec.ExchangeType = busSettings.Type;
ec.ConfigureConsumer<SimpleMessageConsumer>(context);
});
});
});
}
}

vertx.executeBlocking failing

I am new to vertx and am trying to execute a function1 using vertx.executeBlocking from ABCHandler.java
public class ABCHandler implements Handler<RoutingContext> {
public ABCHandler( Vertx vertx)
{this.vertx =vertx;}
#Override
public void handle(RoutingContext routingContext) {
vertx.executeBlocking(future -> {
function1(routingContext, as ->
{
if (as.failed()) {
future.fail(as.cause());
} else {
future.complete(as.result());
}
});
}, rs -> {
if (rs.failed()) {
routingContext.response().putHeader(CONTENT_TYPE,
"application/json").setStatusCode(Integer.valueOf(401)).end("error");
} else {
routingContext.put("key_1", rs.result());
routingContext.next();
}
});
}
}
ABCHandler is meant to validate some data before request is routed to actual URI. But after routingContext.next(); I am getting 500 (Internal server error).
WebClientCreator.getWebClient(vertx);
Router router = Router.router(vertx);
router.route().handler(new ABCHandler(vertx));
router.post(AgentBindingConstants.AGENT_ENROLLMENT_URI).handler(
BodyHandler.create().setBodyLimit(10000));
router.post("/abc").handler(routingContext -> {
//some code
});
Also, when I run same code as non blocking it works.
Any help here is much appreciated.

Vertx instance variable is null when trying to access it from it's method

Below is verticle
package com.api.redis.gateway.verticle;
import java.util.UUID;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import io.vertx.redis.RedisClient;
import io.vertx.redis.RedisOptions;
public class SimpleRestChild extends SimpleRestServer{
RedisClient client;
#Override
public void start() {
// TODO Auto-generated method stub
super.start();
client = RedisClient.create(vertx, new RedisOptions().setHost("127.0.0.1").setPort(6379));
client.subscribe("channelForServiceToPublish", handler -> {
if(handler.succeeded())
System.out.println("SimpleRestServer subscibed to the channel successfully");
});
}
public void handleSubscription(RoutingContext routingContext) {
JsonObject requestAsJson = routingContext.getBodyAsJson();
requestAsJson.put("uuid", getUUID());
// this client object is null.
client.set("request", requestAsJson.toString(), handler ->{
System.out.println("Simple server is setting value to redis client");
if(handler.succeeded()) {
System.out.println("Key and value is stored in Redis Server");
}else if(handler.failed()) {
System.out.println("Key and value is failed to be stored on Redis Server with cause : "+ handler.cause().getMessage());
}
});
client.publish("channelForServerToPublish", "ServiceOne", handler -> {
if(handler.succeeded()) {
System.out.println("Simple Server published message successfully");
}else if(handler.failed()) {
System.out.println("Simple Server failed to published message");
}
});
routingContext.vertx().eventBus().consumer("io.vertx.redis.channelForServiceToPublish", handler -> {
client.get("response", res ->{
if(res.succeeded()) {
JsonObject responseAsJson = new JsonObject(res.result());
if(responseAsJson.getString("uuid").equalsIgnoreCase(requestAsJson.getString("uuid"))) {
routingContext.response().setStatusCode(200).end(res.result());
}
}else if(res.failed()) {
System.out.println("Failed to get message from Redis Server");
routingContext.response().setStatusCode(500).end("Server Error ");
}
});
});
}
private String getUUID() {
UUID uid = UUID.randomUUID();
return uid.toString();
}
}
And below is the main verticle from where the above verticle is getting deployed and on any request to httpserver it's hanlder method is getting called.
package com.api.redis.gateway.verticle;
import io.vertx.core.AbstractVerticle;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.redis.RedisClient;
import io.vertx.redis.RedisOptions;
public class SimpleRestServer extends AbstractVerticle{
#Override
public void start(){
int http_port = 9001;
vertx.deployVerticle("com.api.redis.gateway.verticle.SimpleRestChild", handler -> {
if(handler.succeeded()) {
System.out.println(" SimpleRestChild deployed successfully");
}
});
Router router = Router.router(vertx);
router.route().handler(BodyHandler.create());
SimpleRestChild child = null;
try {
child = (SimpleRestChild) Class.forName("com.api.redis.gateway.verticle.SimpleRestChild").newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
e.printStackTrace();
}
router.route("/subscription").handler(child::handleSubscription);
vertx.createHttpServer().requestHandler(router::accept).listen(http_port);
System.out.println("Server started at port : " + http_port);
}
}
When handleSubscription is getting called for any "/subscription" request. client object is coming as null.
As per my understanding two objects are getting created here. One with start() and other not having start().
I want to initialize Redisclient once.And use this object when handleSubscription() will get called for any request to "/subscription".
How to achieve this ?
How to fix this problem.
the requests may be coming in before the client initialization is actually complete.
AbstractVerticle has two variations of start():
start(), and
start(Future<Void> startFuture)
the overloaded version with the Future parameter should be used to perform potentially long-running initializations that are necessary to do before the Verticle can be considered deployed and ready. (there's a section dedicated to this topic in the docs).
so you might try changing your code as follows:
public class SimpleRestChild extends SimpleRestServer {
RedisClient client;
#Override
public void start(Future<Void> startFuture) {
client = ...
// important point below is that this Verticle's
// deployment status depends on whether or not
// the client initialization succeeds
client.subscribe("...", handler -> {
if(handler.succeeded()) {
startFuture.complete();
} else {
startFuture.fail(handler.cause());
}
);
}
}
and:
public class SimpleRestServer extends AbstractVerticle {
#Override
public void start(Future<Void> startFuture) {
int http_port = 9001;
vertx.deployVerticle("...", handler -> {
// if the child Verticle is successfully deployed
// then move on to completing this Verticle's
// initialization
if(handler.succeeded()) {
Router router = ...
...
// if the server is successfully installed then
// invoke the Future to signal this Verticle
// is deployed
vertx.createHttpServer()
.requestHandler(router::accept)
.listen(http_port, handler -> {
if(handler.succeeded()) {
startFuture.complete();
} else {
startFuture.fail(handler.cause());
}
});
} else {
startFuture.fail(handler.cause());
}
}
using this type of approach, your Verticles will only service requests when all their dependent resources are fully initialized.

RequestFactory and offline clients

I'm trying to create an application which is able to work even when network is down.
The idea is to store data returned from RequestFactory on the localStorage, and to use localStorage when network isn't available.
My problem - I'm not sure exactly how to differentiate between server errors(5XX, 4XX, ...) and network errors.
(I assume that on both cases my Receiver.onFailure() would be called, but I still don't know how to identify this situation)
Any help would be appreciated,
Thanks,
Gilad.
The response code when there is no internet connection is 0.
With RequestFactory to identify that the request was unsuccessful because of the network the response code has to be accessed. The RequestTransport seems like the best place.
Here is a rough implementation of an OfflineAwareRequestTransport.
public class OfflineAwareRequestTransport extends DefaultRequestTransport {
private final EventBus eventBus;
private boolean online = true;
public OfflineAwareRequestTransport(EventBus eventBus) {
this.eventBus = eventBus;
}
#Override
public void send(final String payload, final TransportReceiver receiver) {
// super.send(payload, proxy);
RequestBuilder builder = createRequestBuilder();
configureRequestBuilder(builder);
builder.setRequestData(payload);
builder.setCallback(createRequestCallback(receiver, payload));
try {
builder.send();
} catch (RequestException e) {
}
}
protected static final int SC_OFFLINE = 0;
protected RequestCallback createRequestCallback(final TransportReceiver receiver,
final String payload) {
return new RequestCallback() {
public void onError(Request request, Throwable exception) {
receiver.onTransportFailure(new ServerFailure(exception.getMessage()));
}
public void onResponseReceived(Request request, Response response) {
if (Response.SC_OK == response.getStatusCode()) {
String text = response.getText();
setOnline(true);
receiver.onTransportSuccess(text);
} else if (response.getStatusCode() == SC_OFFLINE) {
setOnline(false);
boolean processedOk = processPayload(payload);
receiver.onTransportFailure(new ServerFailure("You are offline!", OfflineReceiver.name,
"", !processedOk));
} else {
setOnline(true);
String message = "Server Error " + response.getStatusCode() + " " + response.getText();
receiver.onTransportFailure(new ServerFailure(message));
}
}
};
}