Предположим, что у нас есть некий веб-сайт, например, картинная галерея. Требуется предоставить посетителю этой картинной галереи возможность помещать картинки себе на стену в vk.com, сделав этот процесс максимально прозрачным.
С точки зрения вконтакта эта процедура состоит из следующих этапов:
- авторизация в их сети и запрос разрешения на действие от имени пользователя;
- некая подготовительная работа, завершающаяся получением url, на который следует отправить изображение;
- закачка изображения на предоставленный вконтактом ресурс, адрес которого получен в п.2;
- сохранение изображения вконтакте;
- формирование сообщения на стену, содержащего ссылку на сохраненное изображение.
К счастью, вконтакт предоставляет некий API, позволяющий почти все эти действия производить прямо из скриптов, выполняющихся на стороне клиента. Почти все. Кроме одного. Кроме пункта 3. И в этом заключается большая интрига.
Разберём эту схему подробнее. Прежде всего необходимо провести подготовительную работу. Не каждое приложение, умеющее работать с сокетами, может взаимодействовать с вконтактом. Требуется его предварительная регистрация в этой социальной сети. Делается это вот тут. Результатом этой регистрации будет некое число (или строка?), apiId (пусть будет 121212), которое потребуется в п.1.
Сам процесс авторизации выглядит достаточно просто. В заголовок нашей веб-страницы добавляется скрипт VK API:
<script src="//vk.com/js/api/openapi.js" type="text/javascript"></script>Далее вызывается функция, использующая apiId, полученный при регистрации приложения:
VK.init({apiId: "121212"});
После этого производится собственно авторизация:
VK.Auth.login(
function()
{
// действия в случае успешной авторизации
},
5);
Следует обратить внимание на цифру 5. Это на самом деле 1+4, или, проще говоря, установленный нулевой и второй флаги запрашиваемых прав доступа.На этом пункт 1 можно считать выполненным.
Второй пункт продолжает действия в случае успешной авторизации. Он выглядит так:
VK.Api.call(
"photos.getWallUploadServer",
{},
function(data)
{
// объект data содержит либо свойство error, описывающее ошибку,
// либо свойство response, содержащее адрес ресурса, на который
// следует отправлять изображение для сохранения его вконтакте:
// data.response["upload_url"]
}
);
Как видим, по окончании этого этапа у нас на руках оказывается некий длинный url примерно такого вида: http://c12345.vk.com/?param1=11111¶m2=22222. Дальше на этот адрес следует сделать POST-запрос с параметром по имени photo, содеращим файл изображения.Тут начинается проблема. На стороне клиента инструментарий, позволяющий межсайтовый скриптинг, достаточно ограничен. Можно воспользоваться jsonp - но он не позволяет POST-запросы. Можно создать дочернее окно с веб-формой, заполнить её и сделать сабмит - но как в эту форму передать двоичное содержимое файла, которого нет у клиента? Можно, наконец, попросить наш веб-сервер передать за нас нужный файл - но в документации сказано: запрос на получение адреса для загрузки файла и post-запрос с файлом должны осуществляться с одного ip-адреса (и к тому же, по слухам, вконтакт не любит много запросов с одного адреса).
Остаётся единственная альтернатива: использовать flash, точнее, flex-приложение (потому что последнее - бесплатно). Флэш тоже имеет свои представления о безопасности и загрузке файлов, но, к счастью, вконтакте позаботился об этом, и сайты c12345.vk.com предоставляют вполне либеральный файл crossdomain.xml:
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd"> <cross-domain-policy> <site-control permitted-cross-domain-policies="master-only"/> <allow-http-request-headers-from domain="*" headers="*"/> <allow-access-from domain="*" to-ports="80"/> <allow-access-from domain="*" to-ports="443"/> </cross-domain-policy>Сам флэш-ролик на странице мы вольны поместить с атрибутом allowScriptAccess="always", и, таким образом, обеспечить двусторонний обмен скриптов ролика со скриптами из вмещающей страницы:
<object id="mySwf" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" allowScriptAccess="always">
<embed
name="mySwf"
src="vkUpload.swf"
width="500"
height="500"
allowScriptAccess="always"
>
</object>
Что касается внутренности флэш-ролика, то она может быть, например, такой:
<?xml version="1.0" encoding="utf-8"?>
<s:Application
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark"
width="500"
height="500"
creationComplete="init()"
>
<mx:Image id="myImage" width="100%" height="100%"/>
<fx:Script>
<![CDATA[
import flash.net.URLRequest;
import flash.net.URLLoader;
import flash.display.Loader;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.external.*;
import mx.graphics.codec.PNGEncoder;
internal var
imageDownloader : Loader = new Loader(),
imageByteArray : ByteArray = null,
imageUploadUrl : String = "",
imageUploader : URLLoader = new URLLoader(),
uploadResponse : String = "";
public function init() : void
{
Security.allowDomain("*");
ExternalInterface.addCallback("processDataF", processData);
imageDownloader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, imageDownloaderErrorHandler);
imageDownloader.contentLoaderInfo.addEventListener(Event.COMPLETE, imageDownloaderCompleteHandler);
imageUploader.addEventListener(IOErrorEvent.IO_ERROR, imageUploaderErrorHandler);
imageUploader.addEventListener(Event.COMPLETE, imageUploaderCompleteHandler);
imageUploader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, imageUploaderSecurityErrorHandler);
imageUploader.dataFormat = URLLoaderDataFormat.TEXT;
}
public function processData (fileSourceUrl : String, uploadUrl : String) : void
{
// сохраняем адрес выгрузки изображения
imageUploadUrl = uploadUrl;
// получаем изображение
var request:URLRequest = new URLRequest(fileSourceUrl);
try
{
imageDownloader.load(request);
}
catch (error:*)
{
processError("Загрузка изображения", error.message);
}
}
private function processError (step : String, message : String) : void
{
ExternalInterface.call("ReturnFromFlexWithError", step + "\n" + message);
}
private function processComplete (message : String) : void
{
ExternalInterface.call("ReturnFromFlexWithSuccess", encodeURIComponent(message));
}
private function imageDownloaderErrorHandler (e : IOErrorEvent) : void
{
processError("Процесс загрузки изображения", e.text);
}
private function imageDownloaderCompleteHandler (e : Event) : void
{
try
{
var bitmap : Bitmap = Bitmap(imageDownloader.content);
myImage.source = bitmap;
var png:PNGEncoder = new PNGEncoder();
imageByteArray = png.encode(bitmap.bitmapData);
}
catch (error:*)
{
processError("Окончание процесса загрузки изображения", error.message);
return;
}
if (!imageByteArray)
{
processError("Получение данных для выгрузки изображения", "Массив пуст");
return;
}
// выгружаем изображение
var postData : ByteArray = new ByteArray();
postData.writeMultiByte('--TESTTESTTEST\r\n', 'utf-8');
postData.writeMultiByte('Content-Disposition: form-data; name="photo"; filename="photo.png"\r\n', 'utf-8');
postData.writeMultiByte('Content-Type: application/octet-stream\r\n', 'utf-8');
postData.writeMultiByte('Content-Transfer-Encoding: binary\r\n', 'utf-8');
postData.writeMultiByte('\r\n', 'utf-8');
postData.writeBytes(imageByteArray);
postData.writeMultiByte('\r\n', 'utf-8');
postData.writeMultiByte('--TESTTESTTEST--\r\n', 'utf-8');
var request : URLRequest = new URLRequest(imageUploadUrl);
request.method = URLRequestMethod.POST;
request.requestHeaders.push( new URLRequestHeader( 'Content-Type', 'multipart/form-data; boundary=TESTTESTTEST' ) );
request.data = postData;
try
{
imageUploader.load(request);
}
catch (error:*)
{
processError("Начало процесса выгрузки изображения", error.message);
return;
}
}
private function imageUploaderErrorHandler (e : IOErrorEvent) : void
{
processError("Процесс выгрузки изображения, ввод-вывод", e.text);
}
private function imageUploaderSecurityErrorHandler (e : SecurityErrorEvent) : void
{
processError("Процесс выгрузки изображения, безопасность", e.text);
}
private function imageUploaderCompleteHandler (e : Event) : void
{
processComplete(String(imageUploader.data));
}
]]>
</fx:Script>
</s:Application>
Схема взаимодействия этого ролика со скриптом вмещающей страницы проста. Скрипт со страницы вызывает метод ролика:document["mySwf"].processDataF(АдресИзображенияДляЗагрузки, АдресСервераВконтакте);Этот метод по результатам своего труда либо при ошибке передает управление в функцию:
function ReturnFromFlexWithError(data)
{
alert("Ошибка при работе через Flex!\n" + data);
}
либо - при успехе - в функцию:function ReturnFromFlexWithSuccess(data)
{
var
jsonData = JSON.parse(decodeURIComponent(data));
// действия в случае успешной загрузки изображения
// особую ценность представляют jsonData.photo, jsonData.server, jsonData.hash
}
Тут стоит упомянуть забавную тонкость. Если не uri-кодировать строку, возвращаемую роликом в основной скрипт, то браузер выдает ошибку синтаксиса. Поэтому приходится передавать закодированную строку, и декодировать её на стороне javascript-а.Вернемся к нашей схеме. Получив на предыдущем этапе значения photo, server и hash мы можем сохранить загруженное фото:
VK.Api.call(
"photos.saveWallPhoto",
{photo: jsonData.photo, server: jsonData.server, hash: jsonData.hash},
function(data)
{
// объект data содержит либо свойство error, описывающее ошибку,
// либо свойство response, содержащее идентификатор сохраненного изображения
// data.response[0].id
}
);
Осталось выполнить последний пункт: разместить на стене изображение, содержащее фото. Это делается командой:
VK.api(
"wall.post",
{message: "текст сообщения", attachments: data.response[0].id},
function (data)
{
// объект data содержит либо свойство error, описывающее ошибку,
// либо свойство response, содержащее результат операции
}
);
Как видим, основными трудностями, которые нам здесь пришлось обойти, является запрет кроссдоменного скриптинга и помещение в POST-запрос информации, которую сервер бы трактовал как двоичный файл.
А это - в качестве иллюстрации вышеизложенного:
http://это-очередной-унылый-домен-из-кириллической-зоны.рф/web/js/z150525-00.xml
Комментариев нет:
Отправить комментарий