Постоянные читатели блога наверное в курсе, что периодически я публикую посты-ответы на вопросы читателей. Обычно я практикую сей способ работы в блоге в том случае, если вопрос читателя:
- меня заинтересовал
- может не уложиться в пару строк типа «
RTFMНеобходимо сделать вот так, так или вот так« - может пригодиться кому-нибудь ещё, кроме вопрошающего.
Так, например, я давал ответы на вопросы работу с примечаниями в ячейках Excel, работу с (опять же multipart/form-data) imagevenue.com и ещё пару небольших постов про работу с Сетью. Вот сегодня как раз и будет такой пост-ответ на вопрос с форума WebDelphi. Все-таки Яндекс.Фотки — это довольно популярный сервис, да и работа предполагается с Synapse, а к этой библиотеке я, как вы знаете, питаю самые теплые чувства :) В качестве ответа я приготовил небольшое приложение, которое демонстрирует все шаги по работе с API Яндекс.Фоток в т.ч. и загружает изображение на сервис в формате multipart/form-data. Рассмотрим это приложение и посмотрим, что у меня в итоге получилось.
Само приложение выглядит следующим образом:
Каждый шаг работы с программой предполагает каких-либо действий от нас: указать файл, открыть файл и скопировать что-то, внести данные в поля и т.д. Рассмотрим каждый шаг работы подробнее.
Шаг №1. Авторизация
Про авторизацию в сервисах Яндекса я говорил, когда рассказывал про работу с Яндекс.Метрика. Однако время, а вместе с ним и Яндекс не стоят на месте и первый поисковик Рунета сменил способ авторизации в своих сервисах, а именно убрал авторизацию по логину/паролю, который мне так приглянулся. В результате пришлось в срочном порядке перечитывать документацию по OAuth Яндекса и изобретать очередной велосипедик. Раз логин/пароль более использовать нельзя, то будем использовать переход на сайт Яндекса за разрешением. Самый простой вариант в этом случае — использовать старый недобрый TWebBrowser.
Для авторизации я соорудил вот такую простенькую форму:
т.е. ничего кроме TWebBrowser на форме-то и нету. Работает авторизация следующим способом:
у главной формы в public-секции определена вот такая переменная:
type Tfmain = class(TForm) ... private public Token: string; end;
клик по кнопке «Авторизация» выполняет следующий код:
procedure Tfmain.Button1Click(Sender: TObject); begin fAuth.ShowModal; if fAuth.ModalResult=mrOk then lbAuth.Caption:=Token end;
У формы для авторизации в OnShow сразу же выполняется отправка пользователя за подтверждением разрешения для нашего приложения:
procedure TfAuth.FormShow(Sender: TObject); begin WebBrowser1.Navigate2(Format('https://oauth.yandex.ru/authorize?response_type=token&client_id=%s&display=popup',['тут_client_id_приложения'])); end;
Как регистрировать приложения в Яндексе и получать их Client_ID я рассказывал как раз тут (эта часть статьи до сих пор актуальна). Как только пользователь подтверждает, что он дает разрешение нашему приложению копаться у него в фотках и загружать новые изображения, то Яндекс начинает переправлять пользователя на redirect_uri, который мы, кстати, можем задать при регистрации приложения абсолютно произвольно. Я например, указал в качестве redirect_uri http://ya.ru.
Так вот, наша задача заключается в том, чтобы поймать момент, когда Яндекс перебросит пользователя на redirect_uri, а сам URL будет содержать в параметрах заветный токен для доступа к API. Я решил эту задачку элементарно — обработав событие OnNavigateComplete2 вот таким образом:
procedure TfAuth.WebBrowser1NavigateComplete2(ASender: TObject; const pDisp: IDispatch; const URL: OleVariant); var Params: TStringList; begin if pos('access_token',URL)>0 then //URL содержит токен доступа begin Params:=TStringList.Create; try Params.Delimiter:='&'; Params.DelimitedText:=copy(URL,pos('#',URL)+1,Length(URL)-pos('#',URL));//копируем параметры fMain.Token:=Params.Values['access_token'];//получаем значение токена ModalResult:=mrOk; //уходим отсюда finally Params.Free; end; end; end;
Может такой подход и не совсем корректный или выглядит не эстетично, но авторизация в сервисах — это вообще отдельный разговор и сегодня этот момент скорее необходимый шажок в решении задачи, нежели сам ответ на вопрос, поэтому работает пока и то хлеб — кому надо доработают по вкусу и цвету. В итоге, получив токен мы уже можем смело двигаться в сторону Яндекс.Фоток и работать с ними.
Шаг №2. Получение сервисного документа
Разработчики Яндекса решили задачку с получением точек доступа к ресурсам пользователя довольно грамотно. Вместо того, чтобы просить нас конструировать какие-то свои URL доступа, разбираться с параметрами и т.д. разработчики API решили просто — качаете сервисный документ (обычный XML) и получаете в нем все необходимые сведения по URL’ам коллекций, названиям и т.д. и т.п. Кроме того, эти самые URL со временем могут изменяться, поэтому начинать работу с API Яндекс.Фоток с чтения сервисного документы — это уже даже не хороший тон, а необходимость.
В моей программке получение сервисного документа происходит следующим образом:
procedure Tfmain.Button2Click(Sender: TObject); var Redirect: string; begin with THTTPSend.Create do begin Headers.Add('Authorization: OAuth '+Token); if HTTPMethod('GET','http://api-fotki.yandex.ru/api/me/') then begin Headers.NameValueSeparator:=':'; Redirect:=Headers.Values['Location']; if Length(Redirect)>0 then begin Clear; if HTTPMethod('GET',Redirect) then begin Document.SaveToFile('ServeceDocument.xml'); label4.Caption:='Получен' end else raise Exception.Create('Не удалось получить сервисный документ'); end else raise Exception.Create('Не удалось получить адрес сервисного документа'); end else raise Exception.Create('Не удалось получить адрес сервисного документа'); end; end;
Посмотрим, что здесь происходит и, что мы получаем в итоге. Так как на предыдущем этапе мы успешно получили токен доступа, то, согласно документации API Яндекс.Фоток мы можем не запрашивать у пользователя его логин, а просто выполнить специальный запрос на адрес:
http://api-fotki.yandex.ru/api/me/
В результате в заголовках ответа сервера будет содержаться адрес по которому мы можем скачать сервисный документ. Поэтому как только запрос на первый URL прошел я и читаю заголовок Location:
Headers.NameValueSeparator:=':'; Redirect:=Headers.Values['Location'];
Далее очищаем объект THTTPSend от данных предыдущего запроса и GET’ом на полученный из Location URL просим сервисный документ, а получив документ — сохраняем его на диск:
Clear; if HTTPMethod('GET',Redirect) then begin Document.SaveToFile('ServiceDocument.xml'); label4.Caption:='Получен' end
Я не стал забивать программу методами парсинга XML, т.к. это опять же не наша сегодняшняя цель. Поэтому для следующего шага работы с программой надо найти файл ServiceDocument.xml рядом с exe-файлом программы, открыть его, внимательно изучить и скопировать из него ссылку на ссылку на альбом по умолчанию — у неё параметр id равен «photo-list«.
На этом этапе работы с программой мы выполнили три шага: авторизовались, скачали сервисный документ, скопировали необходимый для работы URL. Четвертый шаг — это указание изображения для загрузки на сервер и определение его параметров (заголовка, описания и тегов), но этот шаг мы рассматривать подробно не будем, т.к. в нем нет ничего того, что может не знать человек, собравшийся копаться в API :) Поэтому сразу переходим к последнему шагу — загрузке изображения в формате multipart/form-data на Яндекс.Фотки
Шаг №3(5) Загрузка изображения в формате multipart/form-data
В качестве отправной точки я предлагаю Вам ещё раз ознакомиться со статьей Synapse. Отправка данных на сервер на примере imagevenue.com. Так как сейчас мы будем делать примерно тоже самое — составлять правильно тело документа и скидывать его на сервер.
Итак, согласно документации API Яндекс.Фоток запрос на добавление нового изображения должен выглядеть так:
POST /api/users/alekna/photos/ HTTP/1.1 Host: api-fotki.yandex.ru Content-Type: multipart/form-data; boundary=frekgh738gGHUehfui33qqQ Content-length: 565928 Authorization: OAuth eb1c5.. --frekgh738gGHUehfui33qqQ Content-Disposition: form-data; name="image"; filename="picture" Content-Type: image/jpeg {содержимое файла "picture.jpg" в бинарном виде} --frekgh738gGHUehfui33qqQ Content-Disposition: form-data; name="title" Горы --frekgh738gGHUehfui33qqQ Content-Disposition: form-data; name="description" Сан-Катальдо, Сицилия --frekgh738gGHUehfui33qqQ Content-Disposition: form-data; name="tags" природа --frekgh738gGHUehfui33qqQ Content-Disposition: form-data; name="tags" небо --frekgh738gGHUehfui33qqQ Content-Disposition: form-data; name="album" urn:yandex:fotki:alekna:album:19781
Не проблема — составляем аналогичный запрос, но уже с нашей фоткой. Т.к. я не активный пользователь этого сервиса, то просто загружу фотку в дефолтный альбом, т.е. не буду использовать в запросе идентификатор альбома — сути это никак не изменит. Итак, вот метод отправки изображения:
procedure Tfmain.Button3Click(Sender: TObject); const CR = #$0d; LF = #$0a; CRLF = CR + LF; var Bound,s: string; HTTP: THTTPSend; PicStream: TMemoryStream; begin {определяем разделитель} Bound := IntToHex(Random(MaxInt), 8) + '_PhotoManager_boundary'; HTTP:=THTTPSend.Create; try {вставляем заголовок авторизации и определяем Mime-тип документа} HTTP.Headers.Add('Authorization: OAuth '+Token); HTTP.MimeType:='multipart/form-data; boundary='+Bound; {начинаем составлять тело документа} S:='--' + Bound + CRLF; //тут два дефиса перед разделителем :) {обязательная часть - изображение} s:=s+'Content-Disposition: form-data; name="image"; filename="picture"' + CRLF; {тут надо бы определять Mime-тип картинки, но для примера и так пойдёт} s:=s+'Content-Type: image/jpeg'+CRLF+CRLF; PicStream:=TMemoryStream.Create; try {записываем текстовую часть тела документа} WriteStrToStream(HTTP.Document,AnsiToUtf8(s));//не забываем про кодировку! {грузим картинку в поток} PicStream.LoadFromFile(lbPhoto1.Caption); {копируем картинку в тело документа} PicStream.Position:=0; HTTP.Document.CopyFrom(PicStream,0); {пропускаем одну строку и начинаем расписывать свойства изображения} S:=CRLF+CRLF+'--' + Bound + CRLF;//не забываем про разделитель с двумя дефисами {задали название} S:=S+'Content-Disposition: form-data; name="title"'+CRLF+CRLF; S:=s+edTitle.Text+CRLF+'--'+Bound+CRLF; {задаем описание} s:=s+'Content-Disposition: form-data; name="description"'+CRLF+CRLF; s:=s+edDescription.Text+CRLF+'--'+Bound+CRLF; {задаем тэги} s:=s+'Content-Disposition: form-data; name="tags"'+CRLF+CRLF; s:=s+edTags.Text; {записываем текстовую часть} WriteStrToStream(HTTP.Document,AnsiToUtf8(s)); {скидываем тело документа в файл - вдруг захочется посмотреть что там} HTTP.Document.SaveToFile('Request.txt'); {скидываем документ на сервер} if HTTP.HTTPMethod('POST',edLoadURL.Text) then begin {сервер что-то ответил - сохраняем - потом посмотрим} HTTP.Document.SaveToFile('Response.xml'); lbLoadResult.Caption:='Сервер вернул ответ!'; end else raise Exception.Create('Во время отправки фотки произошла ошибка'); finally PicStream.Free; end; finally HTTP.Free; end; end;
Собственно, в коде постарался как можно более подробно расписать все, что я делал и зачем. Памятуя о том, что в прошлый раз кто-то потерял три дня жизни из-за дефисов в разделителях — на этот раз обратил внимание и на них :).
Теперь запускаем программку, проходимся по всем шагам и в итоге получаем вот такой примерно вид:
Заходим в сервис Яндекс.Фотки и наблюдаем результаты работы:
Как видите — изображение загрузилось, название, описание и теги вставились без проблем, а следовательно — задача решена.
Книжная полка
Описание: Рассмотрены практические вопросы по разработке клиент-серверных приложений в среде Delphi 7 и Delphi 2005 с использованием СУБД MS SQL Server 2000, InterBase и Firebird. Приведена информация о теории построения реляционных баз данных и языке SQL. Освещены вопросы эксплуатации и администрирования СУБД.
|
||
Название: О чем не пишут в книгах по Delphi
Описание: Рассмотрены малоосвещенные вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные режимы их работы, особенности для протоколов TCP и UDP и др.
|
Вы же программист со стажем, а пиши такой код… :(
[code]
s:=s+'Content-Disposition: form-data; name="description"'+CRLF+CRLF;
s:=s+edDescription.Text+CRLF+'--'+Bound+CRLF;
s:=s+'Content-Disposition: form-data; name="tags"'+CRLF+CRLF;
s:=s+edTags.Text;
[/code]
Я понимаю, что часто лень на такие мелочи создавать отдельный класс, но в данном случае он бы имел более элегантный вид, чем эти копи-пасты. Чему вы учите молодежь?
xpert13, явно не правописанию :) Был задан конкретный вопрос, приведен не менее конкретный исходник, этот исходник был немного исправлен и вновь опубликован. Ответ на вопрос есть — есть, код работает — работает, ошибок нет — нет. Кому нужен класс — пусть пишет класс — рабочий пример есть.
Отличная статья получилась.
Всё понятно.
ООП это конечно хорошо, но согласен с Vlad, если так оформлять все статьи то всю жизнь на это можно убить, в том числе и на написание классов для таких тривиальных задач. ДА КРАСИВО было бы — НО ЗАЧЕМ!
Вы ещё к интерфейсу предиритесь, мол, а что это стандарные батоны и эдиты используете, не красиво :)))))
Ничего не понимаю. Делал процедуру для загрузки изображений этим способом на другой сайт, но не работает. Все делал по аналогии, HTTP.Document пустой, потом сделал для теста вот так:
Var S: String;
...
S:= 'Hello, World!';
WriteStrToStream(HTTP.Document,S);
TestStr:= ReadStrFromStream(HTTP.Document, HTTP.Document.Size);
TestStr в Debug-Ad Watch пустая (равна »). Почему?
Перед
TestStr:= ReadStrFromStream(HTTP.Document, HTTP.Document.Size);
сделайте
HTTP.Document.Position:=0
ОХ!
Я так и думал, что я просто чего-то не знаю =) Спасибо большое!
Не за что. Заходите к нам ещё :)
пытаюсь приспособить Ваш пример под с++ builder. авторизацию осилил, а вот с отправкой файла не получается. код:
THTTPSend *http = new THTTPSend;
http->Protocol=»1.1″;
http->TargetHost=»api-fotki.yandex.ru»;
http->MimeType=»image/jpeg»;
http->Headers->Add(«Authorization: OAuth «+token);
TMemoryStream *PicStream = new TMemoryStream;
WriteStrToStream(http->Document,AnsiToUtf8(s));
PicStream->LoadFromFile(«C:\\CIMG9656.JPG»);
PicStream->Position=0;
http->Document->CopyFrom(PicStream,0);
WriteStrToStream(http->Document,AnsiToUtf8(s));
http->Document->SaveToFile(«c:\\Request.txt»);
http->HTTPMethod(«POST»,Form1->Edit1->Text);
http->Document->SaveToFile(«c:\\Response.xml»);
в реквест.тхт сервер сбрасывает ошибку о том, что тип изображения не поддерживается. «Not an image or unsupported image format». где я накосячил?
Может и не вы накосячили, а сам сервер не принимает такой формат? Помнится был случай, когда написание расширения маленькими буковками помогло :)
увы, это не причина
нашел баг — когда упрощал основной неработающий пример забыл выбросить добавление s. без него работает. но, когда пытаюсь добавить через multipart/form-data то ругается на No image data (no field «image») =( при этом кодировка вроде как надо выполняется. при этом в реквесте написано всё как в примере на яндексе. блин, что ж делать-то.
взял картинку, вставил туда теги multipart/form-datа, сохранил как текстовик. гружу этот текстовик с тегами и телом картинки — на сервак улетает только в путь. генерю такой же текстовик программой(то что выплевывает реквест.тхт перед отправкой), открываю его notepad++, сравниваю плагином compare — разницы нет! ну и да, если записываю сгенеренный файл, в потом читаю его сразу на передачу — результат нулевой, т.е. с таким костылём не прокатывает.
победил проблему! оказалось что перехода на новую строку \n недостаточно, надо еще возврат каретки делать \r\n. в notepad++ при изучении начинки всё выглядит как надо, так как он, видимо, сам переносит и смотрится все цивильно, а вот если тупым виндовым блокнотом смотреть — то разница на лицо. админ, я тут нафлудил — наверно всё это можно потереть нафиг =)
и да, большое спасибо за описанный пример, мне, как не программисту, он очень помог!
рад, что проблема решена успешно :) А комменты пусть будут — вдруг кому-нибудь пригодятся
немного оффтопика: заметил сегодня, что вот уже почти 5 лет открыта в браузере эта полезная страница =) спасибо тебе, автор!
и еще немножко — а как уважаемый автор внедрил такой прикольный виджет поддержки сайта?
Спасибо за поздравление. Виджет с Webmoney.Founding