Search code examples
reactjsgoamazon-s3webpackgzip

GoLang conditionally serve .js.gz if it exists, otherwise .js


My background is mostly react/frontend, but I've been tasked with improving the performance of our React web app which is served using GoLang extracting the files from S3 using aws sdk for Go. I have configured Webpack to do its thing and use as many of its optimization features as possible, including using its compression plugin to create gzipped .js.gz files alongside the .js files in the bundle that is deployed to S3.

My question is, is there a way in Go and the aws sdk for when it goes to fetch a file from s3 bucket to determine if a gzipped form of that file exists first and grab that one and if not fetch the regular? Is that even the best way of going about this? I understand there is a library in Go for compression at runtime, but it seems more efficient to have that compression done beforehand.

The Go server part is pretty minimal and has a function for getting the bucket file which basically creates an s3 client and then uses the getObject method on that client to fetch the contents of that bucket and then uses the .write method of http.ResponseWriter with the body of those contents.


Solution

  • Yes, it is possible to send the compressed version directly.

    Here is an example :

    package main
    
    import (
        "fmt"
        "net/http"
        "strings"
    
        "github.com/aws/aws-sdk-go/aws"
        "github.com/aws/aws-sdk-go/aws/session"
        "github.com/aws/aws-sdk-go/service/s3"
    )
    
    func getFileFromS3(bucket, key string, w http.ResponseWriter, r *http.Request) error {
        sess, err := session.NewSession(&aws.Config{
            Region: aws.String("your-region"),
            // Add other necessary configurations
        })
        if err != nil {
            return err
        }
    
        client := s3.New(sess)
    
        // Check if gzipped version exists
        gzKey := key + ".gz"
        _, err = client.HeadObject(&s3.HeadObjectInput{
            Bucket: aws.String(bucket),
            Key:    aws.String(gzKey),
        })
        if err == nil {
            // Gzipped version exists, fetch and serve directly
            obj, err := client.GetObject(&s3.GetObjectInput{
                Bucket: aws.String(bucket),
                Key:    aws.String(gzKey),
            })
            if err != nil {
                return err
            }
            defer obj.Body.Close()
    
            // Set appropriate headers
            w.Header().Set("Content-Encoding", "gzip")
            w.Header().Set("Content-Type", "application/javascript") // Set the appropriate content type
    
            // Copy the gzipped content directly to the response
            _, err = fmt.Fprint(w, obj.Body)
            return err
        }
    
        // Gzipped version doesn't exist, fetch the regular version
        obj, err := client.GetObject(&s3.GetObjectInput{
            Bucket: aws.String(bucket),
            Key:    aws.String(key),
        })
        if err != nil {
            return err
        }
        defer obj.Body.Close()
    
        // Set appropriate headers
        w.Header().Set("Content-Type", "application/javascript") // Set the appropriate content type
    
        // Copy the regular content directly to the response
        _, err = fmt.Fprint(w, obj.Body)
        return err
    }
    
    func handler(w http.ResponseWriter, r *http.Request) {
        // Extract the file key from the request URL or any other way you have it
        fileKey := "your-file-key"
    
        // Set appropriate cache headers, handle CORS, etc.
    
        // Fetch the file from S3
        err := getFileFromS3("your-s3-bucket", fileKey, w, r)
        if err != nil {
            // Handle error, e.g., return a 404 or 500 response
            http.Error(w, "Internal Server Error", http.StatusInternalServerError)
            return
        }
    }
    
    func main() {
        http.HandleFunc("/your-endpoint", handler)
        http.ListenAndServe(":8080", nil)
    }