A Recursive setTimeout Pattern

Monday, October 18, 2010

Colin Snover wrote a good article about why he thinks setInterval is considered harmful:

  1. setInterval ignores errors. If an error occurs in part of your code, it'll be thrown over and over again.
  2. setInterval does not care about network latency. When continuously polling a remote server for new data, the response could take longer than the interval amount, resulting in a bunch of queued up AJAX calls and an out-of-order DOM.

The solution is to recursively call a named function within setTimeout, which guarantees that your logic has completed before attempting to call it again. At such, this also doesn't guarantee that your logic will occur on a regular interval. If you want new data every 10 seconds and it takes 15 for your response to come back, you're now behind by 5 seconds. Colin notes that you can adjust your delay if need be, but you shouldn't really care as JavaScript timers are not accurate to begin with.

So how to do this? Consider an AJAX poller as this is a common use case:

// old and busted - don't do this
setInterval(function(){
   $.ajax({
       url: 'foo.htm',
       success: function( response ){
          // do something with the response
       }
   });
}, 5000);

// new hotness
(function loopsiloop(){
   setTimeout(function(){
       $.ajax({
           url: 'foo.htm',
           success: function( response ){
               // do something with the response

               loopsiloop(); // recurse
           },
           error: function(){
               // do some error handling.  you
               // should probably adjust the timeout
               // here.

               loopsiloop(); // recurse, if you'd like.
           }
       });
   }, 5000);
})();

I'm doing three things here:

  1. Declaring a function loopsiloop that is immediately invoked (notice the parens at the end).
  2. Declaring a timeout handler to fire after 5 seconds.
  3. Polling the server inside the timeout, which upon either success/failure will call loopsiloop and continue the poll.

It's also important to remember that DOM manipulation takes time as well. With this pattern we're guaranteed that the response will be back from the server AND the DOM will be loaded with new content before attempting to request more data.

It's wise to add error handling in cases when the server experiences latency issues or becomes unresponsive. When requests fail, try increasing the polling interval to give the server some breathing room, and only recurse when under a fixed amount of failed attempts:

// keep track of how many requests failed
var failed = 0; 

(function loopsiloop( interval ){
   interval = interval || 5000; // default polling to 1 second

   setTimeout(function(){
       $.ajax({
           url: 'foo.htm',
           success: function( response ){
               // do something with the response

               loopsiloop(); // recurse
           },
           error: function(){

               // only recurse while there's less than 10 failed requests.
               // otherwise the server might be down or is experiencing issues.
               if( ++failed < 10 ){

                   // give the server some breathing room by
                   // increasing the interval
                   interval = interval + 1000;
                   loopsiloop( interval );
               }
           }
       });
   }, interval);
})();

But the success handler can still fire even if the response is an error, the astute reader might say. For that, let's break out the error logic from its handler so it can also be used in the success callback:

// keep track of how many requests failed
var failed = 0, interval = 5000; 

function errorHandler(){
   if( ++failed < 10 ){

       // give the server some breathing room by
       // increasing the interval
       interval = interval + 5000;
       loopsiloop( interval );
   }
}

(function loopsiloop( interval ){
   setTimeout(function(){
       $.ajax({
           url: 'foo.htm',
           success: function( response ){

               // what you consider valid is totally up to you
               if( response === "failure" ){
                   errorHandler();
               }
           },
           error: errorHandler
       });
   }, interval);
})();

Good, except that now we have a bunch of floating variables and functions. Ugly, amirate? Let's organize this into an object literal:

var poller = {

   // number of failed requests
   failed: 0,

   // starting interval - 5 seconds
   interval: 5000,

   // kicks off the setTimeout
   init: function(){
       setTimeout(
           $.proxy(this.getData, this), // ensures 'this' is the poller obj inside getData, not the window object
           this.interval
       );
   },

   // get AJAX data + respond to it
   getData: function(){
       var self = this;

       $.ajax({
           url: 'foo.htm',
           success: function( response ){

               // what you consider valid is totally up to you
               if( response === "failure" ){
                   self.errorHandler();
               } else {
                   // recurse on success
                   self.init();
               }
           },

           // 'this' inside the handler won't be this poller object
           // unless we proxy it.  you could also set the 'context'
           // property of $.ajax.
           error: $.proxy(self.errorHandler, self)
       });
   },

   // handle errors
   errorHandler: function(){
       if( ++this.failed < 10 ){

           // give the server some breathing room by
           // increasing the interval
          this.interval += 1000;

          // recurse
          this.init();
       }
   }
};

// kick this thing off
poller.init();

And there you have it – a safe, basic, and organized AJAX poller to get you started.



출처 - http://www.erichynds.com/blog/a-recursive-settimeout-pattern







Posted by linuxism
,


Selectors not working in STOMP over sockjs spring integration setup

We are using STOMP over sockjs, spring integration and ActiveMQ as the message broker.  The two consumers we have work just fine when no selectors are used, but fail when selectors are used.  At this point we are scratching our heads and are looking for ideas.  The selector is based on a header we are adding in the presend of the outbound channel.  Selector in the test java class is set up as so: 
        String selector = "hello = 'world'"; 
        MessageConsumer consumer = session.createConsumer(destination, selector); 

client side selector is set up as so: 
        var headers = {'selector':'hello=world'}; 
        var connectCallback = function(frame) { 
            stompClient.subscribe("/topic/receipt", function(frame){console.log(frame);}, headers); 

The setup without selectors is as follows: 

Our client side: 
        var socket = new SockJS(this.getUrl()); 
        var stompClient = Stomp.over(socket); 
        stompClient.connect('', '', 
        connectCallback, 
        errorCallback 
        ); 

        var connectCallback = function(frame) { 
            stompClient.subscribe("/topic/receipt", function(frame){console.log(frame);}) 
            stompClient.send("/app/" + url.join('/'), {"content-type": "text/plain"}, "<message>test messaage</message>"); 
        }; 

On the Spring side configuration of the message broker 
@Configuration 
@EnableWebSocketMessageBroker 
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { 

    @Override 
    public void registerStompEndpoints(StompEndpointRegistry registry) { 
        registry.addEndpoint("{product}/{security}/{system}/{tenant}/v1/pub").withSockJS(); 
    } 

    @Override 
    public void configureMessageBroker(MessageBrokerRegistry registry) { 
       //registry.enableSimpleBroker("/topic", "/queue/"); 
       registry.enableStompBrokerRelay("/topic", "/queue/"); 
       registry.setApplicationDestinationPrefixes("/app"); 
    } 

    @Override 
    public void configureClientInboundChannel(ChannelRegistration registration) { 
    } 

    @Override 
    public void configureClientOutboundChannel(ChannelRegistration registration) { 
       registration.setInterceptors(new MyInterceptor()); 
    } 


When the message is published, it goes through a spring controller first before being sent to ActiveMQ 
    @MessageMapping("{product}/{security}/{system}/{tenant}/v1/pub") 
    @SendTo("/topic/receipt") 
    public String publish( @DestinationVariable ("product") String product, 
           @DestinationVariable("security") String security, 
           @DestinationVariable("system") String system, 
           @DestinationVariable("tenant") String tenant, 
           String message) throws Exception 
    { 
                //do some stuff 

In the interceptor presend, I was trying to add tags to the header/nativeHeaders and came up with this message being sent to ActiveMQ 
    @Override 
    public Message<?> preSend(Message<?> message, MessageChannel channel) { 
       StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(message); 
       headerAccessor.setHeader("priority", "5"); 
       headerAccessor.setHeader("tree", "alpha"); 
       if(StompCommand.MESSAGE.equals(headerAccessor.getCommand())) { 
        
               Map<String, Object> map = headerAccessor.toMap(); 
               map.put("key1", "value1"); 
               Map nativeHeaders = new HashMap(); 
               nativeHeaders.put("hello", Collections.singletonList("world")); 
               map.put(NativeMessageHeaderAccessor.NATIVE_HEADERS, nativeHeaders); 
              GenericMessage msg = new GenericMessage(message.getPayload(), map); 
               System.out.println("==> " + msg); 
               return msg; 
       } 
       else { 
            return message; 
       } 
    } 

One consumer is on the client side in the connectCallback, and the other consumer is the java class snippet below. 
        Message replyJMSMessage = consumer.receive(); 
        System.out.println(replyJMSMessage); 
        if (replyJMSMessage != null && replyJMSMessage instanceof BytesMessage) 
        { 
            javax.jms.BytesMessage bytesMessage = (javax.jms.BytesMessage) replyJMSMessage; 
            byte[] bytes = new byte[(int) bytesMessage.getBodyLength()]; 
            bytesMessage.readBytes(bytes); 
            System.out.println("Reply Message"); 
            // the reply message 
            String replyMessage = new String(bytes, "UTF-8"); 
            System.out.println("   " + replyMessage); 
        } 



Reply | Threaded | More    

Re: Selectors not working in STOMP over sockjs spring integration setup

artnaseef
254 posts
Did you check some of the basics?  For example - looking at the message on the broker with the webconsole or via jmx to ensure the header is set on the message?  Also jmx can be used to verify the selector set by the client. 


Sent from my iPhone

On Feb 25, 2014, at 2:32 PM, "legolas [via ActiveMQ]" <[hidden email]> wrote:

We are using STOMP over sockjs, spring integration and ActiveMQ as the message broker.  The two consumers we have work just fine when no selectors are used, but fail when selectors are used.  At this point we are scratching our heads and are looking for ideas.  The selector is based on a header we are adding in the presend of the outbound channel.  Selector in the test java class is set up as so: 
        String selector = "hello = 'world'"; 
        MessageConsumer consumer = session.createConsumer(destination, selector); 

client side selector is set up as so: 
        var headers = {'selector':'hello=world'}; 
        var connectCallback = function(frame) { 
            stompClient.subscribe("/topic/receipt", function(frame){console.log(frame);}, headers); 

The setup without selectors is as follows: 

Our client side: 
        var socket = new SockJS(this.getUrl()); 
        var stompClient = Stomp.over(socket); 
        stompClient.connect('', '', 
        connectCallback, 
        errorCallback 
        ); 

        var connectCallback = function(frame) { 
            stompClient.subscribe("/topic/receipt", function(frame){console.log(frame);}) 
            stompClient.send("/app/" + url.join('/'), {"content-type": "text/plain"}, "<message>test messaage</message>"); 
        }; 

On the Spring side configuration of the message broker 
@Configuration 
@EnableWebSocketMessageBroker 
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { 

    @Override 
    public void registerStompEndpoints(StompEndpointRegistry registry) { 
        registry.addEndpoint("{product}/{security}/{system}/{tenant}/v1/pub").withSockJS(); 
    } 

    @Override 
    public void configureMessageBroker(MessageBrokerRegistry registry) { 
       //registry.enableSimpleBroker("/topic", "/queue/"); 
       registry.enableStompBrokerRelay("/topic", "/queue/"); 
       registry.setApplicationDestinationPrefixes("/app"); 
    } 

    @Override 
    public void configureClientInboundChannel(ChannelRegistration registration) { 
    } 

    @Override 
    public void configureClientOutboundChannel(ChannelRegistration registration) { 
       registration.setInterceptors(new MyInterceptor()); 
    } 


When the message is published, it goes through a spring controller first before being sent to ActiveMQ 
    @MessageMapping("{product}/{security}/{system}/{tenant}/v1/pub") 
    @SendTo("/topic/receipt") 
    public String publish( @DestinationVariable ("product") String product, 
           @DestinationVariable("security") String security, 
           @DestinationVariable("system") String system, 
           @DestinationVariable("tenant") String tenant, 
           String message) throws Exception 
    { 
                //do some stuff 

In the interceptor presend, I was trying to add tags to the header/nativeHeaders and came up with this message being sent to ActiveMQ 
    @Override 
    public Message<?> preSend(Message<?> message, MessageChannel channel) { 
       StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(message); 
       headerAccessor.setHeader("priority", "5"); 
       headerAccessor.setHeader("tree", "alpha"); 
       if(StompCommand.MESSAGE.equals(headerAccessor.getCommand())) { 
        
               Map<String, Object> map = headerAccessor.toMap(); 
               map.put("key1", "value1"); 
               Map nativeHeaders = new HashMap(); 
               nativeHeaders.put("hello", Collections.singletonList("world")); 
               map.put(NativeMessageHeaderAccessor.NATIVE_HEADERS, nativeHeaders); 
              GenericMessage msg = new GenericMessage(message.getPayload(), map); 
               System.out.println("==> " + msg); 
               return msg; 
       } 
       else { 
            return message; 
       } 
    } 

One consumer is on the client side in the connectCallback, and the other consumer is the java class snippet below. 
        Message replyJMSMessage = consumer.receive(); 
        System.out.println(replyJMSMessage); 
        if (replyJMSMessage != null && replyJMSMessage instanceof BytesMessage) 
        { 
            javax.jms.BytesMessage bytesMessage = (javax.jms.BytesMessage) replyJMSMessage; 
            byte[] bytes = new byte[(int) bytesMessage.getBodyLength()]; 
            bytesMessage.readBytes(bytes); 
            System.out.println("Reply Message"); 
            // the reply message 
            String replyMessage = new String(bytes, "UTF-8"); 
            System.out.println("   " + replyMessage); 
        } 






To start a new topic under ActiveMQ - User, email [hidden email] 
To unsubscribe from ActiveMQ - User, click here.
NAML
Reply | Threaded | More    

Re: Selectors not working in STOMP over sockjs spring integration setup

legolas
2 posts
Looks like the custom header is not included in the message when I look at it via the broker webconsole.   

In addition, it looks like it goes through the interceptor before it goes to the controller.  The way I understand it is when it leaves the controller, the return value is sent as a payload in a message to ActiveMQ.  How do I set a header or property in the message that gets sent to ActiveMQ from the controller? 

When the message is published, it goes through a spring controller first before being sent to ActiveMQ 
    @MessageMapping("{product}/{security}/{system}/{tenant}/v1/pub") 
    @SendTo("/topic/receipt") 
    public String publish( @DestinationVariable ("product") String product, 
           @DestinationVariable("security") String security, 
           @DestinationVariable("system") String system, 
           @DestinationVariable("tenant") String tenant, 
           String message) throws Exception 
    { 
                //do some stuff 
               return message // this return value is sent as a payload in a message to ActiveMQ 
     } 
<quote author="artnaseef">
Did you check some of the basics?  For example - looking at the message on the broker with the webconsole or via jmx to ensure the header is set on the message?  Also jmx can be used to verify the selector set by the client. 



출처 - http://activemq.2283324.n4.nabble.com/Selectors-not-working-in-STOMP-over-sockjs-spring-integration-setup-td4678358.html


'Framework & Platform > Spring' 카테고리의 다른 글

spring - spring mvc test  (0) 2014.04.17
spring data - mongodb tip  (0) 2014.04.04
spring - springx2013-websocket  (0) 2014.03.29
spring - WebSocket STOMP  (0) 2014.03.28
spring - WebSocket  (0) 2014.03.27
Posted by linuxism
,


WebSocket Apps
Spring Framework 4 (part 1)

Rossen Stoyanchev


Part 1:

Comprehensive Intro to WebSocket

in Spring Framework 4.0


Part 2:

Building WebSocket Applications

The WebSocket Protocol

How Did We Get Here?

How WebSocket Works

Practical Considerations

Java WebSocket API

Relation To the Servlet API

Server Availability

Spring Framework 4.0

Example




import org.springframework.web.socket.*;


public class MyHandler extends TextWebSocketHandlerAdapter {


  @Override
  public void handleTextMessage(WebSocketSession session,
      TextMessage message) throws Exception {

    session.sendMessage(message);

  }

}

Basic Config


@Configuration
@EnableWebSocket
public class WsConfig implements WebSocketConfigurer {


  @Override
  public void registerWebSocketHandlers(
        WebSocketHandlerRegistry registry) {

    registry.addHandler(new MyHandler(), "/echo");
  }

}

Browser client


var ws = new WebSocket("wss://localhost:8080/myapp/echo");

ws.onopen = function () {
  console.log("opened");
};

ws.onmessage = function (event) {
  console.log('message: ' + event.data);
};

ws.onclose = function (event) {
  console.log('closed:' + event.code);
};

HandshakeInterceptor

Add Interceptor


@Configuration
@EnableWebSocket
public class WsConfig implements WebSocketConfigurer {


  @Override
  public void registerWebSocketHandlers(
        WebSocketHandlerRegistry registry) {

    registry.addHandler(echoHandler(), "/echo")
        .addInterceptors(new MyHandshakeInterceptor());
  }

}

Per-Session Handler

Configure Per-Session Handler


@Configuration
@EnableWebSocket
public class WsConfig implements WebSocketConfigurer {


  @Bean
  public WebSocketHandler snakeHandler() {
    return new PerConnectionWebSocketHandler(
        SnakeWebSocketHandler.class);
  }


  @Override
  public void registerWebSocketHandlers(
        WebSocketHandlerRegistry registry) {

    registry.addHandler(snakeHandler(), "/snake");
  }

}

Deployment Notes

SockJS

SockJS Protocol

SockJS URL Scheme




    GET  /echo

    GET  /echo/info

    POST /echo/<server>/<session>/<transport>

SockJsService

Diagram with SockJS Service

Configure SockJS




@Configuration
@EnableWebSocket
public class WsConfig implements WebSocketConfigurer {


  @Override
  public void registerWebSocketHandlers(
        WebSocketHandlerRegistry registry) {

    registry.addHandler(myHandler(), "/echo").withSockJS();
  }

}

WebSocket Handler

(same as before)




import org.springframework.web.socket.*;


public class MyHandler extends TextWebSocketHandlerAdapter {

  @Override
  public void handleTextMessage(WebSocketSession session,
      TextMessage message) throws Exception {

    session.sendMessage(message);

  }

}

Server Versions

Detecting Client Disconnects

WebSocket/SockJS Sample



https://github.com/rstoyanchev/

spring-websocket-test


Questions?


WebSocket Apps
Spring Framework 4 (part 2)

Rossen Stoyanchev


Non-Trivial Applications

Using a WebSocket API is a bit like

writing a custom Servlet application


Except WebSocket is even lower level

You'll likely have 1 WebSocket handler

for the whole application

Annotations can't help much either...

...not without making assumptions about

what's in a message!

"The basic issue is there isn't enough information in an incoming websocket message for the container to know where to route it if there are multiple methods where it may land."



Danny Coward, JSR-356 lead
In response to question on user mailing list

"The subprotocol attribute of the handshake might serve as a useful place to attach this kind of message description/meta data, or some JSR356 specific headers in the handshake. Of course, the other issue is that the other end is often javascript, which would need to participate in some way in such a scheme."
"These are all things we'll likely look at in the next version."

From next reply on same thread

The Sub-protocol Attribute

Furthermore..

Message Brokers an Option?

We Need a Bit of Both Worlds


Many Approaches Exist



Logos of projects


The Spring Approach




STOMP

STOMP Frame


STOMP Frame Content


Client-to-Server Commands

Server-to-Client Commands

"Destination" Header

Keep in mind:


A server cannot send unsolicited messages

client must subscribe first

Example

Client Sends Message


SEND frame



Example

Client Subscribes To Receive Messages


SUBSCRIBE frame

Example

Client Receives Message


MESSAGE frame

STOMP vs plain WebSocket

STOMP over WebSocket

Spring Framework 4.0

Basic Configuration


@Configuration
@EnableWebSocketMessageBroker
public class Config
    implements WebSocketMessageBrokerConfigurer{


  @Override
  public void registerStompEndpoints(StompEndpointRegistry r){
    r.addEndpoint("/stomp");
  }

  @Override
  public void configureMessageBroker(MessageBrokerConfigurer c){
    c.enableSimpleBroker("/topic/");
    c.setApplicationDestinationPrefixes("/app");
  }

}


Architecture diagram


Handle a Message



@Controller
public class GreetingController {


  @MessageMapping("/greetings")
  public void handle(String greeting) {
    // ...
  }

}

@MessageMapping

Return Values

Send via Return Value



@Controller
public class GreetingController {

  // A message is broadcast to "/topic/greetings"

  @MessageMapping("/greetings")
  public String greet(String greeting) {
      return "[" + getTimestamp() + "]: " + greeting;
  }

}

Send to Different Destination

with @SendTo



@Controller
public class GreetingController {


  @MessageMapping("/greetings")
  @SendTo("/topic/wishes")
  public String greet(String greeting) {
      return "[" + getTimestamp() + "]: " + greeting;
  }

}

Send via SimpMessagingTemplate


@Controller
public class GreetingController {

  @Autowired
  private SimpMessagingTemplate template;


  @RequestMapping(value="/greetings", method=POST)
  public void greet(String greeting) {
    String text = "[" + getTimeStamp() + "]:" + greeting;
    this.template.convertAndSend("/topic/wishes", text);
  }

}

Handle Subscription

(Request-Reply Pattern)


@Controller
public class PortfolioController {


  @SubscribeEvent("/positions")
  public List<Position> getPositions(Principal p) {
    Portfolio portfolio = ...
    return portfolio.getPositions();
  }

}

Plug in Message Broker

Steps to Use Message Broker

Enable "Broker Relay"


@Configuration
@EnableWebSocketMessageBroker
public class Config
    implements WebSocketMessageBrokerConfigurer{


  @Override
  public void configureMessageBroker(MessageBrokerConfigurer c){
    c.enableStompBrokerRelay("/queue/", "/topic/");
    c.setApplicationDestinationPrefixes("/app");
  }

}

Architecture diagram


Authentication

Destination "/user/**"

UserDestinationHandler

Client Subscribes

To "/user/queue/..."


var socket = new SockJS('/myapp/portfolio');
var client = Stomp.over(socket);

client.connect('', '', function(frame) {

  client.subscribe("/user/queue/trade-confirm",function(msg){
    // ...
  });

  client.subscribe("/user/queue/errors",function(msg){
    // ...
  });

}

Send Reply To User



@Controller
public class GreetingController {

  // Message sent to "/user/{username}/queue/greetings"

  @MessageMapping("/greetings")
  @SendToUser
  public String greet(String greeting) {
      return "[" + getTimestamp() + "]: " + greeting;
  }

}

Send Error To User


@Controller
public class GreetingController {



  @MessageExceptionHandler
  @SendToUser("/queue/errors")
  public String handleException(IllegalStateException ex) {
    return ex.getMessage();
  }

}

Send Message To User

via SimpMessagingTemplate



@Service
public class TradeService {

  @Autowired
  private SimpMessagingTemplate template;


  public void executeTrade(Trade trade) {
    String user = trade.getUser();
    String dest = "/queue/trade-confirm";
    TradeResult result = ...
    this.template.convertAndSendToUser(user, dest, result);
  }

}

Architecture diagram



Managing Inactive Queues

(with Full-Featured Broker)

Stock Portfolio App



https://github.com/rstoyanchev/

spring-websocket-portfolio






'Framework & Platform > Spring' 카테고리의 다른 글

spring data - mongodb tip  (0) 2014.04.04
spring - STOMP interceptor  (0) 2014.03.31
spring - WebSocket STOMP  (0) 2014.03.28
spring - WebSocket  (0) 2014.03.27
spring - @Scheduled  (0) 2014.02.11
Posted by linuxism
,