Refactored HttpListenerResponse.cs

This commit is contained in:
sta 2014-05-22 23:42:06 +09:00
parent de88dc3b15
commit 6a063c64d4

View File

@ -33,7 +33,7 @@
#region Authors #region Authors
/* /*
* Authors: * Authors:
* Gonzalo Paniagua Javier <gonzalo@novell.com> * - Gonzalo Paniagua Javier <gonzalo@novell.com>
*/ */
#endregion #endregion
@ -41,14 +41,12 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
namespace WebSocketSharp.Net namespace WebSocketSharp.Net
{ {
/// <summary> /// <summary>
/// Provides access to a response to a request being processed by the /// Provides the access to a response to a request received by the <see cref="HttpListener"/>.
/// <see cref="HttpListener"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The HttpListenerResponse class cannot be inherited. /// The HttpListenerResponse class cannot be inherited.
@ -67,6 +65,7 @@ namespace WebSocketSharp.Net
private bool _disposed; private bool _disposed;
private bool _forceCloseChunked; private bool _forceCloseChunked;
private WebHeaderCollection _headers; private WebHeaderCollection _headers;
private bool _headersSent;
private bool _keepAlive; private bool _keepAlive;
private string _location; private string _location;
private ResponseStream _outputStream; private ResponseStream _outputStream;
@ -76,12 +75,6 @@ namespace WebSocketSharp.Net
#endregion #endregion
#region Internal Fields
internal bool HeadersSent;
#endregion
#region Internal Constructors #region Internal Constructors
internal HttpListenerResponse (HttpListenerContext context) internal HttpListenerResponse (HttpListenerContext context)
@ -104,37 +97,37 @@ namespace WebSocketSharp.Net
} }
} }
internal bool HeadersSent {
get {
return _headersSent;
}
}
#endregion #endregion
#region Public Properties #region Public Properties
/// <summary> /// <summary>
/// Gets or sets the encoding that can be used with the entity body data /// Gets or sets the encoding for the entity body data included in the response.
/// included in the response.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="Encoding"/> that represents the encoding that can be used /// A <see cref="Encoding"/> that represents the encoding for the entity body data, or
/// with the entity body data. /// <see langword="null"/> if no encoding is specified.
/// </value> /// </value>
/// <exception cref="InvalidOperationException"> /// <exception cref="InvalidOperationException">
/// The response has been sent already. /// The response has already been sent.
/// </exception> /// </exception>
/// <exception cref="ObjectDisposedException"> /// <exception cref="ObjectDisposedException">
/// This object is closed. /// This object is closed.
/// </exception> /// </exception>
public Encoding ContentEncoding { public Encoding ContentEncoding {
get { get {
return _contentEncoding ?? (_contentEncoding = Encoding.Default); checkDisposedOrHeadersSent ();
return _contentEncoding;
} }
set { set {
if (_disposed) checkDisposedOrHeadersSent ();
throw new ObjectDisposedException (GetType ().ToString ());
if (HeadersSent)
throw new InvalidOperationException (
"Cannot be changed after headers are sent.");
_contentEncoding = value; _contentEncoding = value;
} }
} }
@ -143,35 +136,28 @@ namespace WebSocketSharp.Net
/// Gets or sets the size of the entity body data included in the response. /// Gets or sets the size of the entity body data included in the response.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="long"/> that represents the value of the Content-Length /// A <see cref="long"/> that represents the value of the Content-Length entity-header field.
/// entity-header field. The value is a number of bytes in the entity body /// The value is a number of bytes in the entity body data.
/// data.
/// </value> /// </value>
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// The value specified for a set operation is less than zero. /// The value specified for a set operation is less than zero.
/// </exception> /// </exception>
/// <exception cref="InvalidOperationException"> /// <exception cref="InvalidOperationException">
/// The response has been sent already. /// The response has already been sent.
/// </exception> /// </exception>
/// <exception cref="ObjectDisposedException"> /// <exception cref="ObjectDisposedException">
/// This object is closed. /// This object is closed.
/// </exception> /// </exception>
public long ContentLength64 { public long ContentLength64 {
get { get {
checkDisposedOrHeadersSent ();
return _contentLength; return _contentLength;
} }
set { set {
if (_disposed) checkDisposedOrHeadersSent ();
throw new ObjectDisposedException (GetType ().ToString ());
if (HeadersSent)
throw new InvalidOperationException (
"Cannot be changed after headers are sent.");
if (value < 0) if (value < 0)
throw new ArgumentOutOfRangeException ( throw new ArgumentOutOfRangeException ("Less than zero.", "value");
"Must not be less than zero.", "value");
_contentLengthSet = true; _contentLengthSet = true;
_contentLength = value; _contentLength = value;
@ -182,8 +168,8 @@ namespace WebSocketSharp.Net
/// Gets or sets the media type of the entity body included in the response. /// Gets or sets the media type of the entity body included in the response.
/// </summary> /// </summary>
/// <value> /// <value>
/// The type of the content. A <see cref="string"/> that represents the value /// The type of the content. A <see cref="string"/> that represents the value of
/// of the Content-Type entity-header field. /// the Content-Type entity-header field.
/// </value> /// </value>
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
/// The value specified for a set operation is empty. /// The value specified for a set operation is empty.
@ -192,30 +178,24 @@ namespace WebSocketSharp.Net
/// The value specified for a set operation is <see langword="null"/>. /// The value specified for a set operation is <see langword="null"/>.
/// </exception> /// </exception>
/// <exception cref="InvalidOperationException"> /// <exception cref="InvalidOperationException">
/// The response has been sent already. /// The response has already been sent.
/// </exception> /// </exception>
/// <exception cref="ObjectDisposedException"> /// <exception cref="ObjectDisposedException">
/// This object is closed. /// This object is closed.
/// </exception> /// </exception>
public string ContentType { public string ContentType {
get { get {
checkDisposedOrHeadersSent ();
return _contentType; return _contentType;
} }
set { set {
if (_disposed) checkDisposedOrHeadersSent ();
throw new ObjectDisposedException (GetType ().ToString ());
if (HeadersSent)
throw new InvalidOperationException (
"Cannot be changed after headers are sent.");
if (value == null) if (value == null)
throw new ArgumentNullException ("value"); throw new ArgumentNullException ("value");
if (value.Length == 0) if (value.Length == 0)
throw new ArgumentException ( throw new ArgumentException ("An empty string.", "value");
"Must not be empty.", "value");
_contentType = value; _contentType = value;
} }
@ -225,15 +205,22 @@ namespace WebSocketSharp.Net
/// Gets or sets the cookies returned with the response. /// Gets or sets the cookies returned with the response.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="CookieCollection"/> that contains the cookies returned with /// A <see cref="CookieCollection"/> that contains the cookies returned with the response.
/// the response.
/// </value> /// </value>
/// <exception cref="InvalidOperationException">
/// The response has already been sent.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This object is closed.
/// </exception>
public CookieCollection Cookies { public CookieCollection Cookies {
get { get {
checkDisposedOrHeadersSent ();
return _cookies ?? (_cookies = new CookieCollection ()); return _cookies ?? (_cookies = new CookieCollection ());
} }
set { set {
checkDisposedOrHeadersSent ();
_cookies = value; _cookies = value;
} }
} }
@ -242,9 +229,14 @@ namespace WebSocketSharp.Net
/// Gets or sets the HTTP headers returned to the client. /// Gets or sets the HTTP headers returned to the client.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="WebHeaderCollection"/> that contains the HTTP headers /// A <see cref="WebHeaderCollection"/> that contains the headers returned to the client.
/// returned to the client.
/// </value> /// </value>
/// <exception cref="InvalidOperationException">
/// The response has already been sent.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This object is closed.
/// </exception>
public WebHeaderCollection Headers { public WebHeaderCollection Headers {
get { get {
return _headers; return _headers;
@ -259,41 +251,34 @@ namespace WebSocketSharp.Net
* headers manually." * headers manually."
*/ */
// TODO: Support for InvalidOperationException.
// TODO: Check if this is marked readonly after headers are sent. // TODO: Check if this is marked readonly after headers are sent.
checkDisposedOrHeadersSent ();
_headers = value; _headers = value;
} }
} }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the server requests a persistent /// Gets or sets a value indicating whether the server requests a persistent connection.
/// connection.
/// </summary> /// </summary>
/// <value> /// <value>
/// <c>true</c> if the server requests a persistent connection; otherwise, /// <c>true</c> if the server requests a persistent connection; otherwise, <c>false</c>.
/// <c>false</c>. The default is <c>true</c>. /// The default value is <c>true</c>.
/// </value> /// </value>
/// <exception cref="InvalidOperationException"> /// <exception cref="InvalidOperationException">
/// The response has been sent already. /// The response has already been sent.
/// </exception> /// </exception>
/// <exception cref="ObjectDisposedException"> /// <exception cref="ObjectDisposedException">
/// This object is closed. /// This object is closed.
/// </exception> /// </exception>
public bool KeepAlive { public bool KeepAlive {
get { get {
checkDisposedOrHeadersSent ();
return _keepAlive; return _keepAlive;
} }
set { set {
if (_disposed) checkDisposedOrHeadersSent ();
throw new ObjectDisposedException (GetType ().ToString ());
if (HeadersSent)
throw new InvalidOperationException (
"Cannot be changed after headers are sent.");
_keepAlive = value; _keepAlive = value;
} }
} }
@ -312,8 +297,7 @@ namespace WebSocketSharp.Net
if (_disposed) if (_disposed)
throw new ObjectDisposedException (GetType ().ToString ()); throw new ObjectDisposedException (GetType ().ToString ());
return _outputStream ?? return _outputStream ?? (_outputStream = _context.Connection.GetResponseStream ());
(_outputStream = _context.Connection.GetResponseStream ());
} }
} }
@ -321,94 +305,77 @@ namespace WebSocketSharp.Net
/// Gets or sets the HTTP version used in the response. /// Gets or sets the HTTP version used in the response.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="Version"/> that represents the HTTP version used in the /// A <see cref="Version"/> that represents the HTTP version used in the response.
/// response.
/// </value> /// </value>
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
/// The value specified for a set operation doesn't have its <c>Major</c> /// The value specified for a set operation doesn't have its <c>Major</c> property set to 1 or
/// property set to 1 or doesn't have its <c>Minor</c> property set to /// doesn't have its <c>Minor</c> property set to either 0 or 1.
/// either 0 or 1.
/// </exception> /// </exception>
/// <exception cref="ArgumentNullException"> /// <exception cref="ArgumentNullException">
/// The value specified for a set operation is <see langword="null"/>. /// The value specified for a set operation is <see langword="null"/>.
/// </exception> /// </exception>
/// <exception cref="InvalidOperationException"> /// <exception cref="InvalidOperationException">
/// The response has been sent already. /// The response has already been sent.
/// </exception> /// </exception>
/// <exception cref="ObjectDisposedException"> /// <exception cref="ObjectDisposedException">
/// This object is closed. /// This object is closed.
/// </exception> /// </exception>
public Version ProtocolVersion { public Version ProtocolVersion {
get { get {
checkDisposedOrHeadersSent ();
return _version; return _version;
} }
set { set {
if (_disposed) checkDisposedOrHeadersSent ();
throw new ObjectDisposedException (GetType ().ToString ());
if (HeadersSent)
throw new InvalidOperationException (
"Cannot be changed after headers are sent.");
if (value == null) if (value == null)
throw new ArgumentNullException ("value"); throw new ArgumentNullException ("value");
if (value.Major != 1 || (value.Minor != 0 && value.Minor != 1)) if (value.Major != 1 || (value.Minor != 0 && value.Minor != 1))
throw new ArgumentException ("Must be 1.0 or 1.1.", "value"); throw new ArgumentException ("Neither 1.0 nor 1.1.", "value");
_version = value; _version = value;
} }
} }
/// <summary> /// <summary>
/// Gets or sets the URL to which the client is redirected to locate /// Gets or sets the URL to which the client is redirected to locate a requested resource.
/// a requested resource.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="string"/> that represents the value of the Location /// A <see cref="string"/> that represents the value of the Location response-header field.
/// response-header field.
/// </value> /// </value>
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
/// The value specified for a set operation is empty. /// The value specified for a set operation is empty.
/// </exception> /// </exception>
/// <exception cref="InvalidOperationException"> /// <exception cref="InvalidOperationException">
/// The response has been sent already. /// The response has already been sent.
/// </exception> /// </exception>
/// <exception cref="ObjectDisposedException"> /// <exception cref="ObjectDisposedException">
/// This object is closed. /// This object is closed.
/// </exception> /// </exception>
public string RedirectLocation { public string RedirectLocation {
get { get {
checkDisposedOrHeadersSent ();
return _location; return _location;
} }
set { set {
if (_disposed) checkDisposedOrHeadersSent ();
throw new ObjectDisposedException (GetType ().ToString ());
if (HeadersSent)
throw new InvalidOperationException (
"Cannot be changed after headers are sent.");
if (value.Length == 0) if (value.Length == 0)
throw new ArgumentException ( throw new ArgumentException ("An empty string.", "value");
"Must not be empty.", "value");
_location = value; _location = value;
} }
} }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the response uses the chunked /// Gets or sets a value indicating whether the response uses the chunked transfer encoding.
/// transfer encoding.
/// </summary> /// </summary>
/// <value> /// <value>
/// <c>true</c> if the response uses the chunked transfer encoding; /// <c>true</c> if the response uses the chunked transfer encoding; otherwise, <c>false</c>.
/// otherwise, <c>false</c>.
/// </value> /// </value>
/// <exception cref="InvalidOperationException"> /// <exception cref="InvalidOperationException">
/// The response has been sent already. /// The response has already been sent.
/// </exception> /// </exception>
/// <exception cref="ObjectDisposedException"> /// <exception cref="ObjectDisposedException">
/// This object is closed. /// This object is closed.
@ -419,13 +386,7 @@ namespace WebSocketSharp.Net
} }
set { set {
if (_disposed) checkDisposedOrHeadersSent ();
throw new ObjectDisposedException (GetType ().ToString ());
if (HeadersSent)
throw new InvalidOperationException (
"Cannot be changed after headers are sent.");
_chunked = value; _chunked = value;
} }
} }
@ -434,32 +395,26 @@ namespace WebSocketSharp.Net
/// Gets or sets the HTTP status code returned to the client. /// Gets or sets the HTTP status code returned to the client.
/// </summary> /// </summary>
/// <value> /// <value>
/// An <see cref="int"/> that represents the HTTP status code for the /// An <see cref="int"/> that represents the HTTP status code for the response to the request.
/// response to the request. The default is <see cref="HttpStatusCode.OK"/>. /// The default value is <see cref="HttpStatusCode.OK"/>.
/// </value> /// </value>
/// <exception cref="InvalidOperationException"> /// <exception cref="InvalidOperationException">
/// The response has been sent already. /// The response has already been sent.
/// </exception> /// </exception>
/// <exception cref="System.Net.ProtocolViolationException"> /// <exception cref="System.Net.ProtocolViolationException">
/// The value specified for a set operation is invalid. Valid values are /// The value specified for a set operation is invalid. Valid values are between 100 and 999.
/// between 100 and 999.
/// </exception> /// </exception>
/// <exception cref="ObjectDisposedException"> /// <exception cref="ObjectDisposedException">
/// This object is closed. /// This object is closed.
/// </exception> /// </exception>
public int StatusCode { public int StatusCode {
get { get {
checkDisposedOrHeadersSent ();
return _statusCode; return _statusCode;
} }
set { set {
if (_disposed) checkDisposedOrHeadersSent ();
throw new ObjectDisposedException (GetType ().ToString ());
if (HeadersSent)
throw new InvalidOperationException (
"Cannot be changed after headers are sent.");
if (value < 100 || value > 999) if (value < 100 || value > 999)
throw new System.Net.ProtocolViolationException ( throw new System.Net.ProtocolViolationException (
"StatusCode must be between 100 and 999."); "StatusCode must be between 100 and 999.");
@ -470,19 +425,25 @@ namespace WebSocketSharp.Net
} }
/// <summary> /// <summary>
/// Gets or sets the description of the HTTP status code returned to the /// Gets or sets the description of the HTTP status code returned to the client.
/// client.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="String"/> that represents the description of the HTTP status /// A <see cref="string"/> that represents the description of the HTTP status code.
/// code returned to the client.
/// </value> /// </value>
/// <exception cref="InvalidOperationException">
/// The response has already been sent.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This object is closed.
/// </exception>
public string StatusDescription { public string StatusDescription {
get { get {
checkDisposedOrHeadersSent ();
return _statusDescription; return _statusDescription;
} }
set { set {
checkDisposedOrHeadersSent ();
_statusDescription = value == null || value.Length == 0 _statusDescription = value == null || value.Length == 0
? _statusCode.GetStatusDescription () ? _statusCode.GetStatusDescription ()
: value; : value;
@ -498,8 +459,8 @@ namespace WebSocketSharp.Net
if (Cookies.Count == 0) if (Cookies.Count == 0)
return true; return true;
var found = findCookie (cookie); var found = findCookie (cookie).ToList ();
if (found.Count () == 0) if (found.Count == 0)
return true; return true;
foreach (var c in found) foreach (var c in found)
@ -509,6 +470,15 @@ namespace WebSocketSharp.Net
return false; return false;
} }
private void checkDisposedOrHeadersSent ()
{
if (_disposed)
throw new ObjectDisposedException (GetType ().ToString ());
if (_headersSent)
throw new InvalidOperationException ("Cannot be changed after headers are sent.");
}
private void close (bool force) private void close (bool force)
{ {
_disposed = true; _disposed = true;
@ -521,11 +491,11 @@ namespace WebSocketSharp.Net
var domain = cookie.Domain; var domain = cookie.Domain;
var path = cookie.Path; var path = cookie.Path;
return from Cookie c in Cookies foreach (Cookie c in Cookies)
where c.Name.Equals (name, StringComparison.OrdinalIgnoreCase) && if (c.Name.Equals (name, StringComparison.OrdinalIgnoreCase) &&
c.Domain.Equals (domain, StringComparison.OrdinalIgnoreCase) && c.Domain.Equals (domain, StringComparison.OrdinalIgnoreCase) &&
c.Path.Equals (path, StringComparison.Ordinal) c.Path.Equals (path, StringComparison.Ordinal))
select c; yield return c;
} }
#endregion #endregion
@ -535,15 +505,12 @@ namespace WebSocketSharp.Net
internal void SendHeaders (bool closing, MemoryStream stream) internal void SendHeaders (bool closing, MemoryStream stream)
{ {
if (_contentType != null) { if (_contentType != null) {
if (_contentEncoding != null && var contentType = _contentEncoding != null &&
_contentType.IndexOf ("charset=", StringComparison.Ordinal) == -1) { _contentType.IndexOf ("charset=", StringComparison.Ordinal) == -1
var charset = _contentEncoding.WebName; ? _contentType + "; charset=" + _contentEncoding.WebName
_headers.SetInternally ( : _contentType;
"Content-Type", _contentType + "; charset=" + charset, true);
} _headers.SetInternally ("Content-Type", contentType, true);
else {
_headers.SetInternally ("Content-Type", _contentType, true);
}
} }
if (_headers ["Server"] == null) if (_headers ["Server"] == null)
@ -551,8 +518,7 @@ namespace WebSocketSharp.Net
var provider = CultureInfo.InvariantCulture; var provider = CultureInfo.InvariantCulture;
if (_headers ["Date"] == null) if (_headers ["Date"] == null)
_headers.SetInternally ( _headers.SetInternally ("Date", DateTime.UtcNow.ToString ("r", provider), true);
"Date", DateTime.UtcNow.ToString ("r", provider), true);
if (!_chunked) { if (!_chunked) {
if (!_contentLengthSet && closing) { if (!_contentLengthSet && closing) {
@ -561,8 +527,7 @@ namespace WebSocketSharp.Net
} }
if (_contentLengthSet) if (_contentLengthSet)
_headers.SetInternally ( _headers.SetInternally ("Content-Length", _contentLength.ToString (provider), true);
"Content-Length", _contentLength.ToString (provider), true);
} }
var version = _context.Request.ProtocolVersion; var version = _context.Request.ProtocolVersion;
@ -570,13 +535,13 @@ namespace WebSocketSharp.Net
_chunked = true; _chunked = true;
/* Apache forces closing the connection for these status codes: /* Apache forces closing the connection for these status codes:
* HttpStatusCode.BadRequest 400 * - HttpStatusCode.BadRequest 400
* HttpStatusCode.RequestTimeout 408 * - HttpStatusCode.RequestTimeout 408
* HttpStatusCode.LengthRequired 411 * - HttpStatusCode.LengthRequired 411
* HttpStatusCode.RequestEntityTooLarge 413 * - HttpStatusCode.RequestEntityTooLarge 413
* HttpStatusCode.RequestUriTooLong 414 * - HttpStatusCode.RequestUriTooLong 414
* HttpStatusCode.InternalServerError 500 * - HttpStatusCode.InternalServerError 500
* HttpStatusCode.ServiceUnavailable 503 * - HttpStatusCode.ServiceUnavailable 503
*/ */
var connClose = _statusCode == 400 || var connClose = _statusCode == 400 ||
_statusCode == 408 || _statusCode == 408 ||
@ -609,8 +574,8 @@ namespace WebSocketSharp.Net
if (!connClose) { if (!connClose) {
_headers.SetInternally ( _headers.SetInternally (
"Keep-Alive", "Keep-Alive", String.Format ("timeout=15,max={0}", 100 - reuses), true);
String.Format ("timeout=15,max={0}", 100 - reuses), true);
if (_context.Request.ProtocolVersion <= HttpVersion.Version10) if (_context.Request.ProtocolVersion <= HttpVersion.Version10)
_headers.SetInternally ("Connection", "keep-alive", true); _headers.SetInternally ("Connection", "keep-alive", true);
} }
@ -618,25 +583,25 @@ namespace WebSocketSharp.Net
if (_location != null) if (_location != null)
_headers.SetInternally ("Location", _location, true); _headers.SetInternally ("Location", _location, true);
if (_cookies != null) { if (_cookies != null)
foreach (Cookie cookie in _cookies) foreach (Cookie cookie in _cookies)
_headers.SetInternally ("Set-Cookie", cookie.ToResponseString (), true); _headers.SetInternally ("Set-Cookie", cookie.ToResponseString (), true);
}
var encoding = _contentEncoding ?? Encoding.Default; var encoding = _contentEncoding ?? Encoding.Default;
var writer = new StreamWriter (stream, encoding, 256); var writer = new StreamWriter (stream, encoding, 256);
writer.Write ( writer.Write ("HTTP/{0} {1} {2}\r\n", _version, _statusCode, _statusDescription);
"HTTP/{0} {1} {2}\r\n", _version, _statusCode, _statusDescription);
var headers = _headers.ToStringMultiValue (true); var headers = _headers.ToStringMultiValue (true);
writer.Write (headers); writer.Write (headers);
writer.Flush (); writer.Flush ();
var preamble = encoding.CodePage == 65001 ? 3 : encoding.GetPreamble ().Length; var preamble = encoding.CodePage == 65001 ? 3 : encoding.GetPreamble ().Length;
if (_outputStream == null) if (_outputStream == null)
_outputStream = _context.Connection.GetResponseStream (); _outputStream = _context.Connection.GetResponseStream ();
// Assumes that the stream was at position 0. // Assumes that the stream was at position 0.
stream.Position = preamble; stream.Position = preamble;
HeadersSent = true; _headersSent = true;
} }
#endregion #endregion
@ -655,86 +620,123 @@ namespace WebSocketSharp.Net
} }
/// <summary> /// <summary>
/// Adds the specified HTTP header <paramref name="name"/> and /// Adds an HTTP header with the specified <paramref name="name"/> and <paramref name="value"/>
/// <paramref name="value"/> to the headers for this response. /// to the headers for the response.
/// </summary> /// </summary>
/// <param name="name"> /// <param name="name">
/// A <see cref="string"/> that contains the name of the HTTP header to add. /// A <see cref="string"/> that represents the name of the header to add.
/// </param> /// </param>
/// <param name="value"> /// <param name="value">
/// A <see cref="string"/> that contains the value of the HTTP header to add. /// A <see cref="string"/> that represents the value of the header to add.
/// </param> /// </param>
/// <exception cref="ArgumentException">
/// <para>
/// <paramref name="name"/> or <paramref name="value"/> contains invalid characters.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="name"/> is a restricted header name.
/// </para>
/// </exception>
/// <exception cref="ArgumentNullException"> /// <exception cref="ArgumentNullException">
/// <paramref name="name"/> is <see langword="null"/> or empty. /// <paramref name="name"/> is <see langword="null"/> or empty.
/// </exception> /// </exception>
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// The length of <paramref name="value"/> is greater than 65,535 characters. /// The length of <paramref name="value"/> is greater than 65,535 characters.
/// </exception> /// </exception>
/// <exception cref="InvalidOperationException">
/// <para>
/// The response has already been sent.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// The header cannot be allowed to add to the current headers.
/// </para>
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This object is closed.
/// </exception>
public void AddHeader (string name, string value) public void AddHeader (string name, string value)
{ {
if (name == null || name.Length == 0) checkDisposedOrHeadersSent ();
throw new ArgumentNullException ("name");
// TODO: Check for forbidden headers and invalid characters.
if (value.Length > 65535)
throw new ArgumentOutOfRangeException (
"value", "Greater than 65,535 characters.");
_headers.Set (name, value); _headers.Set (name, value);
} }
/// <summary> /// <summary>
/// Adds the specified <see cref="Cookie"/> to the <see cref="Cookies"/> sent /// Appends the specified <paramref name="cookie"/> to the cookies sent with the response.
/// with the response.
/// </summary> /// </summary>
/// <param name="cookie"> /// <param name="cookie">
/// A <see cref="Cookie"/> to add to the <see cref="Cookies"/>. /// A <see cref="Cookie"/> to append.
/// </param> /// </param>
/// <exception cref="ArgumentNullException"> /// <exception cref="ArgumentNullException">
/// <paramref name="cookie"/> is <see langword="null"/>. /// <paramref name="cookie"/> is <see langword="null"/>.
/// </exception> /// </exception>
/// <exception cref="InvalidOperationException">
/// The response has already been sent.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This object is closed.
/// </exception>
public void AppendCookie (Cookie cookie) public void AppendCookie (Cookie cookie)
{ {
if (cookie == null) checkDisposedOrHeadersSent ();
throw new ArgumentNullException ("cookie");
Cookies.Add (cookie); Cookies.Add (cookie);
} }
/// <summary> /// <summary>
/// Appends a <paramref name="value"/> to the specified HTTP header sent with /// Appends a <paramref name="value"/> to the specified HTTP header sent with the response.
/// the response.
/// </summary> /// </summary>
/// <param name="name"> /// <param name="name">
/// A <see cref="string"/> that contains the name of the HTTP header to /// A <see cref="string"/> that represents the name of the header to append
/// append <paramref name="value"/> to. /// <paramref name="value"/> to.
/// </param> /// </param>
/// <param name="value"> /// <param name="value">
/// A <see cref="string"/> that contains the value to append to the HTTP /// A <see cref="string"/> that represents the value to append to the header.
/// header.
/// </param> /// </param>
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
/// <para>
/// <paramref name="name"/> or <paramref name="value"/> contains invalid characters.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="name"/> is a restricted header name.
/// </para>
/// </exception>
/// <exception cref="ArgumentNullException">
/// <paramref name="name"/> is <see langword="null"/> or empty. /// <paramref name="name"/> is <see langword="null"/> or empty.
/// </exception> /// </exception>
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// The length of <paramref name="value"/> is greater than 65,535 characters. /// The length of <paramref name="value"/> is greater than 65,535 characters.
/// </exception> /// </exception>
/// <exception cref="InvalidOperationException">
/// <para>
/// The response has already been sent.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// The current headers cannot allow the header to append a value.
/// </para>
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This object is closed.
/// </exception>
public void AppendHeader (string name, string value) public void AppendHeader (string name, string value)
{ {
// TODO: Check for forbidden headers and invalid characters. checkDisposedOrHeadersSent ();
if (name == null || name.Length == 0)
throw new ArgumentException ("Must not be null or empty.", "name");
if (value.Length > 65535)
throw new ArgumentOutOfRangeException (
"value", "Greater than 65,535 characters.");
_headers.Add (name, value); _headers.Add (name, value);
} }
/// <summary> /// <summary>
/// Sends the response to the client and releases the resources associated /// Sends the response to the client, and releases the resources used by
/// with the <see cref="HttpListenerResponse"/> instance. /// this <see cref="HttpListenerResponse"/> instance.
/// </summary> /// </summary>
public void Close () public void Close ()
{ {
@ -745,47 +747,68 @@ namespace WebSocketSharp.Net
} }
/// <summary> /// <summary>
/// Sends the response with the specified array of <see cref="byte"/> to the /// Sends the response with the specified array of <see cref="byte"/> to the client, and
/// client and releases the resources associated with the /// releases the resources used by this <see cref="HttpListenerResponse"/> instance.
/// <see cref="HttpListenerResponse"/> instance.
/// </summary> /// </summary>
/// <param name="responseEntity"> /// <param name="responseEntity">
/// An array of <see cref="byte"/> that contains the response entity body /// An array of <see cref="byte"/> that contains the response entity body data.
/// data.
/// </param> /// </param>
/// <param name="willBlock"> /// <param name="willBlock">
/// <c>true</c> if this method blocks execution while flushing the stream to /// <c>true</c> if this method blocks execution while flushing the stream to the client;
/// the client; otherwise, <c>false</c>. /// otherwise, <c>false</c>.
/// </param> /// </param>
/// <exception cref="ArgumentNullException"> /// <exception cref="ArgumentNullException">
/// <paramref name="responseEntity"/> is <see langword="null"/>. /// <paramref name="responseEntity"/> is <see langword="null"/>.
/// </exception> /// </exception>
/// <exception cref="InvalidOperationException">
/// The response has already been sent.
/// </exception>
/// <exception cref="ObjectDisposedException"> /// <exception cref="ObjectDisposedException">
/// This object is closed. /// This object is closed.
/// </exception> /// </exception>
public void Close (byte [] responseEntity, bool willBlock) public void Close (byte [] responseEntity, bool willBlock)
{ {
if (_disposed)
throw new ObjectDisposedException (GetType ().ToString ());
if (responseEntity == null) if (responseEntity == null)
throw new ArgumentNullException ("responseEntity"); throw new ArgumentNullException ("responseEntity");
// TODO: If willBlock -> BeginWrite + Close? var len = responseEntity.Length;
ContentLength64 = responseEntity.Length; ContentLength64 = len;
OutputStream.Write (responseEntity, 0, (int) _contentLength);
var output = OutputStream;
if (willBlock) {
output.Write (responseEntity, 0, len);
close (false); close (false);
return;
}
output.BeginWrite (
responseEntity,
0,
len,
ar => {
output.EndWrite (ar);
close (false);
},
null);
} }
/// <summary> /// <summary>
/// Copies properties from the specified <see cref="HttpListenerResponse"/> /// Copies properties from the specified <see cref="HttpListenerResponse"/> to this response.
/// to this response.
/// </summary> /// </summary>
/// <param name="templateResponse"> /// <param name="templateResponse">
/// A <see cref="HttpListenerResponse"/> to copy. /// A <see cref="HttpListenerResponse"/> to copy.
/// </param> /// </param>
/// <exception cref="InvalidOperationException">
/// The response has already been sent.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This object is closed.
/// </exception>
public void CopyFrom (HttpListenerResponse templateResponse) public void CopyFrom (HttpListenerResponse templateResponse)
{ {
checkDisposedOrHeadersSent ();
_headers.Clear (); _headers.Clear ();
_headers.Add (templateResponse._headers); _headers.Add (templateResponse._headers);
_contentLength = templateResponse._contentLength; _contentLength = templateResponse._contentLength;
@ -800,9 +823,14 @@ namespace WebSocketSharp.Net
/// <paramref name="url"/>. /// <paramref name="url"/>.
/// </summary> /// </summary>
/// <param name="url"> /// <param name="url">
/// A <see cref="string"/> that represents the URL to redirect the client's /// A <see cref="string"/> that represents the URL to redirect the client's request to.
/// request to.
/// </param> /// </param>
/// <exception cref="InvalidOperationException">
/// The response has already been sent.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This object is closed.
/// </exception>
public void Redirect (string url) public void Redirect (string url)
{ {
StatusCode = (int) HttpStatusCode.Redirect; StatusCode = (int) HttpStatusCode.Redirect;
@ -810,21 +838,26 @@ namespace WebSocketSharp.Net
} }
/// <summary> /// <summary>
/// Adds or updates a <see cref="Cookie"/> in the <see cref="Cookies"/> sent /// Adds or updates a <paramref name="cookie"/> in the cookies sent with the response.
/// with the response.
/// </summary> /// </summary>
/// <param name="cookie"> /// <param name="cookie">
/// A <see cref="Cookie"/> to set. /// A <see cref="Cookie"/> to set.
/// </param> /// </param>
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
/// <paramref name="cookie"/> already exists in the <see cref="Cookies"/> and /// <paramref name="cookie"/> already exists in the cookies, and couldn't be replaced.
/// could not be replaced.
/// </exception> /// </exception>
/// <exception cref="ArgumentNullException"> /// <exception cref="ArgumentNullException">
/// <paramref name="cookie"/> is <see langword="null"/>. /// <paramref name="cookie"/> is <see langword="null"/>.
/// </exception> /// </exception>
/// <exception cref="InvalidOperationException">
/// The response has already been sent.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// This object is closed.
/// </exception>
public void SetCookie (Cookie cookie) public void SetCookie (Cookie cookie)
{ {
checkDisposedOrHeadersSent ();
if (cookie == null) if (cookie == null)
throw new ArgumentNullException ("cookie"); throw new ArgumentNullException ("cookie");
@ -843,8 +876,11 @@ namespace WebSocketSharp.Net
/// </summary> /// </summary>
void IDisposable.Dispose () void IDisposable.Dispose ()
{ {
if (_disposed)
return;
// TODO: Abort or Close? // TODO: Abort or Close?
close (true); close (true); // Same as Abort.
} }
#endregion #endregion