I've used the sample to check how atmosphere works and a little modified it: add a service to send messages:
def sendMessage(String message){
String mapping = "/jabber/chat/12345"
Broadcaster b = BroadcasterFactory.getDefault().lookup(DefaultBroadcaster.class, mapping)
println("Broadcast resources size:" +b.getAtmosphereResources().size())
def resp = [type: "chat", resource: mapping, message: message] as JSON
b.broadcast(resp)
}
But looks like when I call the function some times AtmosphereResource for my connection in broadcaster is missed and client didn't recieve a message. Does anyone know what the problem is? Thanks for help.
First of all what transport protocol you are using ?
If it is 'long-polling'(default) or 'polling' atmosphere try to reconnect after 5 minutes(default) if no message (or event) are sent or received.
Maybe one of reconnections is field ?
I have some similar issue system was reconnecting only 5 times. So after 5x5minutes no connection was between client and server.
Check browser log/established connections to see do your browser have valid connection to server.
Also try to play with atmosphere props (reduce value of timeout conf to 10000 and try to reproduce problem.)
After some debugging switched to SimpleBroadcaster implementation and now code works pretty fine:
Handler class
#Override
void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
String mapping = "/jabber/chat" + request.getPathInfo()
Broadcaster b = BroadcasterFactory.getDefault().lookup(SimpleBroadcaster.class, mapping, true)
Meteor m = Meteor.build(request)
m.setBroadcaster(b)
}
Service
def sendMessage(String message){
String mapping = "/jabber/chat/12345"
Broadcaster b = BroadcasterFactory.getDefault().lookup(mapping)
println("Broadcast resources size:" +b.getAtmosphereResources().size())
def resp = [type: "chat", resource: mapping, message: message] as JSON
b.broadcast(resp)
}
Related
we have a microservice which consumes a message using #RabbitListener and persist data into database, generate a response on successful processing of message and send it using #sendTO to different queue for auditing.
When running Rabbit in HA failover, while sending response if connection is lost the message currently being processed is correctly returned to the queue but database transaction (jpa transaction in our case) is not rolled back , response is never sent.
I read from this issue(https://github.com/spring-projects/spring-amqp/issues/696) that this is "best effort 1PC" transaction synchronization; RabbitMQ does not support XA transactions. The Rabbit tx is committed after the DB tx and there is a possibility the DB tx might commit and the rabbit rolled back; you have to deal with the small possibility of duplicate messages.
But in our case when we retry request, we are treating it as duplicate message and response is never created for this request. is there a way where we can only retry sending response message in case of connection lost exceptions rather than reprocessing request again? I looked at ConditionalRejectingErrorHandler.DefaultExceptionStrategy, it has access only to original request,no way to access response lost during connection failure. Please suggest what's the best way to handle this?
our code looks like:
SpringBootApplication
#EnableJpaRepositories("com.***")
#EnableJpaAuditing
#EnableTransactionManagement
#EnableEncryptableProperties
public class PcaClinicalValidationApplication {
#RabbitListener(queues = "myqueue"
#SendTo("exchange/routingKey")
#Timed) description = "Time taken to process a request")
public Message receivemessage(HashMap<String, Object> myMap, Message requestMessage)
throws Exception {
//business logic goes here
Message message = MessageBuilder.fromMessage(requestMessage)
//add some headers
return message;
}
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory,
SimpleRabbitListenerContainerFactoryConfigurer configurer) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setRetryTemplate(new RetryTemplate());
factory.setReplyRecoveryCallback(ctx -> {
Message failed = SendRetryContextAccessor.getMessage(ctx);
Address replyTo = SendRetryContextAccessor.getAddress(ctx);
Throwable t = ctx.getLastThrowable();
//wrote to a file
serializer.serialize(failed);
return null;
});
return factory;
}
The listener container factory uses a RabbitTemplate in its replyTemplate property - this is used to send the reply.
You can configure a RetryTemplate into that RabbitTemplate to retry sending the reply.
When retries are exhausted, you can add a RecoveryCallback which will get the failed reply and you can save it off someplace and use it when the redelivery occurs.
I'm publishing messages into RabbitMQ and I would like to track the errors when RabbitMQ is down, for this I added one RetryTemplate with the recovery callback, but the recovery callback only provides this method getLastThrowable() and I'm not sure how to provide the details of the messages that failed when RabbitMQ is down. (as per documentation "The RecoveryCallback is somewhat limited in that the retry context only contains the
lastThrowable field. For more sophisticated use cases, you should use an external
RetryTemplate so that you can convey additional information to the RecoveryCallback via
the context’s attributes") but I don't know how to do that, if anyone could help me with one example that will be awesome.
Rabbit Template
public RabbitTemplate rabbitMqTemplate(RecoveryCallback publisherRecoveryCallback) {
RabbitTemplate r = new RabbitTemplate(rabbitConnectionFactory);
r.setExchange(exchangeName);
r.setRoutingKey(routingKey);
r.setConnectionFactory(rabbitConnectionFactory);
r.setMessageConverter(jsonMessageConverter());
RetryTemplate retryTemplate = new RetryTemplate();
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(500);
backOffPolicy.setMultiplier(10.0);
backOffPolicy.setMaxInterval(10000);
retryTemplate.setBackOffPolicy(backOffPolicy);
r.setRetryTemplate(retryTemplate);
r.setRecoveryCallback(publisherRecoveryCallback);
return r;
}
Recovery Callback
#Component
public class PublisherRecoveryCallback implements RecoveryCallback<AssortmentEvent> {
#Override
public AssortmentEvent recover(RetryContext context) throws Exception {
log.error("Error publising event",context.getLastThrowable());
//how to get message details here??
return null;
}
}
AMQP Outbound Adapter
return IntegrationFlows.from("eventsChannel")
.split()
.handle(Amqp.outboundAdapter(rabbitMqTemplate)
.exchangeName(exchangeName)
.confirmCorrelationExpression("payload")
.confirmAckChannel(ackChannel)
.confirmNackChannel(nackChannel)
)
.get();
The isn't possible because the function RabbitTemplate.execute() is already not aware about message you send, because it may be performed from any other method, where we might not have messages to deal:
return this.retryTemplate.execute(
(RetryCallback<T, Exception>) context -> RabbitTemplate.this.doExecute(action, connectionFactory),
(RecoveryCallback<T>) this.recoveryCallback);
What I suggest you to do is like storing message to the ThreadLocal before send and get it from there from your custom RecoveryCallback.
The controller layer can get the IP using request.getRemoteAddr() and/or request.getHeader("Client-IP") etc.
However, down in the bowels of the service layer, we might want to log some detected or suspected fraudulent activity by the user, along with the IP address of the user. However, the IP is not available to the service layer, nor is the request.
Obviously, every call from every controller method to every single service method could also pass in the IP or the request, but as we have thousands of these calls and lots of chains of them, it is not really practical.
Can anyone think of a better way?
As we are not in charge of instantiation of the services (these just get magically injected), we can't even pass the IP in when each service is created for the current HTTP call.
UPDATE 1
As suggested, tried the MDC route. Unfortunately, this does not seem to work.
in filter:
import org.apache.log4j.MDC
class IpFilters {
def filters = {
all() {
before = {
MDC.put "IP", "1.1.1.1"
println "MDC.put:" + MDC.get("IP")
}
afterView = { Exception e ->
println "MDC.remove:" + MDC.get("IP")
MDC.remove 'IP'
}
}
in service:
import org.apache.log4j.MDC
:
def someMethod() {
String ip = MDC.get("IP")
println("someMethod: IP = $ip")
}
The result is always:
MDC.put:1.1.1.1
MDC.remove:1.1.1.1
someMethod: IP = null
So the service cant access MDC variables put on the thread in the filter, which is a real shame. Possibly the problem is that "someMethod" is actually called by springSecuirty.
Well, it is highly recommended that we should keep the business logic aware of the controller logic. But keeping your situation in mind, you have to do that and absolutely available. In your service method, write this to log the IP address of the current request:
import org.springframework.web.context.request.RequestContextHolder
// ... your code and class
def request = RequestContextHolder.currentRequestAttributes().getRequest()
println request.getRemoteAddr()
Just make sure, you handle the whatever exception thrown from that line when the same service method is invoked from outside a Grails request context like from a Job.
my two pence worth
basically been using above and it works perfectly fine when a request is directed through standard grails practices.
In this scenario, user triggers websockets connection this then is injected into websockets listener using Holders.applicationContext.
The issue arises around are your outside of the web request.
the fix was painful but may come in handy for anyone else in this situation:
private static String userIp
String getIp() {
String i
new Thread({
//to bypass :
// Are you referring to request attributes outside of an actual web request, or processing a
// request outside of the originally receiving thread? If you are actually operating within a web request
// and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet:
// In this case, use RequestContextListener or RequestContextFilter to expose the current request.
def webRequest = RequestContextHolder.getRequestAttributes()
if(!webRequest) {
def servletContext = ServletContextHolder.getServletContext()
def applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext)
webRequest = grails.util.GrailsWebMockUtil.bindMockWebRequest(applicationContext)
}
//def request = RequestContextHolder.currentRequestAttributes().request
def request = WebUtils.retrieveGrailsWebRequest().currentRequest
i=request.getRemoteAddr()
if (!i ||i == '127.0.0.1') {
i=request.getHeader("X-Forwarded-For")
}
if (!i ||i == '127.0.0.1') {
i=request.getHeader("Client-IP")
}
if (!i) { i="127.0.0.1"}
this.userIp=i
} as Runnable ).start()
return i
}
Now when calling this some sleep time is required due to it running in as a runnable :
def aa = getIp()
sleep(300)
println "$aa is aa"
println "---- ip ${userIp}"
Also provided alternative way of calling request def request = WebUtils.retrieveGrailsWebRequest().currentRequest in grails 3 the commented out line .request comes up unrecognised in ide (even though it works)
the new Thread({ was still needed since even though it returned ip after getting ip it was attempting to save to a db and some other bizarre issue appeared around
java.lang.RuntimeException: org.springframework.mock.web.MockHttpServletRequest.getServletContext()Ljavax/servlet/ServletContext;
at org.apache.tomcat.websocket.pojo.PojoMessageHandlerBase.handlePojoMethodException(PojoMessageHandlerBase.java:119)
at org.apache.tomcat.websocket.pojo.PojoMessageHandlerWholeBase.onMessage(PojoMessageHandlerWholeBase.java:82)
so the fix to getting hold of request attribute in this scenario is above
for the mock libraries you will require this in build.gradle:
compile 'org.springframework:spring-test:2.5'
So the saga continued - the above did not actually appear to work in my case since basically the request originated by user but when sent to websockets - the session attempting to retrieve Request (ip/session) was not actual real user.
This in the end had to be done a very different way so really steeply off the topic but when this method of attempting ip does not work the only way left is through SessionListeners:
in src/main/groovy/{packageName}
class SessionListener implements HttpSessionListener {
private static List activeUsers = Collections.synchronizedList(new ArrayList())
static Map sessions = [:].asSynchronized()
void sessionCreated (HttpSessionEvent se) {
sessions.put(se.session.id, se.session)
}
void sessionDestroyed (HttpSessionEvent se) {
sessions.remove(se.session.id)
}
}
in grails-app/init/Application.groovy
Closure doWithSpring() {
{ ->
websocketConfig WebSocketConfig
}
}
// this already exists
static void main(String[] args) {
GrailsApp.run(Application, args)
}
in that same init folder:
class WebSocketConfig {
#Bean
public ServletContextInitializer myInitializer() {
return new ServletContextInitializer() {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.addListener(SessionListener)
}
}
}
}
Now to get userIP, when the socket initially connects it sends the user's session to sockets. the socket registers that user's session within the websockets usersession details.
when attempting to get the user ip (i have registered the users ip to session.ip on the controller/page hitting the page opening sockets)
def aa = SessionListener.sessions.find{it.key==sessionId}?.value
println "aa $aa.ip"
I am using apache camel netty4 in grails and I have declared mycustom ServerInitializerFactory as follows
public class MyServerInitializerFactory extends ServerInitializerFactory {
private int maxLineSize = 1048576;
NettyConsumer nettyConsumer
public MimacsServerInitializerFactory() {}
#Override
protected void initChannel(Channel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline()
pipeline.addLast("logger", new LoggingHandler(LogLevel.INFO))
pipeline.addLast("framer", new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, maxLineSize, 2, 2, 6, 0, false))
pipeline.addLast("decoder", new MfuDecoder())
pipeline.addLast("encoder", new MfuEncoder())
pipeline.addLast("handler", new MyServerHandler())
}
}
I have a route which I setup as follows in my routebuilder.
from('netty4:tcp://192.168.254.3:553?serverInitializerFactory=#sif&keepAlive=true&sync=true&allowDefaultCodec=false').to('activemq:queue:Tracking.Queue')
My Camel Context is setup in the BootStrap.groovy as follows
def serverInitializerFactory = new MyServerInitializerFactory()
SimpleRegistry registry = new SimpleRegistry()
registry.put("sif", serverInitializerFactory)
CamelContext camelContext = new DefaultCamelContext(registry)
camelContext.addComponent("activemq", activeMQComponent.activeMQComponent("failover:tcp://localhost:61616"))
camelContext.addRoutes new TrackingMessageRoute()
camelContext.start()
When I run my app, my route is started and my framer, decoder, handler and encoders are all invoked but messages do not reach the Tracking. Queue and responses do not get back to the client.
If I do not use serverInitializerFactory in the netty url and user encoders and decoders instead, My messages are hitting the queue but I lose control of the acknowledgement that I want to sent for each type of message that I receive. It seems activemq tries to sent its own response which is rejected by my encoder.
Am I supposed to then write code to send again or is there something I am missing?
You need to add a handler with the consumer so it can be routed, see the unit test how its done:
https://github.com/apache/camel/blob/master/components/camel-netty4/src/test/java/org/apache/camel/component/netty4/NettyCustomPipelineFactoryAsynchTest.java#L112
I managed to get around that problem. In my channelRead0 method. I added the following lines
Exchange exchange = this.consumer.getEndpoint().createExchange(ctx, msg);
where ctx is the ChannelContextHandler and msg is the Message Object, the two are both parameters of the channelRead0 method.
I also added the following lines
this.consumer.createUoW(exchange);
and after my handling code I inserted the following line
this.consumer.doneUoW(exchange);
and everything works like a charm.
I am working with Spring websocket implementation. For sending a message to clients, there are two ways:
1) Using #SendToUser annotation
2) Using convertAndSendToUser method of SimpMessagingTemplate
#SendToUser takes a boolean parameter called broadcast which if set to false publishes the message to the current session. Is there a way I can have this behaviour in SimpMessagingTemplate.
If we take a look to the SendToMethodReturnValueHandler source code, we'll see:
if (broadcast) {
this.messagingTemplate.convertAndSendToUser(user, destination, returnValue);
}
else {
this.messagingTemplate.convertAndSendToUser(user, destination, returnValue, createHeaders(sessionId));
}
So, what you need for your use-case just use that overloaded convertAndSendToUser and provide a Map with `sessionId:
messagingTemplate.convertAndSendToUser(user, destination, payload,
Collections.singletonMap(SimpMessageHeaderAccessor.SESSION_ID_HEADER, sessionId))
Spring doesn't have a clear document, I tried many different way, only below code works for me.
SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.create();
accessor.setHeader(SimpMessageHeaderAccessor.SESSION_ID_HEADER, sessionId);
messagingTemplate.convertAndSendToUser(sessionId, destination, payload, accessor.getMessageHeaders());
The answer above did not work for me. It turns out that with Spring 4.1.4 something slightly different is required.
The way that seems the cleanest to me looks like the following:
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create();
headerAccessor.setSessionId(cmd.getSessionId());
headerAccessor.setLeaveMutable(true);
MessageHeaders messageHeaders = headerAccessor.getMessageHeaders();
messagingTemplate.convertAndSendToUser(cmd.getPrincipal().getName(),
"/queue/responses", ret, messageHeaders);
The other way which worked was to explicitly add a "nativeHeaders" value to the Map sent to SimpMessagingTemplate.convertAndSendToUser(). However, this way seems to depend too much on implementation details:
Map<String, Object> headers = new HashMap<>();
headers.put("nativeHeaders", new HashMap<String, Object>());
headers.put(SimpMessageHeaderAccessor.SESSION_ID_HEADER, cmd.getSessionId());
messagingTemplate.convertAndSendToUser(cmd.getPrincipal().getName(),
"/queue/responses", ret, headers);
The "offending code" which made setting the "simpSessionId" header and nothing else in a Map not work was in SimpMessagingTemplate.processHeaders() and MessageHeaderAccessor.getAccessor(MessageHeaders, Class requiredType).
The simplest way send to User by SimpMessagingTemplate
#Autowired
private SimpMessagingTemplate messagingTemplate;
#MessageMapping("/getHello")
public void sendReply( MessageHeaders messageHeaders, #Payload String message, #Header(name = "simpSessionId") String sessionId){
messagingTemplate.convertAndSendToUser(sessionId, "/queue/hello", "Hello "+ message, messageHeaders);
}