Способы выгрузки файла
От: 4wd Россия  
Дата: 09.08.07 14:15
Оценка:
В последнее время у нас появилась необходимость отдавать файлы Web-сервисом XML, написанном на C#. Принимать данные файлы нужно клиентам, написанным на php, а также Windows-приложению на .NET.
Путём поиска в интернете и нехитрых манипуляций был написан следующий код на C#:


        [WebMethod()]
        public void GetFile()
        {
//отдаём тестовый файл
            using(FileStream fs = new FileStream("temp.txt", FileMode.Create, FileAccess.Write, FileShare.None))
            {
                fs.Write(new byte[] { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}, 0, 16);
                fs.Flush();
                fs.Close();
            }

            using(FileStream fs = new FileStream("temp.txt", FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                sendDataToClient(fs, "temp.txt", 1024, 128);
                fs.Close();
            }
        }

        private int sendDataToClient(Stream outStream, string filename, int maxSpeedKbPerSec, int chunkSize)
        {
            // всего байт отослано
            int bytesSend = 0;
            // размер "порции"
            int _chunkSize = 1024 * chunkSize;
            // 300Kb 
            // время, за которое нужно отдать порцию
            int sleep = 1000 * (_chunkSize / 1024) / maxSpeedKbPerSec;

            System.Web.HttpContext Context = System.Web.HttpContext.Current;
            // увеличим макс время жизни страницы - 100 минут
            Context.Server.ScriptTimeout = 6000;
            // очищаем все заголовки
            Context.Response.Clear();
            // раздаём mp3 
            Context.Response.ContentType = "audio/mpeg";
            Context.Response.AddHeader("Content-Disposition", "attachment; filename=" + System.Web.HttpUtility.UrlEncode(Context.Request.ContentEncoding.GetBytes(filename)));
            try
            {
                int startAt = 0;
                if (Context.Request.Headers["Range"] != null)
                {
                    Context.Response.StatusCode = 206;
                    Context.Response.StatusDescription = "Partial Content";
                    startAt = int.Parse(Context.Request.Headers["Range"].Replace("bytes=", "").Replace("-", ""), System.Globalization.NumberStyles.AllowLeadingWhite | System.Globalization.NumberStyles.AllowTrailingWhite);
                }
                // Всего нужно будет отдать клиенту
                long _dataToWrite = outStream.Length;
                if (startAt > 0)
                {
                    Context.Response.AddHeader("Content-Range", string.Format("bytes{0}-{1}/{2}", startAt, _dataToWrite - 1, _dataToWrite));
                    // Скорректируем размер выводимого потока
                    _dataToWrite -= startAt;
                }
                else
                {
                    startAt = 0;
                }
                // Установим размер контента, отдаваемого клиенту
                Context.Response.AddHeader("Content-Length", _dataToWrite.ToString());
                // На всякий случай установим Entity Tag, идентифицирующий ресурс
                Context.Response.AddHeader("ETag", (filename + outStream.Length).GetHashCode().ToString("x"));
                // Установим указатель на байт, начиная с которого будем читать
                outStream.Position = startAt;
                // Буфер для вывода данных по частям
                byte[] buffer = new Byte[_chunkSize];
                // Текущая длина
                int length;
                while (_dataToWrite > 0 && Context.Response.IsClientConnected)
                {
                    DateTime from = DateTime.Now;
                    // Считаем данные в буфер и получим размер считанного блока
                    length = outStream.Read(buffer, 0, _chunkSize);
                    // Выведем данные в поток вывода
                    Context.Response.OutputStream.Write(buffer, 0, length);
                    // Скорректируем размер, который осталось вывести
                    _dataToWrite -= length;
                    // Скинем данные в поток вывода
                    Context.Response.Flush();
                    // если клиент отключился, то останавливаем отдачу данных
                    if (!Context.Response.IsClientConnected)
                        break;
                    // вычисляем сколько нам осталось "спать"
                    int tosleep = sleep - (int)DateTime.Now.Subtract(from).TotalMilliseconds;
                    // если нужно, то "засыпаем"
                    if (tosleep > 0)
                        Thread.Sleep(tosleep);
                    //учитываем отданыне данные
                    bytesSend += length;
                }
            }
            catch
             (Exception ex)
            {
                // вай-вай-вай
                Context.Response.StatusCode = 500;
                Context.Response.StatusDescription = ex.Message;
            }
            finally
            {
                Context.Response.End();
            }
            return bytesSend;
        }


Через браузер работает замечательно, открывается окно сохранения файла, всё прекрасно.
На php в свою очередь был написан следующий клиент:


#!/usr/local/bin/php
<?
define('APP_SOAP_WSDL', 'http://xxxxx/MediaService/MediaService.asmx?WSDL');
define('APP_SOAP_XSI','http://www.w3.org/2001/XMLSchema-instance');
define('APP_SOAP_XSD', 'http://www.w3.org/2001/XMLSchema');
define('APP_SOAP_NAMESPACE', 'http://xxxxx/Services/MediaService/');
define('APP_SOAP_VER', SOAP_1_2);
ini_set("soap.wsdl_cache_enabled", "0");


$client = new SoapClient(APP_SOAP_WSDL, array('trace'        =>  true,
                                              'exceptions'   =>  0,
                                              'location'     =>  'http://xxxxx/MediaService/MediaService.asmx',
                                              'xsi'          =>  APP_SOAP_XSI,
                                              'xsd'          =>  APP_SOAP_XSD,
                                              'soap'         =>  APP_SOAP_NAMESPACE,
                                              'style'        =>  SOAP_DOCUMENT,
                                              'use'          =>  SOAP_LITERAL,
                                              'soap_version' =>  APP_SOAP_VER
                                              ));
                                              
   $parametrs = array('parameters' => array());
   $result = $client->__SoapCall('GetFile', $parametrs);
   $str = $client->__getLastResponse();
   $fp = fopen('test.txt', 'w');
   fputs($fp, $str);

exit;

?>


Файл превосходно считывается, сохраняется на диск.
Вопрос заключается в том, что фактически работая с сервисом по протоколу SOAP на момент передачи файла мы опускаемся на уровень протокола HTTP.
С коллегами возник спор, насколько это правильно. С одной стороны, код, приведённый выше, работает быстро и просто (во всяком случае тестовый 16-байтный файл передаётся). С другой стороны, работая с веб-сервисом через SOAP правильнее с точки зрения идеологии подготавливать XML набор данных из таблицы в 1 бинарное поле и записывать туда содержимое в base64 или ещё как-нибудь.

Будем рады, если вы нас рассудите или предложите какой-то иной способ отдачи файла веб-сервисом.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.