summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2012-01-12 08:40:50 -0800
committerAndrew Arnott <andrewarnott@gmail.com>2012-01-12 08:42:14 -0800
commitaf21cdaf77ca72f54e04f22268b740ce262582fa (patch)
tree9b158e3bff1f56264bccb9e45c8b807816beece6 /src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs
parenta73f2a4830aaa2afcb3f13da2206d9b011dad7fb (diff)
downloadDotNetOpenAuth-af21cdaf77ca72f54e04f22268b740ce262582fa.zip
DotNetOpenAuth-af21cdaf77ca72f54e04f22268b740ce262582fa.tar.gz
DotNetOpenAuth-af21cdaf77ca72f54e04f22268b740ce262582fa.tar.bz2
Renamed assembly DotNetOpenAuth.Messaging(.UI) to DotNetOpenAuth.Core(.UI)
Diffstat (limited to 'src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs')
-rw-r--r--src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs300
1 files changed, 300 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs b/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs
new file mode 100644
index 0000000..003cac8
--- /dev/null
+++ b/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs
@@ -0,0 +1,300 @@
+//-----------------------------------------------------------------------
+// <copyright file="OutgoingWebResponse.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Messaging {
+ using System;
+ using System.ComponentModel;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.IO;
+ using System.Net;
+ using System.Net.Mime;
+ using System.Text;
+ using System.Threading;
+ using System.Web;
+
+ /// <summary>
+ /// A protocol message (request or response) that passes from this
+ /// to a remote party via the user agent using a redirect or form
+ /// POST submission, OR a direct message response.
+ /// </summary>
+ /// <remarks>
+ /// <para>An instance of this type describes the HTTP response that must be sent
+ /// in response to the current HTTP request.</para>
+ /// <para>It is important that this response make up the entire HTTP response.
+ /// A hosting ASPX page should not be allowed to render its normal HTML output
+ /// after this response is sent. The normal rendered output of an ASPX page
+ /// can be canceled by calling <see cref="HttpResponse.End"/> after this message
+ /// is sent on the response stream.</para>
+ /// </remarks>
+ public class OutgoingWebResponse {
+ /// <summary>
+ /// The encoder to use for serializing the response body.
+ /// </summary>
+ private static Encoding bodyStringEncoder = new UTF8Encoding(false);
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OutgoingWebResponse"/> class.
+ /// </summary>
+ internal OutgoingWebResponse() {
+ this.Status = HttpStatusCode.OK;
+ this.Headers = new WebHeaderCollection();
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OutgoingWebResponse"/> class
+ /// based on the contents of an <see cref="HttpWebResponse"/>.
+ /// </summary>
+ /// <param name="response">The <see cref="HttpWebResponse"/> to clone.</param>
+ /// <param name="maximumBytesToRead">The maximum bytes to read from the response stream.</param>
+ protected internal OutgoingWebResponse(HttpWebResponse response, int maximumBytesToRead) {
+ Requires.NotNull(response, "response");
+
+ this.Status = response.StatusCode;
+ this.Headers = response.Headers;
+ this.ResponseStream = new MemoryStream(response.ContentLength < 0 ? 4 * 1024 : (int)response.ContentLength);
+ using (Stream responseStream = response.GetResponseStream()) {
+ // BUGBUG: strictly speaking, is the response were exactly the limit, we'd report it as truncated here.
+ this.IsResponseTruncated = responseStream.CopyUpTo(this.ResponseStream, maximumBytesToRead) == maximumBytesToRead;
+ this.ResponseStream.Seek(0, SeekOrigin.Begin);
+ }
+ }
+
+ /// <summary>
+ /// Gets the headers that must be included in the response to the user agent.
+ /// </summary>
+ /// <remarks>
+ /// The headers in this collection are not meant to be a comprehensive list
+ /// of exactly what should be sent, but are meant to augment whatever headers
+ /// are generally included in a typical response.
+ /// </remarks>
+ public WebHeaderCollection Headers { get; internal set; }
+
+ /// <summary>
+ /// Gets the body of the HTTP response.
+ /// </summary>
+ public Stream ResponseStream { get; internal set; }
+
+ /// <summary>
+ /// Gets a value indicating whether the response stream is incomplete due
+ /// to a length limitation imposed by the HttpWebRequest or calling method.
+ /// </summary>
+ public bool IsResponseTruncated { get; internal set; }
+
+ /// <summary>
+ /// Gets or sets the body of the response as a string.
+ /// </summary>
+ public string Body {
+ get { return this.ResponseStream != null ? this.GetResponseReader().ReadToEnd() : null; }
+ set { this.SetResponse(value, null); }
+ }
+
+ /// <summary>
+ /// Gets the HTTP status code to use in the HTTP response.
+ /// </summary>
+ public HttpStatusCode Status { get; internal set; }
+
+ /// <summary>
+ /// Gets or sets a reference to the actual protocol message that
+ /// is being sent via the user agent.
+ /// </summary>
+ internal IProtocolMessage OriginalMessage { get; set; }
+
+ /// <summary>
+ /// Creates a text reader for the response stream.
+ /// </summary>
+ /// <returns>The text reader, initialized for the proper encoding.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Costly operation")]
+ public StreamReader GetResponseReader() {
+ this.ResponseStream.Seek(0, SeekOrigin.Begin);
+ string contentEncoding = this.Headers[HttpResponseHeader.ContentEncoding];
+ if (string.IsNullOrEmpty(contentEncoding)) {
+ return new StreamReader(this.ResponseStream);
+ } else {
+ return new StreamReader(this.ResponseStream, Encoding.GetEncoding(contentEncoding));
+ }
+ }
+
+ /// <summary>
+ /// Automatically sends the appropriate response to the user agent
+ /// and ends execution on the current page or handler.
+ /// </summary>
+ /// <exception cref="ThreadAbortException">Typically thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception>
+ /// <remarks>
+ /// Requires a current HttpContext.
+ /// </remarks>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual void Send() {
+ Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired);
+
+ this.Send(HttpContext.Current);
+ }
+
+ /// <summary>
+ /// Automatically sends the appropriate response to the user agent
+ /// and ends execution on the current page or handler.
+ /// </summary>
+ /// <param name="context">The context of the HTTP request whose response should be set.
+ /// Typically this is <see cref="HttpContext.Current"/>.</param>
+ /// <exception cref="ThreadAbortException">Typically thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual void Send(HttpContext context) {
+ this.Respond(context, true);
+ }
+
+ /// <summary>
+ /// Automatically sends the appropriate response to the user agent
+ /// and signals ASP.NET to short-circuit the page execution pipeline
+ /// now that the response has been completed.
+ /// Not safe to call from ASP.NET web forms.
+ /// </summary>
+ /// <remarks>
+ /// Requires a current HttpContext.
+ /// This call is not safe to make from an ASP.NET web form (.aspx file or code-behind) because
+ /// ASP.NET will render HTML after the protocol message has been sent, which will corrupt the response.
+ /// Use the <see cref="Send()"/> method instead for web forms.
+ /// </remarks>
+ public virtual void Respond() {
+ Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired);
+
+ this.Respond(HttpContext.Current);
+ }
+
+ /// <summary>
+ /// Automatically sends the appropriate response to the user agent
+ /// and signals ASP.NET to short-circuit the page execution pipeline
+ /// now that the response has been completed.
+ /// Not safe to call from ASP.NET web forms.
+ /// </summary>
+ /// <param name="context">The context of the HTTP request whose response should be set.
+ /// Typically this is <see cref="HttpContext.Current"/>.</param>
+ /// <remarks>
+ /// This call is not safe to make from an ASP.NET web form (.aspx file or code-behind) because
+ /// ASP.NET will render HTML after the protocol message has been sent, which will corrupt the response.
+ /// Use the <see cref="Send()"/> method instead for web forms.
+ /// </remarks>
+ public virtual void Respond(HttpContext context) {
+ Requires.NotNull(context, "context");
+
+ this.Respond(context, false);
+ }
+
+ /// <summary>
+ /// Automatically sends the appropriate response to the user agent.
+ /// </summary>
+ /// <param name="response">The response to set to this message.</param>
+ public virtual void Send(HttpListenerResponse response) {
+ Requires.NotNull(response, "response");
+
+ response.StatusCode = (int)this.Status;
+ MessagingUtilities.ApplyHeadersToResponse(this.Headers, response);
+ if (this.ResponseStream != null) {
+ response.ContentLength64 = this.ResponseStream.Length;
+ this.ResponseStream.CopyTo(response.OutputStream);
+ }
+
+ response.OutputStream.Close();
+ }
+
+ /// <summary>
+ /// Gets the URI that, when requested with an HTTP GET request,
+ /// would transmit the message that normally would be transmitted via a user agent redirect.
+ /// </summary>
+ /// <param name="channel">The channel to use for encoding.</param>
+ /// <returns>
+ /// The URL that would transmit the original message. This URL may exceed the normal 2K limit,
+ /// and should therefore be broken up manually and POSTed as form fields when it exceeds this length.
+ /// </returns>
+ /// <remarks>
+ /// This is useful for desktop applications that will spawn a user agent to transmit the message
+ /// rather than cause a redirect.
+ /// </remarks>
+ internal Uri GetDirectUriRequest(Channel channel) {
+ Requires.NotNull(channel, "channel");
+
+ var message = this.OriginalMessage as IDirectedProtocolMessage;
+ if (message == null) {
+ throw new InvalidOperationException(); // this only makes sense for directed messages (indirect responses)
+ }
+
+ var fields = channel.MessageDescriptions.GetAccessor(message).Serialize();
+ UriBuilder builder = new UriBuilder(message.Recipient);
+ MessagingUtilities.AppendQueryArgs(builder, fields);
+ return builder.Uri;
+ }
+
+ /// <summary>
+ /// Sets the response to some string, encoded as UTF-8.
+ /// </summary>
+ /// <param name="body">The string to set the response to.</param>
+ /// <param name="contentType">Type of the content. May be null.</param>
+ internal void SetResponse(string body, ContentType contentType) {
+ if (body == null) {
+ this.ResponseStream = null;
+ return;
+ }
+
+ if (contentType == null) {
+ contentType = new ContentType("text/html");
+ contentType.CharSet = bodyStringEncoder.WebName;
+ } else if (contentType.CharSet != bodyStringEncoder.WebName) {
+ // clone the original so we're not tampering with our inputs if it came as a parameter.
+ contentType = new ContentType(contentType.ToString());
+ contentType.CharSet = bodyStringEncoder.WebName;
+ }
+
+ this.Headers[HttpResponseHeader.ContentType] = contentType.ToString();
+ this.ResponseStream = new MemoryStream();
+ StreamWriter writer = new StreamWriter(this.ResponseStream, bodyStringEncoder);
+ writer.Write(body);
+ writer.Flush();
+ this.ResponseStream.Seek(0, SeekOrigin.Begin);
+ }
+
+ /// <summary>
+ /// Automatically sends the appropriate response to the user agent
+ /// and signals ASP.NET to short-circuit the page execution pipeline
+ /// now that the response has been completed.
+ /// </summary>
+ /// <param name="context">The context of the HTTP request whose response should be set.
+ /// Typically this is <see cref="HttpContext.Current"/>.</param>
+ /// <param name="endRequest">If set to <c>false</c>, this method calls
+ /// <see cref="HttpApplication.CompleteRequest"/> rather than <see cref="HttpResponse.End"/>
+ /// to avoid a <see cref="ThreadAbortException"/>.</param>
+ protected internal virtual void Respond(HttpContext context, bool endRequest) {
+ Requires.NotNull(context, "context");
+
+ context.Response.Clear();
+ context.Response.StatusCode = (int)this.Status;
+ MessagingUtilities.ApplyHeadersToResponse(this.Headers, context.Response);
+ if (this.ResponseStream != null) {
+ try {
+ this.ResponseStream.CopyTo(context.Response.OutputStream);
+ } catch (HttpException ex) {
+ if (ex.ErrorCode == -2147467259 && context.Response.Output != null) {
+ // Test scenarios can generate this, since the stream is being spoofed:
+ // System.Web.HttpException: OutputStream is not available when a custom TextWriter is used.
+ context.Response.Output.Write(this.Body);
+ } else {
+ throw;
+ }
+ }
+ }
+
+ if (endRequest) {
+ // This approach throws an exception in order that
+ // no more code is executed in the calling page.
+ // Microsoft no longer recommends this approach.
+ context.Response.End();
+ } else if (context.ApplicationInstance != null) {
+ // This approach doesn't throw an exception, but
+ // still tells ASP.NET to short-circuit most of the
+ // request handling pipeline to speed things up.
+ context.ApplicationInstance.CompleteRequest();
+ }
+ }
+ }
+}