В большинстве случаев, при рассмотрении в блоге вопросов использования библиотеки Synapse, а именно при работе с THTTPSend я ограничивался тем, что рассказывал как получать какие-либо данные из Сети, например, текст страницы или, на крайний случай, правильно работать с веб-формами, когда надо было залогинится на сайте или отправить на сервер какой-нибудь текст. Но между тем, иногда может потребоваться и отправка более “весомых” данных, чем простой текст, например, загрузить картинку на фотохостинг.
В качестве примера рассмотрим работу с сайтом imagevenue.com.
Итак, повторение – мать учения. Начнем по-порядку — с анализа сайта.
Во-первых, ищем страничку с которой можно хостить свои картинки через простую вею-форму. Такая страничка имеется и её URL — http://imagevenue.com/host.php. Заходим на страничку и начинаем анализировать заголовки сервера. Как анализировать заголовки я рассказывал как минимум дважды – здесь и здесь. Я буду использовать в сниффер для FireFox – HTTP Live Headers.
выбираем какую-нибудь картинку у себя на компьютере, указываем содержимое картинки и отправляем файл на сервер:
После отправки внимательно изучаем заголовки сервера. Необходимо найти POST-запрос в котором отправляется картинка на сервер. На рисунке ниже представлен этот самый запрос, а также выделены заголовки, на которые необходимо обратить внимание:
Какие выводы можно сделать, судя по заголовкам:
- Запрос необходимо отправлять на URL http://imagevenue.com/upload.php (см. путь в первой строке после “POST”)
- В запросе множественное содержимое, т.е. тело запроса может состоять из нескольких частей, разделенных границей. Этот вывод мы делаем, исходя из данных во второй выделенной строке: multipart/form-data – указывает на множественное содержимое, всё, что содержится после “boundary=” – разделитель для частей тела запроса.
- Каждая часть должна сдержать по два заголовка: первый заголовок Content-Disposition в котором можно указать название файла; второй заголовок – тип контента в данном случае хостинг принимает только jpeg, что несколько упрощает наше работу.
Теперь нам остается только правильно составить запрос в Delphi, используя THTTPSend. О том, как составляются подобные запросы я рассказывал здесь, но распишем метод ещё раз более детально. Итак, процедура отправки файла на сервер:
procedure TForm1.Button1Click(Sender: TObject); Const CRLF = #$0d+#$0a; var HTTP: THTTPSend; s: AnsiString; FS: TFileStream; begin if OpenDialog1.Execute then begin try HTTP := THTTPSend.Create; FS:=TFileStream.Create(OpenDialog1.FileName, fmOpenRead); HTTP.MimeType := 'multipart/form-data; boundary=END_OF_PART'; { Записываем Mime-тип и данные по файлу } s := '--END_OF_PART' + CRLF + 'Content-Disposition: form-data; name="userfile[]"; filename="FileName.jpg"' + CRLF + 'Content-Type: image/jpeg' + CRLF + CRLF; HTTP.Document.Write(PAnsiChar(s)^, Length(s)); FS.Position := 0; // записываем файл в тело документа HTTP.Document.CopyFrom(FS, FS.Size); { завершаем тело запроса } s := CRLF + '--END_OF_PART--' + CRLF; HTTP.Document.Write(PAnsiChar(s)^, Length(s)); // завершили тело документа // Отправляем запрос if HTTP.HTTPMethod('POST', 'http://imagevenue.com/upload.php') then begin ShowMessage(HTTP.ResultString); mmo1.Lines.LoadFromStream(HTTP.Document); end; finally FS.Free; HTTP.Free; end; end; end;
Разберем подробно, что мы сделали. Во-первых, создаем объект HTTP:THTTPSend и загружаем выбранный файл в файловый поток FS:TFileStream.
Определяем тип данных в точности как и в ранее полученных заголовках:
HTTP.MimeType := 'multipart/form-data; boundary=END_OF_PART';
Обратите внимание на то как указан разделитель — «END_OF_PART«. Я написал так, чтобы подчеркнуть назначение строки, записываемой в boundary, в рабочем приложении вы обязаны предусмотреть, чтобы последовательность байтов разделителя никогда не встречалась в файле. Для этого можно, например, воспользоваться генератором случайных чисел и плюс добавить к разделителю какую-нибудь строку типа «———ProgName——« — врядли полученная строка встретиться в файле. В общем — фантазируйте :)
Далее мы записываем в тело запроса следующие строки:
—END_OF_PART’ + CRLF — определяем начало новой части документа и переходим на новую строку
Content-Disposition: form-data; name=»userfile[]»; filename=»FileName.jpg» — заголовок. Здесь желательно указать правильное название файла (вместо FileName.jpg), а также обязательно должна присутствовать строка «form-data».
Подстрока name=»userfile[]»; в нашем случае определяет какой по счёту файл из формы отправляется — первый.
CRLF + ‘Content-Type: image/jpeg’ + CRLF + CRLF; — переходим на новую строку, определяем тип данных (картинка jpeg), пропускам одну строку.
После этого – пишем данные из файлового потока в тело документа:
FS.Position := 0; HTTP.Document.CopyFrom(FS, FS.Size);
Завершаем часть документа, записав в тело запроса строку:
s := CRLF + '--END_OF_PART--' + CRLF; HTTP.Document.Write(PAnsiChar(s)^, Length(s));
Только после этого мы отправляем запрос и анализируем результат. В нашем случае мы просто выводим сообщение м ответом сервера и выписываем полученный в ответе документ в Memo:
if HTTP.HTTPMethod('POST', 'http://imagevenue.com/upload.php') then begin ShowMessage(HTTP.ResultString); mmo1.Lines.LoadFromStream(HTTP.Document); end;
В случае успешно отправленного запроса в Memo попадет текст страницы, содержащий миниатюру нашей картинки, а также все HTML-коды для вставки картинки на страницы блогов, форумов и т.д.
Вот, собственно, вся “хитрость” отправки данных на сервер фотохостнга. Кстати, если для вас не принципиально получение HTML-кода ответной страницы, то в Synapse в том же модуле httpsend.pas есть замечательная функция HttpPostBinary, которую можно использовать для отправки бинарных данных на сервер методом POST, если запрос проходит успешно, то функция возвращает TRUE.
Вот и все :) Исходники не выкладываю, т.к. весь исходник – это процедура, представленная выше.
Книжная полка
Описание: Рассмотрены практические вопросы по разработке клиент-серверных приложений в среде Delphi 7 и Delphi 2005 с использованием СУБД MS SQL Server 2000, InterBase и Firebird. Приведена информация о теории построения реляционных баз данных и языке SQL. Освещены вопросы эксплуатации и администрирования СУБД.
|
||
Название: О чем не пишут в книгах по Delphi
Описание: Рассмотрены малоосвещенные вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные режимы их работы, особенности для протоколов TCP и UDP и др.
|
Спасибо за статью.
Давно уже спросить хотел. Vlad, используете ли вы GZip компрессию при работе с Synapse?
Chrome, нет GZip никогда не использовал. Где-то встречал информацию как работать с GZip, но сейчас уже и не вспомню где видел
Всё как-то очень сложно для такой простой операции. Неужели в Synapse нету готовых методов отправки формы, как к примеру в «Clever Internet Suite». Вот пример аналогичного решения для отправки картинки на ImgShackUpload (заметьте, кода в разы меньше)
[code]
function ImgShackUpload(filename : string): string;
var
clHttp1: TclHttp;
Responce: TStringStream;
begin
result := '';
clHttp1 := TclHttp.Create(nil);
clHttp1.Request := TclHttpRequest.Create(nil);
Responce := TStringStream.Create('');
try
clHTTP1.Request.AddSubmitFile('fileupload', filename).ContentType := 'image';
clHTTP1.POST('http://www.imageshack.us/', Responce);
result := GetRegExpMach('>Direct Link</a>.*?>(http://.*?)</textarea>', Responce.DataString);
finally
clHTTP1.Request.Free;
clHTTP1.Free;
Responce.Free;
end;
end;
[/code]
Предыдущий код парсер на сайте немного покорежил, но смысл уловить всё равно можно.
в конце поста небольшое описание функции HttpPostBinary
Ну раз уж про ZGip проговорили, если надо — могу подсказать. Я пользовал в своих грабилках частенько.
Vlad, насчет HttpPostBinary — так ведь он отправляет Application/octet-stream а не multipart/form-data, думается для заливки картинок не подойдет ;).
xpert13, вообще да, если есть желание использовать часто multipart/form-data, то лучше озадачиться написанием собственной обертки. Синапс (на мой не супер продвинутый взгляд) относительно простенькая надстройка над сокетами. В принципе из-за этого он мне и понравился :). Действительно, некоторые вещи в нем намного более громоздкие чем в Indy или ICS, но надежность….
Да, Synapse супер. Только мне в нем тоже подобная «громоздкость» также не очень нравиться. Делал обертку над THTTPSend раньше, только не полностью закончил ее.
Mifody, я уже когда то разбирался с GZip, но хотел бы услышать также твой вариант решения.
Да-да, Mifody, было б интересно услышать как с GZip работать. Можешь ткнуть в ссыль — почитаю :)
Сама либа http://www.base2ti.com (zlib). Конкретно что именно (какие юниты) требуются не разбирался, там в том числе и для С++. Обычно в uses добавляю ZLibExGZ и пользую GZDecompressStream. Синапсовый документ конверчу. В принципе у меня конструкция такая — GZDecompressStream(HTTP.Document, BodyStream); BodyStream : TStringStream, из BodyStream потом уже текст выдираю. Подозреваю, что можно намного красивше :). Vlad, напишеш статейку — будет просто супер, а то я в свой блог не часто пишу (времени, блин, нету), пока сваяю… Chrome~ — обертки это конечно супер, только это, на мой взгляд не очень хорошо. Просто написать правильную обертку — потерять кучу времени и реализовать половину… Подробнее »
сваял корявенькую статейку :) http://www.smw75.ru/delphi-synapse-poluchaem-dannye-v-gzip/, Vlad, жду от тебя нормальной. У тебя намного красивше получается писать :)
Спасибо :) Как раз сейчас сижу с либой разбираюсь по-маленьку.
Mifody, да, тоже разбирался с ZLib. На самом деле, это идеальный вариант для решение вопроса с данными, сжатыми с помощью GZip.
Vlad, Chrome~ кстати, может кто из вас подскажет почему мейловский (mail.ru) gzip не распаковывается.
Со своей стороны я вижу только одно решение — влезать в структуру данных, искать там косяк. Первое что нашел — не полный структура заголовка
Mifody, тоже глянул на mail.ru. В GZip ещё до конца не въехал, но что-то мне эта ситуация напоминает аналогичную, когда хотел перевести API Twitter’а под Synapse. Там была проблема в том, что для какого-то алгоритма шифрования (чейчас точно не скажу) в Synapse использовались сведения из RFC старой версии. Может с mail.ru та же штука? Использует какой-нибудь сверхновый алгоритм для gzip…
эээ, Влад, а что, была проблема с API Twitter + Synapse? Хм, у меня связка работает, правда не совсем API, у меня спамерская направленность :). Но и авторизация, и постинг, и урезанный API работает на ура. Если была проблема с авторизацией по протоколу HTTPS, то там есть одна заковырка. Но эт лучше через личку — шоб спамеры по меньше знали :)
Не, именно с шифрование был косяк, может не обновленную версию синапса использовал, может ещё чего. Сейчас-то и у меня работает все :) Правда я Twitter больше на автомате использую — через RSS. А с https проблем вообще никаких не возникало.
ааа, тогда не то, что я подумал :).
З.Ы. Влад, приближаемся к рекорду комментов на статью ? :))))
Не, до рекордов ещё далековато :)
Влад, помоги!
Очень нужна твоя помощь. :(
Уже 2 дня пытаюсь отправить объявление на сайт e1.ru
Данные отправляются с мультипаровским содержанием. Отправляемые мною данные 100% совпадают с теми, что отправлял через сайт, но они почему-то отвергаются сервером по году 403!!!
Прилагаю лог сделанный в HTTP Analyzer V6: http://otdahni.ru/go/PostE1analyzer_v6.halog
В нём отправка новости через браузер и через мою программу.
403 код — доступ запрещен. Если отправляются 100% правильные данные, то проблема может быть в куках, адресе на который идет отправка, кодировке данных, если есть авторизация — в правильности отправки логина и пароля.
Александр, хоть я и не Vlad, но всеж. Сразу говорю
«Content-Type: multipart/form-data; boundary=—————————32356607229350» — тута количество тирешек должно быть на две меньше, чем ты будеш использовать далее.
к примеру: Bound := ‘—————————32356607229350’;
далее, заполняем переменную Params (или что ты там используеш):
params := ‘—‘ + Bound + CR.
Обрати внимание, я когда заполняю текст запроса, добавляю еще две тирешки ;).
З.Ы. Сам в свое время бился с этим. Еще конечно погляну, может еще чего найду
Mifody, вообще-то не обязательно в заголовке кучу тире лепить — достаточно в теле документа их правильно расставлять ;)
а я и не говорил, что обязательно ;). Я сказал «тута количество тирешек должно быть на две меньше, чем ты будеш использовать далее» :P. Просто за основу взял пример Александра.
403 код – доступ запрещен. Если отправляются 100% правильные данные, то проблема может быть в куках, адресе на который идет отправка, кодировке данных, если есть авторизация – в правильности отправки логина и пароля. Когда экспериментировал я побывал отправить POST 1 в 1 совпадающий с тем, что сервер успешно принял с браузера. От меня он принимать из не хочет. Кодировка соответственно тоже совпадает (хотя я могу ошибаться! Может сниффер автоматом подгоняет кодировку перед отображением! Как можно проверить в правильной-ли кодировке передаю данные?). Авторизация правильная, иначе дело бы не дошло до мультипартовского запроса. Mifody, не думаю, что проблема в тирашках! Об их… Подробнее »
Александр. На первойм этапе работы с сайтом я бы не советовал менять, что-либо в процессе обмена данными между сервером и клиентом. Если использует GZip при обмене — и вы используйте, Кодировку можно проверить:
1. По заголовкам сервера
2. Попробовать «угадать» с помощью MLang
3. По контенту на сайте, но не факт, что эта кодировка подойдет под отправку сообщения. Был у меня сайт один на бесплатном хостинге — БД в кодировке UTF-8, а весь контент сайта в Win-1251 :)
Александр. На первойм этапе работы с сайтом я бы не советовал менять, что-либо в процессе обмена данными между сервером и клиентом. Если использует GZip при обмене – и вы используйте Сделал, как Вы советуете. Научил программу работать с GZip.Но ничего не изменилось. 3. По контенту на сайте, но не факт, что эта кодировка подойдет под отправку сообщения. Кодировка на сайте и в базе вроде бы совпадает. Иначе бы сниффер выводил коверконную кириллицу, но со стороны браузера и сайта всё Ок. Гляньте, пожалуйста, кто-нибудь мой лог HTTP Analyzer V6: http://otdahni.ru/go/PostE1analyzer_v6.halog (обновил). Могу представить в любой другой форме. Это мой первый мультипартовский… Подробнее »
Александр, Ок. Гляну
Проблему это не решит, но всеж: «Блин, да удалите уже из заголовка две лишние тирешки!!!». Чес слово, щас проверил на рабочем спамере — если количество совпадает, сервак не принял сообщение. Просто потом будет ведь биться еще и с самим запросом
Уважаемый Mifody Вы оказались абсолютно правы. Дело было именно в тире. Думаю, что Владу стоит упоминать об этой мелочи в статье. Из-за неё я потратил 3 дня своей жизни в пустую :-))
Ещё раз БОЛЬШОЕ СПАСИБО всем за помощь.
Думаю, что достаточно будет комментариев — они будут не менее ценны, чем пост после индексации в поисковиках. Писать пост на основании двух тире в заголовке как-то, по-моему, не серьезно :)
фот фот, а я ведь еще в самом начале про это говорил :))))
А Влад про это писал, тока он не не говорил «обратите внимание», глазки вверх, и по внимательнее на пример ;). Я ведь практически тож самое и повторил :).
[…] заданными ресурсами в Сети, например, как в статье «Synapse. Отправка данных на сервер на примере imagevenue.com.«, т.к. такие примеры могут быть хороши в виде […]
Всем привет, хочу замутить такое для фотохостинга http://picmasa.com/ вот мои наработки по вашему примеру Const CRLF = #$0a + #$0d; var HTTP: THTTPSend; s: AnsiString; FS: TFileStream; Resp: TStringList; bound:string; begin try Resp := TStringList.Create; HTTP := THTTPSend.Create; bound:='----WebKitFormBoundary'+IntToHex(random(65535), 8); FS:=TFileStream.Create('image.jpg', fmOpenRead); HTTP.MimeType := 'multipart/form-data; boundary='+bound; { Записываем Mime-тип и данные по файлу } s := bound + CRLF + 'Content-Disposition: form-data; name="file[]"; filename="image.jpg"' + CRLF + 'Content-Type: image/jpeg' + CRLF + CRLF; HTTP.Document.Write(PAnsiChar(s)^, Length(s)); FS.Position := 0; // записываем файл в тело документа HTTP.Document.CopyFrom(FS, FS.Size); { завершаем тело запроса } s := CRLF + bound + CRLF; HTTP.Document.Write(PAnsiChar(s)^, Length(s));… Подробнее »
Автор! У Вас ошибка в CRLF = #$0a + #$0d;
ПРАВИЛЬНО: CRLF = #$0d+#$0a;
Возврат каретки (англ. carriage return, CR) — управляющий символ ASCII (0x0D)
Иначе Вашим кодом не загрузить.
Спасибо, исправил