уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

В большинстве случаев, при рассмотрении в блоге вопросов использования библиотеки Synapse, а именно при работе с THTTPSend я ограничивался тем, что рассказывал как получать какие-либо данные из Сети, например, текст страницы или, на крайний случай, правильно работать с веб-формами, когда надо было залогинится на сайте или отправить на сервер какой-нибудь текст. Но между тем, иногда может потребоваться и отправка более “весомых” данных, чем простой текст, например, загрузить картинку на фотохостинг.

В качестве примера рассмотрим работу с сайтом imagevenue.com.

Итак, повторение – мать учения. Начнем по-порядку — с анализа сайта.

Во-первых, ищем страничку с которой можно хостить свои картинки через простую вею-форму. Такая страничка имеется и её URL — http://imagevenue.com/host.php. Заходим на страничку и начинаем анализировать заголовки сервера. Как анализировать заголовки я рассказывал как минимум дважды – здесь и здесь. Я буду использовать в сниффер для FireFox – HTTP Live Headers.

выбираем какую-нибудь картинку у себя на компьютере, указываем содержимое картинки и отправляем файл на сервер:

send

После отправки внимательно изучаем заголовки сервера. Необходимо найти POST-запрос в котором отправляется картинка на сервер. На рисунке ниже представлен этот самый запрос, а также выделены заголовки, на которые необходимо обратить внимание:

send_2

Какие выводы можно сделать, судя по заголовкам:

  1. Запрос необходимо отправлять на URL http://imagevenue.com/upload.php (см. путь в первой строке после “POST”)
  2. В запросе множественное содержимое, т.е. тело запроса может состоять из нескольких частей, разделенных границей. Этот вывод мы делаем, исходя из данных во второй выделенной строке: multipart/form-data – указывает на множественное содержимое, всё, что содержится после “boundary=” – разделитель для частей тела запроса.
  3. Каждая часть должна сдержать по два заголовка: первый заголовок 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 и др.
купить книгу delphi на ЛитРес
0 0 голоса
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
35 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
Chrome~
Chrome~
16/06/2011 18:12

Спасибо за статью.
Давно уже спросить хотел. Vlad, используете ли вы GZip компрессию при работе с Synapse?

xpert13
xpert13
17/06/2011 03:29

Всё как-то очень сложно для такой простой операции. Неужели в 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]

xpert13
xpert13
17/06/2011 03:30

Предыдущий код парсер на сайте немного покорежил, но смысл уловить всё равно можно.

Mifody
Mifody
24/06/2011 13:10

Ну раз уж про ZGip проговорили, если надо — могу подсказать. Я пользовал в своих грабилках частенько.
Vlad, насчет HttpPostBinary — так ведь он отправляет Application/octet-stream а не multipart/form-data, думается для заливки картинок не подойдет ;).
xpert13, вообще да, если есть желание использовать часто multipart/form-data, то лучше озадачиться написанием собственной обертки. Синапс (на мой не супер продвинутый взгляд) относительно простенькая надстройка над сокетами. В принципе из-за этого он мне и понравился :). Действительно, некоторые вещи в нем намного более громоздкие чем в Indy или ICS, но надежность….
 

Chrome~
Chrome~
25/06/2011 23:13

Да, Synapse супер. Только мне в нем тоже подобная «громоздкость» также не очень нравиться. Делал обертку над THTTPSend раньше, только не полностью закончил ее.
Mifody, я уже когда то разбирался с GZip, но хотел бы услышать также твой вариант решения.

Mifody
Mifody
27/06/2011 13:09

Сама либа http://www.base2ti.com (zlib). Конкретно что именно (какие юниты) требуются не разбирался, там в том числе и для С++. Обычно в uses добавляю ZLibExGZ и пользую GZDecompressStream. Синапсовый документ конверчу. В принципе у меня конструкция такая — GZDecompressStream(HTTP.Document, BodyStream); BodyStream : TStringStream, из BodyStream потом уже текст выдираю. Подозреваю, что можно намного красивше :). Vlad, напишеш статейку — будет просто супер, а то я в свой блог не часто пишу (времени, блин, нету), пока сваяю… Chrome~ — обертки это конечно супер, только это, на мой взгляд не очень хорошо. Просто написать правильную обертку — потерять кучу времени и реализовать половину… Подробнее »

Mifody
Mifody
27/06/2011 15:38

сваял корявенькую статейку :) http://www.smw75.ru/delphi-synapse-poluchaem-dannye-v-gzip/, Vlad, жду от тебя нормальной. У тебя намного красивше получается писать :)

Chrome~
Chrome~
27/06/2011 17:22

Mifody, да, тоже разбирался с ZLib. На самом деле, это идеальный вариант для решение вопроса с данными, сжатыми с помощью GZip.

Mifody
Mifody
27/06/2011 17:42

Vlad, Chrome~ кстати, может кто из вас подскажет почему мейловский (mail.ru) gzip не распаковывается.
Со своей стороны я вижу только одно решение — влезать в структуру данных, искать там косяк. Первое что нашел — не полный структура заголовка

Mifody
Mifody
27/06/2011 18:39

эээ, Влад, а что, была проблема с API Twitter + Synapse? Хм, у меня связка работает, правда не совсем API, у меня спамерская направленность :). Но и авторизация, и постинг, и урезанный API работает на ура. Если была проблема с авторизацией по протоколу HTTPS, то там есть одна заковырка. Но эт лучше через личку — шоб спамеры по меньше знали :)

Mifody
Mifody
27/06/2011 18:49

ааа, тогда не то, что я подумал :).
З.Ы. Влад, приближаемся к рекорду комментов на статью ? :))))

Александр
14/07/2011 23:28

 
Влад, помоги!
Очень нужна твоя помощь. :(
Уже 2 дня пытаюсь отправить объявление на сайт e1.ru
Данные отправляются с мультипаровским содержанием. Отправляемые мною данные 100% совпадают с теми, что отправлял через сайт, но они почему-то отвергаются сервером по году 403!!!
 
Прилагаю лог сделанный в HTTP Analyzer V6: http://otdahni.ru/go/PostE1analyzer_v6.halog
В нём отправка новости через браузер и через мою программу.

Mifody
Mifody
15/07/2011 13:41

Александр, хоть я и не Vlad, но всеж. Сразу говорю
«Content-Type: multipart/form-data; boundary=—————————32356607229350» — тута количество тирешек должно быть на две меньше, чем ты будеш использовать далее.
к примеру: Bound := ‘—————————32356607229350’;
далее, заполняем переменную Params (или что ты там используеш):
params := ‘—‘ + Bound + CR.
Обрати внимание, я когда заполняю текст запроса, добавляю еще две тирешки ;).
З.Ы. Сам в свое время бился с этим. Еще конечно погляну, может еще чего найду

Mifody
Mifody
15/07/2011 14:27

а я и не говорил, что обязательно ;). Я сказал «тута количество тирешек должно быть на две меньше, чем ты будеш использовать далее» :P. Просто за основу взял пример Александра.

Александр
15/07/2011 15:12

403 код – доступ запрещен. Если отправляются 100% правильные данные, то проблема может быть в куках, адресе на который идет отправка, кодировке данных, если есть авторизация – в правильности отправки логина и пароля. Когда экспериментировал я побывал отправить POST 1 в 1 совпадающий с тем, что сервер успешно принял с браузера. От меня он принимать из не хочет. Кодировка соответственно тоже совпадает (хотя я могу ошибаться! Может сниффер автоматом подгоняет кодировку перед отображением! Как можно проверить в правильной-ли кодировке передаю данные?). Авторизация правильная, иначе дело бы не дошло до мультипартовского запроса. Mifody, не думаю, что проблема в тирашках! Об их… Подробнее »

Александр
15/07/2011 18:22

Александр. На первойм этапе работы с сайтом я бы не советовал менять, что-либо в процессе обмена данными между сервером и клиентом. Если использует GZip при обмене – и вы используйте Сделал, как Вы советуете. Научил программу работать с GZip.Но ничего не изменилось. 3. По контенту на сайте, но не факт, что эта кодировка подойдет под отправку сообщения. Кодировка на сайте и в базе вроде бы совпадает. Иначе бы сниффер выводил коверконную кириллицу, но со стороны браузера и сайта всё Ок. Гляньте, пожалуйста, кто-нибудь мой лог HTTP Analyzer V6: http://otdahni.ru/go/PostE1analyzer_v6.halog (обновил). Могу представить в любой другой форме. Это мой первый мультипартовский… Подробнее »

Mifody
Mifody
15/07/2011 18:46

Проблему это не решит, но всеж: «Блин, да удалите уже из заголовка две лишние тирешки!!!». Чес слово, щас проверил на рабочем спамере — если количество совпадает, сервак не принял сообщение. Просто потом будет ведь биться еще и с самим запросом

Александр
15/07/2011 20:23

Уважаемый Mifody Вы оказались абсолютно правы. Дело было именно в тире. Думаю, что Владу стоит упоминать об этой мелочи в статье. Из-за неё я потратил 3 дня своей жизни в пустую :-))
Ещё раз БОЛЬШОЕ СПАСИБО всем за помощь.

Mifody
Mifody
15/07/2011 20:30

фот фот, а я ведь еще в самом начале про это говорил :))))
А Влад про это писал, тока он не не говорил «обратите внимание», глазки вверх, и по внимательнее на пример ;). Я ведь практически тож самое и повторил :).

trackback

[…] заданными ресурсами в Сети, например, как в статье «Synapse. Отправка данных на сервер на примере imagevenue.com.«, т.к. такие примеры могут быть хороши в виде […]

Сергей
Сергей
24/08/2014 02:15

Всем привет, хочу замутить такое для фотохостинга 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));… Подробнее »

Sergey
Sergey
04/02/2018 17:50

Автор! У Вас ошибка в CRLF = #$0a + #$0d;

ПРАВИЛЬНО: CRLF = #$0d+#$0a;
Возврат каретки (англ. carriage return, CR) — управляющий символ ASCII (0x0D)

Иначе Вашим кодом не загрузить.