•  

ГлавнаяIndyЧастые вопросы по Indy → загрузить изображение на сайт

Создано: 17.05.2014 1:18:11 · Исправлено: 17.05.2014 1:18:11 · Прочтений: 813

Товарищи, есть такая задача - загрузить изображение на сайт. Если все делать через IE, то на сайте отображается поле для указания имени файла + кнопка Загрузить. (поле имени файла кодируется как
). При нажатии кнопки Загрузить на сайт отправляется POST-запрос: POST /upload.php HTTP/1.1 Host: somesite.ru User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.8.1.9) Gecko/20071025 Firefox/2.0.0.9 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-Language: ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3 Accept-Encoding: gzip,deflate Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Content-Type: multipart/form-data; boundary=---------------------------30742771025321 Content-Length: 3892 -----------------------------30742771025321 Content-Disposition: form-data; name=subm 1 -----------------------------30742771025321 Content-Disposition: form-data; name=photo; filename=0.jpg Content-Type: image/jpeg яШяа ... Вопрос заключается в том, как повторить данный запрос на Дельфи (но без использования indy) - у меня данный запрос будет отправляться из Миранды (с использованием службы MS_NETLIB_HTTPTRANSACTION), потому доступны следующие элементы в запросе: - тип запроса (GET, POST, HEAD) - версия запроса (HTTP 1.1) - headerы
ну так и отправляйте. составляйте POST заголовок и MIME расширение и отсылайте хоть сырыми сокетами. Обязательно наличие следующих полей в POST запросе: POST /upload.php HTTP/1.1 Host: somesite.ru Content-Type: multipart/form-data; boundary=//тут ограничитель Content-Length: 3892 к примеру отсылаемые через POST данные могут быть такими: POST /upload.php HTTP/1.1 Host: somesite.ru Content-Type: multipart/form-data; boundary=---30742771025321 Content-Length: //тут должна быть длина MIME расширения в байтах -----30742771025321 Content-Disposition: form-data; name=photo; filename=0.jpg Content-Type: image/jpeg //тут бинарные данные файла -----30742771025321-- Этого будет достаточно. Обращу ваше внимание на наличие двух черточек вначале boundary и в конце в MIME расширении, переводы строки также значимы.
Видимо, я чего-то не знаю, потому мне все равно не понятно, как отправить файл. Где сам файл должен передаваться? В теле запроса? Также, как передаются данные формы, например?
да. откройте файл скажем через TFileStream и поместите его содержимое в месте, где у меня стоит комментарий //тут бинарные данные файла
можете попрактиковаться скажем на таком исходнике:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ScktComp;

type
  TForm1 = class(TForm)
    ClientSocket: TClientSocket;
    Memo: TMemo;
    Button: TButton;
    Memo2: TMemo;
    Button2: TButton;
    Button3: TButton;
    procedure ButtonClick(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure ClientSocketConnect(Sender: TObject; Socket: TCustomWinSocket);
    procedure ClientSocketDisconnect(Sender: TObject; Socket: TCustomWinSocket);
    procedure ClientSocketError(Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
      var ErrorCode: Integer);
    procedure ClientSocketRead(Sender: TObject; Socket: TCustomWinSocket);
    procedure Button3Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button2Click(Sender: TObject);
begin
  ClientSocket.Active := True;
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  ClientSocket.Active := False;
end;

procedure TForm1.ButtonClick(Sender: TObject);
begin
  ClientSocket.Socket.SendText(Memo.text);
end;

procedure TForm1.ClientSocketConnect(Sender: TObject; Socket: TCustomWinSocket);
begin
  Memo2.Lines.Add(Connect);
end;

procedure TForm1.ClientSocketDisconnect(Sender: TObject; Socket: TCustomWinSocket);
begin
  Memo2.Lines.Add(Disconnect);
end;

procedure TForm1.ClientSocketError(Sender: TObject; Socket: TCustomWinSocket;
  ErrorEvent: TErrorEvent; var ErrorCode: Integer);
begin
  Memo2.Lines.Add(Error);
  ErrorCode := 0;
end;

procedure TForm1.ClientSocketRead(Sender: TObject; Socket: TCustomWinSocket);
begin
  Memo2.Lines.Add(Socket.ReceiveText);
end;

end.

//dfm
object Form24: TForm24
  Left = 0
  Top = 0
  Caption = Form24
  ClientHeight = 523
  ClientWidth = 866
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = Tahoma
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object Memo: TMemo
    Left = 8
    Top = 8
    Width = 737
    Height = 321
    Lines.Strings = (
      
        POST /upload.php HTTP/1 +
        .1
      Host: somehost.ru:80
      Connection: keep-alive
      Content-Type: multipart/form-data; boundary=---30742771025321
      Content-Length: 902
      
      -----30742771025321
      Content-Disposition: form-data; name=photo; filename=0.jpg
      Content-Type: image/jpeg
      
      
        testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest        
      -----30742771025321--)
    ScrollBars = ssVertical
    TabOrder = 0
  end
  object Button: TButton
    Left = 751
    Top = 8
    Width = 75
    Height = 25
    Caption = Send
    TabOrder = 1
    OnClick = ButtonClick
  end
  object Memo2: TMemo
    Left = 8
    Top = 344
    Width = 737
    Height = 153
    Lines.Strings = (
      Memo2)
    ScrollBars = ssVertical
    TabOrder = 2
  end
  object Button2: TButton
    Left = 752
    Top = 48
    Width = 75
    Height = 25
    Caption = Connect
    TabOrder = 3
    OnClick = Button2Click
  end
  object Button3: TButton
    Left = 752
    Top = 88
    Width = 75
    Height = 25
    Caption = Off
    TabOrder = 4
    OnClick = Button3Click
  end
  object ClientSocket: TClientSocket
    Active = False
    ClientType = ctNonBlocking
    Host = somehost.ru
    Port = 80
    OnConnect = ClientSocketConnect
    OnDisconnect = ClientSocketDisconnect
    OnRead = ClientSocketRead
    OnError = ClientSocketError
    Left = 32
    Top = 48
  end
end
и вставлять в Memo1 отсылаемый текст. Принимаемы с сервера текст должен прийти в Memo2. Просто оформленный заголовок MIME нужны для серверной части;)
т.е. можно открыть jpeg-файл Farом, например, скопировать его текст в Memo и запустить отправку? и что это за цифры - 30742771025321?
да, можно. цифры 30742771025321 с черточками - это так называемый boundary, то есть разделитель. это может быть любой разделитель, почт из любых ASCII символов, главное чтобы эта последовательность не встречалась в файле. Разделитель нужен для сервера, чтобы он определил границы файла.
Для Миранды необходимо сформировать запрос вида:
TNETLIBHTTPREQUEST = record
    cbSize      :int;
    requestType  :int;              // REQUEST_* constant
    flags        :DWORD;
    szUrl        :PChar;
    { doesnt contain Content-Length, itll be added automatically }
    headers      :array of TNETLIBHTTPHEADER;
    headersCount :int;              // yes they do
    pData        :PChar;            // data to be sent on POST request
    dataLength  :int;              // must be 0 for REQUEST_GET/REQUEST_CONNECT
    resultCode  :int;
    szResultDescr:PChar;
    nlc          :THANDLE;
  end;
Соответственно, я пишу:
var nlhr: TNETLIBHTTPREQUEST;
    szData: PChar;
    MyFile: TFileStream;
...
MyFile := TFileStream.Create(D:\file.jpg, fmOpenRead);
GetMem(szData, MyFile.Size);
MyFile.Read(szData, MyFile.Size);

FillChar(nlhr, sizeof(nlhr), 0);

// initialize the netlib request
nlhr.cbSize := sizeof(nlhr);
nlhr.requestType := REQUEST_POST;
nlhr.flags := NLHRF_DUMPASTEXT or NLHRF_HTTP11;
nlhr.szUrl := PChar(szUrl);
nlhr.pData := szData;
nlhr.dataLength := Length(szData);
// headers
nlhr.headersCount := 5;
SetLength(nlhr.headers, 5;
nlhr.headers[0].szName  := User-Agent;
nlhr.headers[0].szValue := Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1);
nlhr.headers[1].szName  := Connection;
nlhr.headers[1].szValue := Keep-Alive;
nlhr.headers[2].szName  := Cache-Control;
nlhr.headers[2].szValue := no-cache;
nlhr.headers[3].szName  := Pragma;
nlhr.headers[3].szValue := no-cache;
nlhr.headers[4].szName  := Cookie;
nlhr.headers[4].szValue := MyCookies;
// следующий код вроде не нужен, т.к. миранда добавит его сама
// nlhr.headers[5].szName  := Content-Length;
// nlhr.headers[5].szValue := PChar(IntToStr(nlhr.dataLength));

FreeMem(szData);
где ошибка?
Вы невнимательно прочли что я вам написал. забыли про Content-Type=multipart/form-data; boundary=xxxxxxx. А также нужно в точности описать MIME по стандарту RFC.
Естественно, я обратил на это внимание. Просто смотрел исходный код другого плагина для Миранды (http://code.google.com/p/mirandaimplugins/source/browse/trunk/crshdmp/upload.cpp), который тоже отсылает файл на сервер с использованием request_post и там такие данные в headers не добавляются - я предположил, что сервисы миранды сами добавляют такие данные. Попробую тогда добавить их вручную. Тогда такой вопрос - правильно ли я считываю данные из файла-картинки в szData? Мне потом в этот szData надо будет в конец дописать значение boundary, верно? Кстати, строки >>> Content-Disposition: form-data; name=photo; filename=0.jpg >>> Content-Type: image/jpeg тоже нужно в szData вписать?
Тогда такой вопрос - правильно ли я считываю данные из файла-картинки в szData? Мне потом в этот szData надо будет в конец дописать значение boundary, верно? В целом верно, только нужно еще и в начале строки , и в конце также задать boundary + Content-Disposition как минимум. Кстати, строки >>> Content-Disposition: form-data; name=photo; filename=0.jpg >>> Content-Type: image/jpeg тоже нужно в szData вписать? Зависит от сервера. Некоторые жестко требуют Content-Type, некоторым все равно. Content-Disposition нужен, поскольку в header будет указано multipart/form-data. Писать в szData, а не в header Это самый простой и безболезненный способ загрузить данные - притвориться веб-формой и заливать через POST. Есть и другие способы (управляется полем Content-Type в header) но они более геморные. З.Ы. Есть стандарты RFC на содержимое headerов и MIME. Если вам интересно, посетите разделы в www.w3c.org, посвященные POST запросам и MIME расширениям
Долго мучался, а оказалось, что в строках Content-Type: image/jpeg testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest перенос строки должен задаваться символом #10 (а не #13 и не #10#13).