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