Сокращенный URL Shortener

Большинство из нас знакомы с URL-адресами, такими как bit.ly или t.co видя их в каналах Twitter или Facebook. Это примеры сокращенных URL-адресов, которые похожи на псевдоним или указатель на страницу у которой длинная ссылка. Например, сокращенный URL http://bit.ly/SaaYw5  будет перенаправлять вас совсем на долгой URL .

В этой статье вы узнаете, зачем нужен такой адрес и как создать сокращенный функциональный URL Shortener для вашего сайта, который будет работать, даже если вы не используете front controller/framework. Если вы используете front controller, я расскажу вам, как легко интегрировать этот URL Shortener без необходимости копаться в контроллере.

Ответ на некоторые общие вопросы

— URL-адрес  bit.ly и многие другие shorteners URL не плохие и в свободном доступе, так почему мы должны создать свои собственные?

Во-первых причина по которой нужно создавать сокращенных URL это удобство, эстетика и узнаваемость бренда. Например, если ваш сайт имеет приложение, которое создает большое количество отчетов, очень активный блог или большой фотоальбом, там будет много ссылок. URL Shortener позволит программно создавать URL-адреса, которые могут быть отправлены для ваших читателей или опубликовать на своем сайте. Очевидное преимущество иметь свой собственный URL Shortener в том, что ваши читатели смогут мгновенно узнавать, бренд вашего сайта.

Во-вторых сравните по количеству символов сокращенный URL-адрес и не сокращенный, разница заметна не вооружённым глазом.

Большинство из этих сокращенных URL простые в использовании с API, и мы можем программно генерировать сокращенный URL, и использовать для своих целей с помощью скриптов PHP.

Вы можете удивиться, почему вы всегда видите буквы смешаны с цифрами в укороченных URL. Имея более десяти вариантов (0-9) на цифру, мы в состоянии сделать значительно больше комбинаций, сохраняя при этом код как можно более коротким.

Символы, которые мы будем использовать это цифры 1-9, а также буквы верхнего или нижнего регистра. Удалены все гласные, чтобы предотвратить плохие слова, и убраны все символы, которые можно перепутать друг с другом. Это даёт нам список из 50 символов для каждой цифры, которая из двух символов, у нас есть 2500 возможных комбинаций, 125000 возможностями с тремя сценами, и на целых 6500 тысяч комбинаций только с четырьмя сценами!

Планирование баз данных

Давайте создадим таблицу short_urls. Это простая таблица, создать запрос SQL ниже:



CREATE TABLE IF NOT EXISTS short_urls (
  id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  long_url VARCHAR(255) NOT NULL,
  short_code VARCHAR(6)
      CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
  date_created INTEGER UNSIGNED NOT NULL,
  counter INTEGER UNSIGNED NOT NULL DEFAULT '0',

  PRIMARY KEY (id),
  KEY short_code (short_code)
)
ENGINE=InnoDB;


У нас есть стандарт авто-увеличение первичного ключа и поля для полного URL, сокращение кода для URL-адреса (индекс для ускорения поиска), метки времени, когда строка была создана, и сколько раз был доступен сокращенный URL.

Обратите внимание, что поле long_url имеет максимальную длину 255 символов, которое должно быть достаточно для большинства приложений. Если вам нужно хранить больше URL-адреса, то вам нужно изменить TEXT на своё усмотрение.

Создать сокращенный код URL

Код для создания и декодирования сокращенных URL будет в классе с названием ShortURL. Во-первых, давайте посмотрим на код, отвечающий за создание сокращения:



<?php
class ShortUrl
{
    protected static $chars = "123456789bcdfghjkmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ";
    protected static $table = "short_urls";
    protected static $checkUrlExists = true;

    protected $pdo;
    protected $timestamp;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;

        $this->timestamp = $_SERVER["REQUEST_TIME"];
    }

    public function urlToShortCode($url) {
        if (empty($url)) {
            throw new \Exception("No URL was supplied.");
        }

        if ($this->validateUrlFormat($url) == false) {
            throw new \Exception(
                "URL does not have a valid format.");
        }

        if (self::$checkUrlExists) {
            if (!$this->verifyUrlExists($url)) {
                throw new \Exception(
                    "URL does not appear to exist.");
            }
        }

        $shortCode = $this->urlExistsInDb($url);
        if ($shortCode == false) {
            $shortCode = $this->createShortCode($url);
        }

        return $shortCode;
    }

    protected function validateUrlFormat($url) {
        return filter_var($url, FILTER_VALIDATE_URL,
            FILTER_FLAG_HOST_REQUIRED);
    }

    protected function verifyUrlExists($url) {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_NOBODY, true);
        curl_setopt($ch,  CURLOPT_RETURNTRANSFER, true);
        curl_exec($ch);
        $response = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        return (!empty($response) && $response != 404);
    }

    protected function urlExistsInDb($url) {
        $query = "SELECT short_code FROM " . self::$table .
            " WHERE long_url = :long_url LIMIT 1";
        $stmt = $this->pdo->prepare($query);
        $params = array(
            "long_url" => $url
        );
        $stmt->execute($params);

        $result = $stmt->fetch();
        return (empty($result)) ? false : $result["short_code"];
    }

    protected function createShortCode($url) {
        $id = $this->insertUrlInDb($url);
        $shortCode = $this->convertIntToShortCode($id);
        $this->insertShortCodeInDb($id, $shortCode);
        return $shortCode;
    }

    protected function insertUrlInDb($url) {
        $query = "INSERT INTO " . self::$table .
            " (long_url, date_created) " .
            " VALUES (:long_url, :timestamp)";
        $stmnt = $this->pdo->prepare($query);
        $params = array(
            "long_url" => $url,
            "timestamp" => $this->timestamp
        );
        $stmnt->execute($params);

        return $this->pdo->lastInsertId();
    }

    protected function convertIntToShortCode($id) {
        $id = intval($id);
        if ($id < 1) {
            throw new \Exception(
                "The ID is not a valid integer");
        }

        $length = strlen(self::$chars);
        // make sure length of available characters is at
        // least a reasonable minimum - there should be at
        // least 10 characters
        if ($length < 10) {
            throw new \Exception("Length of chars is too small");
        }

        $code = "";
        while ($id > $length - 1) {
            // determine the value of the next higher character
            // in the short code should be and prepend
            $code = self::$chars[fmod($id, $length)] .
                $code;
            // reset $id to remaining value to be converted
            $id = floor($id / $length);
        }

        // remaining value of $id is less than the length of
        // self::$chars
        $code = self::$chars[$id] . $code;

        return $code;
    }

    protected function insertShortCodeInDb($id, $code) {
        if ($id == null || $code == null) {
            throw new \Exception("Input parameter(s) invalid.");
        }
        $query = "UPDATE " . self::$table .
            " SET short_code = :short_code WHERE id = :id";
        $stmnt = $this->pdo->prepare($query);
        $params = array(
            "short_code" => $code,
            "id" => $id
        );
        $stmnt->execute($params);

        if ($stmnt->rowCount() < 1) {
            throw new \Exception(
                "Row was not updated with short code.");
        }

        return true;
    }
...


Когда мы создаем класс ShortURL, мы передадим его нашим PDO экземпляр объекта. Конструктор сохраняет эту ссылку и устанавливает метку $timestamp .

Мы вызываем метод urlToShortCode() и даём ему долгой сокращенный URL. Метод завершает все необходимое для создания сокращенного URL, которое мы будем прикреплять к нашему имени домина.

urlToShortCode() вызывает validateUrlFormat() , который просто использует фильтр PHP, чтобы убедиться, что адрес правильно сформирован. Значит, если статическая переменная $ checkUrlExists со значением true , будет вызван verifyUrlExists() , который использует CURL, чтобы связаться с URL и убедитесь, что он не возвращает 404 страницу. Вы могли бы альтернативно проверить статус 200 (OK), но это может привести к проблемам, если страницы были неожиданно перенаправлены 301 или 401, код ответа (Unauthorized).

Не имеет смысла иметь дублирующие записи, так что код проверит, что сurlExistsInDb() которая запрашивает базу данных для длинных URL. Если он находит URL, то он вернет соответствующею сокращенную цифру, в противном случае он возвращает false , поэтому мы знаем, что необходимо для его создания. Обратите внимание, что http://www.example.com и http://example.com разные URL-адреса, поэтому если вы хотите, предотвратить такое дублирование, то вам придется добавить несколько регулярных выражений.

createShortCode() делегаты следующих задач конкретных методов:

  1. insertUrlInDb() , вставит длинный URL-адрес в базе данных и возвращает ID новой строки.
  2. convertIntToShortCode() , для преобразования новой строки ID в нашей базе 50 номер схемы.
  3. insertShortCodeInDb() , для обновления строки с вновь созданным сокращенным кодом.

Что бы создать сокращенный URL, все, что нам нужно сделать, это создать экземпляр класса, передавая экземпляр PDO в конструктор, вызвать метод urlToShortCode() с длинным URL который мы хотим сократить и добавить возврат сокращенного кода домена и передать его обратно в контроллер, который запросил его.



<?php
include "../include/config.php";
include "../include/ShortUrl.php";

try {
    $pdo = new PDO(DB_PDODRIVER . ":host=" . DB_HOST .
        ";dbname=" . DB_DATABASE,
        DB_USERNAME, DB_PASSWORD);
}
catch (\PDOException $e) {
    trigger_error("Error: Failed to establish connection to database.");
    exit;
}

$shortUrl = new ShortUrl($pdo);
try {
    $code = $shortUrl->urlToShortCode($_POST["url"]);
    printf('<p><strong>Short URL:</strong> <a href=
http://pixelcom.crimea.ua/"%s">%1$s</a></p>',
        SHORTURL_PREFIX . $code);
    exit;
}
catch (\Exception $e) {
    // log exception and then redirect to error page.
    header("Location: /error");
    exit;
}


Расшифровка сокращенного кода

Расшифровка сокращенного кода и получить длинный URL-адрес тоже является частью класса ShortURL. Мы вызываем метод shortCodeToUrl() и передаём его на короткий номер который мы извлекли из URI. shortCodeToUrl() также принимает необязательный параметр, $increment , который по умолчанию true. Затем следующие делегаты:

  1. validateShortCodeFormat() гарантирует, при условии что сокращенный код содержит только буквы и цифры.
  2. getUrlFromDb() запрашивает в базе данных сокращенный код и возвращает запись id, long_url, и счетчик полей.
  3. Если $increment , c параметром true, IncrementCounter() вызывается для увеличения счетчика строки.

Вот остальной класс:



...
  public function shortCodeToUrl($code, $increment = true) {
  if (empty($code)) {
  throw new \Exception("No short code was supplied.");
  }

  if ($this->validateShortCode($code) == false) {
  throw new \Exception(
  "Short code does not have a valid format.");
  }

  $urlRow = $this->getUrlFromDb($code);
  if (empty($urlRow)) {
  throw new \Exception(
  "Short code does not appear to exist.");
  }

  if ($increment == true) {
  $this->incrementCounter($urlRow["id"]);
  }

  return $urlRow["long_url"];
  }

  protected function validateShortCode($code) {
  return preg_match("|[" . self::$chars . "]+|", $code);
  }

  protected function getUrlFromDb($code) {
  $query = "SELECT id, long_url FROM " . self::$table .
  " WHERE short_code = :short_code LIMIT 1";
  $stmt = $this->pdo->prepare($query);
  $params=array(
  "short_code" => $code
  );
  $stmt->execute($params);

  $result = $stmt->fetch();
  return (empty($result)) ? false : $result;
  }

  protected function incrementCounter($id) {
  $query = "UPDATE " . self::$table .
  " SET counter = counter + 1 WHERE id = :id";
  $stmt = $this->pdo->prepare($query);
  $params = array(
  "id" => $id
  );
  $stmt->execute($params);
  }
  }


Обо всём

Building/altering a front controller или подгонка этого пакета в existing framework выходят за рамки этой статьи, поэтому я решил хранить нашу логику декодирования в файле с названием r.php. Мы можем написать наши сокращенные URL-адреса, как http://example.com/r/X4c где r.php (или г / index.php в зависимости от вашего дизайна) будет контроллер. Этот формат будет легко интегрировать в практически любой framework, не касаясь существующей передней панели.

Если вы хотите узнать, как создать свой ​​собственные фронт-контроллеры, прочтите отличную статью Создание функционального фронт-контроллера .

Одно из преимуществ этой конструкции является то, что, если вы хотите, вы можете иметь отдельный контроллер для различных частей вашего сайта с помощью различных таблиц хранить короткие коды организовано и как можно более сокращение http://example.com/b/ может быть для сообщения в блоге, и http://example.com/i/ может быть для изображений.

Если Вы не используете Front Controller или framework, Вы спросите: «Я что, прочитал эту всю статью зря?» Может это не так красиво но, вы можете использовать формат http://example.com/r?c=X4c, где r/index.php который содержит скрипт декодирования.

Вот как выглядит r.php:



<?php
include "../include/config.php";
include "../include/ShortUrl.php";

// How are you getting your short code?

// from framework or front controller using a URL format like
// http://.example.com/r/X4c
// $code = $uri_data[1];

// from the query string using a URL format like
// http://example.com/r?c=X4c where this file is index.php in the
// directory http_root/r/index.php
$code = $_GET["c"];

try {
    $pdo = new PDO(DB_PDODRIVER . ":host=" . DB_HOST .
        ";dbname=" . DB_DATABASE,
        DB_USERNAME, DB_PASSWORD);
}
catch (\PDOException $e) {
    trigger_error("Error: Failed to establish connection to database.");
    exit;
}

$shortUrl = new ShortUrl($pdo);
try {
    $url = $shortUrl->shortCodeToUrl($code);
    header("Location: " . $url);
    exit;
}
catch (\Exception $e) {
    // log exception and then redirect to error page.
    header("Location: /error");
    exit;
}


В зависимости от того, как вы сделали сокращенный код, переменная $code устанавливается вместе с другими параметрами конфигурации. Мы устанавливаем наши связи PDO, создавая экземпляр ShortURL, и вызывая shortCodeToUrl() передав его на короткий номер и оставив счетчик установки значения по умолчанию.

Заключение

Теперь у вас есть, свой собственный URL Shortener, его легко добавить в существующий сайт. Конечно, есть много способов улучшить его, вот так:

  • Аннотация вашего взаимодействия с базой данных, чтобы удалить избыточный код.
  • Добавить для кэширования запросы сокращенных URL.
  • Добавить некоторые востребованные сокращенные URL-адреса за пределы полей счетчика.
  • Добавить способ фильтрации вредоносных страниц.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML -теги и атрибуты: <a href= http://pixelcom.crimea.ua/"" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>