Working with non-SSL Web Services within an SSL page

I was making a Progressive Web App (PWA) and encountered a problem pretty quickly.  PWAs need to be served over SSL/HTTPS.  The services that they access must also be served over SSL (a page served over SSL cannot access non-SSL resources).  Additionally, since my app is being served from a different domain, there must be a Cross Origin Resource Sharing header permitting the application to use the data.  My problem is that I ran into a situation where I needed to access a resource that met neither of these requirements.

Failed to load http://myUrl.com: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://SomeOtherURL.com' is therefore not allowed access.

The solution to this seemed obvious: a proxy service that would consume the non-SSL feed and make the results available over HTTPS.  There exists some third party services that can do this for you (My SSL Proxy, for example).  But the services that I found were not meant for applications and generally don’t add the required CORS headers.  Implementing something like this isn’t hard, but for a lightweight application for which I wasn’t planning on making any immediate revenue, I wanted to minimize my hosting costs.  This is where two services that Google provides come into play.

The first Google service is Firebase.

Firebase (available at https://Firebase.Google.com) allows you to host static assets in the Google cloud.  These assets are servers over SSL.  This was a perfect place for hosting most of the source code that was going to run on the mobile device.

As for the service proxy, I made a proxy service that ran on the second Google service: App Engine.  Google’s cloud service App Engine (available at https://cloud.google.com/appengine/) allowed me to write my proxy service using NodeJS (available at https://nodejs.org/).  I had it query the data I needed from the non-SSL service and cache the data for 30 seconds at a time.  All of Google’s services use SSL by default, so I didn’t have to do anything special.  When returning the response I added a few headers to handle CORS requirements.  Here’s the code for the node server.  If you use it, you will need to modify it so that any parameters that you need to pass to the non-SSL service are passed through.

const http = require('http')
const port = 80;
const MAX_SCHEDULE_AGE = 30;
const SERVICE_URL=`YOUR_SERVICE_URL`

var schedule = '[]';
var lastUpdate = new Date(1,1,1);


function timeDifference(a,b) { 
    var c = (b.getTime() - a.getTime())/1000;
    return c;
}

function sendSchedule(resp) {
    resp.setHeader('Access-Control-Allow-Origin', '*');
	resp.setHeader('Access-Control-Request-Method', '*');
	resp.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET');
	resp.setHeader('Access-Control-Allow-Headers', '*');
    resp.end(schedule);
}
const requestHandler = (request,response) => {
    var now = new Date();
    var diff = timeDifference(lastUpdate, now);
    if(diff>MAX_SCHEDULE_AGE) {
        console.log('schedule is stale. updating');
        sendSchedule(response);
        return;
    }
    updateSchedule((d)=> {
        console.log('schedule updated')
        sendSchedule(response);
    });
    console.log(request.url);

}

const server = http.createServer(requestHandler);

const https = require('https');


function updateSchedule(onUpdate) { 
    https.get(SERVICE_URL, (resp) => {
        let data = '';
        resp.on('data', (chunk) => {
            data += chunk;
        });
        resp.on('end', ()=> {
            schedule = data;
            lastUpdate = new Date();
            if(onUpdate) {
                onUpdate(schedule);
            }
        })
    });
}

server.listen(port, (err) => {

    if(err) {
        return console.log('something bad happened');
    }
    console.log(`server is listening on port ${port}`);
    updateSchedule();
}) 

One of the other advantages of having this proxy service is that there is a now a layer for hiding any additional information that is necessary for accessing the service of interest.  For example, if you are communicating with a service that requires some key or app id for access, that information would never flow through to the client.

Some configuration was necessary for deployment, but not much.  I had to add a simple app.yaml file to the project.  These are the contents.

# [START runtime]
runtime: nodejs10
# [END runtime]

Deployment of the application was unexpectedly easy.  I already had the source code stored in a git repository.  App Engine exposes a Linux terminal through the browser.  I cloned my repository and typed a few commands.

$  export PORT=8080 && npm install
$  gcloud app create
$  gcloud app deploy

After answering YES to a configuration prompt, the application was deployed and running.

One might wonder why I have the code for my application hosted in two different services.  I could have placed the entire thing in App Engine.  My motivation for separating them is that I plan to have some other applications interface with the same service.  So I wanted to keep the code (for specific clients of the code) separate from the service interface.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.