Skip navigation
All Places > Platform > Blog > 2016 > October > 13

Platform

October 13, 2016 Previous day Next day

By Martin Whittington, Software Engineer DevOps. 7th July 2015


Disclaimer: This work and the technologies are not related to any Alfresco products.

 

There isn’t a great deal of documentation or “best practices” that best describes how ReactJS could or can even receive updates via web-sockets.

 

I trawled the internet for about 10 minutes, which is my typical snapping point of yelling “Fine, I’ll do it myself!”. The first step was to read up on the Observer pattern and I came across a picture and description that fits the bill. You can look at the pattern and description here:

 

https://en.wikipedia.org/wiki/Observer_pattern

 

I love UML diagrams. They are, at least to me, very clear and unambiguous. So this one leaped out at me. I decided to follow this diagram but removed the need for the ConcreteObservers as I had a different idea on how to simplify this even further.

 

Picture1

 

All of the implementation, naturally, is in JS and my JS skills have room for improvement (whose doesn’t?). I am happy to receive feedback based on what you see.

So I firstly implemented the Subject module as such:

 

'use strict';

 

 var _ = require('lodash');

 

 module.exports = {

     observerCollection: [],

     registerObserver: function(observer) {

         // to avoid over subscription, check the element isnt already in the array

         var index = this.observerCollection.indexOf(observer);

         if (index == -1) {

             this.observerCollection.push(observer);

         }

     },

     unregisterObserver: function(observer) {

         _.remove(this.observerCollection, function(e) {

             return observer === e;

         });

     },

     notifyObservers: function(data) {

         for (var observer in this.observerCollection) {

             if (this.observerCollection.hasOwnProperty(observer)) {

                 var subscriber = this.observerCollection[observer];

                 if (typeof subscriber.notify === 'function') {

                     subscriber.notify(data);

                 } else {

                     console.warn('An observer was found without a notify function');

                 }

             }

         }

     }

 };

Pretty simple really. Three methods; registerObserver(), unregisterObserver() and notifyObservers(). The last method is the one methods signature I did have to change so that it accepted an argument. As you can see notifyObservers loops through each observer in a list and calls the notify() method passing in the data the observer may or may not be interested in.

 

Next I created the Web-sockets module, which calls the notifyObservers() method as seen below:

 

'use strict';

 

 var Subject = require('./Subject');

 

 module.exports = {

     connection: new WebSocket('ws://localhost:8080/events'),

     connect: function() {

         var connection = this.connection;

 

         // Setup events once connected

         connection.onopen = function() {

             connection.send('Ping');

         };

 

         connection.onerror = function(error) {

             console.log(error);

         };

 

         // This function reacts anytime a message is received

         connection.onmessage = function(e) {

             console.log('From server: ' + e.data);

             Subject.notifyObservers(e.data);

         };

     }

 };

 

So this module utilises the HTML5 web-sockets library and this is as simple as it gets. Once a connection is established events that the connection should respond to need configuring. There are 3 events to be concerned with; onopen, onerror and onmessage.

 

Onopen is fired only once and handy for sending a message to the server. This message can then be checked in the logs to ensure a connection has been established.

Currently the onerror method only logs an error if the connection is lost or the connection could not be established.

 

The onmessage method is a callback method that fires every time a message is received from the server the web-socket is connected to. So from there it made the most sense, obviously, to call the notifyObservers() method passing in the data. The ‘e’ object is the full event object, but I was only concerned with the data received from the server. I followed the tutorial for HTML5 web-sockets at this link:

 

http://www.html5rocks.com/en/tutorials/websockets/basics/

 

Finally I had to get a ReactJS component to become the observer. Rather than having a “middle-man” observer class I decided to make the ReactJS component actually care about the messages it will receive and respond accordingly.

 

ReactJS components have a lifecycle. Methods are called chronologically as the component is either mounted, rendered or destroyed. If you want to read more on this lifecycle follow the link below:

 

https://facebook.github.io/react/docs/component-specs.html

 

A ReactJS component typically manages its own state. When the state is updated the component is then re-rendered. So the first thing the component needs to do is to register itself with the registerObserver() method as shown below:

componentDidMount: function() {

    if (this.isMounted()) {

            Subject.registerObserver(this);

        }

 },

Look at the line with the registerObserver() method. I am passing in the entire component with this. So now the component is added to the list of observers. Next, for the sake of cleanliness, I added the following method:

componentWillUnmount: function() {

     Subject.unregisterObserver(this);

 },

When the component is unmounted from the DOM, the component is removed from the list. The final part was to make sure that the component implemented a notify method as such:

notify: function(data) {

     console.log('Receiving ' + data);

 },

As part of the notifyObservers() loop, this method in the component will be called. Within each notify method in every component that is registered as an observer will be filtering of the JSON. Every component wont care about every message received. So for example we could filter on type as such:

 

{ type: “type I care about”, action: “action to take” }

 

This is how I implemented web-sockets and messaging with ReactJS. We have adopted ReactJS for the development of our new internal DevOps tools.

 

Next steps will involve further development on what I have shown you. I may update this blog with how we did the filtering of messages and any other improvements but for now, this works well for us.

 

Thanks all!

mwhittington

Preparing for HTTP/2

Posted by mwhittington Employee Oct 13, 2016

By Martin Whittington, Software Engineer DevOps. 23rd July 2015

 

Disclaimer: This work and the technologies are not related to any Alfresco products

 

I claim to be a full-stack developer. I’m happy to work on UI all the way back to server side code. But working on what I call the “OS” layer is something that I don’t get the opportunity to do as often as I’d like. Some of what I’ve done may seem simple or over-complicated to the more trained “Ops” person but I don’t care. If you can do it better, you blog about it!

 

I was recently challenged to configure our web server to be ready for HTTP/2. Luckily, we are using Jetty for one of our internal projects which with its most recent releases is compatible and ready for the new protocol.


These are a summary of the advantages of being ready for HTTP/2:

    • Being able to negotiate between using either HTTP/1.1, HTTP/2 or any other protocol

 

    • Backwards compatibility with HTTP/1.1

 

    • Decreases latency that improves page loading times through several techniques such as:

      • Data compression of HTTP headers

 

      • Server push technologies

 

      • Fixing the head-of-line blocking problem in HTTP 1

 

      • Loading page elements in parallel over a single TCP connection


All of this information can be found at https://en.wikipedia.org/wiki/HTTP/2 so I don’t claim to be an expert, just someone who does their research.

 

These are the steps I took to get us ready for one of our internal applications:

 

Install HAProxy:

 

HAProxy is a free open source solution that provides load balancing and proxying for TCP and HTTP-based applications. The OS I installed this on is OS X Yosemite version 10.10.4 so I used the following code with Homebrew (following the guide at https://www.eclipse.org/jetty/documentation/current/http2-configuring-haproxy.html ):

$ brew update

$ brew install haproxy

But this can be installed either via download or on the Linux command line. Make sure to get the latest version from HAProxy and that, dependant on your OS, the repo’s you download from are kept up to date. If you have any doubts you can download the HAProxy direct from http://www.haproxy.org/download/1.5/src/ or call the following command:

$ sudo apt-get install haproxy

Next I had to create a haproxy.cfg file. A very short and sweet one at that. It’s basically a copy from the link above except for a couple of small changes:

 

global

tune.ssl.default-dh-param 1024
defaults
timeout connect 10000ms
timeout client 60000ms
timeout server 60000ms
frontend fe_http
mode http
bind *:80
# Redirect to https
redirect scheme https code 301
frontend fe_https
mode tcp
bind *:443 ssl no-sslv3 crt domain.pem ciphers TLSv1.2 alpn h2,http/1.1
default_backend be_http
backend be_http
mode tcp
server domain 127.0.0.1:8282

 

I haven’t provided the changes I’ve made on the config file I wrote but I will explain what, at the basic level, needs changing. The first highlighted line needs to be updated to refer to the certificate file of your choice, so domain.pem should be replaced with something.pem.

 

The second highlighted line would just need updating to whatever IP and port your app is running on. Simples!

 

Wherever you decide to save this file is up to you, but I decided to check it into our main code branch. Eventually it will become part of our Chef configuration. Now to run HAProxy all I have to type is:

$ haproxy –f haproxy.cfg (or wherever you file is)

Now if you run the following command:

$ netstat –anlt

You should see HAProxy listening on ports *80 and *443. You will usually see that information at the top of the list.

 

Install Jetty 9.3.1.v20150714:

 

I downloaded the zip archive from http://download.eclipse.org/jetty/ and extracted the archive into /opt/jetty. This is also dependant on java being correctly installed on your system, so make sure you have it installed!! To test that jetty is ready to be configured you can run the following command from the /opt/jetty directory:

$ java –jar start.jar

And you should get a screen like so:

2015-06-04 10:50:44.806:INFO::main: Logging initialized @334ms

2015-06-04 10:50:44.858:WARN:oejs.HomeBaseWarning:main: This instance of Jetty is not running from a separate {jetty.base} directory, this is not recommended.  See documentation at http://www.eclipse.org/jetty/documentation/current/startup.html

2015-06-04 10:50:44.995:INFO:oejs.Server:main: jetty-9.3.0.v20150601

2015-06-04 10:50:45.012:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:///opt/jetty-distribution-9.3.0.v20150601/webapps/] at interval 1

2015-06-04 10:50:45.030:INFO:oejs.ServerConnector:main: Started ServerConnector@19dfb72a{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}

2015-06-04 10:50:45.030:INFO:oejs.Server:main: Started @558ms

Jetty is ready. The next task is to create a new Jetty Base directory. It is mentioned throughout the Jetty documentation that the Jetty Home directory, in this case /opt/jetty/, be separated from where the apps are deployed. This is infact very simple to setup and requires a line of code at the command prompt (again at the directory you have installed Jetty at):

 

$ jetty   mkdir {new name of base, whatever you want it to be. Name of app is good}
$ cd {new base dir}
$ newDir   java -jar ../start.jar --add-to-start=deploy,http,http2c,https,server,ssl,websocket

 

This initialises the new directory with the modules listed after the –add-to-start parameter. The important one to notice is http2c. This is the module that speaks clear-text HTTP/2. Whether you do or don’t need the other modules is for you to decide based on your requirements.

 

Download a Keystore file (optional)

 

If you have decided to install the SSL module as part of the command to configure your new jetty base directory you will need a keystore file. This can be downloaded from http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/plain/jetty-server/src/test/config/etc/keystore?id=master and once downloaded should be placed in /opt/jetty/{basedir}/etc. That’s it, that parts simple.

 

To make the new base directory accessible by IDE’s and configurable its best to copy of the start.ini file into your new base dir. I achieved this like so:

 

$ basedir      cp ../start.ini .

 

Then any changes you want to make (port changes etc) should be made in the newly copied ini file.

 

IDE Setup

 

There are plenty of IDE’s out there that support the use of creating application servers. I use Intellij Ultimate so I followed the guide here:

 

https://www.jetbrains.com/idea/help/run-debug-configuration-jetty-server.html

 

There are obviously plenty of other guides out there for you to follow based on the IDE you use. Once setup, passing in any JVM options you need, you should have a Jetty server ready for HTTP/2 with HAProxy ready to do its work.

 

Obviously a lot more work needs to go into configuring HAProxy but this blog covers enough so that you are at the starting line for HTTP/2!

Cloudformation is wonderful. Its a wonderful way of designing your infrastructure with code (JSON, YAML). But it does have its limitations and there is feature disparity between the template DSL and the AWS cli. Considering the work that's gone into implementing this system, I'm not surprised but it's still super frustrating.

 

What is Cloudformation? Its an AWS feature that allows you to design an infrastructure containing one/some/all of the AWS resources available (EC2, S3, VPC etc) with code. This "template" then becomes like any other piece of source-code; versionable and testable but more importantly, it gives the designer the ability to repeatably and reliably deploy a stack into AWS. With one template I can deploy ten identical yet unique stacks.

 

The issue I was challenged to solve was this; every time a stack we created with Cloudformation was deleted via the console (AWS UI), it would fail. This was because an S3 bucket we created still contained objects. By using the UI there wasn't way of purging the bucket before deletion (unless someone manually empties the bucket via the S3 console) BUT when deleting a stack in the same state (with an S3 bucket that contains objects) using the aws-cli it's possible to just pass a --purge flag with the delete bucket command. Obviously we wanted the stacks to behave the same, regardless of the approach we took to manage the stacks. Some people using the Cloudformation service may not be technical enough to use cli commands.

 

So my solution; create a Lambda-Backed Custom Resource that would manage the emptying and deletion of an S3 bucket.

 

A Custom Resource is similar to the other AWS resources that Cloudformation templates manages; it has a similar set of syntactical sugars. For a more detailed understanding, follow http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html . The essential difference with a custom resource is the necessity to manage and implement the communication between the custom resource, the service its working with (currently Lambda and/or SNS) and the Cloudformation template.

 

The first step was to add the resource to the template. Here's what it looks like (both JSON and YAML):

 

"EmptyBuckets": {
  "Type": "Custom::LambdaDependency",
  "Properties": {
  "ServiceToken": {
  "Fn::Join": [
  "",
  [
  "arn:aws:lambda:",
  {
  "Ref": "AWS::Region"
  },
  ":",
  {
  "Ref": "AWS::AccountId"
  },
  ":function:emptyBucketLambda"
  ]
  ]
  },
  "BucketName": {
  "Ref": "S3BucketName"
  }
  }
  }

 

And the YAML version:

 

EmptyBuckets:
    Type: Custom::LambdaDependency
    Properties:
      ServiceToken:
        Fn::Join:
        - ''
        - - 'arn:aws:lambda:'
          - Ref: AWS::Region
          - ":"
          - Ref: AWS::AccountId
          - ":function:emptyBucketLambda"
      BucketName:
        Ref: S3BucketName

 

The most important part of the resource is the "Properties" section. A "ServiceToken" element must be declared and this must be the ARN (Amazon reference number) of either a SNS topic or a Lambda function. In the examples above we are using references of the region and account the stack is deployed to as that is where the Lambda function has also been uploaded. After the "ServiceToken" element is declared any values can be passed in as either primitive types, objects or arrays. These are the arguments that the Lambda function will pick up for the code to handle as desired. As seen in the example, we simply pass the "BucketName" as that's all the Lambda needs to perform its tasks. The custom resource will fire upon the state change of the template (Create, Update, Delete) and within the Lambda we can decide which states we are interested in and handle that accordingly.

 

Moving onto the Lambda code now, we can see and point to the "BucketName" parameter being passed in:

 

'use strict';


var AWS = require('aws-sdk');
var s3 = new AWS.S3();


exports.handler = (event, context) => {
    if (!event.ResourceProperties.BucketName) {
        return sendResponse(event, context, "FAILED", null, "BucketName not specified");
    }
    var bucketName = event.ResourceProperties.BucketName;
    var physicalResourceId = `${bucketName}-${event.LogicalResourceId}`;
    if (event.RequestType === 'Delete') {
        console.log(JSON.stringify(event, null, '  '));
        // Is the bucket versioned?
        s3.getBucketVersioning({ 'Bucket': bucketName }, (err, data) => {
            if (err) return sendResponse(event, context, "FAILED", null, err);
            console.log('Versioning status: ', JSON.stringify(data));
            switch (data.Status) {
                case "Enabled":
                // Initial params without markers
                return emptyVersionedBucket({
                    'Bucket': bucketName
                }, event, context, physicalResourceId);
                default:
                // Initial params without continuation
                return emptyBucket({
                    'Bucket': bucketName
                }, event, context, physicalResourceId);
            }
        });
    } else return sendResponse(event, context, "SUCCESS", physicalResourceId);
};

 

The ResourceProperties value in the event object contains any of the arbitrary parameters needed for the Lambda to function. If, for some reason, the BucketName param isn't present we send a response back to the pre-signed S3 Url sent with the event to notify the Cloudformation process that firing this function failed. If the event request type is 'Create' we create our own physicalResourceId. This is because if this is not specified the sendResponse function will use the logStreamName which can point to more than one resource, causing issues. As we do not have any logic to action when the request type is 'Create' we simply send a response back stating that all was successful. If the request type is 'Delete', we log the event details and call our emptyBucket function as shown below:

 

function emptyBucket(objParams, event, context, physicalResourceId) {
    console.log("emptyBucket(): ", JSON.stringify(objParams));
    s3.listObjectsV2(objParams, (err, result) => {
        if (err) return sendResponse(event, context, "FAILED", physicalResourceId, err);


        if (result.Contents.length > 0) {
            var objectList = result.Contents.map(c => ({ 'Key': c.Key }));
            console.log(`Deleting ${objectList.length} items...`);
            var obj = {
                'Bucket': objParams.Bucket,
                'Delete': {
                    'Objects': objectList
                }
            };


            s3.deleteObjects(obj, (e, data) => {
                if (e) return sendResponse(event, context, "FAILED", physicalResourceId, e);
                console.log(`Deleted ${data.Deleted.length} items ok.`);


                // If there are more objects to delete, do it
                if (result.isTruncated) {
                    return emptyBucket({
                        'Bucket': obj.Bucket,
                        'ContinuationToken': result.NextContinuationToken
                    }, event, context, physicalResourceId);
                }
                return checkAndDeleteBucket(objParams.BucketName, event, context, physicalResourceId);
            });
        } else return checkAndDeleteBucket(objParams.BucketName, event, context, physicalResourceId);
    });
}

So first we list all of the objects in a bucket and if there was any error, bail out sending a response. The listObjects method will only return a maximum of 1000 items per call so if we have more we need to make subsequent requests with a token property. Next we create a list of objects to delete based on the format required for the deleteObjects method in the aws-sdk. Again, if there is an error we send a response stating so. Otherwise, the first batch of items were deleted and we then check that the listed objects result was truncated or not. If so, we make a recursive call to the emptyBucket function with the continuation token needed to get the next batch of items.

 

You may have noticed logic based on whether the S3 bucket has versioning enabled or not. If the bucket has versioning enabled, we need to handle the listing and deletion of objects a little differently as shown below:

 

function emptyVersionedBucket(params, event, context, physicalResourceId) {
console.log("emptyVersionedBucket(): ", JSON.stringify(params));
    s3.listObjectVersions(params, (e, data) => {
        if (e) return sendResponse(event, context, "FAILED", physicalResourceId, e);
        // Create the object needed to delete items from the bucket
        var obj = {
            'Bucket': params.Bucket,
            'Delete': {'Objects':[]}
        };
        var arr = data.DeleteMarkers.length > 0 ? data.DeleteMarkers : data.Versions;
        obj.Delete.Objects = arr.map(v => ({
            'Key': v.Key,
            'VersionId': v.VersionId
        }));


        return removeVersionedItems(obj, data, event, context, physicalResourceId);
    });
}

 

function removeVersionedItems(obj, data, event, context, physicalResourceId) {
    s3.deleteObjects(obj, (x, d) => {
        if  return sendResponse(event, context, "FAILED", null, x);


        console.log(`Removed ${d.Deleted.length} versioned items ok.`);
        // Was the original request truncated?
        if (data.isTruncated) {
            return emptyVersionedBucket({
                'Bucket': obj.Bucket,
                'KeyMarker': data.NextKeyMarker,
                'VersionIdMarker': data.NextVersionIdMarker
            }, event, context, physicalResourceId);
        }


        // Are there markers to remove?
        var haveMarkers = d.Deleted.some(elem => elem.DeleteMarker);
        if (haveMarkers) {
            return emptyVersionedBucket({
                'Bucket': obj.Bucket
            }, event, context, physicalResourceId);
        }


        return checkAndDeleteBucket(obj.Bucket, event, context, physicalResourceId);
    });
}

 

Here we need to list the object versions, which returns a list of objects with their keys and version ids. Using this data we can make a request to delete the objects, similar to the way we deleted objects from an un-versioned bucket. Once all versioned files have been deleted we move onto deleting the bucket.

 

function checkAndDeleteBucket(bucketName, event, context, physicalResourceId) {
    // Bucket is empty, delete it
    s3.headBucket({ 'Bucket': bucketName }, x => {
        if  {
            // Chances are the bucket has already been deleted
            // as if we are here, based on the fact we have listed
            // and deleted some objects, the deletion of the Bucket
            // has already taken place, so return SUCCESS
            // (Error could be either 404 or 403)
            return sendResponse(event, context, "SUCCESS", physicalResourceId, x);
        }
        s3.deleteBucket({ 'Bucket': bucketName }, error => {
            if (error) {
                console.log("ERROR: ", error);
                return sendResponse(event, context, "FAILED", physicalResourceId, error);
            }
            return sendResponse(event,
                context,
                "SUCCESS",
                physicalResourceId,
                null,
                {
                    'Message': `${bucketName} emptied and deleted!`
                }
            );
        });
    });
}

 

The headBucket function is a very useful API to check that the bucket actually exists. If it does, we call the deleteBucket method as at this point the bucket should be empty. If all goes well we send a response that the bucket has been emptied and deleted! The sendResponse method is shown here for reference only as it was taken from here (with some minor modifications): (The Tapir's Tale: Extending CloudFormation with Lambda-Backed Custom Resources)

 

function sendResponse(event, context, status, physicalResourceId, err, data) {
    var json = JSON.stringify({
        StackId: event.StackId,
        RequestId: event.RequestId,
        LogicalResourceId: event.LogicalResourceId,
        PhysicalResourceId: physicalResourceId || context.logStreamName,
        Status: status,
        Reason: "See details in CloudWatch Log: " + context.logStreamName,
        Data: data || { 'Message': status }
    });
    console.log("RESPONSE: ", json);


    var https = require('https');
    var url = require('url');


    var parsedUrl = url.parse(event.ResponseURL);
    var options = {
        hostname: parsedUrl.hostname,
        port: 443,
        path: parsedUrl.path,
        method: "PUT",
        headers: {
            "content-type": "",
            "content-length": json.length
        }
    };


    var request = https.request(options, response => {
        console.log("STATUS: " + response.statusCode);
        console.log("HEADERS: " + JSON.stringify(response.headers));
        context.done();
    });


    request.on("error", error => {
        console.log("sendResponse Error: ", error);
        context.done();
    });


    request.write(json);
    request.end();
}

 

Now, whenever we build a stack we are confident in the knowledge that any bucket attached to the stack will be cleared out and emptied. Future improvements could be; a scalable queueing system that fires the same Lambda per queue based on how many items there are in the buckets. Potentially we could have tens of thousands of objects that need deleting and we cant be bottlenecked by this.

 

Anyways, thanks for taking the time to read this and as always I welcome your comments and contributions. Laters!