Configure DeadLetter queue for send endpoint - masstransit

I am trying to configure a Producer to send a message to a Consumer that has a deadletter queue configured. The Producer is using a SendEndpoint (Or rather the request/response pattern), but I get an exception from RabbitMQ.
I have the following consumer:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddMassTransit(x =>
{
x.AddConsumer<SomeMessageRequestConsumer>();
x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(busConfig =>
{
busConfig.Host(new Uri("rabbitmq://rabbit#localhost"), "/", hostConfigurator =>
{
hostConfigurator.Password("Guest");
hostConfigurator.Username("Guest");
});
busConfig.ReceiveEndpoint(nameof(SomeMessage), x =>
{
x.ConfigureConsumer<SomeMessageRequestConsumer>(provider);
x.Durable = false;
x.ConfigureConsumeTopology = false;
x.BindDeadLetterQueue("SomeMessageDeadLetter", "SomeMessageDeadLetter", null);
});
}));
});
services.AddMassTransitHostedService();
}
I have the following Producer:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSingleton<IReplyToClientFactory, ReplyToClientFactory>();
services.AddMassTransit(x =>
{
x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(busConfig =>
{
busConfig.Host(new Uri("rabbitmq://rabbit#localhost"), "/", hostConfigurator =>
{
hostConfigurator.Password("Guest");
hostConfigurator.Username("Guest");
});
}));
});
services.AddMassTransitHostedService();
}
In the Producer project I have a controller that send the message like so:
public ProducerController(IReplyToClientFactory clientFactory)
{
this.clientFactory = clientFactory;
}
[HttpPost]
public async Task<IActionResult> Post(CancellationToken cancellationToken)
{
var serviceAddress = new Uri($"queue:{nameof(SomeMessage)}?durable=false");
var client = this.clientFactory.GetFactory().CreateRequestClient<SomeMessage>(serviceAddress);
var (successResponse, failResponse) = await client.GetResponse<SomeMessageSuccessResponse, SomeMessageFailResponse>(new SomeMessage()
{
Text = "Hello",
}, cancellationToken, TimeSpan.FromSeconds(5));
return Ok();
}
I get the following error on RabbitMQ :
operation queue.declare caused a channel exception precondition_failed: inequivalent arg 'x-dead-letter-exchange' for queue 'SomeMessage' in vhost '/': received none but current is the value 'SomeMessageDeadLetter' of type 'longstr'
I have tried to configure the deadletter on the Publish, Send and Message Topologies but with no success. Is what I am trying to do possible or am I chasing the wind here?

You could change the destination address from a queue to an exchange, to decouple your producer from the consumer queue configuration. To send to the exchange, changed your address format to:
$"exchange:{nameof(SomeMessage)}"
That way, you don't need to know the queue configuration to send the request.

Related

Webflux RSocket Server using RSocketRequestor sending message to Rsocket-js client .Responder not receiving

My requirement is that as and when some event happens on server, it should push updates to client. Want to use RSocket and not SSE.
Will responder configured on the rsocket-js client respond to server request?
If yes then how should the rsocket-js responder be configured to accept message on a particular route? That part please clarify.
Not sure if my spring service is correct.
My Webflux Spring Boot RSocket server code-
#Service
#RequiredArgsConstructor
public class RsocketService {
private final RSocketRequester rSocketRequester;
public void serverToClientRequest(){
Mono.just("Your request is completed at "+ LocalDateTime.now())
.delayElement(Duration.ofSeconds(ThreadLocalRandom.current().nextInt(5, 10)))
.flatMap(m -> rSocketRequester.route("request.stream").data(m).send())
.subscribe();
}
}
I have a Controller -
#MessageMapping("request.stream")
public Flux<String> requestStream(#Payload String requestData) {
}
Client side I am using RSocketWebSocketClient from 'rsocket-websocket-client';
const client = new RSocketClient({
responder: new EchoResponder(),
transport: new RSocketWebSocketClient(
{
url: 'ws://localhost:7000/rsocket',
wsCreator: (url: string) => new WebSocket(url),
debug: true,
}
),
setup: {
dataMimeType: "text/plain",
metadataMimeType: 'message/x.rsocket.routing.v0',
keepAlive: 600000,
lifetime: 180000,
}
});
My reactjs component-
async componentDidMount() {
const rsocket= await client.connect();
console.log('rsocket client connected');
rsocket
.requestStream({
data: "client message",
metadata: String.fromCharCode('request.stream'.length) + 'request.stream'
})
.subscribe({
onComplete: () => {
console.log("request stream completed");
},
onNext: value => {
console.log("on next-->got data from sever");
console.log(value.data);
},
onError: (error: any) => {
console.log("got error with requestResponse");
console.error(error);
},
onSubscribe: sub => {
console.log("subscribe request Stream!");
sub.request(2147483647);
}
});
}
EchoResponder is taken from https://github.com/rsocket/rsocket-js/blob/master/packages/rsocket-examples/src/LeaseClientExample.js. But the responder does not get any message. Any help is appreciated please.

rsocket-js routing fireAndForget to Spring Boot #MessageMapping

As I understand RSocket-JS supports routing messages using encodeCompositeMetadata and encodeRoute, however, I cannot get the server to accept a fireAndForget message. The server constantly logs the following message:
o.s.m.r.a.support.RSocketMessageHandler : No handler for fireAndForget to ''
This is the server mapping I am trying to trigger:
#Controller
public class MockController {
private static final Logger LOGGER = LoggerFactory.getLogger(MockController.class);
#MessageMapping("fire-and-forget")
public Mono<Void> fireAndForget(MockData mockData) {
LOGGER.info("fireAndForget: {}", mockData);
return Mono.empty();
}
}
This is the TypeScript code that's trying to make the connection:
client.connect().subscribe({
onComplete: socket => {
console.log("Connected to socket!")
socket.fireAndForget({
data: { someData: "Hello world!" },
metadata: encodeCompositeMetadata([[MESSAGE_RSOCKET_ROUTING, encodeRoute("fire-and-forget")]])
});
},
onError: error => console.error(error),
onSubscribe: cancel => {/* call cancel() to abort */ }
});
I've also tried adding the route in other ways (metadata: String.fromCharCode('route'.length)+'route') I found on the internet, but none seem to work.
What do I need to do to format the route in a way that the Spring Boot server recognizes it and can route the message correctly?
Binary only communication when using CompositeMetadata
Please make sure that you have configured your ClientTransport with binary codecs as follows:
new RSocketWebSocketClient(
{
url: 'ws://<host>:<port>'
},
BufferEncoders,
),
Having Binary encoders you will be able to properly send your routes using composite metadata.
Also, please make sure that you have configured metadataMimeType as:
...
const metadataMimeType = MESSAGE_RSOCKET_COMPOSITE_METADATA.string; // message/x.rsocket.composite-metadata.v0
new RSocketClient<Buffer, Buffer>({
setup: {
...
metadataMimeType,
},
transport: new RSocketWebSocketClient(
{
url: 'ws://<host>:<port>',
},
BufferEncoders,
),
});
Note, once you enabled BufferEncoders your JSONSeriallizer will not work and you would need to encode your JSON to binary yours selves ( I suggest doing that since in the future versions we will remove support of Serializers concept completely). Therefore, your request has to be adjusted as it is in the following example:
client.connect().subscribe({
onComplete: socket => {
console.log("Connected to socket!")
socket.fireAndForget({
data: Buffer.from(JSON.stringify({ someData: "Hello world!" })),
metadata: encodeCompositeMetadata([[MESSAGE_RSOCKET_ROUTING, encodeRoute("fire-and-forget")]])
});
},
onError: error => console.error(error),
onSubscribe: cancel => {/* call cancel() to abort */ }
});
Use #Payload annotation for your payload at spring backend
Also, to handle any data from the client and to let Spring know that the specified parameter argument is your incoming request data, you have to annotate it with the #Payload annotation:
#Controller
public class MockController {
private static final Logger LOGGER = LoggerFactory.getLogger(MockController.class);
#MessageMapping("fire-and-forget")
public Mono<Void> fireAndForget(#Payload MockData mockData) {
LOGGER.info("fireAndForget: {}", mockData);
return Mono.empty();
}
}

React and rSocket REQUEST_CHANNEL error with Spring Boot

We have a working demo between React and Spring Boot Data Geode using rSocket for fire & forget, request response and request stream but when we try and use request channel we get error:
org.springframework.messaging.MessageDeliveryException: Destination 'quotes' does not support REQUEST_CHANNEL. Supported interaction(s): [REQUEST_STREAM]
So far on web it looks like this ought to be possible from RSocket Git
It's a simple spring boot app with #Controller endpoints that run over rSocket like this:
#Controller
public class RSocketController {
private static final Logger log = LogManager.getLogger(RSocketController.class);
#Autowired
PriceService priceService;
#MessageMapping(value = "quotes")
public Flux<Quote> getQuotes() {
log.info("In getQuotes");
return priceService.generatePrices();
}
}
The generatePrices returns a Flux of prices which works fine in request stream but we would prefer to use request channel for bi-directional comms.
Client versions
"rsocket-core": "0.0.19"
"rsocket-flowable": "0.0.14"
"rsocket-tcp-client": "0.0.19"
"rsocket-websocket-client": "0.0.19"
Client code
const transport = new RSocketWebSocketClient(transportOptions);
const rSocketClient = new RSocketClient({serializers, setup, transport});
​
rSocketClient.connect().subscribe({
onComplete: socket => {
console.log('Client connected to the RSocket Server');
​
socket.requestChannel(Flowable.just({
data: 'foyss',
metadata: String.fromCharCode(6) + 'quotes'
})).subscribe({
onComplete: function() {
console.log(`Channel received end of server stream`);
},
onError: function(err) {
console.log("err", err);
},
onNext: payload => {
console.log(payload);
},
onSubscribe: function(subscription) {
console.log("got subscription");
subscription.request(0x7fffffff);
},
onError: error => {
console.log(error);
},
onSubscribe: cancel => {
console.log('onSubscribe cancel');
}})
},
onError: error => {
console.log(error);
},
onSubscribe: cancel => {
// console.log(cancel);
console.log('onSubscribe cancel');
}
})
Some JS libraries still don't support the request-channel model. Please check the official documentation for your JS lib, eg, for: https://www.npmjs.com/package/ng-rsocket-rxjs
Missing:
Lease Handling
Server Setup
Resume Support
Request Channel
.....

Adding Observers to already running MassTransit system

I am trying to add a microservice to a system that contains a MassTransit observer, which will observe request response or publish messages already being used in the system. I cannot redeploy the existing services easily so would prefer to avoid it if possible.
The following code only executes when the service starts, it does not execute when a message is sent.
BusControl = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri($"{settings.Protocol}://{settings.RabbitMqHost}/"), h =>
{
h.Username(settings.RabbitMqConsumerUser);
h.Password(settings.RabbitMqConsumerPassword);
});
cfg.ReceiveEndpoint(host, "pub_sub_flo", ec => { });
host.ConnectSendObserver(new RequestObserver());
host.ConnectPublishObserver(new RequestObserver());
});
Observers:
public class RequestObserver : ISendObserver, IPublishObserver
{
public Task PreSend<T>(SendContext<T> context) where T : class
{
return Task.CompletedTask;
}
public Task PostSend<T>(SendContext<T> context) where T : class
{
var proxy = new StoreProxyFactory().CreateProxy("fabric:/MessagePatterns");
proxy.AddEvent(new ConsumerEvent()
{
Id = Guid.NewGuid(),
ConsumerId = Guid.NewGuid(),
Message = "AMQPRequestResponse",
Date = DateTimeOffset.Now,
Type = "Observer"
}).Wait();
return Task.CompletedTask;
}
public Task SendFault<T>(SendContext<T> context, Exception exception) where T : class
{
return Task.CompletedTask;
}
public Task PrePublish<T>(PublishContext<T> context) where T : class
{
return Task.CompletedTask;
}
public Task PostPublish<T>(PublishContext<T> context) where T : class
{
var proxy = new StoreProxyFactory().CreateProxy("fabric:/MessagePatterns");
proxy.AddEvent(new ConsumerEvent()
{
Id = Guid.NewGuid(),
ConsumerId = Guid.NewGuid(),
Message = "AMQPRequestResponse",
Date = DateTimeOffset.Now,
Type = "Observer"
}).Wait();
return Task.CompletedTask;
}
public Task PublishFault<T>(PublishContext<T> context, Exception exception) where T : class
{
return Task.CompletedTask;
}
}
Can anyone help?
Many thanks in advance.
The observers are only called for messages sent, published, etc. on the bus instance to which they are attached. They will not observe messages sent or received by other bus instances.
If you want to observe those messages, you could create an observer queue and bind that queue to your service exchanges so that copies of the request messages are sent to your service. The replies, however, would not be easy to get since they're sent directly to the client queues via temporary exchanges.
cfg.ReceiveEndpoint(host, "service-observer", e =>
{
e.Consumer<SomeConsumer>(...);
e.Bind("service-endpoint");
});
This will bind the service endpoint exchange to your receive endpoint queue, so that copies of the messages are sent to your consumer.
This is commonly referred to as a wire tap.

SignalR Core and streaming from server using RX Subject

I've been search high and low, but there's just too little information out there covering this particular topic.
This is part of my server side SignalR Hub:
public class MyHub : Hub
{
private ReplaySubject<string> _stream = new ReplaySubject<string>();
public IObservable<string> Stream()
{
return _stream.AsObservable();
}
public void TriggerTest()
{
return _stream.OnNext("message");
}
}
I successfully connect to the hub and are able to invoke the TriggerTest() method on the server.
The following code on the client, however, is never triggered when _stream receives a new message on the server:
this.socket.stream('Stream').subscribe({
next: (value) => {
console.log('stream response:');
console.log(value);
},
error: (err) => {
console.warn(err);
},
complete: () => {
console.log('stream completed');
}
});
I assume the problem lies on the server, more precisely on the Observable / Subject part, but couldn't find any guidance out there. Any help appreciated! Thanks!

Resources