Response.TransmitFile() с общим ресурсом UNC (ASP.NET)
В комментариях к этой странице:
http://msdn.microsoft.com/en-us/library/12s31dhy%28v=VS.90%29.aspx
..it говорит, что TransmitFile() нельзя использовать с общими ресурсами UNC. Насколько я могу судить, это так; Я получаю эту ошибку в журнале событий при попытке:
TransmitFile failed. File Name: \\myshare1\e$\file.zip, Impersonation Enabled: 0, Token Valid: 1, HRESULT: 0x8007052e
Предлагаемая альтернатива - использовать WriteFile(), однако это проблематично, поскольку он загружает файл в память. В моем приложении файлы размером>200 МБ, так что это не будет масштабироваться.
Есть ли в ASP.NET метод для потоковой передачи файлов пользователям:
- масштабируемый (не считывает весь файл в ОЗУ и не занимает потоки ASP.NET)
- работает с акциями UNC
Назначение сетевого диска в качестве виртуального каталога нам не подходит. Я хотел бы также избежать копирования файла на локальный веб-сервер.
Спасибо
7 ответов
Вы пытались настроить IIS vroot, используя удаленный UNC-путь в качестве домашнего каталога? Если это работает, это может быть самым простым решением. Вы по-прежнему можете применять барьеры аутентификации для файлов (например, с помощью HttpModule или, возможно, даже через готовый модуль для проверки подлинности форм), но вы можете положиться на IIS для эффективной потоковой передачи содержимого, как только ваши фильтры авторизации дадут добро.
Предостережение: последний раз, когда я настраивал IIS в сценарии UNC, был очень давно (1998!!!), я столкнулся с периодическими проблемами с файлами, заблокированными на удаленном компьютере, что иногда приводило к проблемам с обновлением файлов. Работа с восстановлением после перезагрузки UNC-сервера также была интересной. Я предполагаю, что за 11 лет с тех пор эти проблемы были устранены, но вы никогда не можете быть уверены!
Другой подход может состоять в том, чтобы установить веб-сервер на компьютере UNC, а затем установить обратный прокси-сервер на своем веб-сервере, например, новый модуль IIS7 Application Request Routing.
Если вы хотите связать серверный поток, вы можете использовать подход, рекомендованный в KB812406, который решает проблемы, связанные с использованием ОЗУ, таймаутами, отключением клиента и т. Д. Обязательно отключите буферизацию ответов!
Идеальным решением для максимального контроля был бы "потоковый HttpHandler", где вместо того, чтобы отправлять вывод сразу, вы могли бы вернуть поток в ASP.NET и позволить ASP.NET разобраться с деталями о результатах разбиения на блоки для клиента., имея дело с разъединениями и т. д. Но я не смог найти хороший способ сделать это.:-(
Я бы порекомендовал второй подход к сайту и реализовать механизм аутентификации на основе токенов. Кодировать куки-файл аутентификации в URL, передаваемом клиенту через Redirect. Это может быть непрозрачное значение, которое является общим за кулисами, или это может быть что-то такое же простое, как, например, хэш секретного пароля и текущей даты.
В одном проекте я использовал хеширование. Один сервер генерировал хэш общего секретного пароля и добавочного номера. Второй сервер (который находился в другой сети) взял тот же пароль и добавочный номер и хэшировал их, чтобы подтвердить, что пользователю было разрешено позвонить с этого добавочного номера на нужный номер телефона.
Не могли бы вы настроить другой веб-сайт IIS, который указывает на UNC, а затем перенаправить их в файл на этом другом веб-сайте?
Response.Redirect (" http://files.somewhere.com/some/file.blah");
Таким образом, он будет выполняться в отдельном рабочем процессе и не будет влиять на ваш текущий сайт, а файлы будут обслуживаться непосредственно IIS, что явно лучше.
Вы можете попробовать открыть сетевой файл с помощью FileStream
и используя цикл для чтения фрагмента файла, передайте фрагмент (используя Response.Write(Char[], Int32, Int32)
), избавьтесь от фрагмента и повторяйте, пока файл не будет полностью прочитан.
Реальной ошибкой, которую вы получили, был сбой входа в общий файловый ресурс (что неудивительно, учитывая, что пользовательский IIS, скорее всего, будет работать как и вы пытаетесь получить доступ к административному общему ресурсу). Вы пытались настроить общий ресурс, который вообще не имеет ограничений доступа, просто чтобы проверить, является ли это проблемой, а не каким-либо конкретным ограничением в TransmitFile.
Если это исправит это, то вам необходимо войти в этот общий ресурс тем или иным образом как текущий пользователь или выдать себя за пользователя, у которого есть разрешения.
Стоит также отметить, что после некоторого осмотра с помощью рефлектора TransmitFile может в любом случае потенциально закончить чтение файла в память, и что WriteFile имеет другую версию, которая принимает логическое значение, которое решает, читать файл в память или нет (в факт, что по умолчанию WriteFile передает false для этого параметра). Может быть, стоит побродить по коду.
Вы можете записать файл в локальный каталог и выполнить задание робокопии, отслеживая каталог, чтобы сделать копию.
Однако, поскольку вы хотите избежать записи на локальный сервер, вы можете захотеть исследовать размещение сервера (например, HTTP или FTP) на целевом сервере и запись файла в эту службу.
Код для TransmitFile очень прост, почему бы не изменить его, чтобы сделать то, что вам нужно?
public void TransmitFile(string filename, long offset, long length)
{
if (filename == null)
{
throw new ArgumentNullException("filename");
}
if (offset < 0L)
{
throw new ArgumentException(SR.GetString("Invalid_range"), "offset");
}
if (length < -1L)
{
throw new ArgumentException(SR.GetString("Invalid_range"), "length");
}
filename = this.GetNormalizedFilename(filename);
using (FileStream stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
{
long num = stream.Length;
if (length == -1L)
{
length = num - offset;
}
if (num < offset)
{
throw new ArgumentException(SR.GetString("Invalid_range"), "offset");
}
if ((num - offset) < length)
{
throw new ArgumentException(SR.GetString("Invalid_range"), "length");
}
if (!this.UsingHttpWriter)
{
this.WriteStreamAsText(stream, offset, length);
return;
}
}
if (length > 0L)
{
bool supportsLongTransmitFile = (this._wr != null) && this._wr.SupportsLongTransmitFile;
this._httpWriter.TransmitFile(filename, offset, length, this._context.IsClientImpersonationConfigured || HttpRuntime.IsOnUNCShareInternal, supportsLongTransmitFile);
}
}
private void WriteStreamAsText(Stream f, long offset, long size)
{
if (size < 0L)
{
size = f.Length - offset;
}
if (size > 0L)
{
if (offset > 0L)
{
f.Seek(offset, SeekOrigin.Begin);
}
byte[] buffer = new byte[(int) size];
int count = f.Read(buffer, 0, (int) size);
this._writer.Write(Encoding.Default.GetChars(buffer, 0, count));
}
}
internal void TransmitFile(string filename, long offset, long size, bool isImpersonating, bool supportsLongTransmitFile)
{
if (this._charBufferLength != this._charBufferFree)
{
this.FlushCharBuffer(true);
}
this._lastBuffer = null;
this._buffers.Add(new HttpFileResponseElement(filename, offset, size, isImpersonating, supportsLongTransmitFile));
if (!this._responseBufferingOn)
{
this._response.Flush();
}
}
Спасибо,
Фил.