带有 UNC 共享的 Response.TransmitFile() (ASP.NET)

发布于 2024-08-09 23:47:51 字数 687 浏览 12 评论 0原文

在此页面的评论中:

http:// /msdn.microsoft.com/en-us/library/12s31dhy%28v=VS.90%29.aspx

..它表示 TransmitFile() 不能与 UNC 共享一起使用。据我所知,情况确实如此;当我尝试这样做时,我在事件日志中收到此错误:

TransmitFile failed. File Name: \\myshare1\e$\file.zip, Impersonation Enabled: 0, Token Valid: 1, HRESULT: 0x8007052e

建议的替代方案是使用 WriteFile(),但是,这是有问题的,因为它将文件加载到内存中。在我的应用程序中,文件大于 200MB,因此无法扩展。

ASP.NET 中是否有一种方法可以将文件流式传输给用户:

  • 可扩展(不会将整个文件读入 RAM 或占用 ASP.NET 线程)
  • 与 UNC 共享一起使用

将网络驱动器映射为虚拟目录对我们来说不是一个选项。我也想避免将文件复制到本地网络服务器。

谢谢

In the comments of this page:

http://msdn.microsoft.com/en-us/library/12s31dhy%28v=VS.90%29.aspx

..it says that TransmitFile() cannot be used with UNC shares. As far as I can tell, this is the case; I get this error in Event Log when I attempt it:

TransmitFile failed. File Name: \\myshare1\e$\file.zip, Impersonation Enabled: 0, Token Valid: 1, HRESULT: 0x8007052e

The suggested alternative is to use WriteFile(), however, this is problematic because it loads the file into memory. In my application, the files are >200MB, so this is not going to scale.

Is there a method in ASP.NET for streaming files to users that's:

  • scalable (doesn't read entire file into RAM or occupy ASP.NET threads)
  • works with UNC shares

Mapping a network drive as a virtual directory is not an option for us. I would like to avoid copying the file to the local web server as well.

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

痴意少年 2024-08-16 23:47:51

您是否尝试过使用远程 UNC 路径作为主目录来设置 IIS vroot?如果有效,这可能是最简单的解决方案。您仍然可以对文件强制执行身份验证屏障(例如,通过 HttpModule,或者甚至通过开箱即用的表单身份验证模块),但一旦您的身份验证过滤器允许,您可以依靠 IIS 有效地传输内容。

需要注意的是:我上次在 UNC 场景中配置 IIS 是很久以前的事了(1998 年!!!),我遇到了文件被锁定在远程计算机上的间歇性问题,导致更新文件有时会出现问题。处理 UNC 服务器重新启动后的恢复也很有趣。我认为自那以后的 11 年里,这些问题已经得到解决,但你永远不能确定!

另一种方法可能是在 UNC 计算机上安装 Web 服务器,然后在 Web 服务器上安装反向代理服务器,例如新的 IIS7 应用程序请求路由 模块。

如果您愿意占用服务器线程,则可以使用 KB812406 它处理有关 RAM 消耗、超时、客户端断开连接等问题。请确保关闭响应缓冲!

理想的最大控制解决方案是“流式 HttpHandler”,您可以将流返回到 ASP.NET,并让 ASP.NET 处理有关向客户端分块结果的详细信息,而不必一次发送所有输出。 ,处理断开连接等。但我无法找到一个好的方法来做到这一点。 :-(

Have you tried setting up an IIS vroot using the remote UNC path as its home directory? If it works, this may be the easiest solution. You can still enforce authentication barriers on the files (e.g. via an HttpModule, or perhaps even via the out-of-box forms auth module) but you can rely on IIS to efficiently stream the content once your auth filters give the go-ahead.

A caveat: the last time I configured IIS in a UNC scenario was a long time ago (1998!!!) and I ran into intermittent problems with files being locked on the remote machine, making updating files sometimes problematic. Dealing with recovery after a UNC server reboot was also interesting. I assume in the 11 years since then those problems have been ironed out, but you can never be sure!

Another approach may be to install a web server on the UNC machine, and then install a reverse proxy server on your web server, like the new IIS7 Application Request Routing module.

If you're willing to tie up a server thread, you can use the approach recommended in KB812406 which deals with issues around RAM consumption, timeouts, client disconnection, etc. Make sure to turn off Response Buffering!

The ideal maximum-control solution would be a "streaming HttpHandler" where, instead of having to send the output all at once, you could return a stream to ASP.NET and let ASP.NET deal with the details about chunking results to the client, dealing with disconnects, etc. But I was unable to find a good way to do this. :-(

您可以尝试使用 FileStream 打开网络文件,并使用循环读取文件的块,传输该块(使用 Response.Write(Char[], Int32, Int32)),处理该块,并重复直到文件被完全读取。

Something you could try is opening the network file using a FileStream, and using a loop to read a chunk of the file, transmit the chunk (using Response.Write(Char[], Int32, Int32)), dispose of the chunk, and repeat until the file has been completely read.

陈年往事 2024-08-16 23:47:51

您可以将文件写入本地目录,并使用 robocopy 作业监视该目录以进行复制。

不过,由于您希望避免写入本地服务器,因此您可能需要研究将服务器(例如 HTTP 或 FTP)放在目标服务器上,并将文件写入该服务。

You could write the file to a local directory, and have a robocopy job monitoring the directory to do the copy.

Since you want to avoid writing to the local server, though, you may want to investigate putting a server (HTTP or FTP, e.g.) on the target server, and writing the file to that service.

凉城 2024-08-16 23:47:51

您可以设置另一个指向 UNC 的 IIS 网站,然后将它们重定向到另一个网站上的文件吗?

Response.Redirect("http://files.somewhere.com/some/file.blah”);

这样它将在单独的工作进程中运行,并且对您当前的站点没有影响,并且文件将直接由 IIS 提供服务,这显然是最好的。

could you set up another IIS website which points to the UNC, then redirect them to the file on that other website?

Response.Redirect("http://files.somewhere.com/some/file.blah");

That way it will be running in a separate worker process and have no effect on your current site, and the files will be served directly by IIS, which is clearly best.

情泪▽动烟 2024-08-16 23:47:51

您返回的实际错误是文件共享登录失败(考虑到 IIS 可能正在运行的用户以及您尝试访问管理共享,这并不令人意外)。您是否尝试过设置一个完全没有访问限制的共享,只是为了检查这是否是问题所在,而不是 TransmitFile 中的任何特定限制。

如果问题解决了,那么您需要以当前用户的身份以某种方式登录该共享,或者模拟具有权限的用户。

还值得指出的是,在使用反射器进行一番查看后,TransmitFile 可能最终会将文件读入内存,而 WriteFile 还有另一个版本,它采用一个布尔值来决定是否将文件读入内存(在事实上,默认的 WriteFile 对此参数传递 false)。可能值得您研究一下代码。

The actual error you got back was a logon failure to the file share (which isn't massively surprising considering the user IIS is likely to be running as, as well as you trying to access an administrative share). Have you tried setting up a share which has no access restrictions at all just to check if that is the issue rather than any specific limit in TransmitFile.

If that fixes it then you need to login to that share one way or another as the current user, or impersonate a user which does have permissions.

It is also worth pointing out that after abit of looking around with reflector TransmitFile can potentially end up reading the file into memory anyway, and that WriteFile has another version which takes a boolean value which decides whether to read the file into memory or not (in fact the default WriteFile passes false for this parameter). Might be worth you poking around in the code.

岁吢 2024-08-16 23:47:51

我会推荐第二种站点方法并实现基于令牌的身份验证机制。在通过重定向传递给客户端的 URL 中对身份验证 cookie 进行编码。例如,这可以是在幕后共享的不透明值,也可以是简单的密码和当前日期的哈希值。

我做的一个项目使用了哈希。一台服务器生成共享秘密密码和分机号码的哈希值。第二台服务器(位于不同的网络上)采用相同的密码和分机号,并对它们进行哈希处理,以确认用户可以从该分机拨打所需的电话号码。

I would recommend the second site approach and implement a token-based authentication mechanism. Encode an authentication cookie in the URL passed to the client via Redirect. This could be an opaque value that is shared behind-the-scenes, or it could be something as simple as a hash of a secret password and the current date, for example.

One project I did used the hashing. One server generated a hash of a shared secret password and an extension number. The second server (which was on a different network) took the same password and extension and hashed them to confirm the user was allowed to place a call from that extension to the desired phone number.

哎呦我呸! 2024-08-16 23:47:51

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();
    }
}

谢谢,

菲尔。

The code for TransmitFile is very simple, why not modify it to do what you need?

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();
    }
}

Thanks,

Phil.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文