Привет.
Сегодня я попытаюсь рассказать тебе о написании простого сервиса для сокращения ссылок на ASP.Net/DBLinq
Предыстория
Пару дней назад прочитал пост о Антивирусе Бабушкина и у меня повилось желание что-нибудь СЖАТЬ, но в творческое русло я смог направить его только сегодня.
Мне захотелось сделать небольшой сервис по сокращению ссылок, а движок написать самому.
Почему же я решил изобрести велосипед, а не воспользоваться готовым продуктом?
Во-первых, все существующие решения написаны на PHP, a PHP-не язык я не люблю.
Во-вторых, движки занимали мегабайты даже в zip-архивах, что слишком много для программы, которая лишь добавляет записи в бд и отдает по ключу.
Наконец, разминка для мозгов никогда не помешает.
Инструменты
У меня был стандартый LAMP сервер, закрытый nginx-фронтэндом и Cloudflare.
В качестве Linq-to-SQL провайдера воспользовался DBLinq.
За рерайт URL отвечает простейший .htaccess
Написание
Структура БД
Нужны были всего три функции: добавление URL, отдача человеку и роботу и логгирование.
Для них хватило этих таблиц:
В таблице main хранятся
- id ссылки(для пользователя — её ключ)-id
- id цели ссылки-url_id
- IP пользователя, добавившего ссылку-added_ip
- дата добавления-date
в log:
- id записи
- id цели ссылки-url_id
- id referrer'a — referrer_id
- дата перехода-date
- IP пользователя, перешедшего по ссылке-ip
В urls хранятся дедуплицированные цели ссылок и referrer'ы.
Ради упрощения кода и уменьшения размера бд не реализована поддержка IPv6 в логах/информации о добавившем.
Код
Итоговое приложение состоит из трех основных страниц:
- add.aspx — добавление человеком
- add_robot.aspx — добавление роботом
- get.aspx — получение
Несмотря на кажущийся объем кода, основная суть сервиса сводится к нескольким строкам, которые приведены ниже в упрощенном виде.
Добавление (упрощено для удобства чтения, в сорцах по-другому):
//Ищем id добавляемой ссылки в бд, если нет - добавляем
var query = _SQLConnection.URLS.Where(a => a.URL == __NewUrlString);
if (query.Any())
__NewUrl=query.First()
else
{
SQLConnection.URLS.InsertOnSubmit(__NewUrl = new URLS() { URL = __NewUrlString });
}
_SQLConnection.Main.InsertOnSubmit(
__NewRecord = new Main() {
AddedIP = BitConverter.ToInt32(IPAddress.Parse(Request.UserHostAddress).GetAddressBytes(),0),
//Сохраняем адрес добавившего
Date = DateTime.Now,
//дату добавления
URL = __NewUrl.ID
//и id ссылки
});
Отдача:
var URLz = _SQLConnection.Main.Where(a => a.ID == urlID).Join(_SQLConnection.URLS,a => a.URL,a => a.ID,( a, b ) => b).Take(1).ToArray();
//пытаемся загрузить ссылки с подходящим ключём.
if ( URLz.Length == 0 ) {
//Не нашли? Выходим!
_e(404);
return;
}
//redirect
Response.Redirect(Server.HtmlDecode(URLz[0].URL), false);
Костыли и велосипеды
Проблема коротких ключей для ссылок была решена достаточно просто: id записи приводится к строке в 64-тичной системе счисления([a-zA-Z0-9!@]+)
static char[] chars = "!0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".ToCharArray();
public static string ToStr(int input) {
StringBuilder s = new StringBuilder(20);
do {
s.Append(chars[input % 64]);//never use bitwise opertions on server!
input /= 64;
}
while ( input > 0 );
return s.ToString();
}
Для генерации xml с ответом для роботов я не стал заморачиваться с нормальной генерацией xml и запилил простейший, но быстрый костыль.
void xw( string msg, string status ) {
Response.Write(
String.Format(
"<?xml version="1.0" encoding="utf-8"?>rn<response>rnt<status>{0}</status>rnt<message>{0}</message>rn</response>",
status,
msg
)
);
}
Рерайты
Естесственно сервис коротких ссылок никому не нужен, если его ссылки длиннее, чем исходные.
Для исправления этого необходимо написать несколько правил рерайтов для Apache.
Делаем короткий URL для запросов на редирект — теперь ссылка будет выглядеть как who.ec/iXXX
RewriteCond %{REQUEST_URI} ^/?i.*
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule /?i(.*) /get.aspx?url=$1 [L]
Аналогично для добавления — ссылки вида who.ec/add_robot.aspx?url=XXX превратились
в who.ec/pXXX
RewriteCond %{REQUEST_URI} ^/?p.*
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule /?p(.*) /add_robot.aspx?url=$1 [L]
Наконец, закроем несуществующие страницы заглушкой
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !^.*add.aspx
RewriteRule (.*) /add.aspx [L]
Использование
Так как скрипт с самого начала был выложен в паблик, то я просто не могу не дать информации по использованию.
Установка
Все просто:
- Скачайте
- Распакуйте
- Соберите
- Выполните whoec_shortener.sql, чтобы создать бд
- Измените web.Config
- ???
- Profit!
Настройка
Все настройки хранятся в web.config — это позволяет обращаться к ним из кода по ConfigurationManager.AppSettings[<ключ>]
- «ConnectionInfoUser» — пользователь SQL
- «ConnectionInfoDatabase» — БД SQL
- «ConnectionInfoPass» — пароль SQL
- «ConnectionInfoServer» — адрес SQL сервера
- «MaxUrlLength» — максимальная длина хранимых ссылок. При превышении порога в 512 символов не забудьте подправитьSQL-базу
- «UrlPrefix» — префикс для ссылок(при рерайтах)
- «LoggingEnabled» — протоколирование переходов по ссылкам
- «Page404» — ссылка на 404
- «Page503» — ссылка на 503
Клиенты
Как написано выше, код рассчитан не только на работу с людьми.
Получение
- /add_robot?url=XXX
Добавление
- /get.aspx?url=XXX (в случае рерайтов возможен вариант вида /iXXX)
Итог
Исходники были выложены на Github.
Пример сокращалки на этом движке можно посмотреть на who.ec.
Дизайн для демонстрационного сайта был сделан моим другом stam'ом на Twitter Bootstrap/jQuery/Fancybox.
Автор: kasthack
kasthack как c вами связаться ? rsv198@rambler.ru
kasthack,интересная тема.хотел посмотреть на исходники,но на git 404.Как с вами связаться?