, ,

Your first Web App in the cloud – AWS and Beanstalk

mf144

Hello fellow readers! 

In this blog you will learn how to set up a web-game with worldwide ranking in the cloud without having to deal with complicated deployment. That means for you: More time on your application. 

The app uses Node.js with Express and MongoDB as backend. The frontend is made out of plain html, css and javascript. For deployment we used the PAAS (platform as a service) from Amazon AWS, Beanstalk.

You can find the game on GitHub:
https://github.com/Pyrokahd/CircleClicker

App 

CircleClicker is a skill game with the goal to click on the appearing circles as fast as possible before they disappear again. 

We used javascript for the Frontend and Backend, in the form of Node.js. Javascript is well known and is used almost everywhere. This is also connected to a huge community and therefore already many answers to questions asked. 

Tip:
When deciding which programming language or tool you want to work with, always consider what the community is like and how many tutorials are available. 

Backend

Getting started

After installing node.js and express, we can use the “Express Application Generator” to generate a skeleton web server. We do this by navigating to the folder, in which we want the project to be and type the following command:

With –view we define which view engine to use, for example pug or ejs. Those are used to generate response pages using templates and variables.
However in this project we don’t use them except for the auto generated uses, to keep it simple.

To install additional modules type npm install ModuleName.

This will add the dependency to the package.json. Beanstalk will install all the modules listed in your package.json file automatically, so you don’t have to worry about uploading or installing them by yourself.

Now we add responses for the server to reply to client requests.

The first one sends the default page to the client, once it is connected. This will be our html site containing the game.

app.get('/', function(req, res, next) {
    res.sendFile(path.join(__dirname +           "/public/main.html")); 
});

Here we tell our server to respond to GET requests on the root server path “/”, which is the URL without anything behind it. The Response is our main.html located in the public folder behind the root. This means we have our main.html as the default web page.

The next GET response will respond to the url “/getScore” by sending the top 10 highscores to the client. 

app.get('/getScore', function(req, res, next) {

In there we make a query to our MongoDB and then construct a JSON string out of it to send it to the client. The query is shown later when we talk about MongoDB.

The last response answers a POST request from the client and is used to create new entries for the database, if the player sends a score.

app.post('/sendScore', function(req, res, next) {

In this function we receive a name and a score from the client in the form of a JSON object. Those variables are then used to create a database entry.

Frontend

The Frontend consists of the HTML page, the CSS stylesheet and one or more javascript files for the logic and functionality.

The HTML and javascript files are stored in their respective folder under the “public” folder in the server.

HTML

For the HTML document the important parts are a form to enter the archived score plus a name and buttons to start the game and send the score to the server. Other important parts, like the HTML canvas are generated once the game is started.

How exactly the html looks is not important. It just needs all the relevant elements with a respective ID, to be called upon in the javascript file, in which all the event handling and game logic happens.

The following should be avoided because it violates the content security policy (CSP):

  • inline css
  • inline javascript
  • inline eventhandler (i.e. onClick=”function” for a button)
  • importing scripts which are not hosted on your own server (i.e. download jquery and put it on your server)

It is also important to import the gameLogic script at the end of the html body. That way the html will be loaded first and we can access all elements in the javascript file without trouble.

Javascript

First we give the buttons on the site some functions to invoke, by adding click events to them. A HTML canvas is created inside an object, which holds the gamelogic, once the site has loaded. 

document.getElementById("startBtn").addEventListener("click", startGame);
document.getElementById("showPopUpBtn").addEventListener("click", showPopUp);
document.getElementById("submitBtn").addEventListener("click", sendScore);
document.getElementById("resetBtn").addEventListener("click", hidePopUp);

The first three of those buttons provide the necessary functionality to play the game and interact with the server. 

The first one “startBtn” does exactly what the name suggests. It starts the game by calling the startGame function. This sets some variables like lifes and points and then starts an intervalTimer to act as our Game Loop. After a certain time, which decreases the longer the game runs, a circle-object is spawned. This has the logic to increase points or decrease lifes and despawn after a while.
The canvas has an EventListener to check for clicks inside it. To check if a circle is clicked, the mouse-position and the circle-position are compared (the circle position needs to be increased by the offset of the canvas position and decreased by the scroll-y height).

The second button has the functionality to show the popup-form, which is used to enter a name and send it, together with the archived, score to the server. This function is also called automatically once you lose the game.

The submit Button calls the sendScore function, which uses jquery and ajax to make a POST request to the server and send the score and username as a JSON object named data. Here we also use the “/sendScore” url which we set for a post response, at the server.

const serverURL = window.location.origin;

$.ajax({
            type:"POST",
            async: true,
            url: serverURL+"/sendScore",
            data:data,
            success: function(dataResponse){
                console.log("success beim senden: ");
                console.log(dataResponse);
                setTimeout(requestLeaderboard,1000);
            },
            statusCode: {
                404: function(){
                    alert("not found");
                } //, 300:()=>{}, ...
            }
});

The last relevant function is to request the leaderboard. This is called upon loading the page and once we send our new score to the server, to update the leaderboard. This also uses jquery and ajax to make it easier to write the GET request.

$.ajax
({
    type:"GET",
    async: true,
    url: serverURL+"/getScore",
    success: function(dataResponse){
    highScores = JSON.parse(dataResponse);

    //Build html String from JSON response
    var leaderBoardString = "<h2>Leaderboard</h2>";
    var tableString = '<table id="table"><tr>
                       <th>Name</th>  <th>Score</th>';
    for (var i = 0; i < highScores.user.length;i++){
        tableString += "<tr><td>"+highScores.user[i].name
                    +"</td><td>"
                    +highScores.user[i].score
                    +"</td></tr>";
    }
    tableString += '</tr></table>';
    //Update leaderBoard with created table
    document.getElementById("leaderboardArea").innerHTML
                    = leaderBoardString+tableString;
     },
     statusCode: {
         404: function(){
             alert("not found");
         } //, 300:()=>{}, ...
    }
});

The “/getScore” url from the GET response of the server used to get the top 10 scores as a JSON object. This JSON object is then used to create a table in the form of a HTML string, which is set as the innerHTML of a div-box.

MongoDB

In our application we use MongoDB together with the mongoose module. The mongoose module simplifies certain MongoDB functions. 

Host

As the host for the database we used MongoDB Atlas, which lets you create a free MongoDB in the cloud of a provider of your choice. We chose AWS, since we also use their Beanstalk service. The free version of this database has some limits, but for a small project like this, it is more than enough.

After a database is created, you can create a user, assign rights and copy a connection-string from MongoDB Atlas. 

Connection

We are using this string and mongoose to connect to the DB in a few lines of code.

mongoose.connect(mongoDBConnectionString, { useNewUrlParser: true },);
//Get the default connection
var db = mongoose.connection;
//Bind connection to error event (to get notification of connection errors)
db.on('error', console.error.bind(console, 'MongoDB connection error:'));

After the connection is established we can create a mongoose model, which is like a blueprint for the entries in one table. This model is created from a mongoose schema in which we define the structure.

var UserModelSchema = new mongoose.Schema({
name: String,
score: Number
});
var UserModel = mongoose.model('UserModel', UserModelSchema );

Create Entry

Now we are ready to add new entries to the database. This is easily done by creating an instance of the mongoose model and filling in the appropriate fields. After calling the save function, a new entry is added.

var testInstance = new UserModel({name: _name, score: _score});
    testInstance.save(function (err, testInstance) {
        if (err) return console.error(err);
        console.log("new Entry saved");
    });

Query

To make a query is just as easy. First we define a query object and set the parameter according to our needs.

// find all athletes that play tennis
    var query = UserModel.find();
    // selecting the 'name' and 'score' fields but no _id field
    query.select('name score -_id'); 
    // sort by score
    query.sort({ score: -1 });
    //limit our results to 10 items
    query.limit(10);
    //to return JS objects not Mongoose Documents
    query.lean();

We can then execute that query and receive a result depending on the settings

query.exec(function (err, queryResult) {
      if (err) return handleError(err);

        //use queryResult (JS-Object)
        //to create JSON string

        //and send it to client
        res.send(resJSON); 
    });

Into the cloud!

But how do we get into the cloud? The first thing we do is decide on a cloud provider. We very quickly chose AWS because we were particularly impressed by the service they offered. It’s about AWS Beanstalk. Amazon promises to take over a lot of backend provisioning, like load-balancing,scaling, e2-instances and security groups.

Which means more time for actually programming. 

Does that work? To a large extent yes! 

How does it work?

Very simple. You create an Amazon AWS account, go into the development console and create a new AWS Beanstalk environment. Here you define the domain name and in which programming language your app is written. Then start the environment. Afterwards you upload the app which is put together to an archive and check on the domain if the app works. Updates are done the same way. Merge the new code into an archive, upload and select. 

You now have a website with your own game. Load Balancer is included, highscore is included and basic security settings are set. 

Check Logs on CloudWatch

In your Beanstalk environment configuration under monitoring, you can enable your logs to be streamed to CloudWatch. This comes with some extra cost, but allows you to have all your logs online and centralized in one location. 

CloudWatch also has many more functionalities like alarms when your application gets downscaled because there is less traffic, or monitoring of the performance of your ressources.

However we are interested in our logs, which you find under your protocol groups in CloudWatch. There is one log with the ending stdout.log (by default). This is where all of the console.log or console.error messages from our server are located.

Security Settings

If you want to make your application even more secure, you can put a little more time into it. We have decided to set up HTTPS and use the SCP protocol. 

HTTPS

To provide HTTPS, proceed as follows:

  • Create a certificate for domain names in AWS Certificate Manager (ACM)
  • LoadBalancer https Configuration
  • Add listener
  • Port 443
  • Protocol HTTPS
  • Upload Certificate

Helmet Security

Helmet is a library that checks some basic security issues regarding the header. One important aspect of this is the Content Security Policy (CSP) header. It forces you to write your html files in a secure manner to avoid cross site scripting attacks.

Problem: CSP-header enforces security policies

Despite not being completely CSP conform it worked on the local server but

to make it work in the cloud we had to follow the CSP guidelines. So we had to remove all inline Javascript and CSS to eradicate all errors regarding CSP.

Problem: HTTPS Forwarding

By activating Helmet security the resources of the website become unreachable. Through the AWS logs we found the following error message, but could not find out exactly where the problem was:

2020/09/05 17:53:16 [error] 19019#0: *7 connect() failed (111: Connection refused) while connecting to upstream, client: 172.31.37.17, server: , request: “GET / HTTP/1.1”, upstream: “http://127.0.0.1:8080/”, host: “172.31.41.163”

We have also looked at load balancers and security groups, but all the settings are correct here. In further research with the Developer Console in the browser, we found out that https requests are required. 

GET https://…elasticbeanstalk.com/javascripts/GameLogic.js net::ERR_CONNECTION_TIMED_OUT

Since we do not have the required permissions, we could not set up https. So we had to turn off the CSP for our game. The already implemented security features such as the removal of css inline tags remain. 

In previous tests, while disabling helmet we didn’t generate a new package-lock.json file, which holds a tree of generated packages. Because this was not updated we didn’t remove helmet completely and got wrong results for the test, which led us wrongly to believe helmet was not the problem.

Conclusion

Although this was not the initial goal, we learned a lot about networking during this project. Especially topics like setting up a server as well as the communication between database, client and server. Despite the roadblocks, we were very positively surprised by the AWS Cloud and Beanstalk. We can recommend Beanstalk if you want to get simple applications and websites in the cloud ASAP. As soon as we implemented security related code we had to make an additional effort, which went beyond beanstalk alone, to get it running.  


by

mf144

Tags:

Comments

Leave a Reply