Short urls, as the name suggests, use shorter urls instead of longer ones. Wikipedia explains it like this:

Short URL also known as URL shortening, shortened URL, URL shortening, etc., refers to a technology and service on the Internet, this service can provide a very short URL to replace the original may be longer URL, shorten the long URL address. Users accessing a shortened URL will usually be redirected to the original long URL


The origin of

Although the Internet is now very developed, there are still many scenarios that limit the length of user input. Such as:

  • Tweets should be no longer than 140 characters
  • Some early BBS articles could not be longer than 78 characters in a single line
  • The length of an SMS message cannot exceed 70 characters

However, most of the content of many media and e-commerce platforms is generated by multiple people through complex systems and frameworks. It is common for links to be dozens or even hundreds of characters long. Therefore, it is an inevitable result to use short URL service to spread links in the above scenarios. For example, you should be familiar with the following text screenshots:


Application scenarios

The original intention of short url service is to shorten the long URL, easy to spread. But short url services can do a lot of other things. Like the following:

  • Limit the number of access times. For example, only one access is allowed, and the service is denied on the second access
  • Time restrictions, such as access only for one week and denial of service after that
  • Depending on the location of the visitor
  • Access by password
  • Traffic statistics
  • Peak access time statistics, etc
  • Collect information about visitors, such as:
    • Source city
    • Access time
    • Terminal device and browser
    • Source IP address


  • In fact, in marketing activities can also generate different channels of short web sites, so that through the statistics of these short web sites can also judge the access to different channels and other information

Based on Knative Serverless technology to achieve a short url service

In Knative mode, on-demand allocation can be realized. When there is no traffic, the instance capacity is reduced to zero, and when there is traffic, the instance capacity is automatically expanded to provide services. Now we are based on Ali Cloud container service Knative to achieve a Serverless mode of short URL service. This sample will give you a complete demo, you can create a Knative cluster on ali Cloud container service, use this sample to provide services. One of the simplest features is implemented in this example

  • Through the interface to achieve long url to short url mapping service
  • When a user accesses a short url through a browser, he or she is redirected to a long url through 301

Let’s implement this feature step by step

The database

Since we want to achieve the mapping from short url to long URL, we need to save the long URL information to the database, and generate a short ID as part of the short URL. So we first need to select what database to use. In this example, we choose to use aliyun’s table storage. The biggest advantage of table storage is the quantity service. You only need to pay for the amount you use, and the price is very affordable. The pay-as-you-go price list is shown below. One gigabyte of data storage costs 3.65292 YUAN per year (0.000417 24365=3.65292), isn’t it a good deal?



Short URL generation API

We need an API to generate short urls

/new? Origin-url =${long url}

  • Origin-url Specifies the access address

Returns the result

vEzm6vCopy the code

Suppose we service domain name is short-url.default.serverless.kuberun.com, so now access short-url.default.serverless.kuberun.com/vEzm6v can jump to long url.

Code implementation

package main

import (
    "crypto/md5"
    "encoding/hex"
    "fmt"
    "log"
    "net/http"
    "os"
    "strconv"
    "time"

    "strings"

    "github.com/aliyun/aliyun-tablestore-go-sdk/tablestore"
)

var (
    alphabet = []byte("abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")
    l        = &Log{}
)

func ShortUrl(url string) string {
    md5Str := getMd5Str(url)
    var tempVal int64
    var result [4]string
    for i := 0; i < 4; i++ {
        tempSubStr := md5Str[i*8 : (i+1)*8]
        hexVal, _ := strconv.ParseInt(tempSubStr, 16, 64)
        tempVal = 0x3FFFFFFF & hexVal
        var index int64
        tempUri := []byte{}
        for i := 0; i < 6; i++ {
            index = 0x0000003D & tempVal
            tempUri = append(tempUri, alphabet[index])
            tempVal = tempVal >> 5
        }
        result[i] = string(tempUri)
    }
    return result[0]
}

func getMd5Str(str string) string {
    m := md5.New()
    m.Write([]byte(str))
    c := m.Sum(nil)
    return hex.EncodeToString(c)
}

type Log struct {
}

func (log*Log) Infof(format string, a ... interface{}) { log.log("INFO", format, a...)
}

func (log *Log) Info(msg string) {
    log.log("INFO"."%s", msg)
}

func (log*Log) Errorf(format string, a ... interface{}) { log.log("ERROR", format, a...)
}

func (log *Log) Error(msg string) {
    log.log("ERROR"."%s", msg)
}

func (log*Log) Fatalf(format string, a ... interface{}) { log.log("FATAL", format, a...)
}

func (log *Log) Fatal(msg string) {
    log.log("FATAL"."%s", msg)
}

func (log *Log) log(level, format string, a ... interface{}) { var cstSh, _ = time.LoadLocation("Asia/Shanghai")
    ft := fmt.Sprintf("%s %s %s\n", time.Now().In(cstSh).Format("The 2006-01-02 15:04:05"), level, format)
    fmt.Printf(ft, a...)
}

func handler(w http.ResponseWriter, r *http.Request) {
    l := &Log{}
    l.Infof("Hello world received a request, url: %s", r.URL.Path)
    l.Infof("url:%s ", r.URL)
    //if r.URL.Path == "/favicon.ico" {
    //    http.NotFound(w, r)
    //    return
    //}

    urls := strings.Split(r.URL.Path, "/")
    originUrl := getOriginUrl(urls[len(urls)-1])
    http.Redirect(w, r, originUrl, http.StatusMovedPermanently)
}

func new(w http.ResponseWriter, r *http.Request) {
    l.Infof("Hello world received a request, url: %s", r.URL)
    l.Infof("url:%s ", r.URL)
    originUrl, ok := r.URL.Query()["origin-url"]
    if! ok { l.Errorf("no origin-url params found")
        w.WriteHeader(http.StatusBadRequest)
        w.Write([]byte("Bad request!"))
        return
    }

    surl := ShortUrl(originUrl[0])
    save(surl, originUrl[0])
    fmt.Fprint(w, surl)

}

func getOriginUrl(surl string) string {
    endpoint := os.Getenv("OTS_TEST_ENDPOINT")
    tableName := os.Getenv("TABLE_NAME")
    instanceName := os.Getenv("OTS_TEST_INSTANCENAME")
    accessKeyId := os.Getenv("OTS_TEST_KEYID")
    accessKeySecret := os.Getenv("OTS_TEST_SECRET")
    client := tablestore.NewClient(endpoint, instanceName, accessKeyId, accessKeySecret)

    getRowRequest := &tablestore.GetRowRequest{}
    criteria := &tablestore.SingleRowQueryCriteria{}

    putPk := &tablestore.PrimaryKey{}
    putPk.AddPrimaryKeyColumn("id". surl) criteria.PrimaryKey = putPk getRowRequest.SingleRowQueryCriteria = criteria getRowRequest.SingleRowQueryCriteria.TableName = tableName getRowRequest.SingleRowQueryCriteria.MaxVersion = 1 getResp, _ := client.GetRow(getRowRequest) colmap := getResp.GetColumnMap()return fmt.Sprintf("%s", colmap.Columns["originUrl"][0].Value)
}

func save(surl, originUrl string) {
    endpoint := os.Getenv("OTS_TEST_ENDPOINT")
    tableName := os.Getenv("TABLE_NAME")
    instanceName := os.Getenv("OTS_TEST_INSTANCENAME")
    accessKeyId := os.Getenv("OTS_TEST_KEYID")
    accessKeySecret := os.Getenv("OTS_TEST_SECRET")
    client := tablestore.NewClient(endpoint, instanceName, accessKeyId, accessKeySecret)

    putRowRequest := &tablestore.PutRowRequest{}
    putRowChange := &tablestore.PutRowChange{}
    putRowChange.TableName = tableName

    putPk := &tablestore.PrimaryKey{}
    putPk.AddPrimaryKeyColumn("id", surl)
    putRowChange.PrimaryKey = putPk

    putRowChange.AddColumn("originUrl". originUrl) putRowChange.SetCondition(tablestore.RowExistenceExpectation_IGNORE) putRowRequest.PutRowChange = putRowChangeif_, err := client.PutRow(putRowRequest); err ! = nil { l.Errorf("putrow failed with error: %s", err)
    }
}

func main() {
    http.HandleFunc("/", handler)
    http.HandleFunc("/new", new)
    port := os.Getenv("PORT")
    if port == "" {
        port = "9090"
    }

    if err := http.ListenAndServe(fmt.Sprintf(":%s", port), nil); err ! = nil { log.Fatalf("ListenAndServe error:%s ", err.Error())
    }

}Copy the code

Code I have compiled into the mirror, you can directly use registry.cn-hangzhou.aliyuncs.com/knative-sam… This image marshals the service.

Three steps to go!!

Step 1 Prepare the database

First go to Ali Cloud to open the form storage service, and then create an instance and table. The structure we need is relatively simple. We only need the mapping from short URL ID to long URL. The structure of the storage table is designed as follows:

Name Description ID Short URL IDoriginUrl Long URL

Step 2 Obtain the access key

After logging in to Ali Cloud, the mouse floats on the top right corner of the page, and then click AccessKeys to jump to the AccessKeys management page



Click Show to display Access Key Secret



Step 3 Deploy the service

The configuration of the Knative Service is as follows: Use the configuration information in the previous two steps to fill in the environment variables of the Knative Service. Then deploy to a Knative cluster

apiVersion: serving.knative.dev/v1alpha1
kind: Service
metadata:
  name: short-url
  namespace: default
spec:
  template:
    metadata:
      labels:
        app: short-url
      annotations:
        autoscaling.knative.dev/maxScale: "20"
        autoscaling.knative.dev/minScale: "0"
        autoscaling.knative.dev/target: "100"
    spec:
      containers:
        - image: registry.cn-hangzhou.aliyuncs.com/knative-sample/shorturl:v1
          ports:
            - name: http1
              containerPort: 8080
          env:
          - name: OTS_TEST_ENDPOINT
            value: http://t.cn-hangzhou.ots.aliyuncs.com
          - name: TABLE_NAME
            value: ${TABLE_NAME}
          - name: OTS_TEST_INSTANCENAME
            value: ${OTS_TEST_INSTANCENAME}
          - name: OTS_TEST_KEYID
            value: ${OTS_TEST_KEYID}
          - name: OTS_TEST_SECRET
            value: ${OTS_TEST_SECRET}Copy the code

Using the above Knative Service to deploy the service, it might look like this:

└ ─# kubectl get ksvc
short-url       http://short-url.default.serverless.kuberun.com       short-url-456q9       short-url-456q9       TrueCopy the code

Now you can start testing

  • Generate a short url
└ ─# curl 'http://short-url.default.serverless.kuberun.com/new?origin-url=https://help.aliyun.com/document_detail/121534.html?spm= A2c4g. 11186623.6.786.41 e074d9oHpbO2 '
vEzm6vCopy the code

The curl command output indicates that VR7baa is the ID of the short url

  • Access to the short url In the browser open short-url.default.serverless.kuberun.com/vEzm6v can jump to long url address.

summary

In this actual practice, we only need three steps to achieve a Serverless short url service based on Knative. This short url service can be reduced to zero when there is no request to save computing resources, and it can be automatically expanded when there are many requests. And the use of Aliyun table storage, so that the database is also on demand. The Serverless short url service is realized based on Knative + TableStore.

The original link

This article is the original content of the cloud habitat community, shall not be reproduced without permission.