Change rabbitmq exchange with nestjs - rabbitmq

I am using rabbitmq with nestjs. I need to replicate a message from one queue to another. I set up an exchange on rabbitmq to make it work. But how can I change the exchange of rabbitmq inside nestjs?
my api gateway
my current rabbitmq configuration inside nestjs:
constructor( ) {
this.rabbitmq = ClientProxyFactory.create({
transport: Transport.RMQ,
options: {
urls: [`amqp://${this.configService.get<string>('RABBITMQ_USER')}:${this.configService.get<string>('RABBITMQ_PASSWORD')}#${this.configService.get<string>('RABBITMQ_URL')}`],
queue: 'students'
}
})
}
createStudent(#Body() body: CreateStudentDto): Observable<any> {
return this.rabbitmq.send('createStudent', body)
}
my client
#MessagePattern('createStudent')
async createStudent(#Payload() student: Student, #Ctx() context: RmqContext) {
const channel = context.getChannelRef()
const originalMsg = context.getMessage()
try {
let response = await this.studentService.createStudent(student)
await channel.ack(originalMsg)
return response;
} catch(error) {
this.logger.log(`error: ${JSON.stringify(error.message)}`)
const filterAckError = ackErrors.filter(ackError => error.message.includes(ackError))
if (filterAckError.length > 0) {
await channel.ack(originalMsg)
}
}
}
I need the message to be sent to two queues.

Related

Send message from service to other components (React Native)

I'm thinking about the best way how to implement rabbitmq or mqtt sending messages from callback(queue.on) to chat component and last dialogs component, where I will update dialogs and chats. I heard about eventemitter, but I don't know best way,try to use eventemitter or make own handlers. I am very new in react.
MessageBrokerService:
import {Connection, Exchange, Queue} from "react-native-rabbitmq";
import uuid from 'react-native-uuid';
export default class RabbitMQService {
startEventSubscribe = (exchange) => {
exchangeName = exchange;
connection.connect();
}
}
const config = {
host: '',
port: 5671,
username: '',
password: '',
virtualhost: '/',
ttl: 10000,
ssl: true,
};
let connection = new Connection(config);
let queue;
let exchangeName;
connection.on('connected', event => {
queue = new Queue(
connection,
{
name: uuid.v4(),
durable: true,
},
{
// queueDeclare args here like x-message-ttl
},
);
let exchange = new Exchange(connection, {
name: exchangeName,
});
queue.bind(exchange, '');
// Receive messages
queue.on('message', message => {
console.log(message);
**I want from here send messages to chat component and last dialogs from here**
queue.basicAck(message.delivery_tag);
});
});
connection.on('error', event => {
console.log('fail');
console.log(event);
});
const publishMessage = message => {
let routing_key = 'message-exchange';
let properties = {
//header authorization jwt
};
let exchangeSendMessages = new Exchange(connection, {
name: 'message-exchange',
type: 'direct',
durable: true,
});
exchangeSendMessages.publish(message, routing_key, properties);
};
Last Dialogs screen:
const LastDialogsScreen = () => {
**Want here to receive messages here from callback in MessageBrokerService**
return ()
}
Chat screen:
const ChatScreen = () => {
**Want here to receive messages in here too from callback in MessageBrokerService**
return ()
}

Keep client connected to WebSocket in react native and express server

I have a react native application where i have two users using the app (customer and restaurant)
So on checkout I connect the customer to websocket on the express server and once the order is placed i send a message to the restaurant which is supposed to be connected to websocket all time.
However, sometimes the restaurant is disconnected somehow, so I am trying to keep the restaurant connected, and if disconnected then reconnect again automatically.
In react native restaurant side implementation i have the following code :
this is useWebSocketLite hook to handle connection, send, receive messages and retry connection to server when closed:
function useWebSocketLite({ socketUrl, retry: defaultRetry = 3, retryInterval = 1000 }) {
const [data, setData] = useState();
const [send, setSend] = useState(() => () => undefined);
const [retry, setRetry] = useState(defaultRetry);
const [readyState, setReadyState] = useState(false);
useEffect(() => {
const ws = new WebSocket(socketUrl);
ws.onopen = () => {
setReadyState(true);
setSend(() => {
return (data) => {
try {
const d = JSON.stringify(data);
ws.send(d);
return true;
} catch (err) {
return false;
}
};
});
ws.onmessage = (event) => {
const msg = formatMessage(event.data);
setData({ message: msg, timestamp: getTimestamp() });
};
};
ws.onclose = () => {
setReadyState(false);
if (retry > 0) {
setTimeout(() => {
setRetry((retry) => retry - 1);
}, retryInterval);
}
};
return () => {
ws.close();
};
}, [retry]);
return { send, data, readyState };
}
So based on this, every-time the connection is closed, the connection will retry again.
Besides, when a restaurant launches the app the following code will be implemented:
const ws = useWebSocketLite({
socketUrl: `wss://${url}/id=${user.user_id}&role=restaurant`
});
This useEffect to establish the connection:
useEffect(() => {
if (ws.readyState === true) {
setConnectionOpen(true);
}
}, [ws.readyState]);
and this useEffect to handle incoming messages
useEffect(() => {
if (ws.data) {
const message = ws.data;
//dispatch...
}
}, [ws.data]);
Express server implementation:
This is the code where i handle socket connections and messages in express server:
var webSockets = {}
function setupWebSocket(server) {
server.on('connection', (socket, req) => {
if (req) {
var clientId = req.url
let regexReplace = /[\[\]/]/g
let regex = /([^=#&]+)=([^?&#]*)/g,
params = {},
match;
while ((match = regex.exec(clientId))) {
params[decodeURIComponent(match[1]).replace(regexReplace, '')] = decodeURIComponent(match[2])
}
if (params.role === 'restaurant') {
webSockets[params.id] = socket
}
}
socket.on('message', data => {
let sData = JSON.parse(JSON.parse(data))
let {id, data} = sData.data
sendToClient(id, 'order', data)
})
socket.on('error', (err) => {
console.log(err)
})
socket.on('close', (code, req) => {
var clientId = req.url
let regexReplace = /[\[\]/]/g
let regex = /([^=#&]+)=([^?&#]*)/g,
params = {},
match;
while ((match = regex.exec(clientId))) {
params[decodeURIComponent(match[1]).replace(regexReplace, '')] = decodeURIComponent(match[2])
}
if (params.role === 'restaurant') {
delete webSockets[clientId]
console.log(`${webSockets[clientId]} disconnected with code ${code} !`);
}
});
});
// sends a message to a specific client
const sendToClient = (clientId, type, data = {}) => {
const payload = { type, data }
const messageToSend = JSON.stringify({ error: false, message: payload })
if (webSockets[clientId]) {
webSockets[clientId].send(messageToSend)
console.log(`${clientId} client notified with this order`)
} else {
console.log(`${clientId} websocket client is not connected.`)
}
}
}
So most of the time I get 13 websocket client is not connected. which means the restaurant has already been deleted from the webSockets object and its connection already closed.
Apologise for long question and hope someone can help me regarding this.
First of all, you should know that this is not a good practice of websockets, where you are forcing the client (the restaurant) to be connected.
Whatever, at the current state of your code, there is an illogical behavior: at the end of the useEffect of your “useWebSocketLite” function, you are closing the socket connection:
return () => {
ws.close();
};
Knowing that the useEffect hook is called twice: after the first render of the component, and then after every change of the dependencies (the “retry” state in your case); Your code can be ridden like so: everytime the “retry” state changes, we will close the socket! So for me that is why you got the client disconnected.

What's the best way to consume messages in RabbitMq?

I have about 100 different types of messages and I'd like to know the correct way to consume them in RabbitMq.
I have 2 solutions and I don't know which one is the best way.
1: Implement 100 consumers for 100 different types of messages.
2: Implement 1 consumer and process the messages by for example switch case or etc.
In your opinion what's the best way?
If you are going to be calling 100 different messaging services then you will need to set up 100 different consumers BUT use only on rabbitmq connection
Here is how I implemented my rabbitmq consumer
module.exports.start = async () => {
try{
const queue1 = 'email';
const connection = await amqplib.connect(process.env.QUEUE_SERVICE);
const channel = await connection.createChannel();
await channel.assertQueue(queue1, { durable: true });
await channel.prefetch(1);
console.log('Listening for emails...');
channel.consume(queue1, async (msg) => {
if (msg !== null) {
winston.info(`Got message ${msg.content.toString()}`);
const queueMessage = JSON.parse(msg.content.toString());
Emails.Mailer(queueMessage)
.then(() => {
channel.ack(msg)
})
.catch(err=> err);
}
});
const queue2 = 'sms';
await channel.assertQueue(queue2, { durable: true });
await channel.prefetch(1);
console.log('Listening for SMS...');
channel.consume(queue2, async (msg) => {
if (msg !== null) {
const queueMessage = JSON.parse(msg.content.toString());
Sms.sendPaymentSms(queueMessage).then(() => channel.ack(msg));
}
});
} catch (error) {
return error
}
};
Another thing you can do to maintain code readability is to modularize the various services you want to call and call them in your consumer using conditionals or strategy design pattern.

How to handle message sent from server to client with RSocket?

I try to use RSocketRequester to send a message from the server to the specific client, but I don't know how to handle it on the frontend. The server is Spring Webflux with the controller like this:
data class Message(val message: String)
#Controller
class RSocketController {
private val log = LoggerFactory.getLogger(RSocketController::class.java)
#MessageMapping("say.hello")
fun sayHello(message: String): Flux<Message> {
log.info("say hello {}", message)
return Flux.just(Message("server says hello"))
}
#MessageMapping("say.hi")
fun sayHi(message: String, rSocketRequester: RSocketRequester): Flux<Message> {
log.info("say hi {}", message)
rSocketRequester
.route("say.hello")
.data(Message("server says hi hello ;)"))
.send()
.subscribe()
return Flux.just(Message("server says hi!!"))
}
}
On the frontend I use rsocket-js. The sayHello method works just fine (request-stream), but when I call the sayHi method I want to send two messages from the server. The first one to say.hello endpoint, and the second to say.hi endpoint. I've got rsocket-js implementation like this:
sayHello() {
console.log("say hello");
this.requestStream("say.hello");
},
sayHi() {
console.log("say hi");
this.requestStream("say.hi");
},
connect() {
const transport = new RSocketWebSocketClient({
url: "ws://localhost:8080/rsocket"
});
const client = new RSocketClient({
serializers: {
data: JsonSerializer,
metadata: IdentitySerializer
},
setup: {
keepAlive: 60000,
lifetime: 180000,
dataMimeType: "application/json",
metadataMimeType: "message/x.rsocket.routing.v0"
},
transport
});
client.connect().subscribe({
onComplete: socket => {
this.socket = socket;
console.log("complete connection");
},
onError: error => {
console.log("got connection error");
console.error(error);
},
onSubscribe: cancel => {
console.log("subscribe connection");
console.log(cancel);
}
});
},
requestStream(url) {
if (this.socket) {
this.socket
.requestStream({
data: url + " from client",
metadata: String.fromCharCode(url.length) + url
})
.subscribe({
onComplete: () => console.log("requestStream done"),
onError: error => {
console.log("got error with requestStream");
console.error(error);
},
onNext: value => {
// console.log("got next value in requestStream..");
console.log("got data from sever");
console.log(value.data);
},
// Nothing happens until `request(n)` is called
onSubscribe: sub => {
console.log("subscribe request Stream!");
sub.request(2147483647);
// sub.request(3);
}
});
} else {
console.log("not connected...");
}
}
I can see both messages in Google Chrome DevTools -> Network -> rsocket. So the client receives them but I can't catch in the code the one sent by RSocketRequester.
It seems that the server uses fireAndForget method. How to handle it on the client side?
As #VladMamaev said, we can provide a responder to the client like in this example https://github.com/rsocket/rsocket-js/blob/master/packages/rsocket-examples/src/LeaseClientExample.js#L104
For me, fireAndForget method is enough.
export class EchoResponder {
constructor(callback) {
this.callback = callback;
}
fireAndForget(payload) {
this.callback(payload);
}
}
import { EchoResponder } from "~/assets/EchoResponder";
...
const messageReceiver = payload => {
//do what you want to do with received message
console.log(payload)
};
const responder = new EchoResponder(messageReceiver);
connect() {
const transport = new RSocketWebSocketClient({
url: "ws://localhost:8080/rsocket"
});
const client = new RSocketClient({
serializers: {
data: JsonSerializer,
metadata: IdentitySerializer
},
setup: {
keepAlive: 60000,
lifetime: 180000,
dataMimeType: "application/json",
metadataMimeType: "message/x.rsocket.routing.v0"
},
responder: responder,
transport
});

Apollo Server & 4xx status codes

Currently, my Apollo Server(running on HapiJS) returns HTTP 200 for every request, including failed ones.
I would like the GraphQL server to return HTTP 4xx for unsuccessful requests. The primary reason for it is that I want to set up monitoring for my ELB.
I know that Apollo Server has an engine platform, but I want to implement it using my current infrastructure.
Any ideas of how I could accomplish that? I tried to capture 'onPreResponse' event for my HapiJS server but I couldn't modify status code there.
After reading this answer. Here is a solution by modifying the hapijs plugin graphqlHapi of hapiApollo.ts file.
server.ts:
import { makeExecutableSchema } from 'apollo-server';
import { ApolloServer, gql } from 'apollo-server-hapi';
import Hapi from 'hapi';
import { graphqlHapi } from './hapiApollo';
const typeDefs = gql`
type Query {
_: String
}
`;
const resolvers = {
Query: {
_: () => {
throw new Error('some error');
},
},
};
const schema = makeExecutableSchema({ typeDefs, resolvers });
const port = 3000;
async function StartServer() {
const app = new Hapi.Server({ port });
graphqlHapi.register(app, { path: '/graphql', graphqlOptions: { schema } });
app.ext('onPreResponse', (request: any, h: any) => {
const response = request.response;
if (!response.isBoom) {
return h.continue;
}
return h.response({ message: response.message }).code(400);
});
await app.start();
}
StartServer()
.then(() => {
console.log(`apollo server is listening on http://localhost:${port}/graphql`);
})
.catch((error) => console.log(error));
hapiApollo.ts:
import Boom from 'boom';
import { Server, Request, RouteOptions } from 'hapi';
import { GraphQLOptions, runHttpQuery, convertNodeHttpToRequest } from 'apollo-server-core';
import { ValueOrPromise } from 'apollo-server-types';
export interface IRegister {
(server: Server, options: any, next?: Function): void;
}
export interface IPlugin {
name: string;
version?: string;
register: IRegister;
}
export interface HapiOptionsFunction {
(request?: Request): ValueOrPromise<GraphQLOptions>;
}
export interface HapiPluginOptions {
path: string;
vhost?: string;
route?: RouteOptions;
graphqlOptions: GraphQLOptions | HapiOptionsFunction;
}
const graphqlHapi: IPlugin = {
name: 'graphql',
register: (server: Server, options: HapiPluginOptions, next?: Function) => {
if (!options || !options.graphqlOptions) {
throw new Error('Apollo Server requires options.');
}
server.route({
method: ['GET', 'POST'],
path: options.path || '/graphql',
vhost: options.vhost || undefined,
options: options.route || {},
handler: async (request, h) => {
try {
const { graphqlResponse, responseInit } = await runHttpQuery([request, h], {
method: request.method.toUpperCase(),
options: options.graphqlOptions,
query:
request.method === 'post'
? // TODO type payload as string or Record
(request.payload as any)
: request.query,
request: convertNodeHttpToRequest(request.raw.req),
});
// add our custom error handle logic
const graphqlResponseObj = JSON.parse(graphqlResponse);
if (graphqlResponseObj.errors && graphqlResponseObj.errors.length) {
throw new Error(graphqlResponseObj.errors[0].message);
}
const response = h.response(graphqlResponse);
Object.keys(responseInit.headers as any).forEach((key) =>
response.header(key, (responseInit.headers as any)[key]),
);
return response;
} catch (error) {
// handle our custom error
if (!error.name) {
throw Boom.badRequest(error.message);
}
if ('HttpQueryError' !== error.name) {
throw Boom.boomify(error);
}
if (true === error.isGraphQLError) {
const response = h.response(error.message);
response.code(error.statusCode);
response.type('application/json');
return response;
}
const err = new Boom(error.message, { statusCode: error.statusCode });
if (error.headers) {
Object.keys(error.headers).forEach((header) => {
err.output.headers[header] = error.headers[header];
});
}
// Boom hides the error when status code is 500
err.output.payload.message = error.message;
throw err;
}
},
});
if (next) {
next();
}
},
};
export { graphqlHapi };
Now, when the GraphQL resolver throws an error, the client-side will receive our custom response with Http status code 400 instead of 200 status code with GraphQL errors response.
General from the browser:
Request URL: http://localhost:3000/graphql
Request Method: POST
Status Code: 400 Bad Request
Remote Address: 127.0.0.1:3000
Referrer Policy: no-referrer-when-downgrade
The response body is: {"message":"some error"}