In daily life, I can see all kinds of strange short links. Every time I click on the jump, I will feel amazed. How does this short link lead the user to the right page?

Short chain principle

The principle of short chains is that short strings are long, so how does a short string become a long string of links? By some magical encryption algorithm? No, we can easily achieve this seemingly magical short and long by relying on the key/value mapping.

Using a picture, you can see the whole process of accessing the short chain very clearly.

First, we have a long link, and the short chain service usually outputs a URL with only one level of directory, which we can then distribute.

Then it comes to the user side. After the user clicks the short link, the first destination is not the target page, but the short link service.

The short-chain service intercepts the pathname on the link and treats it as a key to look up the corresponding value in the mapping.

If the corresponding value cannot be found, it indicates that the short chain does not exist or is invalid. If the query is successful, a short-chain access is performed by the short-chain service directly to 302 to the target link in value.

The specific implementation

Materials: Fast-nest scaffolding, Redis

The entire implementation is split into three parts:

① Receive long links

@Post('/createUrl')
async createUrl(
    @Body('url') url: string.@Body('type') type: string.) {
    const shortUrl = await this.shorturlService.createUrl(url, type);
    return {
        shortUrl,
    };
}
Copy the code

Create a createUrl interface in the service, receive the URL and type field, pass it into the shorturlService, wait for the short link to be generated and output.

(2) generate the shortKey

async createUrl(url: string.type: string = 'normal') {
    const urlKey = await this.handleUrlKey();
    const dataStr = JSON.stringify({
        url,
        type
    });
    await this.client.set(urlKey, dataStr, type= = ='permanent' ? -1 : 300);
    return `${Config.defaultHost}/${urlKey}`;
}

private asynchandleUrlKey(count? :number) :Promise<string> {
    const _count = count || 1;
    const maxCount = Config.maxRetryTimes;
    if (_count >= maxCount) throw new HttpException('Number of retries exceeded, please regenerate the link', HttpStatus.INTERNAL_SERVER_ERROR);
    const urlKey: string = Math.random().toString(36).slice(-4);
    const _url = await this.client.get(urlKey);
    if (_url) {
        return await this.handleUrlKey(_count + 1);
    }
    return urlKey;
}
Copy the code

First fetch a 4-bit random string through math.random ().toString(36).slice(-4), which will be used as the pathname for the short chain.

Before mapping, we need to make a unique judgment on it. Although it is unlikely to happen, we still need to prevent problems such as short chain coverage. The solution of this service is retry generation. If the short chain value is unfortunately repeated, it will enter the retry branch, and the service will have a built-in number of retries. If the number of retries exceeds the configured word count, the conversion will return a failure.

In addition to the URL, the createUrl method also accepts a Type field, which refers to the properties of special short chains. We have three modes of short chain:

  • Normal – A normal short link that will expire within a specified time
  • Once – A one-time short link that will expire within a specified period of time and automatically expire after being accessed
  • Permanent – Long-term short link, does not automatically fail, can only be manually deleted

After the urlKey is generated, it will be converted into a string with type and stored in Redis, and the short link will be printed.

③ Receive the short link and complete the target redirect

@Get('/:key')
@Redirect(Config.defaultIndex, 302)
async getUrl(
        @Param('key') key: string.) {
    if (key) {
        const url = await this.shorturlService.getUrl(key);
        return {
            url
        }
    }
}

// this.shorturlService.getUrl
async getUrl(k: string) {
    const dataStr = await this.client.get(k);
    if(! dataStr)return;
    const { url, type } = JSON.parse(dataStr);
    if (type= = ='once') {
        await this.client.del(k);
    }
    return url;
}
Copy the code

User side will GET a similar link of http://localhost:8000/s/ku6a, click on the following rather then give a short link service sends a GET request.

After receiving the request, the service gets the value of the key field in the link, which is the string ku6a, and uses it to find the mapping in Redis.

The redirection decorator redirects the request to the default destination link. The redirection decorator returns the request to the default destination link. The redirection decorator redirects the request to the default destination link.

If the relevant value is successfully found in Redis, the URL and type fields will be read. If the type is once, it means that this is a one-time link, which will actively trigger the deletion method and eventually return the target link.

Additional functionality

Use the logging system to output reports

When using short links, there is a large probability that relevant data statistics will be required. How to carry out data statistics without using databases?

In this service, we can scan the landing log file to complete the report of the day’s short chain access.

When generating short links, add the urlID field for statistical differentiation and actively output logs as follows:

async createUrl(url: string.type: string = 'normal') {
    const urlKey = await this.handleUrlKey();
    const urlID = UUID.genV4().toString();
    const dataStr = JSON.stringify({
        urlID,
        url,
        type
    });
    this.myLogger.log(`createUrl**${urlID}`.'createUrl'.false);
    await this.client.set(urlKey, dataStr, type= = ='permanent' ? -1 : 300);
    return `${Config.defaultHost}/${urlKey}`;
}
Copy the code

Then, when the user clicks the short link, the urlID field of the short link is obtained, and the log is actively output as follows:

async getUrl(k: string) {
    const dataStr = await this.client.get(k);
    if(! dataStr)return;
    const { url, type, urlID } = JSON.parse(dataStr);
    if (type= = ='once') {
        await this.client.del(k);
    }
    this.myLogger.log(`getUrl**${urlID}`.'getUrl'.false);
    return url;
}
Copy the code

We will then be able to get something like this in the logs directory of the service:

2021-04-25 22:31:03.306 INFO [11999] [-] createUrl** 3f577625-474A-4e30-9933-e469ce3b0dcf 2021-04-25 22:31:38.323 INFO [11999] [-] getUrl** 3f577625-474A-4e30-9933-e469ce3b0dcF 2021-04-25 22:31:39.399 INFO [11999] [-] GetUrl ** 3f577625-474A-4e30-9933-e469ce3b0dcF 2021-04-25 22:31:40.281 INFO [11999] [-] GetUrl ** 3f577625-474A-4e30-9933-e469ce3b0dcF 2021-04-25 22:31:40.997 INFO [11999] [-] GetUrl ** 3f577625-474A-4e30-9933-e469ce3b0dcF 2021-04-25 22:31:41.977 INFO [11999] [-] GetUrl ** 3f577625-474A-4e30-9933-e469ce3b0dcF 2021-04-25 22:31:42.870 INFO [11999] [-] GetUrl ** 3f577625-474A-4e30-9933-e469ce3b0dcF 2021-04-25 22:31:43.716 INFO [11999] [-] GetUrl ** 3f577625-474A-4e30-9933-e469ce3b0dcF 2021-04-25 22:31:44.614 INFO [11999] [-] getUrl**3f577625-474a-4e30-9933-e469ce3b0dcfCopy the code

CreateUrl = getUrl; createUrl = getUrl; getUrl = getUrl; getUrl = getUrl;

use

According to the above process, the author wrote a relatively simple short chain service, we can use out of the box.

Shorturl (Star⭐️⭐️)

Specific startup mode

Make sure you have redis available first, otherwise the service cannot start smoothly.

git clone https://github.com/mykurisu/shorturl.git

cd shorturl

npm install

npm start
Copy the code

Available configuration modification

The configuration related to the short chain is clustered in the root directory config.ts.

serverConfig: {
    port: 8000,},redis: {
    port: 6379.host: '0.0.0.0'.db: 0,},cacheType: 'redis'.defaultHost: 'http://localhost:8000/s'.defaultIndex: 'http://localhost:8000/defaultIndex'.Copy the code
configuration The default value Configuration utility
serverConfig.port 8000 Service startup port
redis.port 6379 Redis port
redis.host 0.0.0.0 Redis service address
redis.db 0 Redis specific repository table
cacheType redis Short chain storage mode, accept memory/ Redis
maxRetryTimes 5 Maximum number of retries for generating a short link
defaultHost http://localhost:8000/s Short link prefix
defaultIndex http://localhost:8000/defaultIndex Redirect address after short link failure

Built-in interface

Interface routing Request way The interface parameters Interface USES
/s/createUrl POST url: string, type? : string Short links generate interfaces
/s/deleteUrl POST k: string Delete the short link interface
/s/:key GET none Target link acquisition

expand

① Storage degrade policy

Shorturl has a local storage scheme, which means we can listen to the status of Redis and temporarily store data in memory if the connection is disconnected in order to degrade the service. Of course, we can also use memory directly to store the short chain content, which can be changed in the config.ts configuration.

② It is not just short link service

Shorturl is already a micro storage service and can be redeveloped to export more modules to support a wider variety of services.

summary

The whole short link service is actually very simple, the trouble is to build the service, which is the first step. The author also struggled in the initial step for countless times, and finally accumulated such a scaffold as fast-nest, hoping to help students in the same situation.

Also, attach the source code for this article – Shorturl (welcome everyone Star⭐️⭐️)