//-----------------------------------------------------------------------
//
// Copyright (c) Outercurve Foundation. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.Messaging {
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net;
using System.Text;
using Validation;
///
/// Cached details on the response from a direct web request to a remote party.
///
[DebuggerDisplay("{Status} {ContentType.MediaType}, length: {ResponseStream.Length}")]
internal class CachedDirectWebResponse : IncomingWebResponse {
///
/// A seekable, repeatable response stream.
///
private MemoryStream responseStream;
///
/// Initializes a new instance of the class.
///
internal CachedDirectWebResponse() {
}
///
/// Initializes a new instance of the class.
///
/// The request URI.
/// The response.
/// The maximum bytes to read.
internal CachedDirectWebResponse(Uri requestUri, HttpWebResponse response, int maximumBytesToRead)
: base(requestUri, response) {
Requires.NotNull(requestUri, "requestUri");
Requires.NotNull(response, "response");
this.responseStream = CacheNetworkStreamAndClose(response, maximumBytesToRead);
// BUGBUG: if the response was exactly maximumBytesToRead, we'll incorrectly believe it was truncated.
this.ResponseTruncated = this.responseStream.Length == maximumBytesToRead;
}
///
/// Initializes a new instance of the class.
///
/// The request URI.
/// The final URI to respond to the request.
/// The headers.
/// The status code.
/// Type of the content.
/// The content encoding.
/// The response stream.
internal CachedDirectWebResponse(Uri requestUri, Uri responseUri, WebHeaderCollection headers, HttpStatusCode statusCode, string contentType, string contentEncoding, MemoryStream responseStream)
: base(requestUri, responseUri, headers, statusCode, contentType, contentEncoding) {
Requires.NotNull(requestUri, "requestUri");
Requires.NotNull(responseStream, "responseStream");
this.responseStream = responseStream;
}
///
/// Gets a value indicating whether the cached response stream was
/// truncated to a maximum allowable length.
///
public bool ResponseTruncated { get; private set; }
///
/// Gets the body of the HTTP response.
///
public override Stream ResponseStream {
get { return this.responseStream; }
}
///
/// Gets or sets the cached response stream.
///
internal MemoryStream CachedResponseStream {
get { return this.responseStream; }
set { this.responseStream = value; }
}
///
/// Creates a text reader for the response stream.
///
/// The text reader, initialized for the proper encoding.
public override StreamReader GetResponseReader() {
this.ResponseStream.Seek(0, SeekOrigin.Begin);
string contentEncoding = this.Headers[HttpResponseHeader.ContentEncoding];
Encoding encoding = null;
if (!string.IsNullOrEmpty(contentEncoding)) {
try {
encoding = Encoding.GetEncoding(contentEncoding);
} catch (ArgumentException ex) {
Logger.Messaging.ErrorFormat("Encoding.GetEncoding(\"{0}\") threw ArgumentException: {1}", contentEncoding, ex);
}
}
return encoding != null ? new StreamReader(this.ResponseStream, encoding) : new StreamReader(this.ResponseStream);
}
///
/// Gets the body of the response as a string.
///
/// The entire body of the response.
internal string GetResponseString() {
if (this.ResponseStream != null) {
string value = this.GetResponseReader().ReadToEnd();
this.ResponseStream.Seek(0, SeekOrigin.Begin);
return value;
} else {
return null;
}
}
///
/// Gets an offline snapshot version of this instance.
///
/// The maximum bytes from the response stream to cache.
/// A snapshot version of this instance.
///
/// If this instance is a creating a snapshot
/// will automatically close and dispose of the underlying response stream.
/// If this instance is a , the result will
/// be the self same instance.
///
internal override CachedDirectWebResponse GetSnapshot(int maximumBytesToCache) {
return this;
}
///
/// Sets the response to some string, encoded as UTF-8.
///
/// The string to set the response to.
internal void SetResponse(string body) {
if (body == null) {
this.responseStream = null;
return;
}
Encoding encoding = Encoding.UTF8;
this.Headers[HttpResponseHeader.ContentEncoding] = encoding.HeaderName;
this.responseStream = new MemoryStream();
StreamWriter writer = new StreamWriter(this.ResponseStream, encoding);
writer.Write(body);
writer.Flush();
this.ResponseStream.Seek(0, SeekOrigin.Begin);
}
///
/// Caches the network stream and closes it if it is open.
///
/// The response whose stream is to be cloned.
/// The maximum bytes to cache.
/// The seekable Stream instance that contains a copy of what was returned in the HTTP response.
[SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Assume(System.Boolean,System.String,System.String)", Justification = "No localization required.")]
private static MemoryStream CacheNetworkStreamAndClose(HttpWebResponse response, int maximumBytesToRead) {
Requires.NotNull(response, "response");
// Now read and cache the network stream
Stream networkStream = response.GetResponseStream();
MemoryStream cachedStream = new MemoryStream(response.ContentLength < 0 ? 4 * 1024 : Math.Min((int)response.ContentLength, maximumBytesToRead));
try {
Assumes.True(networkStream.CanRead, "HttpWebResponse.GetResponseStream() always returns a readable stream."); // CC missing
Assumes.True(cachedStream.CanWrite, "This is a MemoryStream -- it's always writable."); // CC missing
networkStream.CopyTo(cachedStream);
cachedStream.Seek(0, SeekOrigin.Begin);
networkStream.Dispose();
response.Close();
return cachedStream;
} catch {
cachedStream.Dispose();
throw;
}
}
}
}