Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

HTML File Upload

Оригинал статьи: Delphi Web Development

На этой страничке вы узнаете как закачать файл на веб-сервер, используя HTML.

Перед тем как приступить к разработке, желательно ознакомиться с RFC1867: Form-based File Upload in HTML, в котором описаны форматы передачи файлов в HTML. 

Клиент

Чтобы закачать файл на сервер, Вам нужно использовать тэг FORM. Для передачи данных формы, обязательно, используется метод POST и устанавливается MIME media type: multipart/form-data.

Вы должны установить в тэг FORM атрибут ENCTYPE равным multipart/form-data, и в форму добавить элемент INPUT типа file.

Например, давайте создадим следующий HTML файл, назовем его upload.htm и положим его в корневой директорий веб сервера.

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
<title>DWD File Upload</title>
</head>

<body>
<form method="POST" action="/scripts/upload.dll" enctype="multipart/form-data">
<p><input type="checkbox" name="checkbox" value="ON">CheckBox Test
<p>RadioBox Test:
  <input type="radio" value="ON" checked name="radiobox">ON
  <input type="radio" value="OFF" name="radiobox">OFF
<p>TextBox Test:
  <input type="text" name="text" size="20">
<p>File2 Test:
  <input type="file" name="myfile1" size="20">
<p>File2 Test:
  <input type="file" name="myfile2" size="20">
<p>File2 Test:
  <input type="file" name="myfile3" size="20">
<p>
  <input type="submit" value="Submit">
  <input type="reset" value="Reset" name="B2">
</form>
</body>
</html>

Пример 1

Итак, мы создали форму которая содержит элементы checkbox, radio, text и три(!) элемента file.

Загрузите этот файл браузером, например, набирите http://localhost/upload.htm. Если, нажав кноку Browse на форме, выбрать файл 1: C:\image.gif, файл 2: C:\test.exe, файл 3: C:\text.txt и нажать кнопку Submit,  в соответствии с RFC1867 браузер должен сформировать и передать в заголовке HTTP поле

ContentType: multipart/form-data; boundary=--7d015813802c4

и примерно следующий контент:

----7d015813802c4
Content-Disposition: form-data; name="checkbox"

ON
----7d015813802c4
Content-Disposition: form-data; name="radiobox"

ON
----7d015813802c4
Content-Disposition: form-data; name="text"

...текст из элемента текст...
----7d015813802c4
Content-Disposition: form-data; name="myfile1"; filename="C:\image.gif"
Content-Type: image/gif

...содержимое файла 1...
----7d015813802c4
Content-Disposition: form-data; name="myfile2"; filename="C:\test.exe"
Content-Type: application/octet-stream

...содержимое файла 2...
----7d015813802c4
Content-Disposition: form-data; name="myfile3"; filename="C:\text.txt"
Content-Type: text/plain

...содержимое файла 3...
----7d015813802c4--

Пример 2

Где boundary (граница) - разделитель полей.

Сервер

На стороне сервера, для приема данных, мы будем использовать ISAPI приложение, которое сейчас и создадим.

Запускаем Delphi, создаем с помощью Delphi IDE новый проект (мы создали ISAPI DLL, вы можете создать как ISAPI, так и CGI) и создаем обработчик WebActionItem. Установим его свойство Default равным True.

Приступим к написанию кода. Создадим обработчик события OnAction, для созданного WebActionItem. Прежде всего нужно быть уверенным, что принимаемые данные действительно имеют ContentType: multipart/form-data:

procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
  if (Pos('multipart/form-data', LowerCase(Request.ContentType)) > 0) and
    (Pos('boundary', LowerCase(Request.ContentType)) > 0) then
  begin
  
    ...
  
  end
  else
    Response.Content := 'Content-type is not "multipart/form-data".';
end;

Пример 3

Определим boundary, для этого воспользуемся функцией ExtractHeaderFields:

...
var
  Boundary: string;
  Header: TStrings;
begin
  ...
  Header := TStringList.Create;
  try
    ExtractHeaderFields([';'], [' '], PChar(Request.ContentType), Header,
      False, False);
    Boundary := Header.Values['boundary'];
  finally
    Header.Free;
  end;
  ...

Пример 4

Для того, чтобы прочитать переданные клиентом данные, нужно прочитать свойство Content объекта Request. Следует сказать, что Request.Content не обязательно должен содержать все данные т.к. размер свойства Content ограничен, особенно актуально это для передачи файлов большого размера. Для того чтобы прочитать следующую порцию данных, используйте метод ReadClient объекта Request. Метод ReadClient читает данные в буфер. В качесте буфера используем PChar. Определить размер данных, которые необходимо считать можно так:

AllContent := Request.Content;
ByteToRead := Request.ContentLength - Length(AllContent);
Далее следует зарезервировать память для буфера, для того чтобы экономнее расходовать память, ограничим максимальный размер буфера константой MaxReadBlockSize. Определим весь поток данных, переданный клиентом:
const
  MaxReadBlockSize = 8192;

...

var
  Boundary, BufferStr, AllContent: string;
  Header: TStrings;
  ByteToRead, ReadedBytes, RSize: Integer;
  Buffer: PChar;
begin
  ...
  AllContent := Request.Content;
  ByteToRead := Request.ContentLength - Length(AllContent);
  try
    while ByteToRead > 0 do
    begin
      RSize := MaxReadBlockSize;
      if RSize > ByteToRead then
        RSize := ByteToRead;
      GetMem(Buffer, RSize);
      try
        ReadedBytes := Request.ReadClient(Buffer^, RSize);
        SetString(BufferStr, Buffer, ReadedBytes);
        AllContent := AllContent + BufferStr;
      finally
        FreeMem(Buffer, RSize);
      end;
      ByteToRead := Request.ContentLength - Length(AllContent);
    end;
    // и возвратим клиенту справочную информацию
    Response.Content := Response.Content + Format('ContentType = %s<br>' +
      'ContentLength = %d<br>Readed Bytes = %d<br>Boundary = %s<hr>',
      [Request.ContentType, Request.ContentLength, Length(AllContent),
      boundary]);

    ...

  except
    on E: Exception do
      Response.Content := Response.Content + '<p>' + E.Message;
  end;
  ...

Пример 5

Итог: мы прочитали весь переданный контент и присвоили его переменной AllContent, а также узнали разделитель Boundary.

Следующим этапом необходимо полученный контент разобрать по частям. Для этого напишем функцию 

ReadMultipartRequest(const Boundary: string;
ARequest: string; var AHeader: TStrings; var Data: string): string;

которая будет запрашивать разделитель Boundary и AllContent, и возвращать поля заголовка в AHeader и содержимое поля в Data. Результатом выполнения функции будет передан AllContent без первого поля формы. Например, после первой обработки данных, показанных в Примере 2, результат возвращаемый функцией будет:

----7d015813802c4
Content-Disposition: form-data; name="radiobox"

ON
----7d015813802c4
Content-Disposition: form-data; name="text"

...текст из элемента текст...
----7d015813802c4
Content-Disposition: form-data; name="myfile1"; filename="C:\image.gif"
Content-Type: image/gif

...содержимое файла 1...
----7d015813802c4
Content-Disposition: form-data; name="myfile2"; filename="C:\test.exe"
Content-Type: application/octet-stream

...содержимое файла 2...
----7d015813802c4
Content-Disposition: form-data; name="myfile3"; filename="C:\text.txt"
Content-Type: text/plain

...содержимое файла 3...
----7d015813802c4--

Пример 6

объект AHeader будет содержать:

Content-Disposition=form-data; name="checkbox"

а переменная Data = 'ON'.

После четвертой обработки будет: 

----7d015813802c4
Content-Disposition: form-data; name="myfile2"; filename="C:\test.exe"
Content-Type: application/octet-stream

...содержимое файла 2...
----7d015813802c4
Content-Disposition: form-data; name="myfile3"; filename="C:\text.txt"
Content-Type: text/plain

...содержимое файла 3...
----7d015813802c4--

Пример 7

объект AHeader будет содержать:

Content-Disposition=form-data; name="myfile1"; filename="C:\image.gif"
Content-Type=image/gif

а переменная Data = ...содержимое файла 1....

Код функции приведен в Примере 8:

function TWebModule1.ReadMultipartRequest(const Boundary: string;
  ARequest: string; var AHeader: TStrings; var Data: string): string;
var
  Req, RHead: string;
  i: Integer;
begin
  Result := '';
  AHeader.Clear;
  Data := '';
  if (Pos(Boundary, ARequest) < Pos (Boundary + '--', ARequest))
    and (Pos(Boundary, ARequest) = 1) then
  begin
    Delete(ARequest, 1, Length(Boundary) + 2);
    Req := Copy(ARequest, 1, Pos(Boundary, ARequest) - 3);
    Delete(ARequest, 1, Length(Req) + 2);
    RHead := Copy(Req, 1, Pos(#13#10#13#10, Req)-1);
    Delete(Req, 1, Length(RHead) + 4);
    AHeader.Text := RHead;
    for i := 0 to AHeader.Count - 1 do
      if Pos(':', AHeader.Strings[i]) > 0 then
        AHeader.Strings[i] := Trim(Copy(AHeader.Strings[i], 1,
          Pos(':', AHeader.Strings[i])-1)) + '=' + Trim(Copy(AHeader.Strings[i],
          Pos(':', AHeader.Strings[i])+1, Length(AHeader.Strings[i]) -
          Pos(':', AHeader.Strings[i])));
    Data := Req;
    Result := ARequest;
  end
end;

Пример 8

Теперь осталось только вызвать данную процедуру в цикле и сохранить данные на диск.

Определим константу UploadPath, которая будет содержать путь к папке, в которую будем сохранять принятые файлы.

const
  UploadPath = 'C:\temp\upload\';

...

var
  AllContent, Boundary, Data: string;
  Header, HList: TStrings;
  OutStream: TFileStream;
begin
  ...
  if Request.ContentLength = Length(AllContent) then
    while Length(AllContent) > Length('--' + Boundary + '--' + #13#10) do
    begin
      Header := TStringList.Create;
      HList := TStringList.Create;
      try
        AllContent := ReadMultipartRequest('--' + Boundary, AllContent,
          Header, Data);
        ExtractHeaderFields([';'], [' '],
         PChar(Header.Values['Content-Disposition']), HList, False, True);
        if (Header.Values['Content-Type'] <> '') and (Data <> '') then
        begin
          OutStream:=TFileStream.Create(UploadPath +
            ExtractFileName(HList.Values['filename']), fmCreate);
          try
            try
              OutStream.WriteBuffer(Pointer(Data)^, Length(Data));
              Response.Content := Response.Content +
                Format('<p>File <b>%s</b> saved.',
                [ExtractFileName(HList.Values['filename'])]);
            except
              Response.Content := Response.Content +
                Format('<p>Unable to save file <b>%s</b>.', [UploadPath +
                ExtractFileName(HList.Values['filename'])]);
            end;
          finally
            OutStream.Free;
          end
        end
        else
          Response.Content := Response.Content + Format('<p>Field <b>%s</b> = %s',
            [HList.Values['name'], Data]);
      finally
        Header.Free;
        HList.Free;
      end;
    end;
    ...

Пример 9

В результате выполнения данного блока программы, поля, которые содержат файл, будут сохранены на диск в папку заданную константой UploadPath, а поля, не содержащие файл будут показаны в результате выполнения скрипта, имя поля и значение будут переданы клиенту.

Оставить комментарий

Комментарий:
можно использовать BB-коды
Максимальная длина комментария - 4000 символов.
 

Комментарии

1.
91K
30 июля 2013 года
Виталий
0 / / 30.07.2013
+3 / -1
Мне нравитсяМне не нравится
30 июля 2013, 10:25:30
а готовую рабочую функцию можно? ато честно говоря не очень понятно, какой блок кода куда вставляется
1.1.
91K
30 июля 2013 года
Виталий
0 / / 30.07.2013
+3 / -0
Мне нравитсяМне не нравится
30 июля 2013, 10:56:37
уже не надо, сам сделал. ещё добавил ограничение в 1048576 байт (10мб)
Код:
procedure TWebModule1.WebModule1UploadFileAction(Sender: TObject;Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);

  var UploadPath,AllContent,Boundary,Data:string;Header,HList:TStrings;OutStream:TFileStream;ByteToRead:Integer;



function ReadMultipartRequest(const Boundary:string;ARequest:string;var AHeader:TStrings;var Data:string):string;

  var Req,RHead:string;i:Integer;begin Result:='';AHeader.Clear;Data:='';

  if (Pos(Boundary,ARequest)<Pos(Boundary+'--',ARequest)) and (Pos(Boundary,ARequest)=1) then

    begin

      Delete(ARequest,1,Length(Boundary)+2);Req:=Copy(ARequest,1,Pos(Boundary,ARequest)-3);

      Delete(ARequest,1,Length(Req)+2);RHead:=Copy(Req,1,Pos(#13#10#13#10,Req)-1);

      Delete(Req,1,Length(RHead)+4);AHeader.Text:=RHead;

      for i:=0 to AHeader.Count-1 do if Pos(':',AHeader.Strings)>0 then

        AHeader.Strings:=Trim(Copy(AHeader.Strings,1,Pos(':',AHeader.Strings)-1))+'='+Trim(Copy(AHeader.Strings,Pos(':',AHeader.Strings)+1,Length(AHeader.Strings)-Pos(':',AHeader.Strings)));

        Data := Req;Result := ARequest;

      end;

    end;



  begin

    UploadPath:=dirpath+'uploads\'+UserDomain+'\';CreateDir(UploadPath);AllContent := Request.Content;ByteToRead := Request.ContentLength - Length(AllContent);

    if Length(AllContent)>10485760 then begin Response.Content:='File too long';exit;

  end;



  if Request.ContentLength = Length(AllContent) then while Length(AllContent) > Length('--' + Boundary + '--' + #13#10)do

    begin

      Header := TStringList.Create; try ExtractHeaderFields([';'],[' '],PChar(Request.ContentType),Header,False,False);Boundary:=Header.Values['boundary'];

      except end;

    HList := TStringList.Create;try AllContent:=ReadMultipartRequest('--'+Boundary,AllContent,Header,Data);ExtractHeaderFields([';'],[' '],PChar(Header.Values['Content-Disposition']),HList,False,True);

    if (Header.Values['Content-Type']<>'') and (Data<>'') then begin OutStream:=TFileStream.Create(UploadPath+UTF8toANSI(ExtractFileName(HList.Values['filename'])),fmCreate);

    try try OutStream.WriteBuffer(Pointer(Data)^,Length(Data));Response.Content:='fs';except Response.Content:='File save error';end;finally OutStream.Free;end;

      end

      else Response.Content := Response.Content + Format('<p>Field <b>%s</b> = %s', [HList.Values['name'], Data]);finally Header.Free;HList.Free;end;

    end;

  if response.Content='fs' then Response.SendRedirect(Request.Referer);

end;

[/code]
1.1.1.
91K
30 июля 2013 года
Виталий
0 / / 30.07.2013
+5 / -0
Мне нравитсяМне не нравится
30 июля 2013, 11:01:22
UploadPath:=dirpath+'uploads\'+UserDomain+'\';CreateDir(UploadPath); здесь
dirpath - это каталог с вебсайтом, типа c:\webserver\
userdomain - Это подкаталог с доменным именем типа mysite2.ru
соответственно, эта строка кода создаёт подкаталоги для многодоменных систем (у меня на одном cgi.exe 6 сайтов крутится :)
2.
32K
13 сентября 2007 года
XYHHY
0 / / 13.09.2007
+1 / -0
Мне нравитсяМне не нравится
13 сентября 2007, 12:44:39
How can I post data to server from Delphi?

NMHTTP1.Post('http://localhost/createfolder.php','userfolder=newfolder&todo=mkdir');

This code working but the format of PostData is wrong.

PS : This createfolder.php working correctly when I post data from html form.

Help me.
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог