Building a Grails short url service

Probably most of the people are already familiar with existing URL Shortener Websites, like Ow.ly, Tinyurl, Bitly and Goo.gl. They provide short url generation, which is the main purpose of their existence, but also some other features like analytics, custom domains, etc.

In my case, I didn’t need many of the extra features (at least for now) and wanted something simple, just a short url generator based on a long path/url, with my custom domain on it also. So I ended up building a custom solution for Grails, that is simple and does what I needed at that moment.

Creating Grails short url domain

Basically I created a new domain entity that will hold the association between the original url path with the new generated token. The seed attribute will store the string used to generate the url token, so if pathAsSeed is true the path will be used as the seed, otherwise a random uuid string is utilized, and the murmur3_32 hashing function is responsible to create it based on seed variable.

import com.google.common.hash.Hashing
import java.nio.charset.StandardCharsets

class ShortUrl {

    String id
    String seed
    String token
    String path

    static constraints = {
        seed blank: false, nullable: false, unique: true
        token blank: false, nullable: false, unique: true
        path blank: false, nullable: false, unique: true
    }

    static generateToken(String path, boolean pathAsSeed = true) {
        def seed = pathAsSeed ? path : UUID.randomUUID().toString()
        return [token: Hashing.murmur3_32().hashString(seed, StandardCharsets.UTF_8).toString(), seed: seed, path: path]
    }
}

Creating Grails short url service

The main idea of the algorithm is to be able to generate a token based on the incoming url path. As you probably know hashing algorithms have certain probability to collide, so to make it more robust I added a collision resolver up to 3 tries, in this case, but you can tweak it as your desire. So anytime that createShorUrl method is executed it will return the same exact token for the incoming path.

class ShortUrlService {

    def stopAtIteration = 3
    def grailsLinkGenerator

    def createShortUrl(String path) {
        return generateShortUrl(path)
    }

    private generateShortUrl(String path, int iteration = 1) {
        // cut iteration after reaching max and return full path
        if (iteration > stopAtIteration) {
            return buildShortUrl(path)
        }
        def result = ShortUrl.generateToken(path, iteration == 1)
        def shortUrl = ShortUrl.findByToken(result.token)
        // check if token is not already used
        if (!shortUrl) {
            shortUrl = new ShortUrl(result)
            shortUrl.save(validate:true, failOnError: true)
        }
        // check whether is a hash collision
        else if (shortUrl.path != path) {
            // try to find it by path cause token was generated by a different seed
            shortUrl = ShortUrl.findByPath(path)
            if (!shortUrl) {
                // resolve collision cause there is no short url for current path
                return generateShortUrl(path, iteration + 1)
            }
        }
        // return shorten path
        return buildShortUrl(shortUrl.token)
    }

    private buildShortUrl(String path) {
        def server = grailsLinkGenerator.serverBaseURL
        return "$server/$path"
    }
}

Creating Grails short url controller

A new controller was added to handle short url translation into a normal path. Another option could be to use a Grails Filter.

import grails.plugin.springsecurity.annotation.Secured

@Secured(["IS_AUTHENTICATED_ANONYMOUSLY"])
class ShortUrlController {

    def index(String token) {
        def path = ShortUrl.findByToken(token)
        if (path) {
            redirect(uri: path.path)
        }
        else {
            render view: "404"
        }
    }
}

Adding Grails short url to UrlMappings file

Added a new path to UrlMappings.groovy file.

class UrlMappings {

    static mappings = {

        ............

        // for short urls
        "/$token"(controller: "shortUrl")

        ............

    }
}

Execution example

The following code snippet is an example of shortUrlService.createShortUrl method execution.

def link = grailsLinkGenerator.link([
    controller: "myController",
    action: "myAction",
    params: [param1: "param1", param2: "param2"]
])

def shortUrl = shortUrlService.createShortUrl(link)