3

我有这个方法:

public Stream Load(string term)
{
    var url = CreateSearchUrl(term);

    var webRequest = (HttpWebRequest)WebRequest.Create(url);
    var webResponse = webRequest.GetResponse();

    return new GZipStream(webResponse.GetResponseStream(), CompressionMode.Decompress);
}

如您所见,我将流返回给调用者,但我不确定这是否安全,因为 WebRequest 被运行时处理,从而使我返回的流无效。

我可以将其转换为字节数组并返回 MemoryStream 甚至使用 WebClient,但我就是不喜欢这个想法 =)。

谢谢!

4

1 回答 1

3

There isn't an easy way to safely return the Stream without having resource leaks. The main issue is disposing the WebResponse:

public Stream Load(string term)
{
    var url = CreateSearchUrl(term);

    var webRequest = (HttpWebRequest)WebRequest.Create(url);
    var webResponse = webRequest.GetResponse(); // whoops this doesn't get disposed!

    return new GZipStream(webResponse.GetResponseStream(), CompressionMode.Decompress);
}

Closing the WebResponse is actually more important than closing the response stream, since closing the WebResponse implicitly closes the response stream.

The only way I know of getting the WebResponse to be disposed with the Stream would be to implement a decorator around the GZipStream that disposed of the WebResponse (as well as the GZipStream) when it is disposed. Although this would work, it is quite a lot of code:

class WebResponseDisposingStream : Stream
{
    private readonly WebResponse response;
    private readonly Stream stream;

    public WebResponseDisposingStream(WebResponse response, Stream stream)
    {
        if (response == null)
            throw new ArgumentNullException("response");
        if (stream == null)
            throw new ArgumentNullException("stream");

        this.response = response;
        this.stream = stream;
    }

    public override void Close()
    {
        this.response.Close();
        this.stream.Close();
    }

    // override all the methods on stream and delegate the call to this.stream

    public override void Flush() { this.stream.Flush(); } // example delegation for Flush()
    // ... on and on for all the other members of Stream
}

Perhaps a better approach would be a continuation passing style where the code that uses the Stream is passed in as an delegate:

public void Load(string term, Action<Stream> action)
{
    var url = CreateSearchUrl(term);

    var webRequest = (HttpWebRequest)WebRequest.Create(url);

    using (var webResponse = webRequest.GetResponse())
    using (var responseStream = webResponse.GetResponseStream())
    using (var gzipStream = new GZipStream(responseStream, CompressionMode.Decompress))
    {
        action(gzipStream);
    }
}

Now the caller simply passes in what should be done with the Stream. In the following the length is printed to the console:

Load("test", stream => Console.WriteLine("Length=={0}", stream.Length));

One final note: In case you're not aware, HTTP has built-in support for compression. See Wikipedia for more details. The HttpWebRequest has built-in support for HTTP compression via the AutomaticDecompression property. Using the HTTP compression basically makes the compression transparent to your code and also works better with HTTP tooling (browsers, fiddler, etc).

于 2012-05-08T23:54:30.887 回答