Soap web srvices отдать на скачиваение файл с названием на русском языке

Веб сервис написанный при помощи на Delphi может отдавать на скачивание файл по запросу. При этом у пользователя в браузере будет открыто диалоговое окно с кнопками «Открыть», «Сохранить», «Отмена». Как это обычно бывает при загрузке файла через локальную сеть или интернет.

Примеров создания самого веб сервиса на Delphi достаточно большое количество в данной статье рассматриваться не будет. Так же, не мало примеров создания функционала отдачи файла на скачивание. Основная трудность при создании такого веб сервиса – это передача имени файла при запросе его пользователем. Если имя файла не записано латинскими буквами (русскими или в другой национальной кодировке), то на выходе оно будет отображено либо непонятными символами, либо знаками подчеркивания. Все потому, что согласно стандартам передачи данных http://tools.ietf.org/html/rfc2183 имя файла должно быть в кодировке UA_ANSII, что предполагает написание имени файла только латиницей. На просторах интернета часто советуют в этом случае не пользоваться русскими буквами при написании имени файла или транслитерировать имя файла. Ни первый, ни второй способ не подходит для практического применения.

К счастью браузеры при отображении диалогового окна могут проявлять «интеллектуальность» самостоятельно определять кодировку имени файла, который предстоит сохранить. Однако каждый из них делает это согласно своим правилам.

Mozilla «понимает» воспринимает в качестве имени файла вот такую вот конструкцию “=?UTF-8?B?Filename?=”, где имя файла Filename в кодировке UTF-8 и зашифровано в Base64, для удобства конвертация вынесена в отдельную процедуру UnicodeToUTF8Base64 . При этом параметр UTF-8 может быть заменен на любую другую кодировку, и соответственно должна быть изменена кодировка Filename. Желательно брать в двойные кавычки всю конструкцию с именем файла.

Internet Explorer «понимает» имя файла, которое будет в кодировке UTF8, но при этом сама кодировка представлена своим 16-тиричным представлением разделенном знаком «%» для подобной пере конвертации необходимо было написать отдельную процедуру urlEncode2, код которой будет приведен ниже. При этом данное представление имени файла перестают понимать другие браузеры http://blogs.msdn.com/b/ieinternals/archive/2010/06/07/content-disposition-attachment-and-international-unicode-characters.aspx?PageIndex=2 . Подобное предоставление имени скачиваемого файла работает в Internet Explore 8 (IE8), Internet Explore 9 (IE9), Internet Explore 10 (EI 10), Internet Explore 11 (IE 11).

Opera последней версии «поняла», то что понимает Mozilla, однако сразу загружала файл. Возможно это настройки самого браузера.

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

В связи с этим встал вопрос об определении типа браузера. Благо Request.UserAgent дает об этом информацию. Код ниже показывает вариант как это можно сделать.


if (Pos(Uppercase('MSIE'), Uppercase(Request.UserAgent)) > 0)

or (Pos(Uppercase('Trident'), Uppercase(Request.UserAgent)) > 0)  then begin

  // IE

  SetCustomHeader('Content-Disposition', 'attachment;filename="'+(urlEncode2('Черновик.docx'))+'"')

end

else begin

  // Mozilla Opera

  SetCustomHeader('Content-Disposition','attachment;filename="=?UTF-8?B?'+UnicodeToUTF8Base64('Черновик.docx')+'?="');

end;

Пример кода для загрузки файла на сервере. Он реализуется в TWebModule был создан для реализации веб сервиса. В этом же куске кода находиться описание процедур urlEncode2 и UnicodeToUTF8Base64. В процедуре urlEncode2 существенным является то, что при конвертации пробелы стоит заменять символами %20, поскольку в UTF8 есть два «пробела», короткий и длинный, потому стоит сразу определиться, что при конвертации будет использоваться короткий пробел.

unit wmFileDownloader;

interface

uses System.SysUtils, System.Classes, Web.HTTPApp,
  Soap.InvokeRegistry, Soap.WSDLIntf, System.TypInfo, Soap.WebServExp,
  Soap.WSDLBind, Xml.XMLSchema, Soap.WSDLPub, Soap.SOAPPasInv, windows,
  Soap.SOAPHTTPPasInv, Soap.SOAPHTTPDisp, Soap.WebBrokerSOAP, EncdDecd;

type
  TwmdFileDownloader = class(TWebModule)
    HTTPSoapDispatcherServiceDesk: THTTPSoapDispatcher;
    HTTPSoapPascalInvokerServiceDesk: THTTPSoapPascalInvoker;
    WSDLHTMLPublishServiceDesk: TWSDLHTMLPublish;
    procedure WebModule1DefaultHandlerAction(Sender: TObject;
      Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
    procedure wmdServiceDeskDownloadFileAction(Sender: TObject;
      Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
  private
    { Private declarations }
  public
    { Public declarations }

  end;

var
  WebModuleClass: TComponentClass = TwmdFileDownloader;

implementation
{$R *.dfm}

function UnicodeToUTF8Base64(AStr: string): string;
var
  lUTF8String: UTF8String;
begin
  lUTF8String:= UTF8String(AStr);
  with TEncdDecd.Create(nil) do
  try
    Result:= EncodeBase64(PAnsiChar(lUTF8String), Length(lUTF8String));
  finally
    Free;
  end;
end;

function urlEncode2(const URL: WideString): AnsiString;
var
  i: Integer;
  utf8: AnsiString;
begin
  result := '';
  utf8 := UTF8Encode(URL);
  //
  for i := 1 to length(utf8) do begin
    //
    case (utf8[i]) of

      ' ': //result := Result + '%C2%A0'
        Result := Result + '%20'
          ;
      '0'..'1', 'A'..'Z', 'a'..'z', '-', '_': result := result + utf8[i];

      else
        result := result + '%' + AnsiString(IntToHex(byte(utf8[i]), 2));
    end;
  end;
end;

procedure TwmdFileDownloader.wmdServiceDeskDownloadFileAction(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
  ms: TMemoryStream;
begin
  ms := TMemoryStream.Create;
  try
    try
      ms.Position:= 0;
      ms.LoadFromFile('d:\temp\Черновик.docx');
      ms.Position:= 0;
      Response.ContentEncoding:= 'utf-8';
      ms.Position:= 0;
      Response.ContentStream:= TMemoryStream.Create;
      ms.SaveToStream(Response.ContentStream);
      with Response do
      begin
        SetCustomHeader('Content-type','application/octet-stream; charset=UTF-8');
        SetCustomHeader('Cache-Control','must-revalidate');
        SetCustomHeader('Pragma', 'public');
        SetCustomHeader('Content-Length',IntToStr(ms.Size));
        SetCustomHeader('Expires','0');
        SetCustomHeader('Content-Transfer-Encoding','binary');
        SetCustomHeader('Content-Disposition', 'File Transfer');
        if (Pos(Uppercase('MSIE'), Uppercase(Request.UserAgent)) > 0)
          or (Pos(Uppercase('Trident'), Uppercase(Request.UserAgent)) > 0)  then begin
          SetCustomHeader('Content-Disposition',
            'attachment;filename="'+(urlEncode2('Черновик.docx'))+'"')
        end
        else begin
          SetCustomHeader('Content-Disposition',
            'attachment;filename="=?UTF-8?B?'+UnicodeToUTF8Base64('Черновик.docx')+'?="');
        end;
      end;
      Response.Title;
      Response.Content;
    except on e: Exception do
      begin
        Response.Content:=
         Format('<h1>Error while prepare file download "%s"</h1>', [e.Message]);
      end;
    end;
  finally
    ms.Free;
  end;
end;

end.
Share

Tags: , ,

Leave a Reply