HTTPHandler从另一台服务器检索下载文件?

时间:2023-02-06 08:11:21

I would like to provide downloadable files to website users, but want to hide the URL of the files from the user... I'm thinking an HTTPHandler could do the trick, but is it possible to retrieve a file from an external server and stream it to the user?

我想向网站用户提供可下载的文件,但是想要隐藏用户的文件的URL ...我认为HTTPHandler可以做到这一点,但是可以从外部服务器检索文件并且将它流式传输给用户?

Perhaps somebody can give me a hint at how to accomplish this, or point me to a resource where it's been done before?

也许有人可以给我一个如何实现这个目标的暗示,或者指出我以前做过的资源?


Just to elaborate on what I'm trying to achieve... I'm building an ASP.NET website, which contains a music download link. I want to protect the actual URLs of the file, and I also want to store them on an external (PHP) server (MUCH MUCH cheaper)...

只是详细说明我想要实现的目标......我正在构建一个包含音乐下载链接的ASP.NET网站。我想保护文件的实际URL,我也想将它们存储在外部(PHP)服务器上(更便宜)...

So what I need to do is set up a stream that can grab the file from a URL (points to another server), and stream it to the Response object without the user realising it's coming from another server.

所以我需要做的是设置一个流,它可以从URL获取文件(指向另一个服务器),并将其流式传输到Response对象,而无需用户意识到它来自另一个服务器。

Will the TransmitFile method allow streaming of a file from a completely separate server? I don't want the file to be streamed "through" my server, as that defeats the purpose (saving bandwidth)... I want the client (browser) to download the file direct from the other server.

TransmitFile方法是否允许从完全独立的服务器传输文件?我不希望文件通过我的服务器“流过”,因为这会破坏目的(节省带宽)...我希望客户端(浏览器)直接从其他服务器下载文件。

Do I need a handler on the file hosting server perhaps? Maybe a PHP script on the other end is the way to go...?

我可能需要文件托管服务器上的处理程序吗?也许另一端的PHP脚本是要走的路......?

5 个解决方案

#1


1  

With your clarification of wanting the bandwidth to come from the external server and not yours, it changes the question quite a bit.

如果您希望带宽来自外部服务器而不是您的带宽,那么它会更改问题。

In order to accomplish that, the external server would have to have a website on it you could send the user to. You cannot stream the file through your site but not get hit with the bandwidth, or control it from your site but streamed through the other server, so it must be completely handled through the other site. Problem with that is a normal URL based approach would show the user the URL, which you said is the second requirement that it not show the URL.

为了实现这一点,外部服务器必须有一个可以发送给用户的网站。您无法通过您的站点流式传输文件,但不会被带宽限制,或者从您的站点控制它,而是通过其他服务器流式传输,因此必须通过其他站点完全处理。问题是基于普通URL的方法会向用户显示URL,您说这是第二个不显示URL的要求。

But, couldn't you just have a generic page that serves the files on the external site, and the specifics on which file to stream would be passed through a post from the page on the original site? That would remove the URL pointing to a specific file. It would show the domain, but users would not be able to pull files without knowing the post fields.

但是,你不能只有一个通用页面来提供外部网站上的文件,并且流式传输文件的具体细节将通过原始网站页面上的帖子传递吗?这将删除指向特定文件的URL。它将显示域,但用户将无法在不知道帖子字段的情况下提取文件。

This would not need to be an HTTPHandler, just a normal page.

这不需要是HTTPHandler,只需要是普通页面。

#2


1  

I recommend you look at the TransmitFile method: http://msdn.microsoft.com/en-us/library/12s31dhy.aspx

我建议你看看TransmitFile方法:http://msdn.microsoft.com/en-us/library/12s31dhy.aspx

#3


1  

Yes, you can streaming from a remote stream (download from another server) to output stream. Assume serviceUrl is the location of file to stream:

是的,您可以从远程流(从其他服务器下载)流式传输到输出流。假设serviceUrl是要流式传输的文件的位置:

HttpWebRequest webrequest = (HttpWebRequest)WebRequest.Create(serviceUrl);
            webrequest.AllowAutoRedirect = false;
            webrequest.Timeout = 30 * 1000;
            webrequest.ReadWriteTimeout = 30 * 1000;
            webrequest.KeepAlive = false;

            Stream remoteStream = null;
            byte[] buffer = new byte[4 * 1024];
            int bytesRead;

            try {
                WebResponse responce = webrequest.GetResponse();
                remoteStream = responce.GetResponseStream();
                bytesRead = remoteStream.Read(buffer, 0, buffer.Length);

                Server.ScriptTimeout = 30 * 60;
                Response.Buffer = false;
                Response.BufferOutput = false;
                Response.Clear();
                Response.ContentType = "application/octet-stream";
                Response.AppendHeader("Content-Disposition", "attachment; filename=" + Uid + ".EML");
                if (responce.ContentLength != -1)
                    Response.AppendHeader("Content-Length", responce.ContentLength.ToString());

                while (bytesRead > 0 && Response.IsClientConnected) {
                    Response.OutputStream.Write(buffer, 0, bytesRead);
                    bytesRead = remoteStream.Read(buffer, 0, buffer.Length);
                }

            } catch (Exception E) {
                Logger.LogErrorFormat(LogModules.DomainUsers, "Error transfering message from remote host: {0}", E.Message);
                Response.End();
                return;
            } finally {
                if (remoteStream != null) remoteStream.Close();
            }

            Response.End();

#4


0  

I've done this before. First, and obviously, the files have to be in a share on the external server that the user process of the website has access to.

我之前做过这个。首先,显然,文件必须位于网站用户进程可以访问的外部服务器上。

As far as the HTTPHandler goes, I handled this by giving the users zip files containing the files they want to download; this way my handler could intercept any call for .zip files and stream them the zip file I create.

就HTTPHandler而言,我通过给用户包含他们想要下载的文件的zip文件来处理这个问题。这样我的处理程序就可以拦截对.zip文件的任何调用,并将它们传输给我创建的zip文件。

Here's the code (quite a chunk; I use MVP, so it is split into Handler and Presenter): Handler:

这是代码(非常大块;我使用MVP,因此它分为Handler和Presenter):处理程序:

public class ZipDownloadModule: IHttpHandler, ICompressFilesView, IErrorView 
{
    CompressFilesPresenter _presenter;

    public ZipDownloadModule()
    {
        _presenter = new CompressFilesPresenter(this, this);
    }
    #region IHttpHandler Members

    public bool IsReusable
    {
        get { return true; }
    }

    public void ProcessRequest(HttpContext context)
    {
        OnDownloadFiles();
    }

    private void OnDownloadFiles()
    {
        if(Compress != null)
            Compress(this, EventArgs.Empty);
    }

    #endregion

    #region IFileListDownloadView Members

    public IEnumerable<string> FileNames
    {
        get 
        {
            string files = HttpContext.Current.Request["files"] ?? string.Empty;

            return files.Split(new Char[] { ',' });
        }
    }

    public System.IO.Stream Stream
    {
        get
        {
            HttpContext.Current.Response.ContentType = "application/x-zip-compressed";
            HttpContext.Current.Response.AppendHeader("Content-Disposition", "attachment; filename=ads.zip");
            return HttpContext.Current.Response.OutputStream;
        }
    }

    public event EventHandler Compress;

    #endregion

    #region IErrorView Members

    public string errorMessage
    {
        set {  }
    }

    #endregion
}

Presenter:

public class CompressFilesPresenter: PresenterBase<ICompressFilesView>
{
    IErrorView _errorView;

    public CompressFilesPresenter(ICompressFilesView view, IErrorView errorView)
        : base(view)
    {
        _errorView = errorView;
        this.View.Compress += new EventHandler(View_Compress);
    }

    void View_Compress(object sender, EventArgs e)
    {
        CreateZipFile();
    }

    private void CreateZipFile()
    {
        MemoryStream stream = new MemoryStream();

        try
        {
            CreateZip(stream, this.View.FileNames);

            WriteZip(stream);
        }
        catch(Exception ex)
        {
            HandleException(ex);
        }
    }

    private void WriteZip(MemoryStream stream)
    {
        byte[] data = stream.ToArray();

        this.View.Stream.Write(data, 0, data.Length);
    }

    private void CreateZip(MemoryStream stream, IEnumerable<string> filePaths)
    {
        using(ZipOutputStream s = new ZipOutputStream(stream)) // this.View.Stream))
        {
            s.SetLevel(9); // 0 = store only to 9 = best compression

            foreach(string fullPath in filePaths)
                AddFileToZip(fullPath, s);

            s.Finish();
        }
    }

    private static void AddFileToZip(string fullPath, ZipOutputStream s)
    {
        byte[] buffer = new byte[4096];

        ZipEntry entry;

        // Using GetFileName makes the result compatible with XP
        entry = new ZipEntry(Path.GetFileName(fullPath));

        entry.DateTime = DateTime.Now;
        s.PutNextEntry(entry);

        using(FileStream fs = File.OpenRead(fullPath))
        {
            int sourceBytes;
            do
            {
                sourceBytes = fs.Read(buffer, 0, buffer.Length);
                s.Write(buffer, 0, sourceBytes);
            } while(sourceBytes > 0);
        }
    }

    private void HandleException(Exception ex)
    {
        switch(ex.GetType().ToString())
        {
            case "DirectoryNotFoundException":
                _errorView.errorMessage = "The expected directory does not exist.";
                break;
            case "FileNotFoundException":
                _errorView.errorMessage = "The expected file does not exist.";
                break;
            default:
                _errorView.errorMessage = "There has been an error. If this continues please contact AMG IT Support.";
                break;
        }
    }

    private void ClearError()
    {
        _errorView.errorMessage = "";
    }
}

Hope this helps!!

希望这可以帮助!!

#5


0  

Okay, seems my quest to avoid writing/ deploying some php code is in vain... here's what i'm going to run with on the file (php) server:

好吧,似乎我要避免编写/部署一些PHP代码是徒劳的......这就是我要在文件(php)服务器上运行的东西:

http://www.zubrag.com/scripts/download.php

Then the links from my asp.net web server will point to that script, which will then download the relevant file (hence avoiding direct downloads, and allowing tracking of downloads via google analytics)... i think that'll do the trick

然后我的asp.net网络服务器上的链接将指向该脚本,然后将下载相关文件(因此避免直接下载,并允许通过谷歌分析跟踪下载)...我认为这样做的伎俩

Thanks all Greg

谢谢Greg

#1


1  

With your clarification of wanting the bandwidth to come from the external server and not yours, it changes the question quite a bit.

如果您希望带宽来自外部服务器而不是您的带宽,那么它会更改问题。

In order to accomplish that, the external server would have to have a website on it you could send the user to. You cannot stream the file through your site but not get hit with the bandwidth, or control it from your site but streamed through the other server, so it must be completely handled through the other site. Problem with that is a normal URL based approach would show the user the URL, which you said is the second requirement that it not show the URL.

为了实现这一点,外部服务器必须有一个可以发送给用户的网站。您无法通过您的站点流式传输文件,但不会被带宽限制,或者从您的站点控制它,而是通过其他服务器流式传输,因此必须通过其他站点完全处理。问题是基于普通URL的方法会向用户显示URL,您说这是第二个不显示URL的要求。

But, couldn't you just have a generic page that serves the files on the external site, and the specifics on which file to stream would be passed through a post from the page on the original site? That would remove the URL pointing to a specific file. It would show the domain, but users would not be able to pull files without knowing the post fields.

但是,你不能只有一个通用页面来提供外部网站上的文件,并且流式传输文件的具体细节将通过原始网站页面上的帖子传递吗?这将删除指向特定文件的URL。它将显示域,但用户将无法在不知道帖子字段的情况下提取文件。

This would not need to be an HTTPHandler, just a normal page.

这不需要是HTTPHandler,只需要是普通页面。

#2


1  

I recommend you look at the TransmitFile method: http://msdn.microsoft.com/en-us/library/12s31dhy.aspx

我建议你看看TransmitFile方法:http://msdn.microsoft.com/en-us/library/12s31dhy.aspx

#3


1  

Yes, you can streaming from a remote stream (download from another server) to output stream. Assume serviceUrl is the location of file to stream:

是的,您可以从远程流(从其他服务器下载)流式传输到输出流。假设serviceUrl是要流式传输的文件的位置:

HttpWebRequest webrequest = (HttpWebRequest)WebRequest.Create(serviceUrl);
            webrequest.AllowAutoRedirect = false;
            webrequest.Timeout = 30 * 1000;
            webrequest.ReadWriteTimeout = 30 * 1000;
            webrequest.KeepAlive = false;

            Stream remoteStream = null;
            byte[] buffer = new byte[4 * 1024];
            int bytesRead;

            try {
                WebResponse responce = webrequest.GetResponse();
                remoteStream = responce.GetResponseStream();
                bytesRead = remoteStream.Read(buffer, 0, buffer.Length);

                Server.ScriptTimeout = 30 * 60;
                Response.Buffer = false;
                Response.BufferOutput = false;
                Response.Clear();
                Response.ContentType = "application/octet-stream";
                Response.AppendHeader("Content-Disposition", "attachment; filename=" + Uid + ".EML");
                if (responce.ContentLength != -1)
                    Response.AppendHeader("Content-Length", responce.ContentLength.ToString());

                while (bytesRead > 0 && Response.IsClientConnected) {
                    Response.OutputStream.Write(buffer, 0, bytesRead);
                    bytesRead = remoteStream.Read(buffer, 0, buffer.Length);
                }

            } catch (Exception E) {
                Logger.LogErrorFormat(LogModules.DomainUsers, "Error transfering message from remote host: {0}", E.Message);
                Response.End();
                return;
            } finally {
                if (remoteStream != null) remoteStream.Close();
            }

            Response.End();

#4


0  

I've done this before. First, and obviously, the files have to be in a share on the external server that the user process of the website has access to.

我之前做过这个。首先,显然,文件必须位于网站用户进程可以访问的外部服务器上。

As far as the HTTPHandler goes, I handled this by giving the users zip files containing the files they want to download; this way my handler could intercept any call for .zip files and stream them the zip file I create.

就HTTPHandler而言,我通过给用户包含他们想要下载的文件的zip文件来处理这个问题。这样我的处理程序就可以拦截对.zip文件的任何调用,并将它们传输给我创建的zip文件。

Here's the code (quite a chunk; I use MVP, so it is split into Handler and Presenter): Handler:

这是代码(非常大块;我使用MVP,因此它分为Handler和Presenter):处理程序:

public class ZipDownloadModule: IHttpHandler, ICompressFilesView, IErrorView 
{
    CompressFilesPresenter _presenter;

    public ZipDownloadModule()
    {
        _presenter = new CompressFilesPresenter(this, this);
    }
    #region IHttpHandler Members

    public bool IsReusable
    {
        get { return true; }
    }

    public void ProcessRequest(HttpContext context)
    {
        OnDownloadFiles();
    }

    private void OnDownloadFiles()
    {
        if(Compress != null)
            Compress(this, EventArgs.Empty);
    }

    #endregion

    #region IFileListDownloadView Members

    public IEnumerable<string> FileNames
    {
        get 
        {
            string files = HttpContext.Current.Request["files"] ?? string.Empty;

            return files.Split(new Char[] { ',' });
        }
    }

    public System.IO.Stream Stream
    {
        get
        {
            HttpContext.Current.Response.ContentType = "application/x-zip-compressed";
            HttpContext.Current.Response.AppendHeader("Content-Disposition", "attachment; filename=ads.zip");
            return HttpContext.Current.Response.OutputStream;
        }
    }

    public event EventHandler Compress;

    #endregion

    #region IErrorView Members

    public string errorMessage
    {
        set {  }
    }

    #endregion
}

Presenter:

public class CompressFilesPresenter: PresenterBase<ICompressFilesView>
{
    IErrorView _errorView;

    public CompressFilesPresenter(ICompressFilesView view, IErrorView errorView)
        : base(view)
    {
        _errorView = errorView;
        this.View.Compress += new EventHandler(View_Compress);
    }

    void View_Compress(object sender, EventArgs e)
    {
        CreateZipFile();
    }

    private void CreateZipFile()
    {
        MemoryStream stream = new MemoryStream();

        try
        {
            CreateZip(stream, this.View.FileNames);

            WriteZip(stream);
        }
        catch(Exception ex)
        {
            HandleException(ex);
        }
    }

    private void WriteZip(MemoryStream stream)
    {
        byte[] data = stream.ToArray();

        this.View.Stream.Write(data, 0, data.Length);
    }

    private void CreateZip(MemoryStream stream, IEnumerable<string> filePaths)
    {
        using(ZipOutputStream s = new ZipOutputStream(stream)) // this.View.Stream))
        {
            s.SetLevel(9); // 0 = store only to 9 = best compression

            foreach(string fullPath in filePaths)
                AddFileToZip(fullPath, s);

            s.Finish();
        }
    }

    private static void AddFileToZip(string fullPath, ZipOutputStream s)
    {
        byte[] buffer = new byte[4096];

        ZipEntry entry;

        // Using GetFileName makes the result compatible with XP
        entry = new ZipEntry(Path.GetFileName(fullPath));

        entry.DateTime = DateTime.Now;
        s.PutNextEntry(entry);

        using(FileStream fs = File.OpenRead(fullPath))
        {
            int sourceBytes;
            do
            {
                sourceBytes = fs.Read(buffer, 0, buffer.Length);
                s.Write(buffer, 0, sourceBytes);
            } while(sourceBytes > 0);
        }
    }

    private void HandleException(Exception ex)
    {
        switch(ex.GetType().ToString())
        {
            case "DirectoryNotFoundException":
                _errorView.errorMessage = "The expected directory does not exist.";
                break;
            case "FileNotFoundException":
                _errorView.errorMessage = "The expected file does not exist.";
                break;
            default:
                _errorView.errorMessage = "There has been an error. If this continues please contact AMG IT Support.";
                break;
        }
    }

    private void ClearError()
    {
        _errorView.errorMessage = "";
    }
}

Hope this helps!!

希望这可以帮助!!

#5


0  

Okay, seems my quest to avoid writing/ deploying some php code is in vain... here's what i'm going to run with on the file (php) server:

好吧,似乎我要避免编写/部署一些PHP代码是徒劳的......这就是我要在文件(php)服务器上运行的东西:

http://www.zubrag.com/scripts/download.php

Then the links from my asp.net web server will point to that script, which will then download the relevant file (hence avoiding direct downloads, and allowing tracking of downloads via google analytics)... i think that'll do the trick

然后我的asp.net网络服务器上的链接将指向该脚本,然后将下载相关文件(因此避免直接下载,并允许通过谷歌分析跟踪下载)...我认为这样做的伎俩

Thanks all Greg

谢谢Greg