Tag Archives: laravel

Server Sent Events example with laravel

Recently I have read about HTML5 Server Sent Events, and liked the concept of establishing long-lived connections to the server, instead of performing frequent Ajax calls to pull the updates. And I wanted to put it into action by implementing live currency rates widget with Laravel as backend PHP application.

Basic Introduction

What are “Server Sent Events”?
As Wikipedia defines

Server-sent events (SSE) is a technology for a browser to get automatic updates from a server via HTTP connection. The Server-Sent Events EventSource API is standardized as part of HTML5 by the W3C.

Basically, its an HTML5 technology that helps web client getting data from the server, using one connection that lives on the server for long interval and sending stream of data to the browser without closing the connection  (basically the connection will remain active until browser closes); such technique is useful for pushing news updates, automatically send updates in a social network, and populating live prices components…etc.

The older approach is called Ajax Long Polling which implemented requesting the updates from the web client by issuing frequent separate requests (by initiating Ajax request recursively with timeout), like the following example:
(function poll(){
   setTimeout(function(){
      $.ajax({ url: "/path/to/url", success: function(data){
        console.log(data);  
        poll();
      }, dataType: "json"});
  }, 30000);
})();

To make the idea more clear, I will use live currency rates widget as an example; this widget gets the rates to convert between one currency to another, with displaying up & down arrows to indicate the change of the price.

Basic Usage

The following snippet shows the basic usage of SSE with javascript:

<script type="text/javascript">
var es = new EventSource("/path/to/url");
es.addEventListener("message", function(e) {
            console.log(e.data);
}, false);
</script>

This piece of javascript code intialize EventSource object which listen for the specified URL, and process the data, as the server sent it back to the browser. Each time the server send new data, the event listener method will be called and will process the information according to the callback function implementation.

The code

As I said, Laravel will be used to implement this example, I will implement two actions; one for rendering the whole page, and the other will send only modified data in json format to the EventSource; as the following:

First, I defined the routes in the routes.php

// in apps/routes.php 
Route::get('/prices-page', 'HomeController@pricesPage');
Route::get('/prices-values', 'HomeController@pricesValues');

Then, I will implement a method to retrieve the rates values ( I used yahoo service as a free feed source):
    /**
     * retrieve rates of currencies from feed
     * @return array
     */
    protected function getCurrencyRates() {
        $pair_arr = array('EURUSD', 'GBPUSD', 'USDJPY', 'XAUUSD', 'XAGUSD', 'USDJOD');
        $currencies_arr = array();

        foreach ($pair_arr as $pair) {
            try {
                
                $price_csv = file_get_contents("http://finance.yahoo.com/d/quotes.csv?e=.csv&f=sl1d1t1&s=$pair=X");
                $price_data = explode(',', $price_csv);
                $currencies_arr[$pair]['price'] = $price_data[1];
                $currencies_arr[$pair]['status'] = '';
            } catch (Exception $ex) {
                $currencies_arr['error'] = $ex->getMessage();
            }
        }
        return $currencies_arr;
    }

It is not efficient to get file from external source in a controller, but I use it here for the purpose of the example. Usually, I write a backend command to get the prices from external source (usually trading server) and controller methods retrieve data from the database.

Second, I will implement the the action to render the whole price block:

public function pricesPage() {
    $prices = $this->getCurrencyRates();
    return View::make('pricesPage', array('prices' => $prices));        
}

and here is the template:
<h1>Prices here</h1>
<table>
    <thead>
        <tr>
            <th>Currency</th>
            <th>Rate</th>
            <th>status</th>
        </tr>
    </thead>
    <tbody>
        <?php foreach($prices as $currency=>$price_info){?>
        <tr class="price-row">
            <td><?php echo $currency?></td>
            <td data-symbol-price="<?php echo $currency; ?>"><?php echo $price_info['price']; ?></td>
            <td data-symbol-status="<?php echo $currency; ?>"><?php echo $price_info['status']; ?></td>
        </tr>
        <?php }?>
    </tbody>
</table>

<script type="text/javascript">
        var es = new EventSource("<?php echo action('HomeController@pricesValues'); ?>");
        es.addEventListener("message", function(e) {
            arr = JSON.parse(e.data);
            
            for (x in arr) {    	
                $('[data-symbol-price="' + x + '"]').html(arr[x].price);
                $('[data-symbol-status="' + x + '"]').html(arr[x].status);
                //apply some effect on change, like blinking the color of modified cell...
            }
        }, false);
</script>    

And now I will implement pricesValues() action that will push the data to the server, as following:

    /**
     * action to handle streamed response from laravel
     * @return \Symfony\Component\HttpFoundation\StreamedResponse
     */
    public function pricesValues() {

            $response = new Symfony\Component\HttpFoundation\StreamedResponse(function() {
            $old_prices = array();

            while (true) {
                $new_prices = $this->getCurrencyRates();
                $changed_data = $this->getChangedPrices($old_prices, $new_prices);

                if (count($changed_data)) {
                    echo 'data: ' . json_encode($changed_data) . "\n\n";
                    ob_flush();
                    flush();
                }
                sleep(3);
                $old_prices = $new_prices;
            }
        });

        $response->headers->set('Content-Type', 'text/event-stream');
        return $response;
    }
    

    /**
     * comparing old and new prices and return only changed currency rates
     * @param array $old_prices
     * @param array $new_prices
     * @return array
     */
    protected function getChangedPrices($old_prices, $new_prices) {
        $ret = array();
        foreach ($new_prices as $curr => $curr_info) {
            if (!isset($old_prices[$curr])) {
                $ret[$curr]['status'] = '';
                $ret[$curr]['price'] = $curr_info['price'];                
            } elseif ($old_prices[$curr]['price'] != $curr_info['price']) {
                $ret[$curr]['status'] = $old_prices[$curr]['price']>$curr_info['price']?'down':'up';
                $ret[$curr]['price'] = $curr_info['price']; 
            }
        }

        return $ret;
    }

As you notice, the action that push data to the event source, have following properties:

  1. the content type of the response is text/event-stream.
  2. the response I returned here, is of type “StreamedResponse” which is part of Symfony HTTP foundation component, this type of response enables the server to return data to the client as chunks. StreamedResponse object accepts a callback function to output the transferred data chunks.
  3. The prices that have been changed since the latest push will be sent back to browser, (I have compared the old and new prices easily since they reside in the same action), so if the prices didn’t change nothing will be sent back to the browser.
  4. The data returned is prefixed with “data:” and appended “\n\n” characters to the end.
  5. flush() and ob_flush() are called to trigger sending data back to the browser.
For the browsers that don’t support HTML5 features, you can apply simple fallback as following:
<script type="text/javascript">
if(window.EventSource !== undefined){
    // supports eventsource object go a head...
} else {
    // EventSource not supported, 
    // apply ajax long poll fallback
    }
</script>

The final output

Now the live currency rates widget is ready, the widget will auto-refresh prices every 3 seconds, and the server will send only rates that has been changed, so the operation is optimized and will not exchange unnecessary requests/response.

SSE price rate
* screenshot of the final component.