Google Cloud Platform now supports Go 1.11 for Cloud functions. Go and Serverless are 2 things I’m very excited about these days so I decided to give this new GCP feature a try and build a basic Image Resizer in pure Go using the disintegration/imaging package.
Resizing gophers
Sometimes you just have gopher images that are too large to display on your website. They would eat up the precious bandwidth of your users ! Like this massive one:

So we’ll be transforming it into this smaller, more reasonable gopher:
What we’ll build in order to achieve this is a cloud function with an endpoint that can be called in the following way:
https://{region}-{project-name}.
cloudfunctions.net/ResizeImage?url={url}&height={height}&width={width}
We just send an HTTP GET request with our image url, the desired width and height and we receive an image in JPEG format in the HTTP response body.
Setup
To follow along, you’ll need to have Go 1.11 installed on your dev box, a Google Cloud Platform account and the gcloud command line tool setup.
I’ve setup a github repo if you would like to browse it/clone it locally and test as you’re reading.
The ResizeImage cloud function
The cloud function we’ll use is an HTTP function. This cloud function type gives us an HTTPS endpoint for our code without needing an extra API Gateway so that’s pretty convenient for our use case. The other cloud function type: Background functions can be triggered by events such as Pub/Sub or Cloud Storage events. We won’t be using this today but it could come in handy for other tasks such as caching our images, cleaning up the cache periodically etc. I might write about Background functions in a future article.
The ResizeImage function needs to do the following things:
Parse the Query string into url, height and width
Fetch the original image
Resize the image
Encode the output image to Jpg
Write the encoded output image to the HTTP Response stream
To keep things simple I’ve included all the code in a single file. The filename doesn’t matter.
package gcf_go_image_resizer
import (
"bytes"
"errors"
"github.com/disintegration/imaging"
"image"
"image/jpeg"
"io"
"net/http"
"strconv"
)
// Cloud Function entry point
func ResizeImage(w http.ResponseWriter, r *http.Request) {
// parse the url query sting into ResizerParams
p, err := ParseQuery(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// fetch input image and resize
img, err := FetchAndResizeImage(p)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// encode output image to jpeg buffer
encoded, err := EncodeImageToJpg(img)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// set Content-Type and Content-Length headers
w.Header().Set("Content-Type", "image/jpeg")
w.Header().Set("Content-Length", strconv.Itoa(encoded.Len()))
// write the output image to http response body
_, err = io.Copy(w, encoded)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// struct containing the initial query params
type ResizerParams struct {
url string
height int
width int
}
// parse/validate the url params
func ParseQuery(r *http.Request) (*ResizerParams, error) {
var p ResizerParams
query := r.URL.Query()
url := query.Get("url")
if url == "" {
return &p, errors.New("Url Param 'url' is missing")
}
width, _ := strconv.Atoi(query.Get("width"))
height, _ := strconv.Atoi(query.Get("height"))
if width == 0 && height == 0 {
return &p, errors.New("Url Param 'height' or 'width' must be set")
}
p = NewResizerParams(url, height, width)
return &p, nil
}
// ResizerParams factory
func NewResizerParams(url string, height int, width int) ResizerParams {
return ResizerParams{url, height, width}
}
// fetch the image from provided url and resize it
func FetchAndResizeImage(p *ResizerParams) (*image.Image, error) {
var dst image.Image
// fetch input data
response, err := http.Get(p.url)
if err != nil {
return &dst, err
}
// don't forget to close the response
defer response.Body.Close()
// decode input data to image
src, _, err := image.Decode(response.Body)
if err != nil {
return &dst, err
}
// resize input image
dst = imaging.Resize(src, p.width, p.height, imaging.Lanczos)
return &dst, nil
}
// encode image to jpeg
func EncodeImageToJpg(img *image.Image) (*bytes.Buffer, error) {
encoded := &bytes.Buffer{}
err := jpeg.Encode(encoded, *img, nil)
return encoded, err
}
I’ve also included a go.mod file with the dependencies as that’s supported.
To push all this to the cloud, we use the gcloud tool and specify the name of the function that we want to deploy. In this case it’s this go function:
ResizeImage(w http.ResponseWriter, r *http.Request)
The ResizeImage function in the code above has the correct signature so it can be deployed. You’ll notice that it’s just a http.HandlerFunc from the Go standard library.
$ gcloud functions deploy ResizeImage --runtime go111 --trigger-http
Deploying function (may take a while - up to 2 minutes)...done.
availableMemoryMb: 256
entryPoint: ResizeImage
httpsTrigger:
url: https://xxx.cloudfunctions.net/ResizeImage
Our function is deployed and we get the endpoint url in return. Big gopher images here we come !
Local development
To test this code as I was writing it, I’ve used a local development server to run the handler. During development, it would be pretty inconvenient to redeploy each time I make a small change to test and I also couldn’t find a local Golang cloud functions emulator (There is a node.js emulator, so hopefully a Go version is planned ?). However, the setup with a local server worked out pretty well for this simple function.
Next Steps
This a simple example but hopefully it can help you to get started with Golang on Cloud Functions. If we wanted to make this project production-ready here are some examples of the next steps we could take:
Add Unit tests
Cache the input images
Cache the output images
Add Authentication
Benchmark resizing libraries to find the fastest alternative
Thanks for reading and happy resizing !
Github Repo: https://github.com/didil/gcf-go-image-resizer