Вышел Click!

Вышел Click!

Первый стабильный релиз

Недавно ребята из Google объявили о намерении закрыть свой сервис goo.gl. В качестве альтернативы они предлагают перейти на Bitly или Ow.ly, но я хочу показать, как начать пользоваться своим собственным сервисом. 😏

Что сделано

Архитектура

type Link struct {
	ID        string
	Name      string
	Status    string
	CreatedAt string
	UpdatedAt sql.NullString
	Aliases   Aliases
	Targets   Targets
}

type Alias struct {
	ID        uint64
	LinkID    string
	Namespace string
	URN       string
	CreatedAt string
	DeletedAt sql.NullString
}

type Target struct {
	ID        uint64
	LinkID    string
	URI       string
	Rule      Rule
	CreatedAt string
	UpdatedAt sql.NullString
}

const (
	AND byte = iota
	OR
)

type Rule struct {
	Description string
	AliasID     uint64
	Tags        []string
	Conditions  map[string]string
	Match       byte
}

Link является центральной сущностью, которая характеризует конечный ресурс. Alias это её псевдоним, через который осуществляется к ней доступ. Target описывает доступ к конечному ресурсу. Например, статья на Wikipedia - это Link, который можно идентифицировать с помощью Alias.URN = /goto/wiki/article, а конечным ресурсом может быть одно из языковых представлений этой статьи, например, Target.URI = https://ru.wikipedia.org/wiki/Википедия. Как вы понимаете, у Link может быть несколько Alias и Target. И, чтобы найти соответствие для конкретного контекста, существует Rule.


Рассмотрим на реальном кейсе. Следующим запросом я создаю Link для ресурса Click! - Link Manager as a Service. Доступ к этому ресурсу возможен с помощью двух созданных Alias = {github/click, github/click!}. Но то, куда будет осуществлён редирект определяется двумя Target = {https://github.com/kamilsk/click, https://kamilsk.github.io/click/}, имеющими разные правила.

DO $$
  DECLARE demoUser   UUID := uuid_generate_v4();
  DECLARE click      UUID := 'a382922d-b615-4227-b598-6d3633c397aa';
  DECLARE clickPromo "alias"."id"%TYPE;
BEGIN
  INSERT INTO "link" ("user", "id", "name") VALUES (demoUser, click, 'Click! - Link Manager as a Service');

  INSERT INTO "alias" ("link_id", "urn") VALUES (click, 'github/click');
  INSERT INTO "alias" ("link_id", "urn") VALUES (click, 'github/click!') RETURNING "id" INTO clickPromo;

  INSERT INTO "target" ("link_id", "uri", "rule") VALUES
    (click, 'https://github.com/kamilsk/click', '{
      "description": "Project location", "tags": ["src"]
    }'),
     (click, 'https://kamilsk.github.io/click/', ('{
      "description": "Promotion page", "tags": ["promo"], "alias": ' || clickPromo || ', "match": 1
    }') :: JSONB),
END;
$$;

Если сделать запрос /github/click, то будет выбран Target = https://github.com/kamilsk/click, так как ни одно правило не отработало, а цели с наименьшим ID являются целями по умолчанию.

Если сделать запрос /github/click! или /github/click?tag=promo, то будет выбран Target = https://kamilsk.github.io/click/, так как сработает правило доступа по Alias и по тегу соответственно.


Ещё пример того, как можно сделать редиректы на внешние ресурсы через свой сайт.

location /goto/ {
    rewrite  ^/goto/(.*)  /$1  break;

    proxy_pass                           http://click-front;
    proxy_set_header  X-Click-Namespace  $host;
    ...
}

Так я сделал ссылки на свои сертификаты, которые потом разместил на LinkedIn, чтобы отслеживать переходы по ним.

location /certificate/ {
    rewrite  ^/certificate/(.*)  /$1  break;

    proxy_pass                           http://click-front;
    proxy_set_header  X-Click-Namespace  'certificate';
    ...
}

Быстрый старт

Локальная сборка

$ egg github.com/kamilsk/click@^1.0.0 -- make up demo status

     Name                    Command               State                                  Ports
---------------------------------------------------------------------------------------------------------
click_db_1        docker-entrypoint.sh postgres    Up      0.0.0.0:5432->5432/tcp
click_server_1    /bin/sh -c envsubst '$SERV ...   Up      80/tcp, 0.0.0.0:80->8080/tcp
click_service_1   click run --with-profiler  ...   Up      0.0.0.0:8080->80/tcp, 0.0.0.0:8090->8090/tcp,
$

Используя готовый Docker-образ

$ docker network create -d bridge click
$ docker run --rm -d \
             --env-file .env \
             --network click \
             --name click-db \
             -h db \
             -p 5432:5432 \
             postgres:10-alpine
$ docker run --rm -it \
             --env-file .env \
             --network click \
             --name click-migration \
             --link click-db:db \
             -h migration \
             kamilsk/click:latest migrate up --with-demo
$ docker run --rm -d \
             --env-file .env \
             --network click \
             --name click-service \
             --link click-db:db \
             -h service \
             -p 8080:80 \
             kamilsk/click:latest
$ docker ps -f name=click

IMAGE                  COMMAND                  PORTS                       NAMES
kamilsk/click:latest   "click run"              0.0.0.0:8080->80/tcp, ...   click-service
postgres:10-alpine     "docker-entrypoint.s…"   0.0.0.0:5432->5432/tcp      click-db
$

API

$ curl http://localhost:8080/api/v1/a382922d-b615-4227-b598-6d3633c397aa
# {
#   "id": "a382922d-b615-4227-b598-6d3633c397aa",
#   "name": "Click! - Link Manager as a Service",
#   "status": "active",
#   "aliases": [
#     {
#       "id": 1,
#       "namespace": "global",
#       "urn": "github/click"
#     },
#     {
#       "id": 7,
#       "namespace": "global",
#       "urn": "github/click!"
#     }
#   ],
#   "targets": [
#     {
#       "id": 1,
#       "uri": "https://github.com/kamilsk/click",
#       "rule": {
#         "description": "Project location",
#         "tags": ["src"]
#       }
#     },
#     {
#       "id": 2,
#       "uri": "https://kamilsk.github.io/click/",
#       "rule": {
#         "description": "Promotion page",
#         "alias": 7,
#         "tags": ["promo"],
#         "match": 1
#       }
#     }
#   ]
# }
$ curl -v --cookie "token=41ca5e09-3ce2-4094-b108-3ecc257c6fa4" http://localhost:8080/github/click!
# > GET /github/click! HTTP/1.1
# > Host: localhost:8080
# > User-Agent: curl/7.54.0
# > Accept: */*
# > Cookie: token=41ca5e09-3ce2-4094-b108-3ecc257c6fa4
# >
# < HTTP/1.1 302 Found
# < Location: https://kamilsk.github.io/click/
# < Set-Cookie: token=41ca5e09-3ce2-4094-b108-3ecc257c6fa4; Path=/; HttpOnly; Secure
# < Date: Wed, 11 Apr 2018 17:37:48 GMT
# < Content-Length: 0
# <
$

Теги

go click!

Автор

Камиль Самигуллин
Камиль Самигуллин

Разработчик

Первый приватный сервис, который я переписал с PHP на Go и выложил в открытый доступ. В перспективе, моя задача полностью отказаться от использования таких сервисов как Google URL Shortener и Bitly, перейдя на своё решение.

Спонсоры

Опубликовано

10.04.2018