Thursday, July 19, 2018

Contact Me Page for a S3 Hosted Website Part 1

In an effort to branch out and increase my knowledge of cloud computing I recently took on a couple of contracts for redoing the websites of a couple of local businesses.  The existing sites weren't bad necessarily - just aged and in need of some content, style, and SEO related updated.  Additionally the hosting fees for these sites were pretty large considering these were simple 'pamphlet style' sites.

The goals of the new websites were pretty simple - reduce hosting costs and increase SEO visibility.

While the latter is a level of alchemy unknown to most and there are simply better qualified people to discuss this topic I'll avoid it here.  It should be sufficient to state that I did a bunch of research, found a reasonably priced expert, and took some ideas from SEO efforts that were being taken by my employer.  I mixed these ideas together to produce something that kept these businesses on page one for most of their search terms.  Additionally their businesses typically landed on the first three listings on Google Maps when searching for "<insert relevant term> near me".

The former is what I was able to focus on - and I felt the most reasonable priced option was going to be Amazon Web Services (AWS).  When discussing the necessary features with each of the business owners there were some common requirements which are summarized below.

  • Clean updated look
  • No specific need to update the site frequently
  • Needed better tracking via Google Analytics, including the ability to track events
  • Need a "contact me" form.  In one case there would be several on different landing pages.  And in another case the ability to attach several images was required.
It's that last bullet point that really caused me some initial alarm - "how am I supposed to handle a form post on a static S3 web site"?  After some initial searching I narrowed down two potential ways a form post could be handled on an S3 hosted web site.

Post to S3 Bucket

Not a great option and fraught with problems

Amazon has a great write up on how to you can perform a form post and have a file (just the file) stored in an S3 bucket here.  If you haven't had any exposure to the AWS security model or really any experience with AWS the instructions are bit obscure and implementation can be problematic.  However, once implemented the first time it becomes easier to setup someplace else.

So what's the problem with going this direction?

First, the learning curve for setup.  One of the main problems I found trying to get the policy and signature settings correct highly troublesome.  There is a nice tool for this that I found here, which helped alleviate the setup.  However, this quickly become cumbersome as the requirements for more fields in the contact page were added.  Essentially having to run this with iteration became quite torturous exercise.  Additionally, you have a built-in expiration date in the X-Amz-Date element in the form.  I've seen some people generate the policy and signature settings on the fly right after a page load to avoid this expiration issue.

Second, while this worked as expected we were limited to the type of data.  Sure this allowed me to  drop an image file(and only one!) in the S3 bucket, but now I needed another post (typically via an .ajax() call) to send the rest of the contact data (like name, email, etc.) someplace.  An added bonus - the contact information needed to be sent prior to posting the image file - as illustrated by the "success_action_redirect" element in the form and the behavior that occurs after the form post.

Finally, there was a great deal of extra code on the back-end to deal with having the contact information before having the image file.  After all, what's the point of sending the new prospect to the company if you can't attach the image to the email?

None of these could be considered show stoppers.  But really going this direction seemed 'kludgey' and felt very unnatural.  Additionally I felt this approach introduced a heavy support burden which I didn't want to deal with - after all I have day job.  However, what finally killed this approach was I simply couldn't figure out how to post more than one image file - I'm sure there's a way, but I quickly lost interest in this approach given other constraints outlined above.

Gateway API/Lambda


Felt more natural...easier to implement...faster learning curve

If you haven't been introduced to, heard of, or used the Serverless Framework I suggest you stop reading now and go learn this tool.  It is, in my mind, a game changer.  I had been struggling with how to use the Gateway API/Lambda combination for some time when I came across the Serverless Framework.  And while I got 'something' working without it I was still weeks away from getting a proof of concept off the ground with this contact form.

There are some pitfalls with this approach - so don't think that this solution will work in all cases. When sending files through Gateway API there are two limitations.  First, you can't send the raw file(s).  You must base64 encode the file(s) prior to sending them.  To do this first paste this method in your javascript code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    function getBase64(file, onLoadCallback) {

        return new Promise(function (resolve, reject) {
            if (file) {
                var reader = new FileReader();
                reader.onload = function () {
                    resolve(reader.result);
                };
                reader.onerror = reject;
                reader.readAsDataURL(file);
            }
        });
    };

Second, prior to posting the form to Gateway API you have intercept the file(s) on the form and transfer them into a base64 string(s).  This is how I implemented it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
                var promises = new Array();

                for(var i = 0; i < fileCount; i++) {
                    var image_file = document.querySelector("#image_file").files[i];
                    promises.push(getBase64(image_file));
                }

                Promise.all(promises).then( function (imageDatas) {

                    for (var i = 0; i < imageDatas.length; i++) {
                        filesize += imageDatas[i].length;

                        if (filesize > 0 &&
                            filesize < 6e6) {

                            $('<input>').attr({
                                type: 'hidden',
                                id: 'image-file-' + i,
                                name: 'image-file-' + i,
                                value : imageDatas[i]
                            }).appendTo($(form));
                        }
                    }

                    postForm($(form).serializeArray());
                });
So here I am doing a couple of things.  First, doing the conversion to base64 will return a promise.  Second I need to wait for all the promises to return.  Finally I will then create a new element on the form with the base64 encoded string placed in the value for each file added to the form.

So what's with this code?

1
2
3
4
                        filesize += imageDatas[i].length;

                        if (filesize > 0 &&
                            filesize < 6e6) {

Yea there is a gotcha here...the payload being send to Gateway API cannot exceed 6GB - which for our purposes was more than sufficient. If you need to send something larger that 6GB?  Your S3 form post approach is more suited for your needs.

So that covers my initial problem was how to send information from a website hosted by S3 to a back-end system.  You can review my full implementation here to see all the code for html/js files.  My next post will go more into the back-end code that captures this data and then emails the business their new lead information.