Marcin Kaciuba tech blog

Marcin Kaciuba tech notes

Mort 2022

It’s been over 4 years from my post about mort and I’m still using it :). Today I want to discuss what has changed in mort and some let’s say interesting bugs that I’ve found

Improvement in mort 0.21.5

tengo scripts

This is the biggest feature that I’ve added to mort. It allows users to write their own URL decoder without rebuilding of mort. For scripting language, I’ve used tengo

Let’s see an example

# https://github.com/aldor007/mort/blob/master/configuration/config.yml
buckets:
    tengo:
        keys:
          - accessKey: "acc"
            secretAccessKey: "sec"
        transform:
            kind: "tengo" # new transform kind
            tengoPath: 'parse.tengo' # path to tengo script
        storages:
            basic:
                kind: "http"
                url: "https://i.imgur.com/<item>"
                headers:
                  "x--key": "sec"
            transform:
                kind: "local-meta"
                rootPath: "/tmp/mort/"
                pathPrefix: "transforms"

Mort will read parse.tengo file and execute it on each request for bucket tengo.

Content of parse.tengo file:

fmt := import("fmt")
text := import("text")

parse := func(reqUrl, bucketConfigF, obj) {
     // split by "." to remove object extension
    elements := text.split_n(reqUrl.path, ".", 2)
    ext := elements[1]
    if len(elements) == 1 {
        return ""
    }
    // split by "," to find resize parameters
    elements = text.split(elements[0], ",")

    // url has no transform
    if len(elements) == 1 {
        return ""
    }

    // apply parameters
    width := 0
    height := 0
    parent := elements[0] +"." +  ext
    trans := elements[1:]
    for tran in trans {
        if tran[0] == 'w' {
            width = tran[1:]
        }

        if tran[0] == 'h' {
            height = tran[1:]
        }
    }

    obj.transforms.resize(int(width), int(height), false, false, false)
    return parent
}

parent := parse(url, bucketConfig, obj)

Above script will work for URL http://localhost:8084/tengo/udXmD2T,w100,h100.jpeg

More info on how to write your own parse can be found in doc

Redis lock

From now on mort can use a redis server for indicating the generation of the image. Mort instance which will win race for the lock will notify others when a process has ended, so they can read image from cache or storage. Lock has a similar configuration as redis cache. Example:

server:
  lock:
      type: "redis"
      address:
        - "localhost:6379"
      clientConfig:

Implementation details can be found here

access logs

Small feature but useful for debugging. To enable

server:
    accessLogs: true

support for more storages

Mort now supports

  • B2
  • Azure
  • Oracle
  • Google
  • and even sftp storage

More about it here

CI/CD improvements

For CI instead of travis-ci I’m now using Github Actions. Changes:

  • tests executed on each PR and after the merge to master (unit and integrations)
  • mort uses now conventional commits and semantic release for tagging of versions
  • goreleaser for GitHub release with binary
  • auto-deploy to my homelab after docker build

Interesting bugs

During development, I’ve discovered some unintended behaviors of mort.

Issue with serving big files

processTimeout

Processing timeout was applied for all requests not only image creation so if mort was streaming response it has to be completed before the end of processTimeout

processTimeout diff

processTimeout fix

http.Server timeout

Timeout for http.Server is for whole response so if mort is proxying huge file it has now 2h to end it

http.Server diff

processTimeout fix

Git diff

go http.Server timeouts

Golang http.Server timeout. source: Cloudflare blog post

More details about HTTP timeouts of golang net package can be found here https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/

Idea what to do next

  • support for distributed tracing
  • redirect to source storage (so mort will not proxy big files)