Загружать файл клиенту асинхронно

Мне сложно что-то сделать.

Я создаю mailclient, используя asp.net MVC 4.

Я нахожусь в точке, что мне нужно загрузить изображение, связанное с сообщением (НЕ вложением) в браузер клиента.

Теперь у меня есть эта настройка:

Клиентский браузер → Контроллер/сервер → Почтовый сервер

чтобы уточнить: у меня есть запрос клиента, содержащий идентификатор содержимого изображения, правильный почтовый ящик, сообщение и т.д. С помощью этой информации я могу загрузить изображение с почтового сервера и загрузить его клиенту. Теперь идет сложная часть: я хочу сделать это асинхронным. Я хочу иметь возможность загружать кусок размером 512 КБ с почтового сервера, декодировать эту часть и отправлять ее клиенту. Fetch - decode - send. До тех пор, пока браузер не получит все данные изображения.

Я просто не хочу сначала загружать ВСЕ данные сначала на сервер, а затем создавать новый memystream со всеми этими данными и возвращать это как fileresult. Я просто боюсь получить слишком большие файлы в своей памяти и заблокировать другие процессы и т.д.

Я также планирую использовать этот метод для загрузки реальных вложений (которые могут быть 100 МБ). Поэтому мне понадобится этот метод позже.

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

Кто-нибудь может помочь мне?

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

Edit2: может ли http chuncked быть решением? Если да, не могли бы вы дать мне небольшой пример?

Ответ 1

Вам просто нужно скопировать данные из одного потока (соединение tcp на почтовый сервер) в другое (http-соединение с браузером), правильно? Если вы хотите масштабировать, вам нужно использовать неблокирующий IO, как описано в в этой статье. Таким образом, вы захотите вызвать код в этой статье из реализации IHttpAsyncHandler. Вы получите что-то вроде этого:

class MyHandler : IHttpAsyncHandler
{
    public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
    {
        Stream src = null; // remote data source
        Stream dst = context.Response.OutputStream;

        // set content type, etc

        var res = new MyResult();

        AsynchCopy(src, dst, () =>
            {
                ((ManualResetEvent)res.AsyncWaitHandle).Set();
                cb(res);
                src.Close();
                dst.Flush();
            });

        return res;
    }

    public void EndProcessRequest(IAsyncResult result)
    {
    }

    public bool IsReusable
    {
        get { return true; }
    }

    public void ProcessRequest(HttpContext context)
    {
        throw new NotImplementedException();
    }

    class MyResult : IAsyncResult
    {
        public MyResult()
        {
            AsyncWaitHandle = new ManualResetEvent(false);
        }

        public object AsyncState
        {
            get { return null; }
        }

        public WaitHandle AsyncWaitHandle
        {
            get;
            private set;
        }

        public bool CompletedSynchronously
        {
            get { return false; }
        }

        public bool IsCompleted
        {
            get { return AsyncWaitHandle.WaitOne(0); }
        }
    }

    public static void AsynchCopy(Stream src, Stream dst, Action done)
    {
        byte[] buffer = new byte[2560];
        AsyncCallback readCallback = null, writeCallback = null;

        readCallback = (readResult) =>
        {
            int read = src.EndRead(readResult);
            if (read > 0)
            {
                dst.BeginWrite(buffer, 0, read, writeCallback, null);
            }
            else
            {
                done();
            }
        };

        writeCallback = (writeResult) =>
        {
            dst.EndWrite(writeResult);
            src.BeginRead(buffer, 0, buffer.Length, readCallback, null);
        };

        src.BeginRead(buffer, 0, buffer.Length, readCallback, null);
    }
}

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