Добавлен , опубликован
Программирование
Язык:
php
Hello XGM!
Давно не писал я в своем бложике, но вот мне что-то захотелось.

Введение

Написали в тех. поддержку сайта с просьбой добавить интеграцию со SketchFab'ом для отображения моделей прямо на сайте.
Начал изучать, что и как можно было сделать. Так как форматирование txt2 основано на простоте и удобстве форматирования, то использование bb-кодов и html-вставок было не возможным. Классически все интеграции делаются по принципу - вижу ссылку, заменил.

Этап первый

Первым делом пошел по накатанной, начал смотреть как строится код для отображения проигрывателя моделей. Способ для отображения моделей из голой ссылки нашел, а вот для плейлистов - увы...
Почему так? А потому что ссылка модели содержит нужный идентификатор, который потом используется в отображении самой модели. А вот для плейлиста были случаи, когда указывался либо идентификатор, либо имя плейлиста. Из-за этого возникали проблемы, первое что пришло в голову - надо запрашивать этот самый айдишник через Data API, который я нашел на SketchFab'е.

Этап второй

Думая дальше, анализируя примеры, нашел в документации сервиса для девелоперов такой вот интересный пункт - oEmbed. Открыл, смотрю:
  1. Сделайте запрос на наш сервис с параметром в виде ссылки
  2. Получите в ответ html код для вставки на сайте, а так же дополнительную информацию о предоставляемом контенте
Причем поддерживается не только отображение моделей, но и каталогов с моделями, а значит это то, что мне надо. Немного пошаманив с кодом парсера txt2, выделил ссылку на модель сайта SketchFab, отправил запрос ждя получение контента, используя cURL. И все стало выглядеть примерно так:
function txt2_sketchfab_link($url) {
	$targetUrl = 'https://sketchfab.com/oembed?url=' . $url;
	// Request a oEmbed content
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $targetUrl);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    $result = curl_exec($ch);

    // Verify response
    $status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    if ($status_code != 200) {
    	return null;
    }

	// Parse json response
    $response = json_decode($result);
    if ($response == null) {
        return null;
    }

	return $response['html];
}
После этого все ссылки ведущие на SketchFab стали заменяться на плеер моделей. И смотрелось это так:

Этап третий

Немного еще погуглив на тему oEmbed протокола оказалось, что этот протокол был специально разработан для вставки медиа контента в виде ссылок. На официальном сайте данного протокола я нашел большой список поставщиков контента, таких как (sketchfab.com, devianart.com, soundcloud.com). И я подумал, раз все так просто, то почему бы и не прикрутить обработку других поставщиков медиа-контента. Для этого пришлось немного поработать над решением получения медиа контента, сделать его более универсальным. По спецификации все было достаточно просто:
  • Провайдер имеет свой уникальный идентификатор
  • Провайдер имеет точку вызова для получения данных (EndPoint)
  • Провайдер имеет список правил для ссылок, которые он может обрабатывать
И поэтому для создания универсального решения требовалось реализовать небольшую библиотечку, в которой бы были реализованы следующие функции:
  • Получение кода Embed компонента для указанный ссылки с использованием определенного провайдера (OEmbedProvider)
  • Получение провайдера по ссылке (OEmbedProviderFactory)
Класс OEmbedProviderFactory будет заниматься созданием определенного провайдера медиа-контента, чтобы потом в итоге мы могли бы получить данные для конкретной ссылки. Информацию о разрешенных провайдерах будет храниться в специальном конфиге, где будет содержаться вся необходимая информация, указанная выше.
Ну и для этого конечно же написал немного кода, примерно такого
<?php
/**
 * @author Alexey Prey Mulyukin
 * Date: 08.09.2015
 * Time: 0:42
 */

class OEmbedData {
    public $html;
}

/**
 * Class OEmbedProviderFactory
 *
 * Provider configuration Example:
 * {
 *      'name': string,
 *      'endpoint': endpoint,
 *      'schemas': [
 *          url-schema,
 *          url-schema
 *      ]
 * }
 */
class OEmbedProviderFactory {
    private static $instance;

    public static function &GetInst() {
        return self::$instance;
    }

    private $schemaRegexStorage = array();
    private $providerStorage = array();

    private $providerConfiguration;

    public function __construct($providerConfiguration) {
        if (self::$instance != null) {
            throw new ErrorException("OEmbedProviderFactory already exists in this application context!");
        }

        $this->providerConfiguration = $providerConfiguration;

        self::$instance = &$this;
    }

    private function GetSchemaRegex($schema) {
        if (is_null($this->schemaRegexStorage[$schema])) {

            $regex = preg_replace(
                array("/\*/", "/\//", "/\.\*\./"),
                array(".*", "\/", ".*"),
                $schema
            );

            $regex = "/" . $regex . "/";
            $this->schemaRegexStorage[$schema] = $regex;
        }

        return $this->schemaRegexStorage[$schema];
    }

    private function ValidateSchema($schema, $url) {
        $regex = self::GetSchemaRegex($schema);
        return preg_match($regex, $url);
    }

    private function ValidateProviderSchemas($provider, $url) {
        $schemas = $provider['schemas'];
        foreach ($schemas as $schema) {
            if ($this->ValidateSchema($schema, $url)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Create new or find exists @see OEmbedProvider which can process this $url
     * @param $url
     * @return OEmbedProvider|null
     */
    public function GetProvider($url) {
        foreach($this->providerConfiguration as $provider) {
            if ($this->ValidateProviderSchemas($provider, $url)) {
                $name = $provider['name'];
                if ($this->providerStorage[$name] == null) {
                    $this->providerStorage[$name] = new OEmbedProvider($provider['endpoint']);
                }
                return $this->providerStorage[$name];
            }
        }
    }

    /**
     * Return a @see OEmbedData for this $url is possible
     * @param $url
     * @return OEmbedData|null
     */
    public function GetEmbedData($url) {
        $provider = $this->GetProvider($url);
        if ($provider == null) {
            return null;
        }

        return $provider->GetEmbedData($url);
    }
}

class OEmbedProvider {
    private $contentProviderEndpoint;

    public function __construct($contentProviderEndpoint) {
        $this->contentProviderEndpoint = $contentProviderEndpoint;
    }

    /**
     * @param $url
     * @return OEmbedData
     */
    public function GetEmbedData($url) {
        $targetUrl = $this->contentProviderEndpoint . '?format=json&url=' . $url;

        // Request a oEmbed content
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $targetUrl);
        curl_setopt($ch, CURLOPT_TIMEOUT, 10);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        $result = curl_exec($ch);

        // Verify response
        $status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        if ($status_code != 200) {
            return null;
        }

        // Parse json response
        $response = json_decode($result);

        if ($response == null) {
            return null;
        }

        // Build information about links
        $embedData = new OEmbedData();
		// Да, да, да, я знаю, что тут костыль
        if ($response->type == "photo") {
            $embedData->html =
                '<a href="' . $response->url . '" title="by ' . $response->author_name . '">' .
                    '<img src="' . $response->thumbnail_url . '" />' .
                '</a>';
            ;
        } else {
            $embedData->html = $response->html;
        }

        return $embedData;
    }
}
А работать с этим примерно так:
// Инициализация
$config['OEmbedProviders'] = array(
	array(
        'name' => 'sketchfab',
        'endpoint' => 'https://sketchfab.com/oembed',
        'schemas' => array(
            'https://sketchfab.com/models/*',
            'https://sketchfab.com/*/folders/*',
            'http://sketchfab.com/*/folders/*',
            'http://sketchfab.com/models/*'
        )
    ),
);
new OEmbedProviderFactory($config['OEmbedProviders']);

// Применяемс на деле
$url = "....";
$factory = &OEmbedProviderFactory::GetInst();
echo $factory->GetEmbedData($url);
В будущем хочу развивать эту библиотечку ^^ Сейчас она в очень плохом и сыром виде.
В ближайшее время мы подготовим патч для сайта, где добавится поддержка данного протокола для SketchFab'а. Ждите)
Спасибо за внимание!

Дополнительно:
  • oEmbed official site - официальный сайт протокола oEmbed
  • SketchFab - каталог 3д моделей с возможностью интерактивного просмотра
  • GitHub: aphp-oembed - библиотека для работы с oEmbed провайдерами. Подключайтесь)
`
ОЖИДАНИЕ РЕКЛАМЫ...
0
29
9 лет назад
0
Теперь еще и на гитхабе github.com/alexprey/aphp-oembed
0
24
9 лет назад
0
Сразу возникает несколько вопросов
  1. Как обстоят дела с загрузкой страниц сайта, когда на странице есть такой эмбед, но медиа-провайдер по какой-то причине не доступен или очень долго отвечает?
  2. Как влияет на время загрузки страницы сайта наличие на ней множества эмбед-ссылок?
  3. Учтены ли риски безопасности в реализации? Например, на официальном сайте рекомендуют полученное через этот протокол HTML-содержимое отображать в ай-фрейме с другого домена и фильтровать урлы чтобы избежать инъекций.
  4. Есть ли перспективы включения в список поддерживаемых пользовательского oEmbed-провайдера, если кому-то из пользователей XGM придет в голову таковой реализовать?
0
29
9 лет назад
0
  1. Как обстоят дела с загрузкой страниц сайта, когда на странице есть такой эмбед, но медиа-провайдер по какой-то причине не доступен или очень долго отвечает?
  2. Как влияет на время загрузки страницы сайта наличие на ней множества эмбед-ссылок?
Стоит таймаут загрузки, да это конечно влиет на время загрузки страницы, но используется кеширование.
  1. Учтены ли риски безопасности в реализации? Например, на официальном сайте рекомендуют полученное через этот протокол HTML-содержимое отображать в ай-фрейме с другого домена и фильтровать урлы чтобы избежать инъекций.
Да там используется фильтраия доверенных доменов, т.е. если предет какой то пользователь, вставит свою ссылку с поддержкой oembed, то она отобразится как обычная ссылка, т.к. он не находится в доверительных.
prog:
  1. Есть ли перспективы включения в список поддерживаемых пользовательского oEmbed-провайдера, если кому-то из пользователей XGM придет в голову таковой реализовать?
В перспективе вполне возможно
0
24
9 лет назад
Отредактирован prog
0
alexprey, а есть ли возможность грузить такое содержимое асинхронно, а на страницу отдавать, например, аяксом? В общем случае это не нужно, конечно, но пригодилось бы на случай проблем с каким-нибудь конкретным провайдером.
Ну а что касается безопасности - фильтр по доменам это уже хорошо - поможет от желающих целенаправленно нагадить на xgm, но, к сожалению, не спасает от проблем на стороне доверенного провайдера.
0
29
9 лет назад
0
Ну а что касается безопасности - фильтр по доменам это уже хорошо - поможет от желающих целенаправленно нагадить на xgm, но, к сожалению, не спасает от проблем на стороне доверенного провайдера.
Это да, поэтому и рекомендуют хостить на другом домене от основного сайта. Но у нас нет такой возможности к сожалению. А вообще не вижу опасности с доверительных источников, таких как ютуб например.
0
24
9 лет назад
0
alexprey, реальной опасности нет, конечно, но потенциальные риски есть. Ну и пользовательские медиа-провайдеры при такой реализации лучше не пытаться делать, если они не принадлежат кому-то из тех, у кого и так есть полный доступ ко всему.
0
29
9 лет назад
0
пользовательские медиа-провайдеры при такой реализации лучше не пытаться делать
Да пока что и не собираемся)
Чтобы оставить комментарий, пожалуйста, войдите на сайт.