Updated
This commit is contained in:
parent
e6517ad662
commit
08a2be3221
|
@ -84,7 +84,7 @@ namespace EonaCat.Network
|
|||
/// <returns>A collection of authentication parameters.</returns>
|
||||
internal static NameValueCollection ParseParameters(string value)
|
||||
{
|
||||
var res = new NameValueCollection();
|
||||
var result = new NameValueCollection();
|
||||
foreach (var param in value.SplitHeaderValue(','))
|
||||
{
|
||||
var i = param.IndexOf('=');
|
||||
|
@ -95,10 +95,10 @@ namespace EonaCat.Network
|
|||
? param.Substring(i + 1).Trim().Trim('"')
|
||||
: string.Empty;
|
||||
|
||||
res.Add(name, val);
|
||||
result.Add(name, val);
|
||||
}
|
||||
|
||||
return res;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -15,16 +15,6 @@ namespace EonaCat.Network
|
|||
private const string DIGEST = "digest";
|
||||
private const int DIGEST_SIZE = 128;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AuthenticationChallenge"/> class.
|
||||
/// </summary>
|
||||
/// <param name="scheme">The authentication scheme.</param>
|
||||
/// <param name="parameters">The collection of authentication parameters.</param>
|
||||
private AuthenticationChallenge(AuthenticationSchemes scheme, NameValueCollection parameters)
|
||||
: base(scheme, parameters)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AuthenticationChallenge"/> class for Basic or Digest authentication.
|
||||
/// </summary>
|
||||
|
@ -42,6 +32,15 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AuthenticationChallenge"/> class.
|
||||
/// </summary>
|
||||
/// <param name="scheme">The authentication scheme.</param>
|
||||
/// <param name="parameters">The collection of authentication parameters.</param>
|
||||
private AuthenticationChallenge(AuthenticationSchemes scheme, NameValueCollection parameters)
|
||||
: base(scheme, parameters)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the domain for Digest authentication.
|
||||
/// </summary>
|
||||
|
|
|
@ -18,16 +18,6 @@ namespace EonaCat.Network
|
|||
private const string DIGEST = "digest";
|
||||
private uint _nonceCount;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AuthenticationResponse"/> class.
|
||||
/// </summary>
|
||||
/// <param name="scheme">The authentication scheme.</param>
|
||||
/// <param name="parameters">The collection of authentication parameters.</param>
|
||||
private AuthenticationResponse(AuthenticationSchemes scheme, NameValueCollection parameters)
|
||||
: base(scheme, parameters)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AuthenticationResponse"/> class for Basic authentication.
|
||||
/// </summary>
|
||||
|
@ -74,12 +64,14 @@ namespace EonaCat.Network
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the nonce count.
|
||||
/// Initializes a new instance of the <see cref="AuthenticationResponse"/> class.
|
||||
/// </summary>
|
||||
internal uint NonceCount => _nonceCount < uint.MaxValue
|
||||
? _nonceCount
|
||||
: 0;
|
||||
|
||||
/// <param name="scheme">The authentication scheme.</param>
|
||||
/// <param name="parameters">The collection of authentication parameters.</param>
|
||||
private AuthenticationResponse(AuthenticationSchemes scheme, NameValueCollection parameters)
|
||||
: base(scheme, parameters)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the cnonce value for Digest authentication.
|
||||
/// </summary>
|
||||
|
@ -111,97 +103,23 @@ namespace EonaCat.Network
|
|||
public string UserName => Parameters["username"];
|
||||
|
||||
/// <summary>
|
||||
/// Creates the A1 value for Digest authentication.
|
||||
/// Gets the nonce count.
|
||||
/// </summary>
|
||||
/// <param name="username">The username.</param>
|
||||
/// <param name="password">The password.</param>
|
||||
/// <param name="realm">The realm.</param>
|
||||
/// <returns>The A1 value.</returns>
|
||||
private static string CreateA1(string username, string password, string realm)
|
||||
{
|
||||
return $"{username}:{realm}:{password}";
|
||||
}
|
||||
|
||||
internal uint NonceCount => _nonceCount < uint.MaxValue
|
||||
? _nonceCount
|
||||
: 0;
|
||||
/// <summary>
|
||||
/// Creates the A1 value for Digest authentication with cnonce and nonce.
|
||||
/// Converts the authentication response to an identity.
|
||||
/// </summary>
|
||||
/// <param name="username">The username.</param>
|
||||
/// <param name="password">The password.</param>
|
||||
/// <param name="realm">The realm.</param>
|
||||
/// <param name="nonce">The nonce.</param>
|
||||
/// <param name="cnonce">The cnonce.</param>
|
||||
/// <returns>The A1 value.</returns>
|
||||
private static string CreateA1(
|
||||
string username, string password, string realm, string nonce, string cnonce)
|
||||
/// <returns>An instance of <see cref="IIdentity"/>.</returns>
|
||||
public IIdentity ToIdentity()
|
||||
{
|
||||
return $"{Hash(CreateA1(username, password, realm))}:{nonce}:{cnonce}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the A2 value for Digest authentication.
|
||||
/// </summary>
|
||||
/// <param name="method">The HTTP method.</param>
|
||||
/// <param name="uri">The URI.</param>
|
||||
/// <returns>The A2 value.</returns>
|
||||
private static string CreateA2(string method, string uri)
|
||||
{
|
||||
return $"{method}:{uri}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the A2 value for Digest authentication with an entity.
|
||||
/// </summary>
|
||||
/// <param name="method">The HTTP method.</param>
|
||||
/// <param name="uri">The URI.</param>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <returns>The A2 value.</returns>
|
||||
private static string CreateA2(string method, string uri, string entity)
|
||||
{
|
||||
return $"{method}:{uri}:{Hash(entity)}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the MD5 hash of the given value.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to hash.</param>
|
||||
/// <returns>The MD5 hash.</returns>
|
||||
private static string Hash(string value)
|
||||
{
|
||||
var source = Encoding.UTF8.GetBytes(value);
|
||||
var md5 = MD5.Create();
|
||||
var hashed = md5.ComputeHash(source);
|
||||
|
||||
var result = new StringBuilder(64);
|
||||
foreach (var currentByte in hashed)
|
||||
{
|
||||
result.Append(currentByte.ToString("x2"));
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the authentication as Digest.
|
||||
/// </summary>
|
||||
private void InitAsDigest()
|
||||
{
|
||||
var qops = Parameters["qop"];
|
||||
if (qops != null)
|
||||
{
|
||||
if (qops.Split(',').Contains(qop => qop.Trim().ToLower() == "auth"))
|
||||
{
|
||||
Parameters["qop"] = "auth";
|
||||
Parameters["cnonce"] = CreateNonceValue();
|
||||
Parameters["nc"] = string.Format("{0:x8}", ++_nonceCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
Parameters["qop"] = null;
|
||||
}
|
||||
}
|
||||
|
||||
Parameters["method"] = "GET";
|
||||
Parameters["response"] = CreateRequestDigest(Parameters);
|
||||
var scheme = Scheme;
|
||||
return scheme == AuthenticationSchemes.Basic
|
||||
? new HttpBasicIdentity(Parameters["username"], Parameters["password"])
|
||||
: scheme == AuthenticationSchemes.Digest
|
||||
? new HttpDigestIdentity(Parameters)
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -343,17 +261,97 @@ namespace EonaCat.Network
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the authentication response to an identity.
|
||||
/// Creates the A1 value for Digest authentication.
|
||||
/// </summary>
|
||||
/// <returns>An instance of <see cref="IIdentity"/>.</returns>
|
||||
public IIdentity ToIdentity()
|
||||
/// <param name="username">The username.</param>
|
||||
/// <param name="password">The password.</param>
|
||||
/// <param name="realm">The realm.</param>
|
||||
/// <returns>The A1 value.</returns>
|
||||
private static string CreateA1(string username, string password, string realm)
|
||||
{
|
||||
var scheme = Scheme;
|
||||
return scheme == AuthenticationSchemes.Basic
|
||||
? new HttpBasicIdentity(Parameters["username"], Parameters["password"])
|
||||
: scheme == AuthenticationSchemes.Digest
|
||||
? new HttpDigestIdentity(Parameters)
|
||||
: null;
|
||||
return $"{username}:{realm}:{password}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the A1 value for Digest authentication with cnonce and nonce.
|
||||
/// </summary>
|
||||
/// <param name="username">The username.</param>
|
||||
/// <param name="password">The password.</param>
|
||||
/// <param name="realm">The realm.</param>
|
||||
/// <param name="nonce">The nonce.</param>
|
||||
/// <param name="cnonce">The cnonce.</param>
|
||||
/// <returns>The A1 value.</returns>
|
||||
private static string CreateA1(
|
||||
string username, string password, string realm, string nonce, string cnonce)
|
||||
{
|
||||
return $"{Hash(CreateA1(username, password, realm))}:{nonce}:{cnonce}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the A2 value for Digest authentication.
|
||||
/// </summary>
|
||||
/// <param name="method">The HTTP method.</param>
|
||||
/// <param name="uri">The URI.</param>
|
||||
/// <returns>The A2 value.</returns>
|
||||
private static string CreateA2(string method, string uri)
|
||||
{
|
||||
return $"{method}:{uri}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the A2 value for Digest authentication with an entity.
|
||||
/// </summary>
|
||||
/// <param name="method">The HTTP method.</param>
|
||||
/// <param name="uri">The URI.</param>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <returns>The A2 value.</returns>
|
||||
private static string CreateA2(string method, string uri, string entity)
|
||||
{
|
||||
return $"{method}:{uri}:{Hash(entity)}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the MD5 hash of the given value.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to hash.</param>
|
||||
/// <returns>The MD5 hash.</returns>
|
||||
private static string Hash(string value)
|
||||
{
|
||||
var source = Encoding.UTF8.GetBytes(value);
|
||||
var md5 = MD5.Create();
|
||||
var hashed = md5.ComputeHash(source);
|
||||
|
||||
var result = new StringBuilder(64);
|
||||
foreach (var currentByte in hashed)
|
||||
{
|
||||
result.Append(currentByte.ToString("x2"));
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the authentication as Digest.
|
||||
/// </summary>
|
||||
private void InitAsDigest()
|
||||
{
|
||||
var qops = Parameters["qop"];
|
||||
if (qops != null)
|
||||
{
|
||||
if (qops.Split(',').Contains(qop => qop.Trim().ToLower() == "auth"))
|
||||
{
|
||||
Parameters["qop"] = "auth";
|
||||
Parameters["cnonce"] = CreateNonceValue();
|
||||
Parameters["nc"] = string.Format("{0:x8}", ++_nonceCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
Parameters["qop"] = null;
|
||||
}
|
||||
}
|
||||
|
||||
Parameters["method"] = "GET";
|
||||
Parameters["response"] = CreateRequestDigest(Parameters);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,8 +10,8 @@ namespace EonaCat.Network
|
|||
/// </summary>
|
||||
public class NetworkCredential
|
||||
{
|
||||
private string _domain;
|
||||
private static readonly string[] _noRoles;
|
||||
private string _domain;
|
||||
private string _password;
|
||||
private string[] _roles;
|
||||
|
||||
|
|
|
@ -15,11 +15,11 @@ namespace EonaCat.Network
|
|||
/// </summary>
|
||||
internal class ChunkStream
|
||||
{
|
||||
private readonly List<WebChunk> _chunks;
|
||||
private readonly StringBuilder _saved;
|
||||
private int _chunkRead;
|
||||
private int _chunkSize;
|
||||
private readonly List<WebChunk> _chunks;
|
||||
private bool _foundSPCode;
|
||||
private readonly StringBuilder _saved;
|
||||
private bool _gotChunck;
|
||||
private InputChunkState _state;
|
||||
private int _trailerState;
|
||||
|
@ -49,11 +49,6 @@ namespace EonaCat.Network
|
|||
Write(buffer, offset, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the web headers associated with the chunk stream.
|
||||
/// </summary>
|
||||
internal WebHeaderCollection Headers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of bytes left in the current chunk.
|
||||
/// </summary>
|
||||
|
@ -64,6 +59,78 @@ namespace EonaCat.Network
|
|||
/// </summary>
|
||||
public bool WantMore => _state != InputChunkState.End;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the web headers associated with the chunk stream.
|
||||
/// </summary>
|
||||
internal WebHeaderCollection Headers { get; }
|
||||
/// <summary>
|
||||
/// Reads a specified amount of data from the chunk stream.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The destination buffer.</param>
|
||||
/// <param name="offset">The zero-based byte offset in the buffer at which to begin storing the data.</param>
|
||||
/// <param name="count">The maximum number of bytes to read.</param>
|
||||
/// <returns>The total number of bytes read into the buffer.</returns>
|
||||
public int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (count <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return read(buffer, offset, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a specified amount of data to the chunk stream.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The byte array containing data to be written to the chunk stream.</param>
|
||||
/// <param name="offset">The offset in the buffer at which to begin writing.</param>
|
||||
/// <param name="count">The number of bytes to write to the chunk stream.</param>
|
||||
public void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Write(buffer, ref offset, offset + count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the internal buffer and state of the chunk stream.
|
||||
/// </summary>
|
||||
internal void ResetBuffer()
|
||||
{
|
||||
_chunkRead = 0;
|
||||
_chunkSize = -1;
|
||||
_chunks.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a specified amount of data to the chunk stream and reads it back.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The byte array containing data to be written to the chunk stream.</param>
|
||||
/// <param name="offset">The offset in the buffer at which to begin writing.</param>
|
||||
/// <param name="writeCount">The number of bytes to write to the chunk stream.</param>
|
||||
/// <param name="readCount">The number of bytes to read back from the chunk stream.</param>
|
||||
/// <returns>The number of bytes read from the chunk stream.</returns>
|
||||
internal int WriteAndReadBack(byte[] buffer, int offset, int writeCount, int readCount)
|
||||
{
|
||||
Write(buffer, offset, writeCount);
|
||||
return Read(buffer, offset, readCount);
|
||||
}
|
||||
|
||||
private static string RemoveChunkExtension(string value)
|
||||
{
|
||||
var index = value.IndexOf(';');
|
||||
return index > -1 ? value.Substring(0, index) : value;
|
||||
}
|
||||
|
||||
private static void ThrowProtocolViolation(string message)
|
||||
{
|
||||
throw new WebException($"EonaCat Network: {message}", null, WebExceptionStatus.ServerProtocolViolation, null);
|
||||
}
|
||||
|
||||
private int read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var nread = 0;
|
||||
|
@ -92,13 +159,6 @@ namespace EonaCat.Network
|
|||
|
||||
return nread;
|
||||
}
|
||||
|
||||
private static string RemoveChunkExtension(string value)
|
||||
{
|
||||
var index = value.IndexOf(';');
|
||||
return index > -1 ? value.Substring(0, index) : value;
|
||||
}
|
||||
|
||||
private InputChunkState SeekCrLf(byte[] buffer, ref int offset, int length)
|
||||
{
|
||||
if (!_gotChunck)
|
||||
|
@ -256,12 +316,6 @@ namespace EonaCat.Network
|
|||
|
||||
return InputChunkState.End;
|
||||
}
|
||||
|
||||
private static void ThrowProtocolViolation(string message)
|
||||
{
|
||||
throw new WebException($"EonaCat Network: {message}", null, WebExceptionStatus.ServerProtocolViolation, null);
|
||||
}
|
||||
|
||||
private void Write(byte[] buffer, ref int offset, int length)
|
||||
{
|
||||
if (_state == InputChunkState.End)
|
||||
|
@ -337,62 +391,5 @@ namespace EonaCat.Network
|
|||
|
||||
return _chunkRead == _chunkSize ? InputChunkState.DataEnded : InputChunkState.Data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the internal buffer and state of the chunk stream.
|
||||
/// </summary>
|
||||
internal void ResetBuffer()
|
||||
{
|
||||
_chunkRead = 0;
|
||||
_chunkSize = -1;
|
||||
_chunks.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a specified amount of data to the chunk stream and reads it back.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The byte array containing data to be written to the chunk stream.</param>
|
||||
/// <param name="offset">The offset in the buffer at which to begin writing.</param>
|
||||
/// <param name="writeCount">The number of bytes to write to the chunk stream.</param>
|
||||
/// <param name="readCount">The number of bytes to read back from the chunk stream.</param>
|
||||
/// <returns>The number of bytes read from the chunk stream.</returns>
|
||||
internal int WriteAndReadBack(byte[] buffer, int offset, int writeCount, int readCount)
|
||||
{
|
||||
Write(buffer, offset, writeCount);
|
||||
return Read(buffer, offset, readCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a specified amount of data from the chunk stream.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The destination buffer.</param>
|
||||
/// <param name="offset">The zero-based byte offset in the buffer at which to begin storing the data.</param>
|
||||
/// <param name="count">The maximum number of bytes to read.</param>
|
||||
/// <returns>The total number of bytes read into the buffer.</returns>
|
||||
public int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (count <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return read(buffer, offset, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a specified amount of data to the chunk stream.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The byte array containing data to be written to the chunk stream.</param>
|
||||
/// <param name="offset">The offset in the buffer at which to begin writing.</param>
|
||||
/// <param name="count">The number of bytes to write to the chunk stream.</param>
|
||||
public void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Write(buffer, ref offset, offset + count);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,37 +36,6 @@ namespace EonaCat.Network
|
|||
/// </summary>
|
||||
internal ChunkStream Decoder { get; set; }
|
||||
|
||||
private void OnRead(IAsyncResult asyncResult)
|
||||
{
|
||||
var readBufferState = (ReadBufferState)asyncResult.AsyncState;
|
||||
var result = readBufferState.AsyncResult;
|
||||
try
|
||||
{
|
||||
var nread = base.EndRead(asyncResult);
|
||||
Decoder.Write(result.Buffer, result.Offset, nread);
|
||||
nread = Decoder.Read(readBufferState.Buffer, readBufferState.Offset, readBufferState.Count);
|
||||
readBufferState.Offset += nread;
|
||||
readBufferState.Count -= nread;
|
||||
if (readBufferState.Count == 0 || !Decoder.WantMore || nread == 0)
|
||||
{
|
||||
_noMoreData = !Decoder.WantMore && nread == 0;
|
||||
result.Count = readBufferState.InitialCount - readBufferState.Count;
|
||||
result.Complete();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
result.Offset = 0;
|
||||
result.Count = Math.Min(_bufferLength, Decoder.ChunkLeft + 6);
|
||||
base.BeginRead(result.Buffer, result.Offset, result.Count, OnRead, readBufferState);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_context.Connection.SendError(ex.Message, 400);
|
||||
result.Complete(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begins an asynchronous read operation from the stream.
|
||||
/// </summary>
|
||||
|
@ -204,5 +173,36 @@ namespace EonaCat.Network
|
|||
var result = BeginRead(buffer, offset, count, null, null);
|
||||
return EndRead(result);
|
||||
}
|
||||
|
||||
private void OnRead(IAsyncResult asyncResult)
|
||||
{
|
||||
var readBufferState = (ReadBufferState)asyncResult.AsyncState;
|
||||
var result = readBufferState.AsyncResult;
|
||||
try
|
||||
{
|
||||
var nread = base.EndRead(asyncResult);
|
||||
Decoder.Write(result.Buffer, result.Offset, nread);
|
||||
nread = Decoder.Read(readBufferState.Buffer, readBufferState.Offset, readBufferState.Count);
|
||||
readBufferState.Offset += nread;
|
||||
readBufferState.Count -= nread;
|
||||
if (readBufferState.Count == 0 || !Decoder.WantMore || nread == 0)
|
||||
{
|
||||
_noMoreData = !Decoder.WantMore && nread == 0;
|
||||
result.Count = readBufferState.InitialCount - readBufferState.Count;
|
||||
result.Complete();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
result.Offset = 0;
|
||||
result.Count = Math.Min(_bufferLength, Decoder.ChunkLeft + 6);
|
||||
base.BeginRead(result.Buffer, result.Offset, result.Count, OnRead, readBufferState);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_context.Connection.SendError(ex.Message, 400);
|
||||
result.Complete(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -90,6 +90,10 @@ namespace EonaCat.Network
|
|||
};
|
||||
}
|
||||
|
||||
public WebHeaderCollection()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the WebHeaderCollection class with the specified parameters.
|
||||
/// </summary>
|
||||
|
@ -132,18 +136,10 @@ namespace EonaCat.Network
|
|||
throw new ArgumentException(ex.Message, nameof(serializationInfo), ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public WebHeaderCollection()
|
||||
{
|
||||
}
|
||||
|
||||
internal HttpHeaderType State => _state;
|
||||
|
||||
public override string[] AllKeys => base.AllKeys;
|
||||
|
||||
public override int Count => base.Count;
|
||||
|
||||
public override KeysCollection Keys => base.Keys;
|
||||
internal HttpHeaderType State => _state;
|
||||
public string this[HttpRequestHeader header]
|
||||
{
|
||||
get
|
||||
|
@ -169,272 +165,14 @@ namespace EonaCat.Network
|
|||
Add(header, value);
|
||||
}
|
||||
}
|
||||
|
||||
public override KeysCollection Keys => base.Keys;
|
||||
|
||||
private void add(string name, string value, bool ignoreRestricted)
|
||||
public static bool IsRestricted(string headerName)
|
||||
{
|
||||
var act = ignoreRestricted
|
||||
? (Action<string, string>)addWithoutCheckingNameAndRestricted
|
||||
: addWithoutCheckingName;
|
||||
|
||||
DoWithCheckingState(act, CheckName(name), value, true);
|
||||
return isRestricted(CheckName(headerName), false);
|
||||
}
|
||||
|
||||
private void addWithoutCheckingName(string name, string value)
|
||||
public static bool IsRestricted(string headerName, bool response)
|
||||
{
|
||||
DoWithoutCheckingName(base.Add, name, value);
|
||||
}
|
||||
|
||||
private void addWithoutCheckingNameAndRestricted(string name, string value)
|
||||
{
|
||||
base.Add(name, CheckValue(value));
|
||||
}
|
||||
|
||||
private static int checkColonSeparated(string header)
|
||||
{
|
||||
var idx = header.IndexOf(':');
|
||||
if (idx == -1)
|
||||
{
|
||||
throw new ArgumentException("No colon could be found.", nameof(header));
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
private static HttpHeaderType CheckHeaderType(string name)
|
||||
{
|
||||
var info = GetHeaderInfo(name);
|
||||
return info == null
|
||||
? HttpHeaderType.Unspecified
|
||||
: info.IsRequest && !info.IsResponse
|
||||
? HttpHeaderType.Request
|
||||
: !info.IsRequest && info.IsResponse
|
||||
? HttpHeaderType.Response
|
||||
: HttpHeaderType.Unspecified;
|
||||
}
|
||||
|
||||
private static string CheckName(string name)
|
||||
{
|
||||
if (name == null || name.Length == 0)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
name = name.Trim();
|
||||
if (!IsHeaderName(name))
|
||||
{
|
||||
throw new ArgumentException("Contains invalid characters.", nameof(name));
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
private void CheckRestricted(string name)
|
||||
{
|
||||
if (!_internallyUsed && isRestricted(name, true))
|
||||
{
|
||||
throw new ArgumentException("This header must be modified with the appropiate property.");
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckState(bool response)
|
||||
{
|
||||
if (_state == HttpHeaderType.Unspecified)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (response && _state == HttpHeaderType.Request)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"This collection has already been used to store the request headers.");
|
||||
}
|
||||
|
||||
if (!response && _state == HttpHeaderType.Response)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"This collection has already been used to store the response headers.");
|
||||
}
|
||||
}
|
||||
|
||||
private static string CheckValue(string value)
|
||||
{
|
||||
if (value == null || value.Length == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
value = value.Trim();
|
||||
if (value.Length > 65535)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), "Greater than 65,535 characters.");
|
||||
}
|
||||
|
||||
if (!IsHeaderValue(value))
|
||||
{
|
||||
throw new ArgumentException("Contains invalid characters.", nameof(value));
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static string Convert(string key)
|
||||
{
|
||||
return _headers.TryGetValue(key, out HttpHeaderInfo info) ? info.Name : string.Empty;
|
||||
}
|
||||
|
||||
private void DoWithCheckingState(
|
||||
Action<string, string> action, string name, string value, bool setState)
|
||||
{
|
||||
var type = CheckHeaderType(name);
|
||||
if (type == HttpHeaderType.Request)
|
||||
{
|
||||
DoWithCheckingState(action, name, value, false, setState);
|
||||
}
|
||||
else if (type == HttpHeaderType.Response)
|
||||
{
|
||||
DoWithCheckingState(action, name, value, true, setState);
|
||||
}
|
||||
else
|
||||
{
|
||||
action(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void DoWithCheckingState(
|
||||
Action<string, string> action, string name, string value, bool response, bool setState)
|
||||
{
|
||||
CheckState(response);
|
||||
action(name, value);
|
||||
if (setState && _state == HttpHeaderType.Unspecified)
|
||||
{
|
||||
_state = response ? HttpHeaderType.Response : HttpHeaderType.Request;
|
||||
}
|
||||
}
|
||||
|
||||
private void DoWithoutCheckingName(Action<string, string> action, string name, string value)
|
||||
{
|
||||
CheckRestricted(name);
|
||||
action(name, CheckValue(value));
|
||||
}
|
||||
|
||||
private static HttpHeaderInfo GetHeaderInfo(string name)
|
||||
{
|
||||
foreach (var info in _headers.Values)
|
||||
{
|
||||
if (info.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool isRestricted(string name, bool response)
|
||||
{
|
||||
var info = GetHeaderInfo(name);
|
||||
return info != null && info.IsRestricted(response);
|
||||
}
|
||||
|
||||
private void removeWithoutCheckingName(string name, string unuse)
|
||||
{
|
||||
CheckRestricted(name);
|
||||
base.Remove(name);
|
||||
}
|
||||
|
||||
private void setWithoutCheckingName(string name, string value)
|
||||
{
|
||||
DoWithoutCheckingName(base.Set, name, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the specified HttpRequestHeader to a string.
|
||||
/// </summary>
|
||||
/// <param name="header">The HttpRequestHeader to convert.</param>
|
||||
/// <returns>A string representing the converted HttpRequestHeader.</returns>
|
||||
internal static string Convert(HttpRequestHeader header)
|
||||
{
|
||||
return Convert(header.ToString());
|
||||
}
|
||||
|
||||
internal static string Convert(HttpResponseHeader header)
|
||||
{
|
||||
return Convert(header.ToString());
|
||||
}
|
||||
|
||||
internal void InternalRemove(string name)
|
||||
{
|
||||
base.Remove(name);
|
||||
}
|
||||
|
||||
internal void InternalSet(string header, bool response)
|
||||
{
|
||||
var pos = checkColonSeparated(header);
|
||||
InternalSet(header.Substring(0, pos), header.Substring(pos + 1), response);
|
||||
}
|
||||
|
||||
internal void InternalSet(string name, string value, bool response)
|
||||
{
|
||||
value = CheckValue(value);
|
||||
if (IsMultiValue(name, response))
|
||||
{
|
||||
base.Add(name, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.Set(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsHeaderName(string name)
|
||||
{
|
||||
return name != null && name.Length > 0 && name.IsToken();
|
||||
}
|
||||
|
||||
internal static bool IsHeaderValue(string value)
|
||||
{
|
||||
return value.IsText();
|
||||
}
|
||||
|
||||
internal static bool IsMultiValue(string headerName, bool response)
|
||||
{
|
||||
if (headerName == null || headerName.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var info = GetHeaderInfo(headerName);
|
||||
return info != null && info.IsMultiValue(response);
|
||||
}
|
||||
|
||||
internal string ToStringMultiValue(bool response)
|
||||
{
|
||||
var buff = new StringBuilder();
|
||||
Count.Times(
|
||||
i =>
|
||||
{
|
||||
var key = GetKey(i);
|
||||
if (IsMultiValue(key, response))
|
||||
{
|
||||
foreach (var val in GetValues(i))
|
||||
{
|
||||
buff.AppendFormat($"{key}: {val}\r\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
buff.AppendFormat($"{key}: {Get(i)}\r\n");
|
||||
}
|
||||
});
|
||||
|
||||
return buff.Append("\r\n").ToString();
|
||||
}
|
||||
|
||||
protected void AddWithoutValidate(string headerName, string headerValue)
|
||||
{
|
||||
add(headerName, headerValue, true);
|
||||
return isRestricted(CheckName(headerName), response);
|
||||
}
|
||||
|
||||
public void Add(string header)
|
||||
|
@ -489,18 +227,6 @@ namespace EonaCat.Network
|
|||
return base.GetKey(index);
|
||||
}
|
||||
|
||||
public override string[] GetValues(int index)
|
||||
{
|
||||
var vals = base.GetValues(index);
|
||||
return vals != null && vals.Length > 0 ? vals : null;
|
||||
}
|
||||
|
||||
public override string[] GetValues(string header)
|
||||
{
|
||||
var vals = base.GetValues(header);
|
||||
return vals != null && vals.Length > 0 ? vals : null;
|
||||
}
|
||||
|
||||
[SecurityPermission(
|
||||
SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
|
||||
public override void GetObjectData(
|
||||
|
@ -524,14 +250,26 @@ namespace EonaCat.Network
|
|||
});
|
||||
}
|
||||
|
||||
public static bool IsRestricted(string headerName)
|
||||
[SecurityPermission(
|
||||
SecurityAction.LinkDemand,
|
||||
Flags = SecurityPermissionFlag.SerializationFormatter,
|
||||
SerializationFormatter = true)]
|
||||
void ISerializable.GetObjectData(
|
||||
SerializationInfo serializationInfo, StreamingContext streamingContext)
|
||||
{
|
||||
return isRestricted(CheckName(headerName), false);
|
||||
GetObjectData(serializationInfo, streamingContext);
|
||||
}
|
||||
|
||||
public static bool IsRestricted(string headerName, bool response)
|
||||
public override string[] GetValues(int index)
|
||||
{
|
||||
return isRestricted(CheckName(headerName), response);
|
||||
var vals = base.GetValues(index);
|
||||
return vals != null && vals.Length > 0 ? vals : null;
|
||||
}
|
||||
|
||||
public override string[] GetValues(string header)
|
||||
{
|
||||
var vals = base.GetValues(header);
|
||||
return vals != null && vals.Length > 0 ? vals : null;
|
||||
}
|
||||
|
||||
public override void OnDeserialization(object sender)
|
||||
|
@ -589,14 +327,266 @@ namespace EonaCat.Network
|
|||
return buff.Append("\r\n").ToString();
|
||||
}
|
||||
|
||||
[SecurityPermission(
|
||||
SecurityAction.LinkDemand,
|
||||
Flags = SecurityPermissionFlag.SerializationFormatter,
|
||||
SerializationFormatter = true)]
|
||||
void ISerializable.GetObjectData(
|
||||
SerializationInfo serializationInfo, StreamingContext streamingContext)
|
||||
/// <summary>
|
||||
/// Converts the specified HttpRequestHeader to a string.
|
||||
/// </summary>
|
||||
/// <param name="header">The HttpRequestHeader to convert.</param>
|
||||
/// <returns>A string representing the converted HttpRequestHeader.</returns>
|
||||
internal static string Convert(HttpRequestHeader header)
|
||||
{
|
||||
GetObjectData(serializationInfo, streamingContext);
|
||||
return Convert(header.ToString());
|
||||
}
|
||||
|
||||
internal static string Convert(HttpResponseHeader header)
|
||||
{
|
||||
return Convert(header.ToString());
|
||||
}
|
||||
|
||||
internal static bool IsHeaderName(string name)
|
||||
{
|
||||
return name != null && name.Length > 0 && name.IsToken();
|
||||
}
|
||||
|
||||
internal static bool IsHeaderValue(string value)
|
||||
{
|
||||
return value.IsText();
|
||||
}
|
||||
|
||||
internal static bool IsMultiValue(string headerName, bool response)
|
||||
{
|
||||
if (headerName == null || headerName.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var info = GetHeaderInfo(headerName);
|
||||
return info != null && info.IsMultiValue(response);
|
||||
}
|
||||
|
||||
internal void InternalRemove(string name)
|
||||
{
|
||||
base.Remove(name);
|
||||
}
|
||||
|
||||
internal void InternalSet(string header, bool response)
|
||||
{
|
||||
var pos = checkColonSeparated(header);
|
||||
InternalSet(header.Substring(0, pos), header.Substring(pos + 1), response);
|
||||
}
|
||||
|
||||
internal void InternalSet(string name, string value, bool response)
|
||||
{
|
||||
value = CheckValue(value);
|
||||
if (IsMultiValue(name, response))
|
||||
{
|
||||
base.Add(name, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.Set(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
internal string ToStringMultiValue(bool response)
|
||||
{
|
||||
var buff = new StringBuilder();
|
||||
Count.Times(
|
||||
i =>
|
||||
{
|
||||
var key = GetKey(i);
|
||||
if (IsMultiValue(key, response))
|
||||
{
|
||||
foreach (var val in GetValues(i))
|
||||
{
|
||||
buff.AppendFormat($"{key}: {val}\r\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
buff.AppendFormat($"{key}: {Get(i)}\r\n");
|
||||
}
|
||||
});
|
||||
|
||||
return buff.Append("\r\n").ToString();
|
||||
}
|
||||
|
||||
protected void AddWithoutValidate(string headerName, string headerValue)
|
||||
{
|
||||
add(headerName, headerValue, true);
|
||||
}
|
||||
|
||||
private static int checkColonSeparated(string header)
|
||||
{
|
||||
var idx = header.IndexOf(':');
|
||||
if (idx == -1)
|
||||
{
|
||||
throw new ArgumentException("No colon could be found.", nameof(header));
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
private static HttpHeaderType CheckHeaderType(string name)
|
||||
{
|
||||
var info = GetHeaderInfo(name);
|
||||
return info == null
|
||||
? HttpHeaderType.Unspecified
|
||||
: info.IsRequest && !info.IsResponse
|
||||
? HttpHeaderType.Request
|
||||
: !info.IsRequest && info.IsResponse
|
||||
? HttpHeaderType.Response
|
||||
: HttpHeaderType.Unspecified;
|
||||
}
|
||||
|
||||
private static string CheckName(string name)
|
||||
{
|
||||
if (name == null || name.Length == 0)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
name = name.Trim();
|
||||
if (!IsHeaderName(name))
|
||||
{
|
||||
throw new ArgumentException("Contains invalid characters.", nameof(name));
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
private static string CheckValue(string value)
|
||||
{
|
||||
if (value == null || value.Length == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
value = value.Trim();
|
||||
if (value.Length > 65535)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), "Greater than 65,535 characters.");
|
||||
}
|
||||
|
||||
if (!IsHeaderValue(value))
|
||||
{
|
||||
throw new ArgumentException("Contains invalid characters.", nameof(value));
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static string Convert(string key)
|
||||
{
|
||||
return _headers.TryGetValue(key, out HttpHeaderInfo info) ? info.Name : string.Empty;
|
||||
}
|
||||
|
||||
private static HttpHeaderInfo GetHeaderInfo(string name)
|
||||
{
|
||||
foreach (var info in _headers.Values)
|
||||
{
|
||||
if (info.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool isRestricted(string name, bool response)
|
||||
{
|
||||
var info = GetHeaderInfo(name);
|
||||
return info != null && info.IsRestricted(response);
|
||||
}
|
||||
|
||||
private void add(string name, string value, bool ignoreRestricted)
|
||||
{
|
||||
var act = ignoreRestricted
|
||||
? (Action<string, string>)addWithoutCheckingNameAndRestricted
|
||||
: addWithoutCheckingName;
|
||||
|
||||
DoWithCheckingState(act, CheckName(name), value, true);
|
||||
}
|
||||
|
||||
private void addWithoutCheckingName(string name, string value)
|
||||
{
|
||||
DoWithoutCheckingName(base.Add, name, value);
|
||||
}
|
||||
|
||||
private void addWithoutCheckingNameAndRestricted(string name, string value)
|
||||
{
|
||||
base.Add(name, CheckValue(value));
|
||||
}
|
||||
private void CheckRestricted(string name)
|
||||
{
|
||||
if (!_internallyUsed && isRestricted(name, true))
|
||||
{
|
||||
throw new ArgumentException("This header must be modified with the appropiate property.");
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckState(bool response)
|
||||
{
|
||||
if (_state == HttpHeaderType.Unspecified)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (response && _state == HttpHeaderType.Request)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"This collection has already been used to store the request headers.");
|
||||
}
|
||||
|
||||
if (!response && _state == HttpHeaderType.Response)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"This collection has already been used to store the response headers.");
|
||||
}
|
||||
}
|
||||
private void DoWithCheckingState(
|
||||
Action<string, string> action, string name, string value, bool setState)
|
||||
{
|
||||
var type = CheckHeaderType(name);
|
||||
if (type == HttpHeaderType.Request)
|
||||
{
|
||||
DoWithCheckingState(action, name, value, false, setState);
|
||||
}
|
||||
else if (type == HttpHeaderType.Response)
|
||||
{
|
||||
DoWithCheckingState(action, name, value, true, setState);
|
||||
}
|
||||
else
|
||||
{
|
||||
action(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void DoWithCheckingState(
|
||||
Action<string, string> action, string name, string value, bool response, bool setState)
|
||||
{
|
||||
CheckState(response);
|
||||
action(name, value);
|
||||
if (setState && _state == HttpHeaderType.Unspecified)
|
||||
{
|
||||
_state = response ? HttpHeaderType.Response : HttpHeaderType.Request;
|
||||
}
|
||||
}
|
||||
|
||||
private void DoWithoutCheckingName(Action<string, string> action, string name, string value)
|
||||
{
|
||||
CheckRestricted(name);
|
||||
action(name, CheckValue(value));
|
||||
}
|
||||
private void removeWithoutCheckingName(string name, string unuse)
|
||||
{
|
||||
CheckRestricted(name);
|
||||
base.Remove(name);
|
||||
}
|
||||
|
||||
private void setWithoutCheckingName(string name, string value)
|
||||
{
|
||||
DoWithoutCheckingName(base.Set, name, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,11 +33,6 @@ namespace EonaCat.Network
|
|||
_websocket = new WSClient(this, protocol);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stream of the underlying TCP connection.
|
||||
/// </summary>
|
||||
internal Stream Stream => _context.Connection.Stream;
|
||||
|
||||
public override CookieCollection CookieCollection => _context.Request.Cookies;
|
||||
|
||||
public override NameValueCollection Headers => _context.Request.Headers;
|
||||
|
@ -85,6 +80,16 @@ namespace EonaCat.Network
|
|||
|
||||
public override WSClient WebSocket => _websocket;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stream of the underlying TCP connection.
|
||||
/// </summary>
|
||||
internal Stream Stream => _context.Connection.Stream;
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return _context.Request.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the WebSocket connection.
|
||||
/// </summary>
|
||||
|
@ -101,11 +106,5 @@ namespace EonaCat.Network
|
|||
{
|
||||
_context.Response.Close(code);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return _context.Request.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,15 +21,14 @@ namespace EonaCat.Network
|
|||
/// </remarks>
|
||||
internal class TcpListenerWSContext : WSContext
|
||||
{
|
||||
private CookieCollection _cookies;
|
||||
private NameValueCollection _queryString;
|
||||
private WebRequest _request;
|
||||
private readonly bool _secure;
|
||||
private readonly TcpClient _tcpClient;
|
||||
private readonly Uri _uri;
|
||||
private IPrincipal _user;
|
||||
private readonly WSClient _websocket;
|
||||
|
||||
private CookieCollection _cookies;
|
||||
private NameValueCollection _queryString;
|
||||
private WebRequest _request;
|
||||
private IPrincipal _user;
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TcpListenerWSContext"/> class.
|
||||
/// </summary>
|
||||
|
@ -76,11 +75,6 @@ namespace EonaCat.Network
|
|||
_websocket = new WSClient(this, protocol);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stream of the underlying TCP connection.
|
||||
/// </summary>
|
||||
internal Stream Stream { get; }
|
||||
|
||||
public override CookieCollection CookieCollection => _cookies ??= _request.Cookies;
|
||||
|
||||
public override NameValueCollection Headers => _request.Headers;
|
||||
|
@ -101,7 +95,7 @@ namespace EonaCat.Network
|
|||
HttpUtility.InternalParseQueryString(
|
||||
_uri?.Query, Encoding.UTF8
|
||||
)
|
||||
;
|
||||
;
|
||||
|
||||
public override Uri RequestUri => _uri;
|
||||
|
||||
|
@ -132,6 +126,16 @@ namespace EonaCat.Network
|
|||
|
||||
public override WSClient WebSocket => _websocket;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stream of the underlying TCP connection.
|
||||
/// </summary>
|
||||
internal Stream Stream { get; }
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return _request.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authenticates the WebSocket connection based on the specified authentication scheme.
|
||||
/// </summary>
|
||||
|
@ -220,11 +224,5 @@ namespace EonaCat.Network
|
|||
Stream.Write(buff, 0, buff.Length);
|
||||
_request = WebRequest.Read(Stream, 15000);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return _request.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,6 +13,9 @@ namespace EonaCat.Network
|
|||
[Serializable]
|
||||
public sealed class Cookie
|
||||
{
|
||||
private static readonly char[] _reservedCharsForName;
|
||||
private static readonly char[] _reservedCharsForValue;
|
||||
private readonly DateTime _timestamp;
|
||||
private string _comment;
|
||||
private Uri _commentUri;
|
||||
private bool _discard;
|
||||
|
@ -23,10 +26,7 @@ namespace EonaCat.Network
|
|||
private string _path;
|
||||
private string _port;
|
||||
private int[] _ports;
|
||||
private static readonly char[] _reservedCharsForName;
|
||||
private static readonly char[] _reservedCharsForValue;
|
||||
private bool _secure;
|
||||
private readonly DateTime _timestamp;
|
||||
private string _value;
|
||||
private int _version;
|
||||
|
||||
|
@ -90,33 +90,6 @@ namespace EonaCat.Network
|
|||
Domain = domain;
|
||||
}
|
||||
|
||||
internal bool ExactDomain
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal int MaxAge
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_expires == DateTime.MinValue)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var expires = _expires.Kind != DateTimeKind.Local
|
||||
? _expires.ToLocalTime()
|
||||
: _expires;
|
||||
|
||||
var span = expires - DateTime.Now;
|
||||
return span > TimeSpan.Zero
|
||||
? (int)span.TotalSeconds
|
||||
: 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal int[] Ports => _ports;
|
||||
|
||||
public string Comment
|
||||
{
|
||||
get
|
||||
|
@ -333,6 +306,118 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
|
||||
internal bool ExactDomain
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal int MaxAge
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_expires == DateTime.MinValue)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var expires = _expires.Kind != DateTimeKind.Local
|
||||
? _expires.ToLocalTime()
|
||||
: _expires;
|
||||
|
||||
var span = expires - DateTime.Now;
|
||||
return span > TimeSpan.Zero
|
||||
? (int)span.TotalSeconds
|
||||
: 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal int[] Ports => _ports;
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object comparand)
|
||||
{
|
||||
return comparand is Cookie cookie &&
|
||||
_name.Equals(cookie.Name, StringComparison.InvariantCultureIgnoreCase) &&
|
||||
_value.Equals(cookie.Value, StringComparison.InvariantCulture) &&
|
||||
_path.Equals(cookie.Path, StringComparison.InvariantCulture) &&
|
||||
_domain.Equals(cookie.Domain, StringComparison.InvariantCultureIgnoreCase) &&
|
||||
_version == cookie.Version;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return hash(
|
||||
StringComparer.InvariantCultureIgnoreCase.GetHashCode(_name),
|
||||
_value.GetHashCode(),
|
||||
_path.GetHashCode(),
|
||||
StringComparer.InvariantCultureIgnoreCase.GetHashCode(_domain),
|
||||
_version);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return ToRequestString(null);
|
||||
}
|
||||
|
||||
// From client to server
|
||||
internal string ToRequestString(Uri uri)
|
||||
{
|
||||
if (_name.Length == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (_version == 0)
|
||||
{
|
||||
return string.Format("{0}={1}", _name, _value);
|
||||
}
|
||||
|
||||
var output = new StringBuilder(64);
|
||||
output.AppendFormat("$Version={0}; {1}={2}", _version, _name, _value);
|
||||
|
||||
if (!_path.IsNullOrEmpty())
|
||||
{
|
||||
output.AppendFormat("; $Path={0}", _path);
|
||||
}
|
||||
else if (uri != null)
|
||||
{
|
||||
output.AppendFormat("; $Path={0}", uri.GetAbsolutePath());
|
||||
}
|
||||
else
|
||||
{
|
||||
output.Append("; $Path=/");
|
||||
}
|
||||
|
||||
var appendDomain = uri == null || uri.Host != _domain;
|
||||
if (appendDomain && !_domain.IsNullOrEmpty())
|
||||
{
|
||||
output.AppendFormat("; $Domain={0}", _domain);
|
||||
}
|
||||
|
||||
if (!_port.IsNullOrEmpty())
|
||||
{
|
||||
if (_port == "\"\"")
|
||||
{
|
||||
output.Append("; $Port");
|
||||
}
|
||||
else
|
||||
{
|
||||
output.AppendFormat("; $Port={0}", _port);
|
||||
}
|
||||
}
|
||||
|
||||
return output.ToString();
|
||||
}
|
||||
|
||||
// From server to client
|
||||
internal string ToResponseString()
|
||||
{
|
||||
return _name.Length > 0
|
||||
? (_version == 0 ? toResponseStringVersion0() : toResponseStringVersion1())
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
private static bool canSetName(string name, out string message)
|
||||
{
|
||||
if (name.IsNullOrEmpty())
|
||||
|
@ -378,6 +463,36 @@ namespace EonaCat.Network
|
|||
(m << 20 | m >> 12);
|
||||
}
|
||||
|
||||
private static bool tryCreatePorts(string value, out int[] result, out string parseError)
|
||||
{
|
||||
var ports = value.Trim('"').Split(',');
|
||||
var len = ports.Length;
|
||||
var res = new int[len];
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
res[i] = int.MinValue;
|
||||
|
||||
var port = ports[i].Trim();
|
||||
if (port.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!int.TryParse(port, out res[i]))
|
||||
{
|
||||
result = new int[0];
|
||||
parseError = port;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
result = res;
|
||||
parseError = string.Empty;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private string toResponseStringVersion0()
|
||||
{
|
||||
var output = new StringBuilder(64);
|
||||
|
@ -470,121 +585,5 @@ namespace EonaCat.Network
|
|||
|
||||
return output.ToString();
|
||||
}
|
||||
|
||||
private static bool tryCreatePorts(string value, out int[] result, out string parseError)
|
||||
{
|
||||
var ports = value.Trim('"').Split(',');
|
||||
var len = ports.Length;
|
||||
var res = new int[len];
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
res[i] = int.MinValue;
|
||||
|
||||
var port = ports[i].Trim();
|
||||
if (port.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!int.TryParse(port, out res[i]))
|
||||
{
|
||||
result = new int[0];
|
||||
parseError = port;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
result = res;
|
||||
parseError = string.Empty;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// From client to server
|
||||
internal string ToRequestString(Uri uri)
|
||||
{
|
||||
if (_name.Length == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (_version == 0)
|
||||
{
|
||||
return string.Format("{0}={1}", _name, _value);
|
||||
}
|
||||
|
||||
var output = new StringBuilder(64);
|
||||
output.AppendFormat("$Version={0}; {1}={2}", _version, _name, _value);
|
||||
|
||||
if (!_path.IsNullOrEmpty())
|
||||
{
|
||||
output.AppendFormat("; $Path={0}", _path);
|
||||
}
|
||||
else if (uri != null)
|
||||
{
|
||||
output.AppendFormat("; $Path={0}", uri.GetAbsolutePath());
|
||||
}
|
||||
else
|
||||
{
|
||||
output.Append("; $Path=/");
|
||||
}
|
||||
|
||||
var appendDomain = uri == null || uri.Host != _domain;
|
||||
if (appendDomain && !_domain.IsNullOrEmpty())
|
||||
{
|
||||
output.AppendFormat("; $Domain={0}", _domain);
|
||||
}
|
||||
|
||||
if (!_port.IsNullOrEmpty())
|
||||
{
|
||||
if (_port == "\"\"")
|
||||
{
|
||||
output.Append("; $Port");
|
||||
}
|
||||
else
|
||||
{
|
||||
output.AppendFormat("; $Port={0}", _port);
|
||||
}
|
||||
}
|
||||
|
||||
return output.ToString();
|
||||
}
|
||||
|
||||
// From server to client
|
||||
internal string ToResponseString()
|
||||
{
|
||||
return _name.Length > 0
|
||||
? (_version == 0 ? toResponseStringVersion0() : toResponseStringVersion1())
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object comparand)
|
||||
{
|
||||
return comparand is Cookie cookie &&
|
||||
_name.Equals(cookie.Name, StringComparison.InvariantCultureIgnoreCase) &&
|
||||
_value.Equals(cookie.Value, StringComparison.InvariantCulture) &&
|
||||
_path.Equals(cookie.Path, StringComparison.InvariantCulture) &&
|
||||
_domain.Equals(cookie.Domain, StringComparison.InvariantCultureIgnoreCase) &&
|
||||
_version == cookie.Version;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return hash(
|
||||
StringComparer.InvariantCultureIgnoreCase.GetHashCode(_name),
|
||||
_value.GetHashCode(),
|
||||
_path.GetHashCode(),
|
||||
StringComparer.InvariantCultureIgnoreCase.GetHashCode(_domain),
|
||||
_version);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return ToRequestString(null);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,22 +27,6 @@ namespace EonaCat.Network
|
|||
}
|
||||
|
||||
|
||||
internal IList<Cookie> List => _list;
|
||||
|
||||
internal IEnumerable<Cookie> Sorted
|
||||
{
|
||||
get
|
||||
{
|
||||
var list = new List<Cookie>(_list);
|
||||
if (list.Count > 1)
|
||||
{
|
||||
list.Sort(compareCookieWithinSorted);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of cookies in the collection.
|
||||
/// </summary>
|
||||
|
@ -58,6 +42,26 @@ namespace EonaCat.Network
|
|||
/// </summary>
|
||||
public bool IsSynchronized => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets an object that can be used to synchronize access to the collection.
|
||||
/// </summary>
|
||||
public object SyncRoot => _sync ??= ((ICollection)_list).SyncRoot;
|
||||
|
||||
internal IList<Cookie> List => _list;
|
||||
|
||||
internal IEnumerable<Cookie> Sorted
|
||||
{
|
||||
get
|
||||
{
|
||||
var list = new List<Cookie>(_list);
|
||||
if (list.Count > 1)
|
||||
{
|
||||
list.Sort(compareCookieWithinSorted);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets the cookie at the specified index.
|
||||
/// </summary>
|
||||
|
@ -101,11 +105,164 @@ namespace EonaCat.Network
|
|||
return null;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Adds the specified cookie to the collection, updating it if it already exists.
|
||||
/// </summary>
|
||||
/// <param name="cookie">The cookie to add or update.</param>
|
||||
public void Add(Cookie cookie)
|
||||
{
|
||||
if (cookie == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(cookie));
|
||||
}
|
||||
|
||||
var pos = searchCookie(cookie);
|
||||
if (pos == -1)
|
||||
{
|
||||
_list.Add(cookie);
|
||||
return;
|
||||
}
|
||||
|
||||
_list[pos] = cookie;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an object that can be used to synchronize access to the collection.
|
||||
/// Adds the cookies from the specified <see cref="CookieCollection"/> to this collection, updating existing cookies.
|
||||
/// </summary>
|
||||
public object SyncRoot => _sync ??= ((ICollection)_list).SyncRoot;
|
||||
/// <param name="cookies">The <see cref="CookieCollection"/> to add or update from.</param>
|
||||
public void Add(CookieCollection cookies)
|
||||
{
|
||||
if (cookies == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(cookies));
|
||||
}
|
||||
|
||||
foreach (Cookie cookie in cookies)
|
||||
{
|
||||
Add(cookie);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the cookies in the collection to the specified array, starting at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="array">The destination array.</param>
|
||||
/// <param name="index">The index in the destination array at which copying begins.</param>
|
||||
public void CopyTo(Array array, int index)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
}
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index), "Less than zero.");
|
||||
}
|
||||
|
||||
if (array.Rank > 1)
|
||||
{
|
||||
throw new ArgumentException("Multidimensional.", nameof(array));
|
||||
}
|
||||
|
||||
if (array.Length - index < _list.Count)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"The number of elements in this collection is greater than the available space of the destination array.");
|
||||
}
|
||||
|
||||
if (!array.GetType().GetElementType().IsAssignableFrom(typeof(Cookie)))
|
||||
{
|
||||
throw new InvalidCastException(
|
||||
"The elements in this collection cannot be cast automatically to the type of the destination array.");
|
||||
} ((IList)_list).CopyTo(array, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the cookies in the collection to the specified <see cref="Cookie"/> array, starting at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="array">The destination array.</param>
|
||||
/// <param name="index">The index in the destination array at which copying begins.</param>
|
||||
public void CopyTo(Cookie[] array, int index)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
}
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index), "Less than zero.");
|
||||
}
|
||||
|
||||
if (array.Length - index < _list.Count)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"The number of elements in this collection is greater than the available space of the destination array.");
|
||||
}
|
||||
|
||||
_list.CopyTo(array, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the collection.
|
||||
/// </summary>
|
||||
/// <returns>An enumerator for the collection.</returns>
|
||||
public IEnumerator GetEnumerator()
|
||||
{
|
||||
return _list.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the specified cookie string, creating a <see cref="CookieCollection"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The cookie string to parse.</param>
|
||||
/// <param name="response">True if parsing a response header; otherwise, false.</param>
|
||||
/// <returns>A <see cref="CookieCollection"/> instance representing the parsed cookies.</returns>
|
||||
internal static CookieCollection Parse(string value, bool response)
|
||||
{
|
||||
return response
|
||||
? parseResponse(value)
|
||||
: parseRequest(value);
|
||||
}
|
||||
|
||||
internal void SetOrRemove(Cookie cookie)
|
||||
{
|
||||
var pos = searchCookie(cookie);
|
||||
if (pos == -1)
|
||||
{
|
||||
if (!cookie.Expired)
|
||||
{
|
||||
_list.Add(cookie);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cookie.Expired)
|
||||
{
|
||||
_list[pos] = cookie;
|
||||
return;
|
||||
}
|
||||
|
||||
_list.RemoveAt(pos);
|
||||
}
|
||||
|
||||
internal void SetOrRemove(CookieCollection cookies)
|
||||
{
|
||||
foreach (Cookie cookie in cookies)
|
||||
{
|
||||
SetOrRemove(cookie);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Sort()
|
||||
{
|
||||
if (_list.Count > 1)
|
||||
{
|
||||
_list.Sort(compareCookieWithinSort);
|
||||
}
|
||||
}
|
||||
|
||||
private static int compareCookieWithinSort(Cookie x, Cookie y)
|
||||
{
|
||||
|
@ -357,6 +514,11 @@ namespace EonaCat.Network
|
|||
return cookies;
|
||||
}
|
||||
|
||||
private static string[] splitCookieHeaderValue(string value)
|
||||
{
|
||||
return new List<string>(value.SplitHeaderValue(',', ';')).ToArray();
|
||||
}
|
||||
|
||||
private int searchCookie(Cookie cookie)
|
||||
{
|
||||
var name = cookie.Name;
|
||||
|
@ -378,169 +540,5 @@ namespace EonaCat.Network
|
|||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static string[] splitCookieHeaderValue(string value)
|
||||
{
|
||||
return new List<string>(value.SplitHeaderValue(',', ';')).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the specified cookie string, creating a <see cref="CookieCollection"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The cookie string to parse.</param>
|
||||
/// <param name="response">True if parsing a response header; otherwise, false.</param>
|
||||
/// <returns>A <see cref="CookieCollection"/> instance representing the parsed cookies.</returns>
|
||||
internal static CookieCollection Parse(string value, bool response)
|
||||
{
|
||||
return response
|
||||
? parseResponse(value)
|
||||
: parseRequest(value);
|
||||
}
|
||||
|
||||
internal void SetOrRemove(Cookie cookie)
|
||||
{
|
||||
var pos = searchCookie(cookie);
|
||||
if (pos == -1)
|
||||
{
|
||||
if (!cookie.Expired)
|
||||
{
|
||||
_list.Add(cookie);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cookie.Expired)
|
||||
{
|
||||
_list[pos] = cookie;
|
||||
return;
|
||||
}
|
||||
|
||||
_list.RemoveAt(pos);
|
||||
}
|
||||
|
||||
internal void SetOrRemove(CookieCollection cookies)
|
||||
{
|
||||
foreach (Cookie cookie in cookies)
|
||||
{
|
||||
SetOrRemove(cookie);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Sort()
|
||||
{
|
||||
if (_list.Count > 1)
|
||||
{
|
||||
_list.Sort(compareCookieWithinSort);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified cookie to the collection, updating it if it already exists.
|
||||
/// </summary>
|
||||
/// <param name="cookie">The cookie to add or update.</param>
|
||||
public void Add(Cookie cookie)
|
||||
{
|
||||
if (cookie == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(cookie));
|
||||
}
|
||||
|
||||
var pos = searchCookie(cookie);
|
||||
if (pos == -1)
|
||||
{
|
||||
_list.Add(cookie);
|
||||
return;
|
||||
}
|
||||
|
||||
_list[pos] = cookie;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the cookies from the specified <see cref="CookieCollection"/> to this collection, updating existing cookies.
|
||||
/// </summary>
|
||||
/// <param name="cookies">The <see cref="CookieCollection"/> to add or update from.</param>
|
||||
public void Add(CookieCollection cookies)
|
||||
{
|
||||
if (cookies == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(cookies));
|
||||
}
|
||||
|
||||
foreach (Cookie cookie in cookies)
|
||||
{
|
||||
Add(cookie);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the cookies in the collection to the specified array, starting at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="array">The destination array.</param>
|
||||
/// <param name="index">The index in the destination array at which copying begins.</param>
|
||||
public void CopyTo(Array array, int index)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
}
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index), "Less than zero.");
|
||||
}
|
||||
|
||||
if (array.Rank > 1)
|
||||
{
|
||||
throw new ArgumentException("Multidimensional.", nameof(array));
|
||||
}
|
||||
|
||||
if (array.Length - index < _list.Count)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"The number of elements in this collection is greater than the available space of the destination array.");
|
||||
}
|
||||
|
||||
if (!array.GetType().GetElementType().IsAssignableFrom(typeof(Cookie)))
|
||||
{
|
||||
throw new InvalidCastException(
|
||||
"The elements in this collection cannot be cast automatically to the type of the destination array.");
|
||||
} ((IList)_list).CopyTo(array, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the cookies in the collection to the specified <see cref="Cookie"/> array, starting at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="array">The destination array.</param>
|
||||
/// <param name="index">The index in the destination array at which copying begins.</param>
|
||||
public void CopyTo(Cookie[] array, int index)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
}
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index), "Less than zero.");
|
||||
}
|
||||
|
||||
if (array.Length - index < _list.Count)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"The number of elements in this collection is greater than the available space of the destination array.");
|
||||
}
|
||||
|
||||
_list.CopyTo(array, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the collection.
|
||||
/// </summary>
|
||||
/// <returns>An enumerator for the collection.</returns>
|
||||
public IEnumerator GetEnumerator()
|
||||
{
|
||||
return _list.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,6 +13,14 @@ namespace EonaCat.Network
|
|||
[Serializable]
|
||||
public class CookieException : FormatException, ISerializable
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CookieException"/> class.
|
||||
/// </summary>
|
||||
public CookieException()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CookieException"/> class with a specified error message.
|
||||
/// </summary>
|
||||
|
@ -41,15 +49,6 @@ namespace EonaCat.Network
|
|||
: base(serializationInfo, streamingContext)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CookieException"/> class.
|
||||
/// </summary>
|
||||
public CookieException()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates a <see cref="SerializationInfo"/> with the data needed to serialize the exception.
|
||||
/// </summary>
|
||||
|
|
|
@ -18,15 +18,14 @@ namespace EonaCat.Network
|
|||
/// </summary>
|
||||
internal sealed class EndPointListener
|
||||
{
|
||||
private List<HttpListenerPrefix> _all; // host == '+'
|
||||
private static readonly string _defaultCertFolderPath;
|
||||
private readonly IPEndPoint _endpoint;
|
||||
private Dictionary<HttpListenerPrefix, HttpListener> _prefixes;
|
||||
private readonly Socket _socket;
|
||||
private List<HttpListenerPrefix> _unhandled; // host == '*'
|
||||
private readonly Dictionary<HttpConnection, HttpConnection> _unregistered;
|
||||
private readonly object _unregisteredSync;
|
||||
|
||||
private List<HttpListenerPrefix> _all; // host == '+'
|
||||
private Dictionary<HttpListenerPrefix, HttpListener> _prefixes;
|
||||
private List<HttpListenerPrefix> _unhandled; // host == '*'
|
||||
static EndPointListener()
|
||||
{
|
||||
_defaultCertFolderPath =
|
||||
|
@ -93,293 +92,6 @@ namespace EonaCat.Network
|
|||
/// </summary>
|
||||
public SSLConfigServer SSL { get; }
|
||||
|
||||
private static void addSpecial(List<HttpListenerPrefix> prefixes, HttpListenerPrefix prefix)
|
||||
{
|
||||
var path = prefix.Path;
|
||||
foreach (var pref in prefixes)
|
||||
{
|
||||
if (pref.Path == path)
|
||||
{
|
||||
throw new HttpListenerException(87, "The prefix is already in use.");
|
||||
}
|
||||
}
|
||||
|
||||
prefixes.Add(prefix);
|
||||
}
|
||||
|
||||
private static RSACryptoServiceProvider createRSAFromFile(string filename)
|
||||
{
|
||||
byte[] pvk = null;
|
||||
using (var fs = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
pvk = new byte[fs.Length];
|
||||
fs.Read(pvk, 0, pvk.Length);
|
||||
}
|
||||
|
||||
var rsa = new RSACryptoServiceProvider();
|
||||
rsa.ImportCspBlob(pvk);
|
||||
|
||||
return rsa;
|
||||
}
|
||||
|
||||
private static X509Certificate2 getCertificate(
|
||||
int port, string folderPath, X509Certificate2 defaultCertificate
|
||||
)
|
||||
{
|
||||
if (folderPath == null || folderPath.Length == 0)
|
||||
{
|
||||
folderPath = _defaultCertFolderPath;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var cer = Path.Combine(folderPath, string.Format("{0}.cer", port));
|
||||
var key = Path.Combine(folderPath, string.Format("{0}.key", port));
|
||||
if (File.Exists(cer) && File.Exists(key))
|
||||
{
|
||||
var cert = new X509Certificate2(cer);
|
||||
cert.PrivateKey = createRSAFromFile(key);
|
||||
|
||||
return cert;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return defaultCertificate;
|
||||
}
|
||||
|
||||
private void leaveIfNoPrefix()
|
||||
{
|
||||
if (_prefixes.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var prefs = _unhandled;
|
||||
if (prefs != null && prefs.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
prefs = _all;
|
||||
if (prefs != null && prefs.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EndPointManager.RemoveEndPoint(_endpoint);
|
||||
}
|
||||
|
||||
private static void onAccept(IAsyncResult asyncResult)
|
||||
{
|
||||
var lsnr = (EndPointListener)asyncResult.AsyncState;
|
||||
|
||||
Socket sock = null;
|
||||
try
|
||||
{
|
||||
sock = lsnr._socket.EndAccept(asyncResult);
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
lsnr._socket.BeginAccept(onAccept, lsnr);
|
||||
}
|
||||
catch
|
||||
{
|
||||
sock?.Close();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (sock == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
processAccepted(sock, lsnr);
|
||||
}
|
||||
|
||||
private static void processAccepted(Socket socket, EndPointListener listener)
|
||||
{
|
||||
HttpConnection conn = null;
|
||||
try
|
||||
{
|
||||
conn = new HttpConnection(socket, listener);
|
||||
lock (listener._unregisteredSync)
|
||||
{
|
||||
listener._unregistered[conn] = conn;
|
||||
}
|
||||
|
||||
conn.BeginReadRequest();
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (conn != null)
|
||||
{
|
||||
conn.Close(true);
|
||||
return;
|
||||
}
|
||||
|
||||
socket.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool removeSpecial(List<HttpListenerPrefix> prefixes, HttpListenerPrefix prefix)
|
||||
{
|
||||
var path = prefix.Path;
|
||||
var cnt = prefixes.Count;
|
||||
for (var i = 0; i < cnt; i++)
|
||||
{
|
||||
if (prefixes[i].Path == path)
|
||||
{
|
||||
prefixes.RemoveAt(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static HttpListener searchHttpListenerFromSpecial(
|
||||
string path, List<HttpListenerPrefix> prefixes
|
||||
)
|
||||
{
|
||||
if (prefixes == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpListener bestMatch = null;
|
||||
|
||||
var bestLen = -1;
|
||||
foreach (var pref in prefixes)
|
||||
{
|
||||
var prefPath = pref.Path;
|
||||
|
||||
var len = prefPath.Length;
|
||||
if (len < bestLen)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (path.StartsWith(prefPath))
|
||||
{
|
||||
bestLen = len;
|
||||
bestMatch = pref.Listener;
|
||||
}
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
internal static bool CertificateExists(int port, string folderPath)
|
||||
{
|
||||
if (folderPath == null || folderPath.Length == 0)
|
||||
{
|
||||
folderPath = _defaultCertFolderPath;
|
||||
}
|
||||
|
||||
var cer = Path.Combine(folderPath, string.Format("{0}.cer", port));
|
||||
var key = Path.Combine(folderPath, string.Format("{0}.key", port));
|
||||
|
||||
return File.Exists(cer) && File.Exists(key);
|
||||
}
|
||||
|
||||
internal void RemoveConnection(HttpConnection connection)
|
||||
{
|
||||
lock (_unregisteredSync)
|
||||
{
|
||||
_unregistered.Remove(connection);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool TrySearchHttpListener(Uri uri, out HttpListener listener)
|
||||
{
|
||||
listener = null;
|
||||
|
||||
if (uri == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var host = uri.Host;
|
||||
var dns = Uri.CheckHostName(host) == UriHostNameType.Dns;
|
||||
var port = uri.Port.ToString();
|
||||
var path = HttpUtility.UrlDecode(uri.AbsolutePath);
|
||||
var pathSlash = path[path.Length - 1] != '/' ? path + "/" : path;
|
||||
|
||||
if (host != null && host.Length > 0)
|
||||
{
|
||||
var bestLen = -1;
|
||||
foreach (var pref in _prefixes.Keys)
|
||||
{
|
||||
if (dns)
|
||||
{
|
||||
var prefHost = pref.Host;
|
||||
if (Uri.CheckHostName(prefHost) == UriHostNameType.Dns && prefHost != host)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (pref.Port != port)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var prefPath = pref.Path;
|
||||
|
||||
var len = prefPath.Length;
|
||||
if (len < bestLen)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (path.StartsWith(prefPath) || pathSlash.StartsWith(prefPath))
|
||||
{
|
||||
bestLen = len;
|
||||
listener = _prefixes[pref];
|
||||
}
|
||||
}
|
||||
|
||||
if (bestLen != -1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
var prefs = _unhandled;
|
||||
listener = searchHttpListenerFromSpecial(path, prefs);
|
||||
if (listener == null && pathSlash != path)
|
||||
{
|
||||
listener = searchHttpListenerFromSpecial(pathSlash, prefs);
|
||||
}
|
||||
|
||||
if (listener != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
prefs = _all;
|
||||
listener = searchHttpListenerFromSpecial(path, prefs);
|
||||
if (listener == null && pathSlash != path)
|
||||
{
|
||||
listener = searchHttpListenerFromSpecial(pathSlash, prefs);
|
||||
}
|
||||
|
||||
return listener != null;
|
||||
}
|
||||
|
||||
public void AddPrefix(HttpListenerPrefix prefix, HttpListener listener)
|
||||
{
|
||||
List<HttpListenerPrefix> current, future;
|
||||
|
@ -528,5 +240,292 @@ namespace EonaCat.Network
|
|||
|
||||
leaveIfNoPrefix();
|
||||
}
|
||||
|
||||
internal static bool CertificateExists(int port, string folderPath)
|
||||
{
|
||||
if (folderPath == null || folderPath.Length == 0)
|
||||
{
|
||||
folderPath = _defaultCertFolderPath;
|
||||
}
|
||||
|
||||
var cer = Path.Combine(folderPath, string.Format("{0}.cer", port));
|
||||
var key = Path.Combine(folderPath, string.Format("{0}.key", port));
|
||||
|
||||
return File.Exists(cer) && File.Exists(key);
|
||||
}
|
||||
|
||||
internal void RemoveConnection(HttpConnection connection)
|
||||
{
|
||||
lock (_unregisteredSync)
|
||||
{
|
||||
_unregistered.Remove(connection);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool TrySearchHttpListener(Uri uri, out HttpListener listener)
|
||||
{
|
||||
listener = null;
|
||||
|
||||
if (uri == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var host = uri.Host;
|
||||
var dns = Uri.CheckHostName(host) == UriHostNameType.Dns;
|
||||
var port = uri.Port.ToString();
|
||||
var path = HttpUtility.UrlDecode(uri.AbsolutePath);
|
||||
var pathSlash = path[path.Length - 1] != '/' ? path + "/" : path;
|
||||
|
||||
if (host != null && host.Length > 0)
|
||||
{
|
||||
var bestLen = -1;
|
||||
foreach (var pref in _prefixes.Keys)
|
||||
{
|
||||
if (dns)
|
||||
{
|
||||
var prefHost = pref.Host;
|
||||
if (Uri.CheckHostName(prefHost) == UriHostNameType.Dns && prefHost != host)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (pref.Port != port && !_prefixes[pref].AllowForwardedRequest)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var prefPath = pref.Path;
|
||||
|
||||
var len = prefPath.Length;
|
||||
if (len < bestLen)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (path.StartsWith(prefPath) || pathSlash.StartsWith(prefPath))
|
||||
{
|
||||
bestLen = len;
|
||||
listener = _prefixes[pref];
|
||||
}
|
||||
}
|
||||
|
||||
if (bestLen != -1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
var prefs = _unhandled;
|
||||
listener = searchHttpListenerFromSpecial(path, prefs);
|
||||
if (listener == null && pathSlash != path)
|
||||
{
|
||||
listener = searchHttpListenerFromSpecial(pathSlash, prefs);
|
||||
}
|
||||
|
||||
if (listener != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
prefs = _all;
|
||||
listener = searchHttpListenerFromSpecial(path, prefs);
|
||||
if (listener == null && pathSlash != path)
|
||||
{
|
||||
listener = searchHttpListenerFromSpecial(pathSlash, prefs);
|
||||
}
|
||||
|
||||
return listener != null;
|
||||
}
|
||||
|
||||
private static void addSpecial(List<HttpListenerPrefix> prefixes, HttpListenerPrefix prefix)
|
||||
{
|
||||
var path = prefix.Path;
|
||||
foreach (var pref in prefixes)
|
||||
{
|
||||
if (pref.Path == path)
|
||||
{
|
||||
throw new HttpListenerException(87, "The prefix is already in use.");
|
||||
}
|
||||
}
|
||||
|
||||
prefixes.Add(prefix);
|
||||
}
|
||||
|
||||
private static RSACryptoServiceProvider createRSAFromFile(string filename)
|
||||
{
|
||||
byte[] pvk = null;
|
||||
using (var fs = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
pvk = new byte[fs.Length];
|
||||
fs.Read(pvk, 0, pvk.Length);
|
||||
}
|
||||
|
||||
var rsa = new RSACryptoServiceProvider();
|
||||
rsa.ImportCspBlob(pvk);
|
||||
|
||||
return rsa;
|
||||
}
|
||||
|
||||
private static X509Certificate2 getCertificate(
|
||||
int port, string folderPath, X509Certificate2 defaultCertificate
|
||||
)
|
||||
{
|
||||
if (folderPath == null || folderPath.Length == 0)
|
||||
{
|
||||
folderPath = _defaultCertFolderPath;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var cer = Path.Combine(folderPath, string.Format("{0}.cer", port));
|
||||
var key = Path.Combine(folderPath, string.Format("{0}.key", port));
|
||||
if (File.Exists(cer) && File.Exists(key))
|
||||
{
|
||||
var cert = new X509Certificate2(cer);
|
||||
cert.PrivateKey = createRSAFromFile(key);
|
||||
|
||||
return cert;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return defaultCertificate;
|
||||
}
|
||||
|
||||
private static void onAccept(IAsyncResult asyncResult)
|
||||
{
|
||||
var lsnr = (EndPointListener)asyncResult.AsyncState;
|
||||
|
||||
Socket sock = null;
|
||||
try
|
||||
{
|
||||
sock = lsnr._socket.EndAccept(asyncResult);
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
lsnr._socket.BeginAccept(onAccept, lsnr);
|
||||
}
|
||||
catch
|
||||
{
|
||||
sock?.Close();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (sock == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
processAccepted(sock, lsnr);
|
||||
}
|
||||
|
||||
private static void processAccepted(Socket socket, EndPointListener listener)
|
||||
{
|
||||
HttpConnection conn = null;
|
||||
try
|
||||
{
|
||||
conn = new HttpConnection(socket, listener);
|
||||
lock (listener._unregisteredSync)
|
||||
{
|
||||
listener._unregistered[conn] = conn;
|
||||
}
|
||||
|
||||
conn.BeginReadRequest();
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (conn != null)
|
||||
{
|
||||
conn.Close(true);
|
||||
return;
|
||||
}
|
||||
|
||||
socket.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool removeSpecial(List<HttpListenerPrefix> prefixes, HttpListenerPrefix prefix)
|
||||
{
|
||||
var path = prefix.Path;
|
||||
var cnt = prefixes.Count;
|
||||
for (var i = 0; i < cnt; i++)
|
||||
{
|
||||
if (prefixes[i].Path == path)
|
||||
{
|
||||
prefixes.RemoveAt(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static HttpListener searchHttpListenerFromSpecial(
|
||||
string path, List<HttpListenerPrefix> prefixes
|
||||
)
|
||||
{
|
||||
if (prefixes == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpListener bestMatch = null;
|
||||
|
||||
var bestLen = -1;
|
||||
foreach (var pref in prefixes)
|
||||
{
|
||||
var prefPath = pref.Path;
|
||||
|
||||
var len = prefPath.Length;
|
||||
if (len < bestLen)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (path.StartsWith(prefPath))
|
||||
{
|
||||
bestLen = len;
|
||||
bestMatch = pref.Listener;
|
||||
}
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
private void leaveIfNoPrefix()
|
||||
{
|
||||
if (_prefixes.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var prefs = _unhandled;
|
||||
if (prefs != null && prefs.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
prefs = _all;
|
||||
if (prefs != null && prefs.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EndPointManager.RemoveEndPoint(_endpoint);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,6 +30,97 @@ namespace EonaCat.Network
|
|||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an HTTP listener and its associated prefixes.
|
||||
/// </summary>
|
||||
/// <param name="listener">The HTTP listener to be added.</param>
|
||||
public static void AddListener(HttpListener listener)
|
||||
{
|
||||
var added = new List<string>();
|
||||
lock (((ICollection)_endpoints).SyncRoot)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var pref in listener.Prefixes)
|
||||
{
|
||||
addPrefix(pref, listener);
|
||||
added.Add(pref);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
foreach (var pref in added)
|
||||
{
|
||||
removePrefix(pref, listener);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an HTTP listener prefix.
|
||||
/// </summary>
|
||||
/// <param name="uriPrefix">The URI prefix to be added.</param>
|
||||
/// <param name="listener">The HTTP listener associated with the prefix.</param>
|
||||
public static void AddPrefix(string uriPrefix, HttpListener listener)
|
||||
{
|
||||
lock (((ICollection)_endpoints).SyncRoot)
|
||||
{
|
||||
addPrefix(uriPrefix, listener);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an HTTP listener and its associated prefixes.
|
||||
/// </summary>
|
||||
/// <param name="listener">The HTTP listener to be removed.</param>
|
||||
public static void RemoveListener(HttpListener listener)
|
||||
{
|
||||
lock (((ICollection)_endpoints).SyncRoot)
|
||||
{
|
||||
foreach (var pref in listener.Prefixes)
|
||||
{
|
||||
removePrefix(pref, listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an HTTP listener prefix.
|
||||
/// </summary>
|
||||
/// <param name="uriPrefix">The URI prefix to be removed.</param>
|
||||
/// <param name="listener">The HTTP listener associated with the prefix.</param>
|
||||
public static void RemovePrefix(string uriPrefix, HttpListener listener)
|
||||
{
|
||||
lock (((ICollection)_endpoints).SyncRoot)
|
||||
{
|
||||
removePrefix(uriPrefix, listener);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an endpoint and closes its associated listener.
|
||||
/// </summary>
|
||||
/// <param name="endpoint">The endpoint to be removed.</param>
|
||||
/// <returns><c>true</c> if the endpoint is successfully removed; otherwise, <c>false</c>.</returns>
|
||||
internal static bool RemoveEndPoint(IPEndPoint endpoint)
|
||||
{
|
||||
lock (((ICollection)_endpoints).SyncRoot)
|
||||
{
|
||||
if (!_endpoints.TryGetValue(endpoint, out EndPointListener lsnr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_endpoints.Remove(endpoint);
|
||||
lsnr.Close();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void addPrefix(string uriPrefix, HttpListener listener)
|
||||
{
|
||||
var pref = new HttpListenerPrefix(uriPrefix);
|
||||
|
@ -137,96 +228,5 @@ namespace EonaCat.Network
|
|||
|
||||
lsnr.RemovePrefix(pref, listener);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an endpoint and closes its associated listener.
|
||||
/// </summary>
|
||||
/// <param name="endpoint">The endpoint to be removed.</param>
|
||||
/// <returns><c>true</c> if the endpoint is successfully removed; otherwise, <c>false</c>.</returns>
|
||||
internal static bool RemoveEndPoint(IPEndPoint endpoint)
|
||||
{
|
||||
lock (((ICollection)_endpoints).SyncRoot)
|
||||
{
|
||||
if (!_endpoints.TryGetValue(endpoint, out EndPointListener lsnr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_endpoints.Remove(endpoint);
|
||||
lsnr.Close();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an HTTP listener and its associated prefixes.
|
||||
/// </summary>
|
||||
/// <param name="listener">The HTTP listener to be added.</param>
|
||||
public static void AddListener(HttpListener listener)
|
||||
{
|
||||
var added = new List<string>();
|
||||
lock (((ICollection)_endpoints).SyncRoot)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var pref in listener.Prefixes)
|
||||
{
|
||||
addPrefix(pref, listener);
|
||||
added.Add(pref);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
foreach (var pref in added)
|
||||
{
|
||||
removePrefix(pref, listener);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an HTTP listener prefix.
|
||||
/// </summary>
|
||||
/// <param name="uriPrefix">The URI prefix to be added.</param>
|
||||
/// <param name="listener">The HTTP listener associated with the prefix.</param>
|
||||
public static void AddPrefix(string uriPrefix, HttpListener listener)
|
||||
{
|
||||
lock (((ICollection)_endpoints).SyncRoot)
|
||||
{
|
||||
addPrefix(uriPrefix, listener);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an HTTP listener and its associated prefixes.
|
||||
/// </summary>
|
||||
/// <param name="listener">The HTTP listener to be removed.</param>
|
||||
public static void RemoveListener(HttpListener listener)
|
||||
{
|
||||
lock (((ICollection)_endpoints).SyncRoot)
|
||||
{
|
||||
foreach (var pref in listener.Prefixes)
|
||||
{
|
||||
removePrefix(pref, listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an HTTP listener prefix.
|
||||
/// </summary>
|
||||
/// <param name="uriPrefix">The URI prefix to be removed.</param>
|
||||
/// <param name="listener">The HTTP listener associated with the prefix.</param>
|
||||
public static void RemovePrefix(string uriPrefix, HttpListener listener)
|
||||
{
|
||||
lock (((ICollection)_endpoints).SyncRoot)
|
||||
{
|
||||
removePrefix(uriPrefix, listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,10 +17,13 @@ namespace EonaCat.Network
|
|||
/// </summary>
|
||||
internal sealed class HttpConnection
|
||||
{
|
||||
private byte[] _buffer;
|
||||
private const int _bufferLength = 8192;
|
||||
private const int TIMEOUT_CONTINUE = 15000;
|
||||
private const int TIMEOUT_INITIAL = 90000;
|
||||
private readonly EndPointListener _listener;
|
||||
private readonly object _lock;
|
||||
private readonly Dictionary<int, bool> _timeoutCanceled;
|
||||
private byte[] _buffer;
|
||||
private HttpListenerContext _context;
|
||||
private bool _contextRegistered;
|
||||
private StringBuilder _currentLine;
|
||||
|
@ -28,14 +31,11 @@ namespace EonaCat.Network
|
|||
private RequestStream _inputStream;
|
||||
private HttpListener _lastListener;
|
||||
private LineState _lineState;
|
||||
private readonly EndPointListener _listener;
|
||||
private ResponseStream _outputStream;
|
||||
private int _position;
|
||||
private MemoryStream _requestBuffer;
|
||||
private Socket _socket;
|
||||
private readonly object _lock;
|
||||
private int _timeout;
|
||||
private readonly Dictionary<int, bool> _timeoutCanceled;
|
||||
private Timer _timer;
|
||||
|
||||
/// <summary>
|
||||
|
@ -106,375 +106,6 @@ namespace EonaCat.Network
|
|||
/// </summary>
|
||||
public Stream Stream { get; private set; }
|
||||
|
||||
private void close()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_socket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DisposeTimer();
|
||||
DisposeRequestBuffer();
|
||||
DisposeStream();
|
||||
CloseSocket();
|
||||
}
|
||||
|
||||
UnregisterContext();
|
||||
RemoveConnection();
|
||||
}
|
||||
|
||||
private void CloseSocket()
|
||||
{
|
||||
try
|
||||
{
|
||||
_socket.Shutdown(SocketShutdown.Both);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
_socket.Close();
|
||||
_socket = null;
|
||||
}
|
||||
|
||||
private void DisposeRequestBuffer()
|
||||
{
|
||||
if (_requestBuffer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_requestBuffer.Dispose();
|
||||
_requestBuffer = null;
|
||||
}
|
||||
|
||||
private void DisposeStream()
|
||||
{
|
||||
if (Stream == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_inputStream = null;
|
||||
_outputStream = null;
|
||||
|
||||
Stream.Dispose();
|
||||
Stream = null;
|
||||
}
|
||||
|
||||
private void DisposeTimer()
|
||||
{
|
||||
if (_timer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_timer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
_timer.Dispose();
|
||||
_timer = null;
|
||||
}
|
||||
|
||||
private void Setup()
|
||||
{
|
||||
_context = new HttpListenerContext(this);
|
||||
_inputState = InputState.RequestLine;
|
||||
_inputStream = null;
|
||||
_lineState = LineState.None;
|
||||
_outputStream = null;
|
||||
_position = 0;
|
||||
_requestBuffer = new MemoryStream();
|
||||
}
|
||||
|
||||
private static void OnRead(IAsyncResult asyncResult)
|
||||
{
|
||||
var httpConnection = (HttpConnection)asyncResult.AsyncState;
|
||||
if (httpConnection._socket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (httpConnection._lock)
|
||||
{
|
||||
if (httpConnection._socket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var nread = -1;
|
||||
var len = 0;
|
||||
try
|
||||
{
|
||||
var current = httpConnection.Reuses;
|
||||
if (!httpConnection._timeoutCanceled[current])
|
||||
{
|
||||
httpConnection._timer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
httpConnection._timeoutCanceled[current] = true;
|
||||
}
|
||||
|
||||
nread = httpConnection.Stream.EndRead(asyncResult);
|
||||
httpConnection._requestBuffer.Write(httpConnection._buffer, 0, nread);
|
||||
len = (int)httpConnection._requestBuffer.Length;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (httpConnection._requestBuffer != null && httpConnection._requestBuffer.Length > 0)
|
||||
{
|
||||
httpConnection.SendError(exception.Message, 400);
|
||||
return;
|
||||
}
|
||||
|
||||
httpConnection.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (nread <= 0)
|
||||
{
|
||||
httpConnection.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (httpConnection.ProcessInput(httpConnection._requestBuffer.GetBuffer(), len))
|
||||
{
|
||||
if (!httpConnection._context.HasError)
|
||||
{
|
||||
httpConnection._context.Request.FinishInitialization();
|
||||
}
|
||||
|
||||
if (httpConnection._context.HasError)
|
||||
{
|
||||
httpConnection.SendError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!httpConnection._listener.TrySearchHttpListener(httpConnection._context.Request.Url, out HttpListener httpListener))
|
||||
{
|
||||
httpConnection.SendError(null, 404);
|
||||
return;
|
||||
}
|
||||
|
||||
if (httpConnection._lastListener != httpListener)
|
||||
{
|
||||
httpConnection.RemoveConnection();
|
||||
if (!httpListener.AddConnection(httpConnection))
|
||||
{
|
||||
httpConnection.close();
|
||||
return;
|
||||
}
|
||||
|
||||
httpConnection._lastListener = httpListener;
|
||||
}
|
||||
|
||||
httpConnection._context.Listener = httpListener;
|
||||
if (!httpConnection._context.Authenticate())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (httpConnection._context.Register())
|
||||
{
|
||||
httpConnection._contextRegistered = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
httpConnection.Stream.BeginRead(httpConnection._buffer, 0, _bufferLength, OnRead, httpConnection);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnTimeout(object state)
|
||||
{
|
||||
var httpConnection = (HttpConnection)state;
|
||||
var current = httpConnection.Reuses;
|
||||
if (httpConnection._socket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (httpConnection._lock)
|
||||
{
|
||||
if (httpConnection._socket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (httpConnection._timeoutCanceled[current])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
httpConnection.SendError(null, 408);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ProcessInput(byte[] data, int length)
|
||||
{
|
||||
_currentLine ??= new StringBuilder(64);
|
||||
|
||||
var nread = 0;
|
||||
try
|
||||
{
|
||||
string line;
|
||||
while ((line = ReadLineFrom(data, _position, length, out nread)) != null)
|
||||
{
|
||||
_position += nread;
|
||||
if (line.Length == 0)
|
||||
{
|
||||
if (_inputState == InputState.RequestLine)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_position > 32768)
|
||||
{
|
||||
_context.ErrorMessage = "Headers too long";
|
||||
}
|
||||
|
||||
_currentLine = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_inputState == InputState.RequestLine)
|
||||
{
|
||||
_context.Request.SetRequestLine(line);
|
||||
_inputState = InputState.Headers;
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.Request.AddHeader(line);
|
||||
}
|
||||
|
||||
if (_context.HasError)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_context.ErrorMessage = exception.Message;
|
||||
return true;
|
||||
}
|
||||
|
||||
_position += nread;
|
||||
if (_position >= 32768)
|
||||
{
|
||||
_context.ErrorMessage = "Headers too long";
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string ReadLineFrom(byte[] buffer, int offset, int length, out int read)
|
||||
{
|
||||
read = 0;
|
||||
|
||||
for (var i = offset; i < length && _lineState != LineState.LineFeed; i++)
|
||||
{
|
||||
read++;
|
||||
|
||||
var b = buffer[i];
|
||||
if (b == 13)
|
||||
{
|
||||
_lineState = LineState.CarriageReturn;
|
||||
}
|
||||
else if (b == 10)
|
||||
{
|
||||
_lineState = LineState.LineFeed;
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentLine.Append((char)b);
|
||||
}
|
||||
}
|
||||
|
||||
if (_lineState != LineState.LineFeed)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var line = _currentLine.ToString();
|
||||
|
||||
_currentLine.Length = 0;
|
||||
_lineState = LineState.None;
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
private void RemoveConnection()
|
||||
{
|
||||
if (_lastListener != null)
|
||||
{
|
||||
_lastListener.RemoveConnection(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
_listener.RemoveConnection(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void UnregisterContext()
|
||||
{
|
||||
if (!_contextRegistered)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_context.Unregister();
|
||||
_contextRegistered = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the connection.
|
||||
/// </summary>
|
||||
/// <param name="force">True to force close, false otherwise.</param>
|
||||
internal void Close(bool force)
|
||||
{
|
||||
if (_socket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_socket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!force)
|
||||
{
|
||||
GetResponseStream().Close(false);
|
||||
if (!_context.Response.CloseConnection && _context.Request.FlushInput())
|
||||
{
|
||||
// Don't close. Keep working.
|
||||
Reuses++;
|
||||
DisposeRequestBuffer();
|
||||
UnregisterContext();
|
||||
Setup();
|
||||
BeginReadRequest();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_outputStream?.Close(true);
|
||||
}
|
||||
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initiates reading the request.
|
||||
/// </summary>
|
||||
|
@ -629,5 +260,373 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the connection.
|
||||
/// </summary>
|
||||
/// <param name="force">True to force close, false otherwise.</param>
|
||||
internal void Close(bool force)
|
||||
{
|
||||
if (_socket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_socket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!force)
|
||||
{
|
||||
GetResponseStream().Close(false);
|
||||
if (!_context.Response.CloseConnection && _context.Request.FlushInput())
|
||||
{
|
||||
// Don't close. Keep working.
|
||||
Reuses++;
|
||||
DisposeRequestBuffer();
|
||||
UnregisterContext();
|
||||
Setup();
|
||||
BeginReadRequest();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_outputStream?.Close(true);
|
||||
}
|
||||
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnRead(IAsyncResult asyncResult)
|
||||
{
|
||||
var httpConnection = (HttpConnection)asyncResult.AsyncState;
|
||||
if (httpConnection._socket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (httpConnection._lock)
|
||||
{
|
||||
if (httpConnection._socket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var nread = -1;
|
||||
var len = 0;
|
||||
try
|
||||
{
|
||||
var current = httpConnection.Reuses;
|
||||
if (!httpConnection._timeoutCanceled[current])
|
||||
{
|
||||
httpConnection._timer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
httpConnection._timeoutCanceled[current] = true;
|
||||
}
|
||||
|
||||
nread = httpConnection.Stream.EndRead(asyncResult);
|
||||
httpConnection._requestBuffer.Write(httpConnection._buffer, 0, nread);
|
||||
len = (int)httpConnection._requestBuffer.Length;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (httpConnection._requestBuffer != null && httpConnection._requestBuffer.Length > 0)
|
||||
{
|
||||
httpConnection.SendError(exception.Message, 400);
|
||||
return;
|
||||
}
|
||||
|
||||
httpConnection.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (nread <= 0)
|
||||
{
|
||||
httpConnection.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (httpConnection.ProcessInput(httpConnection._requestBuffer.GetBuffer(), len))
|
||||
{
|
||||
if (!httpConnection._context.HasError)
|
||||
{
|
||||
httpConnection._context.Request.FinishInitialization();
|
||||
}
|
||||
|
||||
if (httpConnection._context.HasError)
|
||||
{
|
||||
httpConnection.SendError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!httpConnection._listener.TrySearchHttpListener(httpConnection._context.Request.Url, out HttpListener httpListener))
|
||||
{
|
||||
httpConnection.SendError(null, 404);
|
||||
return;
|
||||
}
|
||||
|
||||
if (httpConnection._lastListener != httpListener)
|
||||
{
|
||||
httpConnection.RemoveConnection();
|
||||
if (!httpListener.AddConnection(httpConnection))
|
||||
{
|
||||
httpConnection.close();
|
||||
return;
|
||||
}
|
||||
|
||||
httpConnection._lastListener = httpListener;
|
||||
}
|
||||
|
||||
httpConnection._context.Listener = httpListener;
|
||||
if (!httpConnection._context.Authenticate())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (httpConnection._context.Register())
|
||||
{
|
||||
httpConnection._contextRegistered = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
httpConnection.Stream.BeginRead(httpConnection._buffer, 0, _bufferLength, OnRead, httpConnection);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnTimeout(object state)
|
||||
{
|
||||
var httpConnection = (HttpConnection)state;
|
||||
var current = httpConnection.Reuses;
|
||||
if (httpConnection._socket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (httpConnection._lock)
|
||||
{
|
||||
if (httpConnection._socket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (httpConnection._timeoutCanceled[current])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
httpConnection.SendError(null, 408);
|
||||
}
|
||||
}
|
||||
|
||||
private void close()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_socket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DisposeTimer();
|
||||
DisposeRequestBuffer();
|
||||
DisposeStream();
|
||||
CloseSocket();
|
||||
}
|
||||
|
||||
UnregisterContext();
|
||||
RemoveConnection();
|
||||
}
|
||||
|
||||
private void CloseSocket()
|
||||
{
|
||||
try
|
||||
{
|
||||
_socket.Shutdown(SocketShutdown.Both);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
_socket.Close();
|
||||
_socket = null;
|
||||
}
|
||||
|
||||
private void DisposeRequestBuffer()
|
||||
{
|
||||
if (_requestBuffer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_requestBuffer.Dispose();
|
||||
_requestBuffer = null;
|
||||
}
|
||||
|
||||
private void DisposeStream()
|
||||
{
|
||||
if (Stream == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_inputStream = null;
|
||||
_outputStream = null;
|
||||
|
||||
Stream.Dispose();
|
||||
Stream = null;
|
||||
}
|
||||
|
||||
private void DisposeTimer()
|
||||
{
|
||||
if (_timer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_timer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
_timer.Dispose();
|
||||
_timer = null;
|
||||
}
|
||||
|
||||
private bool ProcessInput(byte[] data, int length)
|
||||
{
|
||||
_currentLine ??= new StringBuilder(64);
|
||||
|
||||
var nread = 0;
|
||||
try
|
||||
{
|
||||
string line;
|
||||
while ((line = ReadLineFrom(data, _position, length, out nread)) != null)
|
||||
{
|
||||
_position += nread;
|
||||
if (line.Length == 0)
|
||||
{
|
||||
if (_inputState == InputState.RequestLine)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_position > 32768)
|
||||
{
|
||||
_context.ErrorMessage = "Headers too long";
|
||||
}
|
||||
|
||||
_currentLine = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_inputState == InputState.RequestLine)
|
||||
{
|
||||
_context.Request.SetRequestLine(line);
|
||||
_inputState = InputState.Headers;
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.Request.AddHeader(line);
|
||||
}
|
||||
|
||||
if (_context.HasError)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_context.ErrorMessage = exception.Message;
|
||||
return true;
|
||||
}
|
||||
|
||||
_position += nread;
|
||||
if (_position >= 32768)
|
||||
{
|
||||
_context.ErrorMessage = "Headers too long";
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string ReadLineFrom(byte[] buffer, int offset, int length, out int read)
|
||||
{
|
||||
read = 0;
|
||||
|
||||
for (var i = offset; i < length && _lineState != LineState.LineFeed; i++)
|
||||
{
|
||||
read++;
|
||||
|
||||
var b = buffer[i];
|
||||
if (b == 13)
|
||||
{
|
||||
_lineState = LineState.CarriageReturn;
|
||||
}
|
||||
else if (b == 10)
|
||||
{
|
||||
_lineState = LineState.LineFeed;
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentLine.Append((char)b);
|
||||
}
|
||||
}
|
||||
|
||||
if (_lineState != LineState.LineFeed)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var line = _currentLine.ToString();
|
||||
|
||||
_currentLine.Length = 0;
|
||||
_lineState = LineState.None;
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
private void RemoveConnection()
|
||||
{
|
||||
if (_lastListener != null)
|
||||
{
|
||||
_lastListener.RemoveConnection(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
_listener.RemoveConnection(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void Setup()
|
||||
{
|
||||
_context = new HttpListenerContext(this);
|
||||
_inputState = InputState.RequestLine;
|
||||
_inputStream = null;
|
||||
_lineState = LineState.None;
|
||||
_outputStream = null;
|
||||
_position = 0;
|
||||
_requestBuffer = new MemoryStream();
|
||||
}
|
||||
private void UnregisterContext()
|
||||
{
|
||||
if (!_contextRegistered)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_context.Unregister();
|
||||
_contextRegistered = false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,16 +19,6 @@ namespace EonaCat.Network
|
|||
Type = type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the header is multi-value in a request.
|
||||
/// </summary>
|
||||
internal bool IsMultiValueInRequest => (Type & HttpHeaderType.MultiValueInRequest) == HttpHeaderType.MultiValueInRequest;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the header is multi-value in a response.
|
||||
/// </summary>
|
||||
internal bool IsMultiValueInResponse => (Type & HttpHeaderType.MultiValueInResponse) == HttpHeaderType.MultiValueInResponse;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the header is for a request.
|
||||
/// </summary>
|
||||
|
@ -49,6 +39,15 @@ namespace EonaCat.Network
|
|||
/// </summary>
|
||||
public HttpHeaderType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the header is multi-value in a request.
|
||||
/// </summary>
|
||||
internal bool IsMultiValueInRequest => (Type & HttpHeaderType.MultiValueInRequest) == HttpHeaderType.MultiValueInRequest;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the header is multi-value in a response.
|
||||
/// </summary>
|
||||
internal bool IsMultiValueInResponse => (Type & HttpHeaderType.MultiValueInResponse) == HttpHeaderType.MultiValueInResponse;
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the header is multi-value.
|
||||
/// </summary>
|
||||
|
|
|
@ -13,25 +13,25 @@ namespace EonaCat.Network
|
|||
/// </summary>
|
||||
public sealed class HttpListener : IDisposable
|
||||
{
|
||||
private AuthenticationSchemes _authSchemes;
|
||||
private Func<HttpListenerRequest, AuthenticationSchemes> _authSchemeSelector;
|
||||
private string _certFolderPath;
|
||||
private static readonly string _defaultRealm;
|
||||
private readonly Dictionary<HttpConnection, HttpConnection> _connections;
|
||||
private readonly object _connectionsSync;
|
||||
private readonly List<HttpListenerContext> contextQueue;
|
||||
private readonly object _contextQueueLock;
|
||||
private readonly Dictionary<HttpListenerContext, HttpListenerContext> _contextRegistry;
|
||||
private readonly object _contextRegistryLock;
|
||||
private static readonly string _defaultRealm;
|
||||
private readonly HttpListenerPrefixCollection _prefixes;
|
||||
private readonly List<HttpListenerAsyncResult> _waitQueue;
|
||||
private readonly object _waitQueueLock;
|
||||
private readonly List<HttpListenerContext> contextQueue;
|
||||
private bool _allowForwardedRequest;
|
||||
private AuthenticationSchemes _authSchemes;
|
||||
private Func<HttpListenerRequest, AuthenticationSchemes> _authSchemeSelector;
|
||||
private string _certFolderPath;
|
||||
private bool _ignoreWriteExceptions;
|
||||
private volatile bool _listening;
|
||||
private readonly HttpListenerPrefixCollection _prefixes;
|
||||
private string _realm;
|
||||
private SSLConfigServer _sslConfig;
|
||||
private Func<IIdentity, NetworkCredential> _userCredFinder;
|
||||
private readonly List<HttpListenerAsyncResult> _waitQueue;
|
||||
private readonly object _waitQueueLock;
|
||||
|
||||
static HttpListener()
|
||||
{
|
||||
_defaultRealm = "SECRET AREA";
|
||||
|
@ -59,9 +59,29 @@ namespace EonaCat.Network
|
|||
_waitQueueLock = ((ICollection)_waitQueue).SyncRoot;
|
||||
}
|
||||
|
||||
internal bool IsDisposed { get; private set; }
|
||||
|
||||
internal bool ReuseAddress { get; set; }
|
||||
public static bool IsSupported => true;
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the server accepts every
|
||||
/// handshake request without checking the request URI.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The set operation does nothing if the server has already started or
|
||||
/// it is shutting down.
|
||||
/// </remarks>
|
||||
/// <value>
|
||||
/// <para>
|
||||
/// <c>true</c> if the server accepts every handshake request without
|
||||
/// checking the request URI; otherwise, <c>false</c>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The default value is <c>false</c>.
|
||||
/// </para>
|
||||
/// </value>
|
||||
public bool AllowForwardedRequest
|
||||
{
|
||||
get { return _allowForwardedRequest; }
|
||||
set { _allowForwardedRequest = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication schemes used by this listener.
|
||||
|
@ -127,9 +147,6 @@ namespace EonaCat.Network
|
|||
}
|
||||
|
||||
public bool IsListening => _listening;
|
||||
|
||||
public static bool IsSupported => true;
|
||||
|
||||
public HttpListenerPrefixCollection Prefixes
|
||||
{
|
||||
get
|
||||
|
@ -197,6 +214,289 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
|
||||
internal bool IsDisposed { get; private set; }
|
||||
|
||||
internal bool ReuseAddress { get; set; }
|
||||
/// <summary>
|
||||
/// Aborts the listener and releases all resources associated with it.
|
||||
/// </summary>
|
||||
public void Abort()
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
close(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begins asynchronously getting an HTTP context from the listener.
|
||||
/// </summary>
|
||||
/// <param name="callback">The method to call when the operation completes.</param>
|
||||
/// <param name="state">A user-defined object that contains information about the asynchronous operation.</param>
|
||||
/// <returns>An <see cref="IAsyncResult"/> that represents the asynchronous operation.</returns>
|
||||
public IAsyncResult BeginGetContext(AsyncCallback callback, object state)
|
||||
{
|
||||
CheckDisposed();
|
||||
if (_prefixes.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("The listener has no URI prefix on which listens.");
|
||||
}
|
||||
|
||||
if (!_listening)
|
||||
{
|
||||
throw new InvalidOperationException("The listener hasn't been started.");
|
||||
}
|
||||
|
||||
return BeginGetContext(new HttpListenerAsyncResult(callback, state));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the listener.
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
close(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of the resources used by the <see cref="HttpListener"/>.
|
||||
/// </summary>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
close(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends an asynchronous operation to get an HTTP context from the listener.
|
||||
/// </summary>
|
||||
/// <param name="asyncResult">The reference to the pending asynchronous request to finish.</param>
|
||||
/// <returns>An <see cref="HttpListenerContext"/> that represents the context of the asynchronous operation.</returns>
|
||||
public HttpListenerContext EndGetContext(IAsyncResult asyncResult)
|
||||
{
|
||||
CheckDisposed();
|
||||
if (asyncResult == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(asyncResult));
|
||||
}
|
||||
|
||||
if (asyncResult is not HttpListenerAsyncResult result)
|
||||
{
|
||||
throw new ArgumentException("A wrong IAsyncResult.", nameof(asyncResult));
|
||||
}
|
||||
|
||||
if (result.EndCalled)
|
||||
{
|
||||
throw new InvalidOperationException("This IAsyncResult cannot be reused.");
|
||||
}
|
||||
|
||||
result.EndCalled = true;
|
||||
if (!result.IsCompleted)
|
||||
{
|
||||
result.AsyncWaitHandle.WaitOne();
|
||||
}
|
||||
|
||||
return result.GetContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next available HTTP context from the listener.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="HttpListenerContext"/> that represents the context of the HTTP request.</returns>
|
||||
public HttpListenerContext GetContext()
|
||||
{
|
||||
CheckDisposed();
|
||||
if (_prefixes.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("The listener has no URI prefix on which listens.");
|
||||
}
|
||||
|
||||
if (!_listening)
|
||||
{
|
||||
throw new InvalidOperationException("The listener hasn't been started.");
|
||||
}
|
||||
|
||||
var result = BeginGetContext(new HttpListenerAsyncResult(null, null));
|
||||
result.InGet = true;
|
||||
|
||||
return EndGetContext(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts listening for incoming requests.
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
CheckDisposed();
|
||||
if (_listening)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EndPointManager.AddListener(this);
|
||||
_listening = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops listening for incoming requests.
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
CheckDisposed();
|
||||
if (!_listening)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_listening = false;
|
||||
EndPointManager.RemoveListener(this);
|
||||
|
||||
lock (_contextRegistryLock)
|
||||
{
|
||||
CleanupContextQueue(true);
|
||||
}
|
||||
|
||||
CleanupContextRegistry();
|
||||
CleanupConnections();
|
||||
CleanupWaitQueue(new HttpListenerException(995, "The listener is closed."));
|
||||
}
|
||||
|
||||
internal bool AddConnection(HttpConnection connection)
|
||||
{
|
||||
if (!_listening)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_connectionsSync)
|
||||
{
|
||||
if (!_listening)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_connections[connection] = connection;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal HttpListenerAsyncResult BeginGetContext(HttpListenerAsyncResult asyncResult)
|
||||
{
|
||||
lock (_contextRegistryLock)
|
||||
{
|
||||
if (!_listening)
|
||||
{
|
||||
throw new HttpListenerException(995);
|
||||
}
|
||||
|
||||
var ctx = GetContextFromQueue();
|
||||
if (ctx == null)
|
||||
{
|
||||
_waitQueue.Add(asyncResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
asyncResult.Complete(ctx, true);
|
||||
}
|
||||
|
||||
return asyncResult;
|
||||
}
|
||||
}
|
||||
|
||||
internal void CheckDisposed()
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
throw new ObjectDisposedException(GetType().ToString());
|
||||
}
|
||||
}
|
||||
|
||||
internal string GetRealm()
|
||||
{
|
||||
var realm = _realm;
|
||||
return realm != null && realm.Length > 0 ? realm : _defaultRealm;
|
||||
}
|
||||
|
||||
internal Func<IIdentity, NetworkCredential> GetUserCredentialsFinder()
|
||||
{
|
||||
return _userCredFinder;
|
||||
}
|
||||
|
||||
internal bool RegisterContext(HttpListenerContext context)
|
||||
{
|
||||
if (!_listening)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_contextRegistryLock)
|
||||
{
|
||||
if (!_listening)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_contextRegistry[context] = context;
|
||||
|
||||
var result = GetAsyncResultFromQueue();
|
||||
if (result == null)
|
||||
{
|
||||
contextQueue.Add(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Complete(context);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal void RemoveConnection(HttpConnection connection)
|
||||
{
|
||||
lock (_connectionsSync)
|
||||
{
|
||||
_connections.Remove(connection);
|
||||
}
|
||||
}
|
||||
|
||||
internal AuthenticationSchemes SelectAuthenticationScheme(HttpListenerRequest request)
|
||||
{
|
||||
var selector = _authSchemeSelector;
|
||||
if (selector == null)
|
||||
{
|
||||
return _authSchemes;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return selector(request);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return AuthenticationSchemes.None;
|
||||
}
|
||||
}
|
||||
|
||||
internal void UnregisterContext(HttpListenerContext context)
|
||||
{
|
||||
lock (_contextRegistryLock)
|
||||
{
|
||||
_contextRegistry.Remove(context);
|
||||
}
|
||||
}
|
||||
|
||||
private void CleanupConnections()
|
||||
{
|
||||
HttpConnection[] httpConnections = null;
|
||||
|
@ -335,285 +635,5 @@ namespace EonaCat.Network
|
|||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
internal bool AddConnection(HttpConnection connection)
|
||||
{
|
||||
if (!_listening)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_connectionsSync)
|
||||
{
|
||||
if (!_listening)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_connections[connection] = connection;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal HttpListenerAsyncResult BeginGetContext(HttpListenerAsyncResult asyncResult)
|
||||
{
|
||||
lock (_contextRegistryLock)
|
||||
{
|
||||
if (!_listening)
|
||||
{
|
||||
throw new HttpListenerException(995);
|
||||
}
|
||||
|
||||
var ctx = GetContextFromQueue();
|
||||
if (ctx == null)
|
||||
{
|
||||
_waitQueue.Add(asyncResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
asyncResult.Complete(ctx, true);
|
||||
}
|
||||
|
||||
return asyncResult;
|
||||
}
|
||||
}
|
||||
|
||||
internal void CheckDisposed()
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
throw new ObjectDisposedException(GetType().ToString());
|
||||
}
|
||||
}
|
||||
|
||||
internal string GetRealm()
|
||||
{
|
||||
var realm = _realm;
|
||||
return realm != null && realm.Length > 0 ? realm : _defaultRealm;
|
||||
}
|
||||
|
||||
internal Func<IIdentity, NetworkCredential> GetUserCredentialsFinder()
|
||||
{
|
||||
return _userCredFinder;
|
||||
}
|
||||
|
||||
internal bool RegisterContext(HttpListenerContext context)
|
||||
{
|
||||
if (!_listening)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_contextRegistryLock)
|
||||
{
|
||||
if (!_listening)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_contextRegistry[context] = context;
|
||||
|
||||
var result = GetAsyncResultFromQueue();
|
||||
if (result == null)
|
||||
{
|
||||
contextQueue.Add(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Complete(context);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal void RemoveConnection(HttpConnection connection)
|
||||
{
|
||||
lock (_connectionsSync)
|
||||
{
|
||||
_connections.Remove(connection);
|
||||
}
|
||||
}
|
||||
|
||||
internal AuthenticationSchemes SelectAuthenticationScheme(HttpListenerRequest request)
|
||||
{
|
||||
var selector = _authSchemeSelector;
|
||||
if (selector == null)
|
||||
{
|
||||
return _authSchemes;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return selector(request);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return AuthenticationSchemes.None;
|
||||
}
|
||||
}
|
||||
|
||||
internal void UnregisterContext(HttpListenerContext context)
|
||||
{
|
||||
lock (_contextRegistryLock)
|
||||
{
|
||||
_contextRegistry.Remove(context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aborts the listener and releases all resources associated with it.
|
||||
/// </summary>
|
||||
public void Abort()
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
close(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begins asynchronously getting an HTTP context from the listener.
|
||||
/// </summary>
|
||||
/// <param name="callback">The method to call when the operation completes.</param>
|
||||
/// <param name="state">A user-defined object that contains information about the asynchronous operation.</param>
|
||||
/// <returns>An <see cref="IAsyncResult"/> that represents the asynchronous operation.</returns>
|
||||
public IAsyncResult BeginGetContext(AsyncCallback callback, object state)
|
||||
{
|
||||
CheckDisposed();
|
||||
if (_prefixes.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("The listener has no URI prefix on which listens.");
|
||||
}
|
||||
|
||||
if (!_listening)
|
||||
{
|
||||
throw new InvalidOperationException("The listener hasn't been started.");
|
||||
}
|
||||
|
||||
return BeginGetContext(new HttpListenerAsyncResult(callback, state));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the listener.
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
close(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends an asynchronous operation to get an HTTP context from the listener.
|
||||
/// </summary>
|
||||
/// <param name="asyncResult">The reference to the pending asynchronous request to finish.</param>
|
||||
/// <returns>An <see cref="HttpListenerContext"/> that represents the context of the asynchronous operation.</returns>
|
||||
public HttpListenerContext EndGetContext(IAsyncResult asyncResult)
|
||||
{
|
||||
CheckDisposed();
|
||||
if (asyncResult == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(asyncResult));
|
||||
}
|
||||
|
||||
if (asyncResult is not HttpListenerAsyncResult result)
|
||||
{
|
||||
throw new ArgumentException("A wrong IAsyncResult.", nameof(asyncResult));
|
||||
}
|
||||
|
||||
if (result.EndCalled)
|
||||
{
|
||||
throw new InvalidOperationException("This IAsyncResult cannot be reused.");
|
||||
}
|
||||
|
||||
result.EndCalled = true;
|
||||
if (!result.IsCompleted)
|
||||
{
|
||||
result.AsyncWaitHandle.WaitOne();
|
||||
}
|
||||
|
||||
return result.GetContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next available HTTP context from the listener.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="HttpListenerContext"/> that represents the context of the HTTP request.</returns>
|
||||
public HttpListenerContext GetContext()
|
||||
{
|
||||
CheckDisposed();
|
||||
if (_prefixes.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("The listener has no URI prefix on which listens.");
|
||||
}
|
||||
|
||||
if (!_listening)
|
||||
{
|
||||
throw new InvalidOperationException("The listener hasn't been started.");
|
||||
}
|
||||
|
||||
var result = BeginGetContext(new HttpListenerAsyncResult(null, null));
|
||||
result.InGet = true;
|
||||
|
||||
return EndGetContext(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts listening for incoming requests.
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
CheckDisposed();
|
||||
if (_listening)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EndPointManager.AddListener(this);
|
||||
_listening = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops listening for incoming requests.
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
CheckDisposed();
|
||||
if (!_listening)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_listening = false;
|
||||
EndPointManager.RemoveListener(this);
|
||||
|
||||
lock (_contextRegistryLock)
|
||||
{
|
||||
CleanupContextQueue(true);
|
||||
}
|
||||
|
||||
CleanupContextRegistry();
|
||||
CleanupConnections();
|
||||
CleanupWaitQueue(new HttpListenerException(995, "The listener is closed."));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of the resources used by the <see cref="HttpListener"/>.
|
||||
/// </summary>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
close(true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,10 +12,10 @@ namespace EonaCat.Network
|
|||
internal class HttpListenerAsyncResult : IAsyncResult
|
||||
{
|
||||
private readonly AsyncCallback _callback;
|
||||
private readonly object _sync;
|
||||
private bool _completed;
|
||||
private HttpListenerContext _context;
|
||||
private Exception _exception;
|
||||
private readonly object _sync;
|
||||
private ManualResetEvent _waitHandle;
|
||||
|
||||
/// <summary>
|
||||
|
@ -30,16 +30,6 @@ namespace EonaCat.Network
|
|||
_sync = new object();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the <see cref="EndGetContext"/> method has been called.
|
||||
/// </summary>
|
||||
internal bool EndCalled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the asynchronous operation is in progress.
|
||||
/// </summary>
|
||||
internal bool InGet { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user-defined object that contains information about the asynchronous operation.
|
||||
/// </summary>
|
||||
|
@ -78,38 +68,15 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
|
||||
// Private method to complete the asynchronous operation
|
||||
private static void complete(HttpListenerAsyncResult asyncResult)
|
||||
{
|
||||
lock (asyncResult._sync)
|
||||
{
|
||||
asyncResult._completed = true;
|
||||
|
||||
var waitHandle = asyncResult._waitHandle;
|
||||
waitHandle?.Set();
|
||||
}
|
||||
|
||||
var callback = asyncResult._callback;
|
||||
if (callback == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadPool.QueueUserWorkItem(
|
||||
state =>
|
||||
{
|
||||
try
|
||||
{
|
||||
callback(asyncResult);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
},
|
||||
null
|
||||
);
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the <see cref="EndGetContext"/> method has been called.
|
||||
/// </summary>
|
||||
internal bool EndCalled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the asynchronous operation is in progress.
|
||||
/// </summary>
|
||||
internal bool InGet { get; set; }
|
||||
/// <summary>
|
||||
/// Completes the asynchronous operation with the specified exception.
|
||||
/// </summary>
|
||||
|
@ -158,5 +125,37 @@ namespace EonaCat.Network
|
|||
|
||||
return _context;
|
||||
}
|
||||
|
||||
// Private method to complete the asynchronous operation
|
||||
private static void complete(HttpListenerAsyncResult asyncResult)
|
||||
{
|
||||
lock (asyncResult._sync)
|
||||
{
|
||||
asyncResult._completed = true;
|
||||
|
||||
var waitHandle = asyncResult._waitHandle;
|
||||
waitHandle?.Set();
|
||||
}
|
||||
|
||||
var callback = asyncResult._callback;
|
||||
if (callback == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadPool.QueueUserWorkItem(
|
||||
state =>
|
||||
{
|
||||
try
|
||||
{
|
||||
callback(asyncResult);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
},
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,6 +25,21 @@ namespace EonaCat.Network
|
|||
Response = new HttpListenerResponse(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="HttpListenerRequest"/> associated with the context.
|
||||
/// </summary>
|
||||
public HttpListenerRequest Request { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="HttpListenerResponse"/> associated with the context.
|
||||
/// </summary>
|
||||
public HttpListenerResponse Response { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="IPrincipal"/> associated with the user.
|
||||
/// </summary>
|
||||
public IPrincipal User { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying <see cref="HttpConnection"/> for the context.
|
||||
/// </summary>
|
||||
|
@ -49,21 +64,34 @@ namespace EonaCat.Network
|
|||
/// Gets or sets the <see cref="HttpListener"/> associated with the context.
|
||||
/// </summary>
|
||||
internal HttpListener Listener { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="HttpListenerRequest"/> associated with the context.
|
||||
/// Accepts a WebSocket connection with the specified protocol.
|
||||
/// </summary>
|
||||
public HttpListenerRequest Request { get; }
|
||||
/// <param name="protocol">The WebSocket subprotocol to negotiate.</param>
|
||||
/// <returns>The <see cref="HttpListenerWSContext"/> for the WebSocket connection.</returns>
|
||||
public HttpListenerWSContext AcceptWebSocket(string protocol)
|
||||
{
|
||||
if (_websocketContext != null)
|
||||
{
|
||||
throw new InvalidOperationException("Accepting already in progress.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="HttpListenerResponse"/> associated with the context.
|
||||
/// </summary>
|
||||
public HttpListenerResponse Response { get; }
|
||||
if (protocol != null)
|
||||
{
|
||||
if (protocol.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Empty string.", nameof(protocol));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="IPrincipal"/> associated with the user.
|
||||
/// </summary>
|
||||
public IPrincipal User { get; private set; }
|
||||
if (!protocol.IsToken())
|
||||
{
|
||||
throw new ArgumentException("Contains invalid characters", nameof(protocol));
|
||||
}
|
||||
}
|
||||
|
||||
_websocketContext = new HttpListenerWSContext(this, protocol);
|
||||
return _websocketContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authenticates the user based on the specified authentication scheme.
|
||||
|
@ -119,34 +147,5 @@ namespace EonaCat.Network
|
|||
{
|
||||
Listener.UnregisterContext(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accepts a WebSocket connection with the specified protocol.
|
||||
/// </summary>
|
||||
/// <param name="protocol">The WebSocket subprotocol to negotiate.</param>
|
||||
/// <returns>The <see cref="HttpListenerWSContext"/> for the WebSocket connection.</returns>
|
||||
public HttpListenerWSContext AcceptWebSocket(string protocol)
|
||||
{
|
||||
if (_websocketContext != null)
|
||||
{
|
||||
throw new InvalidOperationException("Accepting already in progress.");
|
||||
}
|
||||
|
||||
if (protocol != null)
|
||||
{
|
||||
if (protocol.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Empty string.", nameof(protocol));
|
||||
}
|
||||
|
||||
if (!protocol.IsToken())
|
||||
{
|
||||
throw new ArgumentException("Contains invalid characters", nameof(protocol));
|
||||
}
|
||||
}
|
||||
|
||||
_websocketContext = new HttpListenerWSContext(this, protocol);
|
||||
return _websocketContext;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,15 +13,6 @@ namespace EonaCat.Network
|
|||
[Serializable]
|
||||
public class HttpListenerException : Win32Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpListenerException"/> class.
|
||||
/// </summary>
|
||||
protected HttpListenerException(
|
||||
SerializationInfo serializationInfo, StreamingContext streamingContext)
|
||||
: base(serializationInfo, streamingContext)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpListenerException"/> class with no error message.
|
||||
/// </summary>
|
||||
|
@ -48,6 +39,14 @@ namespace EonaCat.Network
|
|||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpListenerException"/> class.
|
||||
/// </summary>
|
||||
protected HttpListenerException(
|
||||
SerializationInfo serializationInfo, StreamingContext streamingContext)
|
||||
: base(serializationInfo, streamingContext)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the Win32 error code associated with this exception.
|
||||
/// </summary>
|
||||
|
|
|
@ -52,35 +52,6 @@ namespace EonaCat.Network
|
|||
/// </summary>
|
||||
public string Port { get; private set; }
|
||||
|
||||
private void parse(string uriPrefix)
|
||||
{
|
||||
if (uriPrefix.StartsWith("https"))
|
||||
{
|
||||
IsSecure = true;
|
||||
}
|
||||
|
||||
var len = uriPrefix.Length;
|
||||
var startHost = uriPrefix.IndexOf(':') + 3;
|
||||
var root = uriPrefix.IndexOf('/', startHost + 1, len - startHost - 1);
|
||||
|
||||
var colon = uriPrefix.LastIndexOf(':', root - 1, root - startHost - 1);
|
||||
if (uriPrefix[root - 1] != ']' && colon > startHost)
|
||||
{
|
||||
Host = uriPrefix.Substring(startHost, colon - startHost);
|
||||
Port = uriPrefix.Substring(colon + 1, root - colon - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Host = uriPrefix.Substring(startHost, root - startHost);
|
||||
Port = IsSecure ? "443" : "80";
|
||||
}
|
||||
|
||||
Path = uriPrefix.Substring(root);
|
||||
|
||||
_prefix =
|
||||
string.Format("http{0}://{1}:{2}{3}", IsSecure ? "s" : "", Host, Port, Path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the specified URI prefix is valid.
|
||||
/// </summary>
|
||||
|
@ -163,5 +134,34 @@ namespace EonaCat.Network
|
|||
{
|
||||
return _prefix;
|
||||
}
|
||||
|
||||
private void parse(string uriPrefix)
|
||||
{
|
||||
if (uriPrefix.StartsWith("https"))
|
||||
{
|
||||
IsSecure = true;
|
||||
}
|
||||
|
||||
var len = uriPrefix.Length;
|
||||
var startHost = uriPrefix.IndexOf(':') + 3;
|
||||
var root = uriPrefix.IndexOf('/', startHost + 1, len - startHost - 1);
|
||||
|
||||
var colon = uriPrefix.LastIndexOf(':', root - 1, root - startHost - 1);
|
||||
if (uriPrefix[root - 1] != ']' && colon > startHost)
|
||||
{
|
||||
Host = uriPrefix.Substring(startHost, colon - startHost);
|
||||
Port = uriPrefix.Substring(colon + 1, root - colon - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Host = uriPrefix.Substring(startHost, root - startHost);
|
||||
Port = IsSecure ? "443" : "80";
|
||||
}
|
||||
|
||||
Path = uriPrefix.Substring(root);
|
||||
|
||||
_prefix =
|
||||
string.Format("http{0}://{1}:{2}{3}", IsSecure ? "s" : "", Host, Port, Path);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -120,6 +120,15 @@ namespace EonaCat.Network
|
|||
return _prefixes.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the collection.
|
||||
/// </summary>
|
||||
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _prefixes.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified URI prefix from the collection.
|
||||
/// </summary>
|
||||
|
@ -141,14 +150,5 @@ namespace EonaCat.Network
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the collection.
|
||||
/// </summary>
|
||||
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _prefixes.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,7 +6,6 @@ using System.Collections.Generic;
|
|||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
|
||||
namespace EonaCat.Network
|
||||
|
@ -17,12 +16,12 @@ namespace EonaCat.Network
|
|||
public sealed class HttpListenerRequest
|
||||
{
|
||||
private static readonly byte[] _100continue;
|
||||
private readonly HttpListenerContext _context;
|
||||
private readonly WebHeaderCollection _headers;
|
||||
private bool _chunked;
|
||||
private Encoding _contentEncoding;
|
||||
private bool _contentLengthSet;
|
||||
private readonly HttpListenerContext _context;
|
||||
private CookieCollection _cookies;
|
||||
private readonly WebHeaderCollection _headers;
|
||||
private Stream _inputStream;
|
||||
private bool _keepAlive;
|
||||
private bool _keepAliveSet;
|
||||
|
@ -162,7 +161,7 @@ namespace EonaCat.Network
|
|||
/// <summary>
|
||||
/// Gets the query string in the request.
|
||||
/// </summary>
|
||||
public NameValueCollection QueryString => _queryString ??= HttpUtility.InternalParseQueryString(Url.Query, Encoding.UTF8);
|
||||
public NameValueCollection QueryString => _queryString ??= HttpUtility.InternalParseQueryString(Url.Query, ContentEncoding);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw URL of the request.
|
||||
|
@ -209,18 +208,13 @@ namespace EonaCat.Network
|
|||
/// </summary>
|
||||
public string[] UserLanguages { get; private set; }
|
||||
|
||||
private static bool tryCreateVersion(string version, out Version result)
|
||||
public override string ToString()
|
||||
{
|
||||
try
|
||||
{
|
||||
result = new Version(version);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
var buff = new StringBuilder(64);
|
||||
buff.AppendFormat("{0} {1} HTTP/{2}\r\n", HttpMethod, _uri, _version);
|
||||
buff.Append(_headers.ToString());
|
||||
|
||||
return buff.ToString();
|
||||
}
|
||||
|
||||
internal void AddHeader(string header)
|
||||
|
@ -404,13 +398,18 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
private static bool tryCreateVersion(string version, out Version result)
|
||||
{
|
||||
var buff = new StringBuilder(64);
|
||||
buff.AppendFormat("{0} {1} HTTP/{2}\r\n", HttpMethod, _uri, _version);
|
||||
buff.Append(_headers.ToString());
|
||||
|
||||
return buff.ToString();
|
||||
try
|
||||
{
|
||||
result = new Version(version);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,10 @@
|
|||
// This file is part of the EonaCat project(s) which is released under the Apache License.
|
||||
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
|
||||
|
||||
using EonaCat.Logger;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace EonaCat.Network
|
||||
|
@ -16,10 +14,10 @@ namespace EonaCat.Network
|
|||
/// </summary>
|
||||
public sealed class HttpListenerResponse : IDisposable
|
||||
{
|
||||
private readonly HttpListenerContext _context;
|
||||
private Encoding _contentEncoding;
|
||||
private long _contentLength;
|
||||
private string _contentType;
|
||||
private readonly HttpListenerContext _context;
|
||||
private CookieCollection _cookies;
|
||||
private bool _disposed;
|
||||
private WebHeaderCollection _headers;
|
||||
|
|
|
@ -9,8 +9,8 @@ namespace EonaCat.Network
|
|||
internal class HttpStreamAsyncResult : IAsyncResult
|
||||
{
|
||||
private readonly AsyncCallback _callback;
|
||||
private bool _isCompleted;
|
||||
private readonly object _sync;
|
||||
private bool _isCompleted;
|
||||
private ManualResetEvent _waitHandle;
|
||||
|
||||
internal HttpStreamAsyncResult(AsyncCallback callback, object state)
|
||||
|
@ -20,6 +20,30 @@ namespace EonaCat.Network
|
|||
_sync = new object();
|
||||
}
|
||||
|
||||
public object AsyncState { get; }
|
||||
public WaitHandle AsyncWaitHandle
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
return _waitHandle ??= new ManualResetEvent(_isCompleted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool CompletedSynchronously => SyncRead == Count;
|
||||
public bool IsCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
return _isCompleted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal byte[] Buffer { get; set; }
|
||||
|
||||
internal int Count { get; set; }
|
||||
|
@ -31,33 +55,6 @@ namespace EonaCat.Network
|
|||
internal int Offset { get; set; }
|
||||
|
||||
internal int SyncRead { get; set; }
|
||||
|
||||
public object AsyncState { get; }
|
||||
|
||||
public WaitHandle AsyncWaitHandle
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
return _waitHandle ??= new ManualResetEvent(_isCompleted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool CompletedSynchronously => SyncRead == Count;
|
||||
|
||||
public bool IsCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
return _isCompleted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void Complete()
|
||||
{
|
||||
lock (_sync)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -25,62 +25,23 @@ namespace EonaCat.Network
|
|||
|
||||
internal class Logger
|
||||
{
|
||||
internal static string LoggingDirectory { get; private set; }
|
||||
private static readonly LogManager _logManager;
|
||||
|
||||
internal static bool IsLoggingDirectorySet => !string.IsNullOrWhiteSpace(LoggingDirectory);
|
||||
private static bool HasBeenSetup { get; set; }
|
||||
internal static bool DisableConsole { get; set; }
|
||||
internal static bool IsLoggingEnabled { get; set; }
|
||||
|
||||
static Logger()
|
||||
{
|
||||
_logManager = new LogManager(new LoggerSettings { RemoveMessagePrefix = true });
|
||||
_logManager.OnException += _logManager_OnException;
|
||||
}
|
||||
|
||||
internal static void Setup(string loggingDirectory = null)
|
||||
{
|
||||
LoggingDirectory = loggingDirectory;
|
||||
_logManager.Settings.FileLoggerOptions.FileNamePrefix = "EonaCat.Network";
|
||||
|
||||
if (IsLoggingDirectorySet)
|
||||
{
|
||||
_logManager.Settings.FileLoggerOptions.LogDirectory = LoggingDirectory;
|
||||
}
|
||||
_logManager.Settings.UseLocalTime = true;
|
||||
_logManager.Settings.FileLoggerOptions.UseLocalTime = true;
|
||||
_logManager.Settings.SysLogServers = new List<SyslogServer>();
|
||||
_logManager.Settings.SplunkServers = new List<SplunkServer>();
|
||||
_logManager.Settings.GrayLogServers = new List<GrayLogServer>();
|
||||
_logManager.StartNewLogAsync();
|
||||
HasBeenSetup = true;
|
||||
}
|
||||
|
||||
private static void _logManager_OnException(object? sender, ErrorMessage e)
|
||||
{
|
||||
Console.WriteLine(e.Message);
|
||||
if (e.Exception != null)
|
||||
{
|
||||
Console.WriteLine(e.Exception);
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool DisableConsole { get; set; }
|
||||
internal static bool IsLoggingDirectorySet => !string.IsNullOrWhiteSpace(LoggingDirectory);
|
||||
internal static bool IsLoggingEnabled { get; set; }
|
||||
internal static string LoggingDirectory { get; private set; }
|
||||
private static bool HasBeenSetup { get; set; }
|
||||
internal static void AddGrayLogServer(string hostname, int port)
|
||||
{
|
||||
_logManager.Settings.GrayLogServers.Add(new GrayLogServer(hostname, port));
|
||||
}
|
||||
|
||||
internal static bool RemoveGrayLogServer(GrayLogServer grayLogServer)
|
||||
{
|
||||
return _logManager.Settings.GrayLogServers.Remove(grayLogServer);
|
||||
}
|
||||
|
||||
internal static void GrayLogState(bool state)
|
||||
{
|
||||
_logManager.Settings.SendToGrayLogServers = state;
|
||||
}
|
||||
|
||||
internal static void AddSplunkServer(string splunkHecUrl, string splunkHecToken, bool disableSSL = false)
|
||||
{
|
||||
var splunkServer = new SplunkServer(splunkHecUrl, splunkHecToken);
|
||||
|
@ -91,56 +52,14 @@ namespace EonaCat.Network
|
|||
_logManager.Settings.SplunkServers.Add(splunkServer);
|
||||
}
|
||||
|
||||
internal static bool RemoveSplunkServer(SplunkServer splunkServer)
|
||||
{
|
||||
return _logManager.Settings.SplunkServers.Remove(splunkServer);
|
||||
}
|
||||
|
||||
internal static void SplunkState(bool state)
|
||||
{
|
||||
_logManager.Settings.SendToSplunkServers = state;
|
||||
}
|
||||
|
||||
internal static void AddSyslogServer(string ipAddress, int port)
|
||||
{
|
||||
_logManager.Settings.SysLogServers.Add(new SyslogServer(ipAddress, port));
|
||||
}
|
||||
|
||||
internal static bool RemoveSyslogServer(SyslogServer syslogServer)
|
||||
internal static void Critical(string message, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
|
||||
{
|
||||
return _logManager.Settings.SysLogServers.Remove(syslogServer);
|
||||
}
|
||||
|
||||
internal static void SysLogState(bool state)
|
||||
{
|
||||
_logManager.Settings.SendToSyslogServers = state;
|
||||
}
|
||||
|
||||
internal static void Info(string message, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
|
||||
{
|
||||
Write(message, ELogType.INFO, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings);
|
||||
}
|
||||
|
||||
private static void Write(string message, ELogType logType, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
|
||||
{
|
||||
if (!HasBeenSetup)
|
||||
{
|
||||
Setup();
|
||||
}
|
||||
|
||||
if (DisableConsole)
|
||||
{
|
||||
writeToConsole = false;
|
||||
}
|
||||
|
||||
if (grayLogSettings != null)
|
||||
{
|
||||
_logManager.Write(message, logType, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings.Facility, grayLogSettings.Source, grayLogSettings.Version);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logManager.Write(message, logType, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers);
|
||||
}
|
||||
Write(message, ELogType.CRITICAL, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings);
|
||||
}
|
||||
|
||||
internal static void Debug(string message, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
|
||||
|
@ -182,14 +101,62 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
|
||||
internal static void Warning(string message, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
|
||||
internal static void GrayLogState(bool state)
|
||||
{
|
||||
Write(message, ELogType.WARNING, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings);
|
||||
_logManager.Settings.SendToGrayLogServers = state;
|
||||
}
|
||||
|
||||
internal static void Critical(string message, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
|
||||
internal static void Info(string message, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
|
||||
{
|
||||
Write(message, ELogType.CRITICAL, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings);
|
||||
Write(message, ELogType.INFO, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings);
|
||||
}
|
||||
|
||||
internal static bool RemoveGrayLogServer(GrayLogServer grayLogServer)
|
||||
{
|
||||
return _logManager.Settings.GrayLogServers.Remove(grayLogServer);
|
||||
}
|
||||
|
||||
internal static bool RemoveSplunkServer(SplunkServer splunkServer)
|
||||
{
|
||||
return _logManager.Settings.SplunkServers.Remove(splunkServer);
|
||||
}
|
||||
|
||||
internal static bool RemoveSyslogServer(SyslogServer syslogServer)
|
||||
{
|
||||
return _logManager.Settings.SysLogServers.Remove(syslogServer);
|
||||
}
|
||||
|
||||
internal static void Setup(string loggingDirectory = null)
|
||||
{
|
||||
LoggingDirectory = loggingDirectory;
|
||||
_logManager.Settings.FileLoggerOptions.FileNamePrefix = "EonaCat.Network";
|
||||
|
||||
if (IsLoggingDirectorySet)
|
||||
{
|
||||
_logManager.Settings.FileLoggerOptions.LogDirectory = LoggingDirectory;
|
||||
}
|
||||
_logManager.Settings.UseLocalTime = true;
|
||||
_logManager.Settings.FileLoggerOptions.UseLocalTime = true;
|
||||
_logManager.Settings.SysLogServers = new List<SyslogServer>();
|
||||
_logManager.Settings.SplunkServers = new List<SplunkServer>();
|
||||
_logManager.Settings.GrayLogServers = new List<GrayLogServer>();
|
||||
_logManager.StartNewLogAsync();
|
||||
HasBeenSetup = true;
|
||||
}
|
||||
|
||||
internal static void SplunkState(bool state)
|
||||
{
|
||||
_logManager.Settings.SendToSplunkServers = state;
|
||||
}
|
||||
|
||||
internal static void SysLogState(bool state)
|
||||
{
|
||||
_logManager.Settings.SendToSyslogServers = state;
|
||||
}
|
||||
|
||||
internal static void Trace(string message, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
|
||||
{
|
||||
Write(message, ELogType.TRACE, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings);
|
||||
}
|
||||
|
||||
internal static void Traffic(string message, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
|
||||
|
@ -197,9 +164,39 @@ namespace EonaCat.Network
|
|||
Write(message, ELogType.TRAFFIC, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings);
|
||||
}
|
||||
|
||||
internal static void Trace(string message, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
|
||||
internal static void Warning(string message, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
|
||||
{
|
||||
Write(message, ELogType.TRACE, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings);
|
||||
Write(message, ELogType.WARNING, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings);
|
||||
}
|
||||
|
||||
private static void _logManager_OnException(object? sender, ErrorMessage e)
|
||||
{
|
||||
Console.WriteLine(e.Message);
|
||||
if (e.Exception != null)
|
||||
{
|
||||
Console.WriteLine(e.Exception);
|
||||
}
|
||||
}
|
||||
private static void Write(string message, ELogType logType, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
|
||||
{
|
||||
if (!HasBeenSetup)
|
||||
{
|
||||
Setup();
|
||||
}
|
||||
|
||||
if (DisableConsole)
|
||||
{
|
||||
writeToConsole = false;
|
||||
}
|
||||
|
||||
if (grayLogSettings != null)
|
||||
{
|
||||
_logManager.Write(message, logType, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings.Facility, grayLogSettings.Source, grayLogSettings.Version);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logManager.Write(message, logType, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,19 +10,19 @@ namespace EonaCat.Network
|
|||
{
|
||||
public class SSLConfigClient
|
||||
{
|
||||
private LocalCertificateSelectionCallback _clientCertSelectionCallback;
|
||||
private X509CertificateCollection _clientCertificates;
|
||||
private LocalCertificateSelectionCallback _clientCertSelectionCallback;
|
||||
private RemoteCertificateValidationCallback _serverCertValidationCallback;
|
||||
|
||||
public SSLConfigClient()
|
||||
{
|
||||
SslProtocols = SslProtocols.Tls12;
|
||||
SslProtocols = SslProtocols.None;
|
||||
}
|
||||
|
||||
public SSLConfigClient(string targetHost)
|
||||
{
|
||||
TargetHost = targetHost;
|
||||
SslProtocols = SslProtocols.Tls12;
|
||||
SslProtocols = SslProtocols.None;
|
||||
}
|
||||
|
||||
public SSLConfigClient(SSLConfigClient sslConfig)
|
||||
|
@ -40,8 +40,6 @@ namespace EonaCat.Network
|
|||
TargetHost = sslConfig.TargetHost;
|
||||
}
|
||||
|
||||
public bool CheckForCertificateRevocation { get; set; }
|
||||
|
||||
public X509CertificateCollection Certificates
|
||||
{
|
||||
get
|
||||
|
@ -56,6 +54,7 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
|
||||
public bool CheckForCertificateRevocation { get; set; }
|
||||
public LocalCertificateSelectionCallback ClientCertificateSelectionCallback
|
||||
{
|
||||
get
|
||||
|
@ -71,8 +70,6 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
|
||||
public SslProtocols SslProtocols { get; set; }
|
||||
|
||||
public RemoteCertificateValidationCallback ServerCertificateValidationCallback
|
||||
{
|
||||
get
|
||||
|
@ -88,6 +85,7 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
|
||||
public SslProtocols SslProtocols { get; set; }
|
||||
public string TargetHost { get; set; }
|
||||
|
||||
private static X509Certificate SelectClientCertificate(
|
||||
|
|
|
@ -14,13 +14,13 @@ namespace EonaCat.Network
|
|||
|
||||
public SSLConfigServer()
|
||||
{
|
||||
SslProtocols = SslProtocols.Tls12;
|
||||
SslProtocols = SslProtocols.None;
|
||||
}
|
||||
|
||||
public SSLConfigServer(X509Certificate2 certificate)
|
||||
{
|
||||
Certificate = certificate;
|
||||
SslProtocols = SslProtocols.Tls12;
|
||||
SslProtocols = SslProtocols.None;
|
||||
}
|
||||
|
||||
public SSLConfigServer(SSLConfigServer sslConfig)
|
||||
|
@ -37,10 +37,9 @@ namespace EonaCat.Network
|
|||
Certificate = sslConfig.Certificate;
|
||||
}
|
||||
|
||||
public X509Certificate2 Certificate { get; set; }
|
||||
public bool CheckForCertificateRevocation { get; set; }
|
||||
|
||||
public bool IsClientCertificateRequired { get; set; }
|
||||
|
||||
public RemoteCertificateValidationCallback ClientCertificateValidationCallback
|
||||
{
|
||||
get
|
||||
|
@ -56,10 +55,8 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
|
||||
public bool IsClientCertificateRequired { get; set; }
|
||||
public SslProtocols SslProtocols { get; set; }
|
||||
|
||||
public X509Certificate2 Certificate { get; set; }
|
||||
|
||||
private static bool ValidateClientCertificate(
|
||||
object sender,
|
||||
X509Certificate certificate,
|
||||
|
|
|
@ -8,13 +8,12 @@ namespace EonaCat.Network
|
|||
{
|
||||
internal class RequestStream : Stream
|
||||
{
|
||||
private long _bodyLeft;
|
||||
private readonly byte[] _buffer;
|
||||
private readonly Stream _stream;
|
||||
private long _bodyLeft;
|
||||
private int _count;
|
||||
private bool _disposed;
|
||||
private int _offset;
|
||||
private readonly Stream _stream;
|
||||
|
||||
internal RequestStream(Stream stream, byte[] buffer, int offset, int count)
|
||||
: this(stream, buffer, offset, count, -1)
|
||||
{
|
||||
|
@ -51,64 +50,6 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
|
||||
// Returns 0 if we can keep reading from the base stream,
|
||||
// > 0 if we read something from the buffer,
|
||||
// -1 if we had a content length set and we finished reading that many bytes.
|
||||
private int FillFromBuffer(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
}
|
||||
|
||||
if (offset < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(offset), "A negative value.");
|
||||
}
|
||||
|
||||
if (count < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(count), "A negative value.");
|
||||
}
|
||||
|
||||
var bufferLength = buffer.Length;
|
||||
if (offset + count > bufferLength)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"The sum of 'offset' and 'count' is greater than 'buffer' length.");
|
||||
}
|
||||
|
||||
if (_bodyLeft == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (_count == 0 || count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (count > _count)
|
||||
{
|
||||
count = _count;
|
||||
}
|
||||
|
||||
if (_bodyLeft > 0 && count > _bodyLeft)
|
||||
{
|
||||
count = (int)_bodyLeft;
|
||||
}
|
||||
|
||||
Buffer.BlockCopy(_buffer, _offset, buffer, offset, count);
|
||||
_offset += count;
|
||||
_count -= count;
|
||||
if (_bodyLeft > 0)
|
||||
{
|
||||
_bodyLeft -= count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public override IAsyncResult BeginRead(
|
||||
byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
{
|
||||
|
@ -233,5 +174,63 @@ namespace EonaCat.Network
|
|||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
// Returns 0 if we can keep reading from the base stream,
|
||||
// > 0 if we read something from the buffer,
|
||||
// -1 if we had a content length set and we finished reading that many bytes.
|
||||
private int FillFromBuffer(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
}
|
||||
|
||||
if (offset < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(offset), "A negative value.");
|
||||
}
|
||||
|
||||
if (count < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(count), "A negative value.");
|
||||
}
|
||||
|
||||
var bufferLength = buffer.Length;
|
||||
if (offset + count > bufferLength)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"The sum of 'offset' and 'count' is greater than 'buffer' length.");
|
||||
}
|
||||
|
||||
if (_bodyLeft == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (_count == 0 || count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (count > _count)
|
||||
{
|
||||
count = _count;
|
||||
}
|
||||
|
||||
if (_bodyLeft > 0 && count > _bodyLeft)
|
||||
{
|
||||
count = (int)_bodyLeft;
|
||||
}
|
||||
|
||||
Buffer.BlockCopy(_buffer, _offset, buffer, offset, count);
|
||||
_offset += count;
|
||||
_count -= count;
|
||||
if (_bodyLeft > 0)
|
||||
{
|
||||
_bodyLeft -= count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,16 +9,15 @@ namespace EonaCat.Network
|
|||
{
|
||||
internal class ResponseStream : Stream
|
||||
{
|
||||
private MemoryStream _body;
|
||||
private static readonly byte[] _crlf = new byte[] { 13, 10 };
|
||||
private readonly Action<byte[], int, int> _write;
|
||||
private readonly Action<byte[], int, int> _writeChunked;
|
||||
private MemoryStream _body;
|
||||
private bool _disposed;
|
||||
private HttpListenerResponse _response;
|
||||
private bool _sendChunked;
|
||||
private Stream _stream;
|
||||
private readonly Action<byte[], int, int> _write;
|
||||
private Action<byte[], int, int> _writeBody;
|
||||
private readonly Action<byte[], int, int> _writeChunked;
|
||||
|
||||
internal ResponseStream(
|
||||
Stream stream, HttpListenerResponse response, bool ignoreWriteExceptions)
|
||||
{
|
||||
|
@ -60,6 +59,121 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
|
||||
public override IAsyncResult BeginRead(
|
||||
byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override IAsyncResult BeginWrite(
|
||||
byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(GetType().ToString());
|
||||
}
|
||||
|
||||
return _body.BeginWrite(buffer, offset, count, callback, state);
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
Close(false);
|
||||
}
|
||||
|
||||
public override int EndRead(IAsyncResult asyncResult)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void EndWrite(IAsyncResult asyncResult)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(GetType().ToString());
|
||||
}
|
||||
|
||||
_body.EndWrite(asyncResult);
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
if (!_disposed && (_sendChunked || _response.SendInChunks))
|
||||
{
|
||||
flush(false);
|
||||
}
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(GetType().ToString());
|
||||
}
|
||||
|
||||
_body.Write(buffer, offset, count);
|
||||
}
|
||||
|
||||
internal void Close(bool force)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
if (!force && flush(true))
|
||||
{
|
||||
_response.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_sendChunked)
|
||||
{
|
||||
var last = getChunkSizeBytes(0, true);
|
||||
_write(last, 0, last.Length);
|
||||
}
|
||||
|
||||
_body.Dispose();
|
||||
_body = null;
|
||||
|
||||
_response.Abort();
|
||||
}
|
||||
|
||||
_response = null;
|
||||
_stream = null;
|
||||
}
|
||||
|
||||
internal void InternalWrite(byte[] buffer, int offset, int count)
|
||||
{
|
||||
_write(buffer, offset, count);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
Close(!disposing);
|
||||
}
|
||||
|
||||
private static byte[] getChunkSizeBytes(int size, bool final)
|
||||
{
|
||||
return Encoding.ASCII.GetBytes(string.Format("{0:x}\r\n{1}", size, final ? "\r\n" : ""));
|
||||
}
|
||||
|
||||
private bool flush(bool closing)
|
||||
{
|
||||
if (!_response.HeadersSent)
|
||||
|
@ -137,12 +251,6 @@ namespace EonaCat.Network
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static byte[] getChunkSizeBytes(int size, bool final)
|
||||
{
|
||||
return Encoding.ASCII.GetBytes(string.Format("{0:x}\r\n{1}", size, final ? "\r\n" : ""));
|
||||
}
|
||||
|
||||
private void writeChunked(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var size = getChunkSizeBytes(count, false);
|
||||
|
@ -172,115 +280,5 @@ namespace EonaCat.Network
|
|||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal void Close(bool force)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
if (!force && flush(true))
|
||||
{
|
||||
_response.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_sendChunked)
|
||||
{
|
||||
var last = getChunkSizeBytes(0, true);
|
||||
_write(last, 0, last.Length);
|
||||
}
|
||||
|
||||
_body.Dispose();
|
||||
_body = null;
|
||||
|
||||
_response.Abort();
|
||||
}
|
||||
|
||||
_response = null;
|
||||
_stream = null;
|
||||
}
|
||||
|
||||
internal void InternalWrite(byte[] buffer, int offset, int count)
|
||||
{
|
||||
_write(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override IAsyncResult BeginRead(
|
||||
byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override IAsyncResult BeginWrite(
|
||||
byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(GetType().ToString());
|
||||
}
|
||||
|
||||
return _body.BeginWrite(buffer, offset, count, callback, state);
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
Close(false);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
Close(!disposing);
|
||||
}
|
||||
|
||||
public override int EndRead(IAsyncResult asyncResult)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void EndWrite(IAsyncResult asyncResult)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(GetType().ToString());
|
||||
}
|
||||
|
||||
_body.EndWrite(asyncResult);
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
if (!_disposed && (_sendChunked || _response.SendInChunks))
|
||||
{
|
||||
flush(false);
|
||||
}
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(GetType().ToString());
|
||||
}
|
||||
|
||||
_body.Write(buffer, offset, count);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -37,12 +37,9 @@ namespace EonaCat.Network
|
|||
{
|
||||
}
|
||||
|
||||
internal PayloadData PayloadData { get; }
|
||||
|
||||
public ushort Code => PayloadData.Code;
|
||||
|
||||
public string Reason => PayloadData.Reason ?? string.Empty;
|
||||
|
||||
public bool WasClean { get; internal set; }
|
||||
internal PayloadData PayloadData { get; }
|
||||
}
|
||||
}
|
|
@ -7,10 +7,9 @@ namespace EonaCat.Network
|
|||
{
|
||||
public class MessageEventArgs : EventArgs
|
||||
{
|
||||
private readonly byte[] _rawData;
|
||||
private string _data;
|
||||
private bool _dataSet;
|
||||
private readonly byte[] _rawData;
|
||||
|
||||
internal MessageEventArgs(WSFrame frame)
|
||||
{
|
||||
Opcode = frame.Opcode;
|
||||
|
@ -28,8 +27,6 @@ namespace EonaCat.Network
|
|||
_rawData = rawData;
|
||||
}
|
||||
|
||||
internal OperationCode Opcode { get; }
|
||||
|
||||
public string Data
|
||||
{
|
||||
get
|
||||
|
@ -40,11 +37,8 @@ namespace EonaCat.Network
|
|||
}
|
||||
|
||||
public bool IsBinary => Opcode == OperationCode.Binary;
|
||||
|
||||
public bool IsPing => Opcode == OperationCode.Ping;
|
||||
|
||||
public bool IsText => Opcode == OperationCode.Text;
|
||||
|
||||
public byte[] RawData
|
||||
{
|
||||
get
|
||||
|
@ -54,6 +48,7 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
|
||||
internal OperationCode Opcode { get; }
|
||||
private void setData()
|
||||
{
|
||||
if (_dataSet)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,7 +5,7 @@ namespace EonaCat.Network
|
|||
{
|
||||
internal enum OperationCode : byte
|
||||
{
|
||||
Cont = 0x0,
|
||||
Continue = 0x0,
|
||||
|
||||
Text = 0x1,
|
||||
|
||||
|
|
|
@ -37,6 +37,21 @@ namespace EonaCat.Network
|
|||
_reasonSet = true;
|
||||
}
|
||||
|
||||
internal PayloadData(PayloadData original)
|
||||
{
|
||||
_code = original._code;
|
||||
_codeSet = original._codeSet;
|
||||
ExtensionDataLength = original.ExtensionDataLength;
|
||||
_length = original._length;
|
||||
_reason = original._reason;
|
||||
_reasonSet = original._reasonSet;
|
||||
|
||||
_data = new byte[_length];
|
||||
original._data.CopyTo(_data, 0);
|
||||
|
||||
|
||||
}
|
||||
|
||||
internal PayloadData(byte[] data)
|
||||
: this(data, data.LongLength)
|
||||
{
|
||||
|
|
|
@ -27,36 +27,6 @@ namespace EonaCat.Network
|
|||
|
||||
public IPrincipal User => _context.User;
|
||||
|
||||
private string createFilePath(string childPath)
|
||||
{
|
||||
childPath = childPath.TrimStart('/', '\\');
|
||||
return new StringBuilder(_docRootPath, 32)
|
||||
.AppendFormat("/{0}", childPath)
|
||||
.ToString()
|
||||
.Replace('\\', '/');
|
||||
}
|
||||
|
||||
private static bool tryReadFile(string path, out byte[] contents)
|
||||
{
|
||||
contents = null;
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
contents = File.ReadAllBytes(path);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public byte[] ReadFile(string path)
|
||||
{
|
||||
if (path == null)
|
||||
|
@ -98,5 +68,35 @@ namespace EonaCat.Network
|
|||
|
||||
return tryReadFile(createFilePath(path), out contents);
|
||||
}
|
||||
|
||||
private static bool tryReadFile(string path, out byte[] contents)
|
||||
{
|
||||
contents = null;
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
contents = File.ReadAllBytes(path);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private string createFilePath(string childPath)
|
||||
{
|
||||
childPath = childPath.TrimStart('/', '\\');
|
||||
return new StringBuilder(_docRootPath, 32)
|
||||
.AppendFormat("/{0}", childPath)
|
||||
.ToString()
|
||||
.Replace('\\', '/');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,13 +11,13 @@ namespace EonaCat.Network
|
|||
{
|
||||
public class HttpServer
|
||||
{
|
||||
private bool _allowForwardedRequest;
|
||||
private string _docRootPath;
|
||||
private string _hostname;
|
||||
private HttpListener _listener;
|
||||
private Thread _receiveThread;
|
||||
private volatile ServerState _state;
|
||||
private object _sync;
|
||||
|
||||
public HttpServer()
|
||||
{
|
||||
init("*", System.Net.IPAddress.Any, 80, false);
|
||||
|
@ -100,8 +100,49 @@ namespace EonaCat.Network
|
|||
init(address.ToString(true), address, port, secure);
|
||||
}
|
||||
|
||||
public event EventHandler<HttpRequestEventArgs> OnConnect;
|
||||
|
||||
public event EventHandler<HttpRequestEventArgs> OnDelete;
|
||||
|
||||
public event EventHandler<HttpRequestEventArgs> OnGet;
|
||||
|
||||
public event EventHandler<HttpRequestEventArgs> OnHead;
|
||||
|
||||
public event EventHandler<HttpRequestEventArgs> OnOptions;
|
||||
|
||||
public event EventHandler<HttpRequestEventArgs> OnPatch;
|
||||
|
||||
public event EventHandler<HttpRequestEventArgs> OnPost;
|
||||
|
||||
public event EventHandler<HttpRequestEventArgs> OnPut;
|
||||
|
||||
public event EventHandler<HttpRequestEventArgs> OnTrace;
|
||||
|
||||
public System.Net.IPAddress Address { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the server accepts every
|
||||
/// handshake request without checking the request URI.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The set operation does nothing if the server has already started or
|
||||
/// it is shutting down.
|
||||
/// </remarks>
|
||||
/// <value>
|
||||
/// <para>
|
||||
/// <c>true</c> if the server accepts every handshake request without
|
||||
/// checking the request URI; otherwise, <c>false</c>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The default value is <c>false</c>.
|
||||
/// </para>
|
||||
/// </value>
|
||||
public bool AllowForwardedRequest
|
||||
{
|
||||
get { return _allowForwardedRequest; }
|
||||
set { _allowForwardedRequest = value; }
|
||||
}
|
||||
|
||||
public AuthenticationSchemes AuthenticationSchemes
|
||||
{
|
||||
get
|
||||
|
@ -208,6 +249,7 @@ namespace EonaCat.Network
|
|||
|
||||
public bool IsListening => _state == ServerState.Start;
|
||||
|
||||
public bool IsLoggingEnabled { get; private set; }
|
||||
public bool IsSecure { get; private set; }
|
||||
|
||||
public bool KeepClean
|
||||
|
@ -222,11 +264,7 @@ namespace EonaCat.Network
|
|||
WebSocketServices.AutoCleanSessions = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsLoggingEnabled { get; private set; }
|
||||
|
||||
public int Port { get; private set; }
|
||||
|
||||
public string Realm
|
||||
{
|
||||
get
|
||||
|
@ -339,24 +377,174 @@ namespace EonaCat.Network
|
|||
}
|
||||
|
||||
public WSEndpointManager WebSocketServices { get; private set; }
|
||||
public void AddWebSocketService<TEndpoint>(string path)
|
||||
where TEndpoint : WSEndpoint, new()
|
||||
{
|
||||
WebSocketServices.AddService<TEndpoint>(path, null);
|
||||
}
|
||||
|
||||
public event EventHandler<HttpRequestEventArgs> OnConnect;
|
||||
public void AddWebSocketService<TEndpoint>(
|
||||
string path, Action<TEndpoint> initializer
|
||||
)
|
||||
where TEndpoint : WSEndpoint, new()
|
||||
{
|
||||
WebSocketServices.AddService(path, initializer);
|
||||
}
|
||||
|
||||
public event EventHandler<HttpRequestEventArgs> OnDelete;
|
||||
public bool RemoveWebSocketService(string path)
|
||||
{
|
||||
return WebSocketServices.RemoveService(path);
|
||||
}
|
||||
|
||||
public event EventHandler<HttpRequestEventArgs> OnGet;
|
||||
public void Start()
|
||||
{
|
||||
if (IsSecure)
|
||||
{
|
||||
if (!checkCertificate(out string message))
|
||||
{
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler<HttpRequestEventArgs> OnHead;
|
||||
start();
|
||||
}
|
||||
|
||||
public event EventHandler<HttpRequestEventArgs> OnOptions;
|
||||
public void Stop()
|
||||
{
|
||||
stop((ushort)CloseStatusCode.NoStatus, string.Empty);
|
||||
}
|
||||
|
||||
public event EventHandler<HttpRequestEventArgs> OnPatch;
|
||||
public void Stop(ushort code, string reason)
|
||||
{
|
||||
if (!code.IsCloseStatusCode())
|
||||
{
|
||||
var message = "Less than 1000 or greater than 4999.";
|
||||
throw new ArgumentOutOfRangeException(nameof(code), message);
|
||||
}
|
||||
|
||||
public event EventHandler<HttpRequestEventArgs> OnPost;
|
||||
if (code == (ushort)CloseStatusCode.MandatoryExtension)
|
||||
{
|
||||
var message = $"{(ushort)CloseStatusCode.MandatoryExtension} cannot be used.";
|
||||
throw new ArgumentException(message, nameof(code));
|
||||
}
|
||||
|
||||
public event EventHandler<HttpRequestEventArgs> OnPut;
|
||||
if (!reason.IsNullOrEmpty())
|
||||
{
|
||||
if (code == (ushort)CloseStatusCode.NoStatus)
|
||||
{
|
||||
var message = $"{(ushort)CloseStatusCode.NoStatus} cannot be used.";
|
||||
throw new ArgumentException(message, nameof(code));
|
||||
}
|
||||
|
||||
public event EventHandler<HttpRequestEventArgs> OnTrace;
|
||||
if (!reason.TryGetUTF8EncodedBytes(out byte[] bytes))
|
||||
{
|
||||
var message = "It could not be UTF-8-encoded.";
|
||||
throw new ArgumentException(message, nameof(reason));
|
||||
}
|
||||
|
||||
if (bytes.Length > 123)
|
||||
{
|
||||
var message = "Its size is greater than 123 bytes.";
|
||||
throw new ArgumentOutOfRangeException(nameof(reason), message);
|
||||
}
|
||||
}
|
||||
|
||||
stop(code, reason);
|
||||
}
|
||||
|
||||
public void Stop(CloseStatusCode code, string reason)
|
||||
{
|
||||
if (code == CloseStatusCode.MandatoryExtension)
|
||||
{
|
||||
var message = "MandatoryExtension cannot be used.";
|
||||
throw new ArgumentException(message, nameof(code));
|
||||
}
|
||||
|
||||
if (!reason.IsNullOrEmpty())
|
||||
{
|
||||
if (code == CloseStatusCode.NoStatus)
|
||||
{
|
||||
var message = "NoStatus cannot be used.";
|
||||
throw new ArgumentException(message, nameof(code));
|
||||
}
|
||||
|
||||
if (!reason.TryGetUTF8EncodedBytes(out byte[] bytes))
|
||||
{
|
||||
var message = "It could not be UTF-8-encoded.";
|
||||
throw new ArgumentException(message, nameof(reason));
|
||||
}
|
||||
|
||||
if (bytes.Length > 123)
|
||||
{
|
||||
var message = "Its size is greater than 123 bytes.";
|
||||
throw new ArgumentOutOfRangeException(nameof(reason), message);
|
||||
}
|
||||
}
|
||||
|
||||
stop((ushort)code, reason);
|
||||
}
|
||||
|
||||
private static HttpListener createListener(
|
||||
string hostname, int port, bool secure
|
||||
)
|
||||
{
|
||||
var lsnr = new HttpListener();
|
||||
|
||||
var schm = secure ? "https" : "http";
|
||||
var pref = string.Format("{0}://{1}:{2}/", schm, hostname, port);
|
||||
lsnr.Prefixes.Add(pref);
|
||||
|
||||
return lsnr;
|
||||
}
|
||||
|
||||
private static bool tryCreateUri(
|
||||
string uriString, out Uri result, out string message
|
||||
)
|
||||
{
|
||||
result = null;
|
||||
message = null;
|
||||
|
||||
var uri = uriString.ToUri();
|
||||
if (uri == null)
|
||||
{
|
||||
message = "An invalid URI string.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!uri.IsAbsoluteUri)
|
||||
{
|
||||
message = "A relative URI.";
|
||||
return false;
|
||||
}
|
||||
|
||||
var schm = uri.Scheme;
|
||||
if (!(schm == "http" || schm == "https"))
|
||||
{
|
||||
message = "The scheme part is not 'http' or 'https'.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (uri.PathAndQuery != "/")
|
||||
{
|
||||
message = "It includes either or both path and query components.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (uri.Fragment.Length > 0)
|
||||
{
|
||||
message = "It includes the fragment component.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (uri.Port == 0)
|
||||
{
|
||||
message = "The port part is zero.";
|
||||
return false;
|
||||
}
|
||||
|
||||
result = uri;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void abort()
|
||||
{
|
||||
|
@ -432,20 +620,6 @@ namespace EonaCat.Network
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static HttpListener createListener(
|
||||
string hostname, int port, bool secure
|
||||
)
|
||||
{
|
||||
var lsnr = new HttpListener();
|
||||
|
||||
var schm = secure ? "https" : "http";
|
||||
var pref = string.Format("{0}://{1}:{2}/", schm, hostname, port);
|
||||
lsnr.Prefixes.Add(pref);
|
||||
|
||||
return lsnr;
|
||||
}
|
||||
|
||||
private void init(
|
||||
string hostname, System.Net.IPAddress address, int port, bool secure
|
||||
)
|
||||
|
@ -703,161 +877,5 @@ namespace EonaCat.Network
|
|||
_listener.Stop();
|
||||
_receiveThread.Join(millisecondsTimeout);
|
||||
}
|
||||
|
||||
private static bool tryCreateUri(
|
||||
string uriString, out Uri result, out string message
|
||||
)
|
||||
{
|
||||
result = null;
|
||||
message = null;
|
||||
|
||||
var uri = uriString.ToUri();
|
||||
if (uri == null)
|
||||
{
|
||||
message = "An invalid URI string.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!uri.IsAbsoluteUri)
|
||||
{
|
||||
message = "A relative URI.";
|
||||
return false;
|
||||
}
|
||||
|
||||
var schm = uri.Scheme;
|
||||
if (!(schm == "http" || schm == "https"))
|
||||
{
|
||||
message = "The scheme part is not 'http' or 'https'.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (uri.PathAndQuery != "/")
|
||||
{
|
||||
message = "It includes either or both path and query components.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (uri.Fragment.Length > 0)
|
||||
{
|
||||
message = "It includes the fragment component.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (uri.Port == 0)
|
||||
{
|
||||
message = "The port part is zero.";
|
||||
return false;
|
||||
}
|
||||
|
||||
result = uri;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AddWebSocketService<TEndpoint>(string path)
|
||||
where TEndpoint : WSEndpoint, new()
|
||||
{
|
||||
WebSocketServices.AddService<TEndpoint>(path, null);
|
||||
}
|
||||
|
||||
public void AddWebSocketService<TEndpoint>(
|
||||
string path, Action<TEndpoint> initializer
|
||||
)
|
||||
where TEndpoint : WSEndpoint, new()
|
||||
{
|
||||
WebSocketServices.AddService(path, initializer);
|
||||
}
|
||||
|
||||
public bool RemoveWebSocketService(string path)
|
||||
{
|
||||
return WebSocketServices.RemoveService(path);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (IsSecure)
|
||||
{
|
||||
if (!checkCertificate(out string message))
|
||||
{
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
|
||||
start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
stop((ushort)CloseStatusCode.NoStatus, string.Empty);
|
||||
}
|
||||
|
||||
public void Stop(ushort code, string reason)
|
||||
{
|
||||
if (!code.IsCloseStatusCode())
|
||||
{
|
||||
var message = "Less than 1000 or greater than 4999.";
|
||||
throw new ArgumentOutOfRangeException(nameof(code), message);
|
||||
}
|
||||
|
||||
if (code == (ushort)CloseStatusCode.MandatoryExtension)
|
||||
{
|
||||
var message = $"{(ushort)CloseStatusCode.MandatoryExtension} cannot be used.";
|
||||
throw new ArgumentException(message, nameof(code));
|
||||
}
|
||||
|
||||
if (!reason.IsNullOrEmpty())
|
||||
{
|
||||
if (code == (ushort)CloseStatusCode.NoStatus)
|
||||
{
|
||||
var message = $"{(ushort)CloseStatusCode.NoStatus} cannot be used.";
|
||||
throw new ArgumentException(message, nameof(code));
|
||||
}
|
||||
|
||||
if (!reason.TryGetUTF8EncodedBytes(out byte[] bytes))
|
||||
{
|
||||
var message = "It could not be UTF-8-encoded.";
|
||||
throw new ArgumentException(message, nameof(reason));
|
||||
}
|
||||
|
||||
if (bytes.Length > 123)
|
||||
{
|
||||
var message = "Its size is greater than 123 bytes.";
|
||||
throw new ArgumentOutOfRangeException(nameof(reason), message);
|
||||
}
|
||||
}
|
||||
|
||||
stop(code, reason);
|
||||
}
|
||||
|
||||
public void Stop(CloseStatusCode code, string reason)
|
||||
{
|
||||
if (code == CloseStatusCode.MandatoryExtension)
|
||||
{
|
||||
var message = "MandatoryExtension cannot be used.";
|
||||
throw new ArgumentException(message, nameof(code));
|
||||
}
|
||||
|
||||
if (!reason.IsNullOrEmpty())
|
||||
{
|
||||
if (code == CloseStatusCode.NoStatus)
|
||||
{
|
||||
var message = "NoStatus cannot be used.";
|
||||
throw new ArgumentException(message, nameof(code));
|
||||
}
|
||||
|
||||
if (!reason.TryGetUTF8EncodedBytes(out byte[] bytes))
|
||||
{
|
||||
var message = "It could not be UTF-8-encoded.";
|
||||
throw new ArgumentException(message, nameof(reason));
|
||||
}
|
||||
|
||||
if (bytes.Length > 123)
|
||||
{
|
||||
var message = "Its size is greater than 123 bytes.";
|
||||
throw new ArgumentOutOfRangeException(nameof(reason), message);
|
||||
}
|
||||
}
|
||||
|
||||
stop((ushort)code, reason);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,12 +17,8 @@ namespace EonaCat.Network
|
|||
StartTime = DateTime.MaxValue;
|
||||
}
|
||||
|
||||
protected WSSessionManager Sessions { get; private set; }
|
||||
|
||||
public WSContext Context { get; private set; }
|
||||
|
||||
public Func<CookieCollection, CookieCollection, bool> CookiesValidator { get; set; }
|
||||
|
||||
public bool EmitOnPing
|
||||
{
|
||||
get
|
||||
|
@ -43,11 +39,8 @@ namespace EonaCat.Network
|
|||
}
|
||||
|
||||
public string ID { get; private set; }
|
||||
|
||||
public bool IgnoreExtensions { get; set; }
|
||||
|
||||
public Func<string, bool> OriginValidator { get; set; }
|
||||
|
||||
public string Protocol
|
||||
{
|
||||
get
|
||||
|
@ -72,53 +65,8 @@ namespace EonaCat.Network
|
|||
}
|
||||
|
||||
public DateTime StartTime { get; private set; }
|
||||
|
||||
public WSState State => _websocket != null ? _websocket.ReadyState : WSState.Connecting;
|
||||
|
||||
private string checkHandshakeRequest(WSContext context)
|
||||
{
|
||||
return OriginValidator != null && !OriginValidator(context.Origin)
|
||||
? "Includes no Origin header, or it has an invalid value."
|
||||
: CookiesValidator != null
|
||||
&& !CookiesValidator(context.CookieCollection, context.WebSocket.CookieCollection)
|
||||
? "Includes no cookie, or an invalid cookie exists."
|
||||
: null;
|
||||
}
|
||||
|
||||
private void onClose(object sender, CloseEventArgs e)
|
||||
{
|
||||
if (ID == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Sessions.Remove(ID);
|
||||
OnClose(e);
|
||||
}
|
||||
|
||||
private void onError(object sender, ErrorEventArgs e)
|
||||
{
|
||||
OnError(e);
|
||||
}
|
||||
|
||||
private void onMessage(object sender, MessageEventArgs e)
|
||||
{
|
||||
OnMessage(e);
|
||||
}
|
||||
|
||||
private void onOpen(object sender, EventArgs e)
|
||||
{
|
||||
ID = Sessions.Add(this);
|
||||
if (ID == null)
|
||||
{
|
||||
_websocket.Close(CloseStatusCode.Away);
|
||||
return;
|
||||
}
|
||||
|
||||
StartTime = DateTime.Now;
|
||||
OnOpen();
|
||||
}
|
||||
|
||||
protected WSSessionManager Sessions { get; private set; }
|
||||
internal void Start(WSContext context, WSSessionManager sessions)
|
||||
{
|
||||
if (_websocket != null)
|
||||
|
@ -210,5 +158,49 @@ namespace EonaCat.Network
|
|||
{
|
||||
_websocket?.SendAsync(stream, length, completed);
|
||||
}
|
||||
|
||||
private string checkHandshakeRequest(WSContext context)
|
||||
{
|
||||
return OriginValidator != null && !OriginValidator(context.Origin)
|
||||
? "Includes no Origin header, or it has an invalid value."
|
||||
: CookiesValidator != null
|
||||
&& !CookiesValidator(context.CookieCollection, context.WebSocket.CookieCollection)
|
||||
? "Includes no cookie, or an invalid cookie exists."
|
||||
: null;
|
||||
}
|
||||
|
||||
private void onClose(object sender, CloseEventArgs e)
|
||||
{
|
||||
if (ID == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Sessions.Remove(ID);
|
||||
OnClose(e);
|
||||
}
|
||||
|
||||
private void onError(object sender, ErrorEventArgs e)
|
||||
{
|
||||
OnError(e);
|
||||
}
|
||||
|
||||
private void onMessage(object sender, MessageEventArgs e)
|
||||
{
|
||||
OnMessage(e);
|
||||
}
|
||||
|
||||
private void onOpen(object sender, EventArgs e)
|
||||
{
|
||||
ID = Sessions.Add(this);
|
||||
if (ID == null)
|
||||
{
|
||||
_websocket.Close(CloseStatusCode.Away);
|
||||
return;
|
||||
}
|
||||
|
||||
StartTime = DateTime.Now;
|
||||
OnOpen();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,8 +13,7 @@ namespace EonaCat.Network
|
|||
Sessions = new WSSessionManager();
|
||||
}
|
||||
|
||||
internal ServerState State => Sessions.State;
|
||||
|
||||
public abstract Type EndpointType { get; }
|
||||
public bool KeepClean
|
||||
{
|
||||
get
|
||||
|
@ -29,11 +28,7 @@ namespace EonaCat.Network
|
|||
}
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public WSSessionManager Sessions { get; }
|
||||
|
||||
public abstract Type EndpointType { get; }
|
||||
|
||||
public TimeSpan WaitTime
|
||||
{
|
||||
get
|
||||
|
@ -47,6 +42,7 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
|
||||
internal ServerState State => Sessions.State;
|
||||
internal void Start()
|
||||
{
|
||||
Sessions.Start();
|
||||
|
|
|
@ -11,10 +11,10 @@ namespace EonaCat.Network
|
|||
{
|
||||
public class WSEndpointManager
|
||||
{
|
||||
private volatile bool _clean;
|
||||
private readonly Dictionary<string, WSEndpointHost> _hosts;
|
||||
private volatile ServerState _state;
|
||||
private readonly object _sync;
|
||||
private volatile bool _clean;
|
||||
private volatile ServerState _state;
|
||||
private TimeSpan _waitTime;
|
||||
|
||||
internal WSEndpointManager()
|
||||
|
@ -26,59 +26,6 @@ namespace EonaCat.Network
|
|||
_waitTime = TimeSpan.FromSeconds(1);
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
return _hosts.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<WSEndpointHost> Hosts
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
return _hosts.Values.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public WSEndpointHost this[string path]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
if (path.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("An empty string.", nameof(path));
|
||||
}
|
||||
|
||||
if (path[0] != '/')
|
||||
{
|
||||
throw new ArgumentException("Not an absolute path.", nameof(path));
|
||||
}
|
||||
|
||||
if (path.IndexOfAny(new[] { '?', '#' }) > -1)
|
||||
{
|
||||
var message = "It includes either or both query and fragment components.";
|
||||
throw new ArgumentException(message, nameof(path));
|
||||
}
|
||||
|
||||
InternalTryGetServiceHost(path, out WSEndpointHost host);
|
||||
|
||||
return host;
|
||||
}
|
||||
}
|
||||
|
||||
public bool AutoCleanSessions
|
||||
{
|
||||
get
|
||||
|
@ -112,6 +59,28 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
return _hosts.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<WSEndpointHost> Hosts
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
return _hosts.Values.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> Paths
|
||||
{
|
||||
get
|
||||
|
@ -161,169 +130,36 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
|
||||
private void broadcast(OperationCode opcode, byte[] data, Action completed)
|
||||
public WSEndpointHost this[string path]
|
||||
{
|
||||
var cache = new Dictionary<CompressionMethod, byte[]>();
|
||||
|
||||
try
|
||||
get
|
||||
{
|
||||
foreach (var host in Hosts)
|
||||
if (path == null)
|
||||
{
|
||||
if (_state != ServerState.Start)
|
||||
{
|
||||
Logger.Error("The server is shutting down.");
|
||||
break;
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
host.Sessions.Broadcast(opcode, data, cache);
|
||||
if (path.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("An empty string.", nameof(path));
|
||||
}
|
||||
|
||||
if (completed != null)
|
||||
if (path[0] != '/')
|
||||
{
|
||||
completed();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "Could not broadcast");
|
||||
}
|
||||
finally
|
||||
{
|
||||
cache.Clear();
|
||||
}
|
||||
throw new ArgumentException("Not an absolute path.", nameof(path));
|
||||
}
|
||||
|
||||
private void broadcast(OperationCode opcode, Stream stream, Action completed)
|
||||
if (path.IndexOfAny(new[] { '?', '#' }) > -1)
|
||||
{
|
||||
var cache = new Dictionary<CompressionMethod, Stream>();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var host in Hosts)
|
||||
{
|
||||
if (_state != ServerState.Start)
|
||||
{
|
||||
Logger.Error("The server is shutting down.");
|
||||
break;
|
||||
var message = "It includes either or both query and fragment components.";
|
||||
throw new ArgumentException(message, nameof(path));
|
||||
}
|
||||
|
||||
host.Sessions.Broadcast(opcode, stream, cache);
|
||||
}
|
||||
InternalTryGetServiceHost(path, out WSEndpointHost host);
|
||||
|
||||
if (completed != null)
|
||||
{
|
||||
completed();
|
||||
return host;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "Could not broadcast");
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var cached in cache.Values)
|
||||
{
|
||||
cached.Dispose();
|
||||
}
|
||||
|
||||
cache.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private bool canSet(out string message)
|
||||
{
|
||||
message = null;
|
||||
|
||||
if (_state == ServerState.Start)
|
||||
{
|
||||
message = "The server has already started.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_state == ServerState.ShuttingDown)
|
||||
{
|
||||
message = "The server is shutting down.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void Add<TEndpoint>(string path, Func<TEndpoint> creator)
|
||||
where TEndpoint : WSEndpoint
|
||||
{
|
||||
path = HttpUtility.UrlDecode(path).TrimSlashFromEnd();
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
if (_hosts.TryGetValue(path, out WSEndpointHost host))
|
||||
{
|
||||
throw new ArgumentException("Already in use.", nameof(path));
|
||||
}
|
||||
|
||||
host = new WebSocketEndpointHost<TEndpoint>(
|
||||
path, creator, null
|
||||
);
|
||||
|
||||
if (!_clean)
|
||||
{
|
||||
host.KeepClean = false;
|
||||
}
|
||||
|
||||
if (_waitTime != host.WaitTime)
|
||||
{
|
||||
host.WaitTime = _waitTime;
|
||||
}
|
||||
|
||||
if (_state == ServerState.Start)
|
||||
{
|
||||
host.Start();
|
||||
}
|
||||
|
||||
_hosts.Add(path, host);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool InternalTryGetServiceHost(
|
||||
string path, out WSEndpointHost host
|
||||
)
|
||||
{
|
||||
path = HttpUtility.UrlDecode(path).TrimSlashFromEnd();
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
return _hosts.TryGetValue(path, out host);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Start()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
foreach (var host in _hosts.Values)
|
||||
{
|
||||
host.Start();
|
||||
}
|
||||
|
||||
_state = ServerState.Start;
|
||||
}
|
||||
}
|
||||
|
||||
internal void Stop(ushort code, string reason)
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
_state = ServerState.ShuttingDown;
|
||||
|
||||
foreach (var host in _hosts.Values)
|
||||
{
|
||||
host.Stop(code, reason);
|
||||
}
|
||||
|
||||
_state = ServerState.Stop;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddService<TEndpoint>(
|
||||
string path, Action<TEndpoint> initializer
|
||||
)
|
||||
|
@ -470,5 +306,168 @@ namespace EonaCat.Network
|
|||
|
||||
return InternalTryGetServiceHost(path, out host);
|
||||
}
|
||||
|
||||
internal void Add<TEndpoint>(string path, Func<TEndpoint> creator)
|
||||
where TEndpoint : WSEndpoint
|
||||
{
|
||||
path = HttpUtility.UrlDecode(path).TrimSlashFromEnd();
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
if (_hosts.TryGetValue(path, out WSEndpointHost host))
|
||||
{
|
||||
throw new ArgumentException("Already in use.", nameof(path));
|
||||
}
|
||||
|
||||
host = new WebSocketEndpointHost<TEndpoint>(
|
||||
path, creator, null
|
||||
);
|
||||
|
||||
if (!_clean)
|
||||
{
|
||||
host.KeepClean = false;
|
||||
}
|
||||
|
||||
if (_waitTime != host.WaitTime)
|
||||
{
|
||||
host.WaitTime = _waitTime;
|
||||
}
|
||||
|
||||
if (_state == ServerState.Start)
|
||||
{
|
||||
host.Start();
|
||||
}
|
||||
|
||||
_hosts.Add(path, host);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool InternalTryGetServiceHost(
|
||||
string path, out WSEndpointHost host
|
||||
)
|
||||
{
|
||||
path = HttpUtility.UrlDecode(path).TrimSlashFromEnd();
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
return _hosts.TryGetValue(path, out host);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Start()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
foreach (var host in _hosts.Values)
|
||||
{
|
||||
host.Start();
|
||||
}
|
||||
|
||||
_state = ServerState.Start;
|
||||
}
|
||||
}
|
||||
|
||||
internal void Stop(ushort code, string reason)
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
_state = ServerState.ShuttingDown;
|
||||
|
||||
foreach (var host in _hosts.Values)
|
||||
{
|
||||
host.Stop(code, reason);
|
||||
}
|
||||
|
||||
_state = ServerState.Stop;
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcast(OperationCode opcode, byte[] data, Action completed)
|
||||
{
|
||||
var cache = new Dictionary<CompressionMethod, byte[]>();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var host in Hosts)
|
||||
{
|
||||
if (_state != ServerState.Start)
|
||||
{
|
||||
Logger.Error("The server is shutting down.");
|
||||
break;
|
||||
}
|
||||
|
||||
host.Sessions.Broadcast(opcode, data, cache);
|
||||
}
|
||||
|
||||
if (completed != null)
|
||||
{
|
||||
completed();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "Could not broadcast");
|
||||
}
|
||||
finally
|
||||
{
|
||||
cache.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcast(OperationCode opcode, Stream stream, Action completed)
|
||||
{
|
||||
var cache = new Dictionary<CompressionMethod, Stream>();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var host in Hosts)
|
||||
{
|
||||
if (_state != ServerState.Start)
|
||||
{
|
||||
Logger.Error("The server is shutting down.");
|
||||
break;
|
||||
}
|
||||
|
||||
host.Sessions.Broadcast(opcode, stream, cache);
|
||||
}
|
||||
|
||||
if (completed != null)
|
||||
{
|
||||
completed();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "Could not broadcast");
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var cached in cache.Values)
|
||||
{
|
||||
cached.Dispose();
|
||||
}
|
||||
|
||||
cache.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private bool canSet(out string message)
|
||||
{
|
||||
message = null;
|
||||
|
||||
if (_state == ServerState.Start)
|
||||
{
|
||||
message = "The server has already started.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_state == ServerState.ShuttingDown)
|
||||
{
|
||||
message = "The server is shutting down.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,11 +10,9 @@ namespace EonaCat.Network
|
|||
{
|
||||
public class WSServer
|
||||
{
|
||||
public bool IsConsoleLoggingEnabled { get; set; }
|
||||
public bool IsLoggingEnabled { get; set; }
|
||||
private static readonly string _defaultRealm;
|
||||
private bool _allowForwardedRequest;
|
||||
private AuthenticationSchemes _authSchemes;
|
||||
private static readonly string _defaultRealm;
|
||||
private bool _dnsStyle;
|
||||
private string _hostname;
|
||||
private TcpListener _listener;
|
||||
|
@ -27,7 +25,6 @@ namespace EonaCat.Network
|
|||
private volatile ServerState _state;
|
||||
private object _sync;
|
||||
private Func<IIdentity, NetworkCredential> _userCredentialsFinder;
|
||||
|
||||
static WSServer()
|
||||
{
|
||||
_defaultRealm = "SECRET AREA";
|
||||
|
@ -35,8 +32,8 @@ namespace EonaCat.Network
|
|||
|
||||
public WSServer()
|
||||
{
|
||||
var addr = System.Net.IPAddress.Any;
|
||||
init(addr.ToString(), addr, 80, false);
|
||||
var address = System.Net.IPAddress.Any;
|
||||
init(address.ToString(), address, 80, false);
|
||||
}
|
||||
|
||||
public WSServer(int port)
|
||||
|
@ -118,7 +115,6 @@ namespace EonaCat.Network
|
|||
}
|
||||
|
||||
public System.Net.IPAddress Address { get; private set; }
|
||||
|
||||
public bool AllowForwardedRequest
|
||||
{
|
||||
get
|
||||
|
@ -175,10 +171,6 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
|
||||
public bool IsListening => _state == ServerState.Start;
|
||||
|
||||
public bool IsSecure { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if sessions need to be removed automatically
|
||||
/// </summary>
|
||||
|
@ -195,6 +187,39 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
|
||||
public WSEndpointManager Endpoints { get; private set; }
|
||||
public Func<IIdentity, NetworkCredential> FindCredentials
|
||||
{
|
||||
get
|
||||
{
|
||||
return _userCredentialsFinder;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (!CanSet(out string message))
|
||||
{
|
||||
Logger.Warning(message);
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
if (!CanSet(out message))
|
||||
{
|
||||
Logger.Warning(message);
|
||||
return;
|
||||
}
|
||||
|
||||
_userCredentialsFinder = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsConsoleLoggingEnabled { get; set; }
|
||||
public bool IsListening => _state == ServerState.Start;
|
||||
public bool IsLoggingEnabled { get; set; }
|
||||
public bool IsSecure { get; private set; }
|
||||
public int Port { get; private set; }
|
||||
|
||||
public string Realm
|
||||
|
@ -266,35 +291,6 @@ namespace EonaCat.Network
|
|||
return GetSSLConfig();
|
||||
}
|
||||
}
|
||||
|
||||
public Func<IIdentity, NetworkCredential> FindCredentials
|
||||
{
|
||||
get
|
||||
{
|
||||
return _userCredentialsFinder;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (!CanSet(out string message))
|
||||
{
|
||||
Logger.Warning(message);
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
if (!CanSet(out message))
|
||||
{
|
||||
Logger.Warning(message);
|
||||
return;
|
||||
}
|
||||
|
||||
_userCredentialsFinder = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TimeSpan WaitTime
|
||||
{
|
||||
get
|
||||
|
@ -307,8 +303,146 @@ namespace EonaCat.Network
|
|||
Endpoints.WaitTime = value;
|
||||
}
|
||||
}
|
||||
public void AddEndpoint<TEndpoint>(string path) where TEndpoint : WSEndpoint, new()
|
||||
{
|
||||
Endpoints.AddService<TEndpoint>(path, null);
|
||||
}
|
||||
|
||||
public WSEndpointManager Endpoints { get; private set; }
|
||||
public void AddEndpoint<TEndpoint>(string path, Action<TEndpoint> initializer) where TEndpoint : WSEndpoint, new()
|
||||
{
|
||||
Endpoints.AddService(path, initializer);
|
||||
}
|
||||
|
||||
public bool RemoveEndpoint(string path)
|
||||
{
|
||||
return Endpoints.RemoveService(path);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
SSLConfigServer sslConfig = null;
|
||||
|
||||
if (IsSecure)
|
||||
{
|
||||
sslConfig = new SSLConfigServer(GetSSLConfig());
|
||||
|
||||
if (!CheckSslConfig(sslConfig, out string message))
|
||||
{
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
|
||||
start(sslConfig);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
stop((ushort)CloseStatusCode.NoStatus, string.Empty);
|
||||
}
|
||||
|
||||
public void Stop(ushort code, string reason)
|
||||
{
|
||||
if (!code.IsCloseStatusCode())
|
||||
{
|
||||
var message = "Less than 1000 or greater than 4999.";
|
||||
throw new ArgumentOutOfRangeException(nameof(code), message);
|
||||
}
|
||||
|
||||
if (code == (ushort)CloseStatusCode.MandatoryExtension)
|
||||
{
|
||||
var message = $"{(ushort)CloseStatusCode.MandatoryExtension} cannot be used.";
|
||||
throw new ArgumentException(message, nameof(code));
|
||||
}
|
||||
|
||||
if (!reason.IsNullOrEmpty())
|
||||
{
|
||||
if (code == (ushort)CloseStatusCode.NoStatus)
|
||||
{
|
||||
var message = $"{(ushort)CloseStatusCode.NoStatus} cannot be used.";
|
||||
throw new ArgumentException(message, nameof(code));
|
||||
}
|
||||
|
||||
if (!reason.TryGetUTF8EncodedBytes(out byte[] bytes))
|
||||
{
|
||||
var message = "It could not be UTF-8-encoded.";
|
||||
throw new ArgumentException(message, nameof(reason));
|
||||
}
|
||||
|
||||
if (bytes.Length > 123)
|
||||
{
|
||||
var message = "Its size is greater than 123 bytes.";
|
||||
throw new ArgumentOutOfRangeException(nameof(reason), message);
|
||||
}
|
||||
}
|
||||
|
||||
stop(code, reason);
|
||||
}
|
||||
|
||||
public void Stop(CloseStatusCode code, string reason)
|
||||
{
|
||||
if (code == CloseStatusCode.MandatoryExtension)
|
||||
{
|
||||
var message = "MandatoryExtension cannot be used.";
|
||||
throw new ArgumentException(message, nameof(code));
|
||||
}
|
||||
|
||||
if (!reason.IsNullOrEmpty())
|
||||
{
|
||||
if (code == CloseStatusCode.NoStatus)
|
||||
{
|
||||
var message = "NoStatus cannot be used.";
|
||||
throw new ArgumentException(message, nameof(code));
|
||||
}
|
||||
|
||||
if (!reason.TryGetUTF8EncodedBytes(out byte[] bytes))
|
||||
{
|
||||
var message = "It could not be UTF-8-encoded.";
|
||||
throw new ArgumentException(message, nameof(reason));
|
||||
}
|
||||
|
||||
if (bytes.Length > 123)
|
||||
{
|
||||
var message = "Its size is greater than 123 bytes.";
|
||||
throw new ArgumentOutOfRangeException(nameof(reason), message);
|
||||
}
|
||||
}
|
||||
|
||||
stop((ushort)code, reason);
|
||||
}
|
||||
|
||||
private static bool CheckSslConfig(SSLConfigServer sslConfig, out string message
|
||||
)
|
||||
{
|
||||
message = null;
|
||||
|
||||
if (sslConfig.Certificate == null)
|
||||
{
|
||||
message = "There is no server certificate for secure connections.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool tryCreateUri(
|
||||
string uriString, out Uri result, out string message
|
||||
)
|
||||
{
|
||||
if (!uriString.TryCreateWebSocketUri(out result, out message))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result.PathAndQuery != "/")
|
||||
{
|
||||
result = null;
|
||||
message = "It includes either or both path and query components.";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void abort()
|
||||
{
|
||||
|
@ -365,21 +499,6 @@ namespace EonaCat.Network
|
|||
|| Uri.CheckHostName(name) != UriHostNameType.Dns
|
||||
|| name == _hostname;
|
||||
}
|
||||
|
||||
private static bool CheckSslConfig(SSLConfigServer sslConfig, out string message
|
||||
)
|
||||
{
|
||||
message = null;
|
||||
|
||||
if (sslConfig.Certificate == null)
|
||||
{
|
||||
message = "There is no server certificate for secure connections.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private string GetRealm()
|
||||
{
|
||||
var realm = _realm;
|
||||
|
@ -662,132 +781,5 @@ namespace EonaCat.Network
|
|||
|
||||
_receiveThread.Join(millisecondsTimeout);
|
||||
}
|
||||
|
||||
private static bool tryCreateUri(
|
||||
string uriString, out Uri result, out string message
|
||||
)
|
||||
{
|
||||
if (!uriString.TryCreateWebSocketUri(out result, out message))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result.PathAndQuery != "/")
|
||||
{
|
||||
result = null;
|
||||
message = "It includes either or both path and query components.";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AddEndpoint<TEndpoint>(string path) where TEndpoint : WSEndpoint, new()
|
||||
{
|
||||
Endpoints.AddService<TEndpoint>(path, null);
|
||||
}
|
||||
|
||||
public void AddEndpoint<TEndpoint>(string path, Action<TEndpoint> initializer) where TEndpoint : WSEndpoint, new()
|
||||
{
|
||||
Endpoints.AddService(path, initializer);
|
||||
}
|
||||
|
||||
public bool RemoveEndpoint(string path)
|
||||
{
|
||||
return Endpoints.RemoveService(path);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
SSLConfigServer sslConfig = null;
|
||||
|
||||
if (IsSecure)
|
||||
{
|
||||
sslConfig = new SSLConfigServer(GetSSLConfig());
|
||||
|
||||
if (!CheckSslConfig(sslConfig, out string message))
|
||||
{
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
|
||||
start(sslConfig);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
stop((ushort)CloseStatusCode.NoStatus, string.Empty);
|
||||
}
|
||||
|
||||
public void Stop(ushort code, string reason)
|
||||
{
|
||||
if (!code.IsCloseStatusCode())
|
||||
{
|
||||
var message = "Less than 1000 or greater than 4999.";
|
||||
throw new ArgumentOutOfRangeException(nameof(code), message);
|
||||
}
|
||||
|
||||
if (code == (ushort)CloseStatusCode.MandatoryExtension)
|
||||
{
|
||||
var message = $"{(ushort)CloseStatusCode.MandatoryExtension} cannot be used.";
|
||||
throw new ArgumentException(message, nameof(code));
|
||||
}
|
||||
|
||||
if (!reason.IsNullOrEmpty())
|
||||
{
|
||||
if (code == (ushort)CloseStatusCode.NoStatus)
|
||||
{
|
||||
var message = $"{(ushort)CloseStatusCode.NoStatus} cannot be used.";
|
||||
throw new ArgumentException(message, nameof(code));
|
||||
}
|
||||
|
||||
if (!reason.TryGetUTF8EncodedBytes(out byte[] bytes))
|
||||
{
|
||||
var message = "It could not be UTF-8-encoded.";
|
||||
throw new ArgumentException(message, nameof(reason));
|
||||
}
|
||||
|
||||
if (bytes.Length > 123)
|
||||
{
|
||||
var message = "Its size is greater than 123 bytes.";
|
||||
throw new ArgumentOutOfRangeException(nameof(reason), message);
|
||||
}
|
||||
}
|
||||
|
||||
stop(code, reason);
|
||||
}
|
||||
|
||||
public void Stop(CloseStatusCode code, string reason)
|
||||
{
|
||||
if (code == CloseStatusCode.MandatoryExtension)
|
||||
{
|
||||
var message = "MandatoryExtension cannot be used.";
|
||||
throw new ArgumentException(message, nameof(code));
|
||||
}
|
||||
|
||||
if (!reason.IsNullOrEmpty())
|
||||
{
|
||||
if (code == CloseStatusCode.NoStatus)
|
||||
{
|
||||
var message = "NoStatus cannot be used.";
|
||||
throw new ArgumentException(message, nameof(code));
|
||||
}
|
||||
|
||||
if (!reason.TryGetUTF8EncodedBytes(out byte[] bytes))
|
||||
{
|
||||
var message = "It could not be UTF-8-encoded.";
|
||||
throw new ArgumentException(message, nameof(reason));
|
||||
}
|
||||
|
||||
if (bytes.Length > 123)
|
||||
{
|
||||
var message = "Its size is greater than 123 bytes.";
|
||||
throw new ArgumentOutOfRangeException(nameof(reason), message);
|
||||
}
|
||||
}
|
||||
|
||||
stop((ushort)code, reason);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,13 +12,13 @@ namespace EonaCat.Network
|
|||
{
|
||||
public class WSSessionManager
|
||||
{
|
||||
private volatile bool _clean;
|
||||
private readonly object _forSweep;
|
||||
private readonly Dictionary<string, IWSSession> _sessions;
|
||||
private readonly object _sync;
|
||||
private volatile bool _clean;
|
||||
private volatile ServerState _state;
|
||||
private volatile bool _sweeping;
|
||||
private System.Timers.Timer _sweepTimer;
|
||||
private readonly object _sync;
|
||||
private TimeSpan _waitTime;
|
||||
|
||||
internal WSSessionManager()
|
||||
|
@ -33,8 +33,6 @@ namespace EonaCat.Network
|
|||
setSweepTimer(60000);
|
||||
}
|
||||
|
||||
internal ServerState State => _state;
|
||||
|
||||
public IEnumerable<string> ActiveIDs
|
||||
{
|
||||
get
|
||||
|
@ -95,26 +93,6 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
|
||||
public IWSSession this[string id]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
}
|
||||
|
||||
if (id.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("An empty string.", nameof(id));
|
||||
}
|
||||
|
||||
tryGetSession(id, out IWSSession session);
|
||||
|
||||
return session;
|
||||
}
|
||||
}
|
||||
|
||||
public bool KeepClean
|
||||
{
|
||||
get
|
||||
|
@ -197,277 +175,26 @@ namespace EonaCat.Network
|
|||
}
|
||||
}
|
||||
|
||||
private void broadcast(OperationCode opcode, byte[] data, Action completed)
|
||||
internal ServerState State => _state;
|
||||
public IWSSession this[string id]
|
||||
{
|
||||
var cache = new Dictionary<CompressionMethod, byte[]>();
|
||||
|
||||
try
|
||||
get
|
||||
{
|
||||
foreach (var session in Sessions)
|
||||
if (id == null)
|
||||
{
|
||||
if (_state != ServerState.Start)
|
||||
{
|
||||
Logger.Error("The service is shutting down.");
|
||||
break;
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
}
|
||||
|
||||
session.Context.WebSocket.Send(opcode, data, cache);
|
||||
if (id.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("An empty string.", nameof(id));
|
||||
}
|
||||
|
||||
if (completed != null)
|
||||
{
|
||||
completed();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex.Message);
|
||||
Logger.Debug(ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
cache.Clear();
|
||||
}
|
||||
}
|
||||
tryGetSession(id, out IWSSession session);
|
||||
|
||||
private void broadcast(OperationCode opcode, Stream stream, Action completed)
|
||||
{
|
||||
var cache = new Dictionary<CompressionMethod, Stream>();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var session in Sessions)
|
||||
{
|
||||
if (_state != ServerState.Start)
|
||||
{
|
||||
Logger.Error("The service is shutting down.");
|
||||
break;
|
||||
}
|
||||
|
||||
session.Context.WebSocket.Send(opcode, stream, cache);
|
||||
}
|
||||
|
||||
if (completed != null)
|
||||
{
|
||||
completed();
|
||||
return session;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex.Message);
|
||||
Logger.Debug(ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var cached in cache.Values)
|
||||
{
|
||||
cached.Dispose();
|
||||
}
|
||||
|
||||
cache.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastAsync(OperationCode opcode, byte[] data, Action completed)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(
|
||||
state => broadcast(opcode, data, completed)
|
||||
);
|
||||
}
|
||||
|
||||
private void broadcastAsync(OperationCode opcode, Stream stream, Action completed)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(
|
||||
state => broadcast(opcode, stream, completed)
|
||||
);
|
||||
}
|
||||
|
||||
private Dictionary<string, bool> broadping(byte[] frameAsBytes)
|
||||
{
|
||||
var ret = new Dictionary<string, bool>();
|
||||
|
||||
foreach (var session in Sessions)
|
||||
{
|
||||
if (_state != ServerState.Start)
|
||||
{
|
||||
Logger.Error("The service is shutting down.");
|
||||
break;
|
||||
}
|
||||
|
||||
var res = session.Context.WebSocket.Ping(frameAsBytes, _waitTime);
|
||||
ret.Add(session.ID, res);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private bool canSet(out string message)
|
||||
{
|
||||
message = null;
|
||||
|
||||
if (_state == ServerState.Start)
|
||||
{
|
||||
message = "The service has already started.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_state == ServerState.ShuttingDown)
|
||||
{
|
||||
message = "The service is shutting down.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string createID()
|
||||
{
|
||||
return Guid.NewGuid().ToString("N");
|
||||
}
|
||||
|
||||
private void setSweepTimer(double interval)
|
||||
{
|
||||
_sweepTimer = new System.Timers.Timer(interval);
|
||||
_sweepTimer.Elapsed += (sender, e) => Sweep();
|
||||
}
|
||||
|
||||
private void stop(PayloadData payloadData, bool send)
|
||||
{
|
||||
var bytes = send
|
||||
? WSFrame.CreateCloseFrame(payloadData, false).ToArray()
|
||||
: null;
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
_state = ServerState.ShuttingDown;
|
||||
|
||||
_sweepTimer.Enabled = false;
|
||||
foreach (var session in _sessions.Values.ToList())
|
||||
{
|
||||
session.Context.WebSocket.Close(payloadData, bytes);
|
||||
}
|
||||
|
||||
_state = ServerState.Stop;
|
||||
}
|
||||
}
|
||||
|
||||
private bool tryGetSession(string id, out IWSSession session)
|
||||
{
|
||||
session = null;
|
||||
|
||||
if (_state != ServerState.Start)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
if (_state != ServerState.Start)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _sessions.TryGetValue(id, out session);
|
||||
}
|
||||
}
|
||||
|
||||
internal string Add(IWSSession session)
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
if (_state != ServerState.Start)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var id = createID();
|
||||
_sessions.Add(id, session);
|
||||
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
internal void Broadcast(
|
||||
OperationCode opcode, byte[] data, Dictionary<CompressionMethod, byte[]> cache
|
||||
)
|
||||
{
|
||||
foreach (var session in Sessions)
|
||||
{
|
||||
if (_state != ServerState.Start)
|
||||
{
|
||||
Logger.Error("The service is shutting down.");
|
||||
break;
|
||||
}
|
||||
|
||||
session.Context.WebSocket.Send(opcode, data, cache);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Broadcast(
|
||||
OperationCode opcode, Stream stream, Dictionary<CompressionMethod, Stream> cache
|
||||
)
|
||||
{
|
||||
foreach (var session in Sessions)
|
||||
{
|
||||
if (_state != ServerState.Start)
|
||||
{
|
||||
Logger.Error("The service is shutting down.");
|
||||
break;
|
||||
}
|
||||
|
||||
session.Context.WebSocket.Send(opcode, stream, cache);
|
||||
}
|
||||
}
|
||||
|
||||
internal Dictionary<string, bool> Broadping(
|
||||
byte[] frameAsBytes, TimeSpan timeout
|
||||
)
|
||||
{
|
||||
var ret = new Dictionary<string, bool>();
|
||||
|
||||
foreach (var session in Sessions)
|
||||
{
|
||||
if (_state != ServerState.Start)
|
||||
{
|
||||
Logger.Error("The service is shutting down.");
|
||||
break;
|
||||
}
|
||||
|
||||
var res = session.Context.WebSocket.Ping(frameAsBytes, timeout);
|
||||
ret.Add(session.ID, res);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
internal bool Remove(string id)
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
return _sessions.Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Start()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
_sweepTimer.Enabled = _clean;
|
||||
_state = ServerState.Start;
|
||||
}
|
||||
}
|
||||
|
||||
internal void Stop(ushort code, string reason)
|
||||
{
|
||||
if (code == (ushort)CloseStatusCode.NoStatus)
|
||||
{ // == no status
|
||||
stop(PayloadData.Empty, true);
|
||||
return;
|
||||
}
|
||||
|
||||
stop(new PayloadData(code, reason), !code.IsReserved());
|
||||
}
|
||||
|
||||
public void Broadcast(byte[] data)
|
||||
{
|
||||
if (_state != ServerState.Start)
|
||||
|
@ -872,5 +599,275 @@ namespace EonaCat.Network
|
|||
|
||||
return tryGetSession(id, out session);
|
||||
}
|
||||
|
||||
internal string Add(IWSSession session)
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
if (_state != ServerState.Start)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var id = createID();
|
||||
_sessions.Add(id, session);
|
||||
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
internal void Broadcast(
|
||||
OperationCode opcode, byte[] data, Dictionary<CompressionMethod, byte[]> cache
|
||||
)
|
||||
{
|
||||
foreach (var session in Sessions)
|
||||
{
|
||||
if (_state != ServerState.Start)
|
||||
{
|
||||
Logger.Error("The service is shutting down.");
|
||||
break;
|
||||
}
|
||||
|
||||
session.Context.WebSocket.Send(opcode, data, cache);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Broadcast(
|
||||
OperationCode opcode, Stream stream, Dictionary<CompressionMethod, Stream> cache
|
||||
)
|
||||
{
|
||||
foreach (var session in Sessions)
|
||||
{
|
||||
if (_state != ServerState.Start)
|
||||
{
|
||||
Logger.Error("The service is shutting down.");
|
||||
break;
|
||||
}
|
||||
|
||||
session.Context.WebSocket.Send(opcode, stream, cache);
|
||||
}
|
||||
}
|
||||
|
||||
internal Dictionary<string, bool> Broadping(
|
||||
byte[] frameAsBytes, TimeSpan timeout
|
||||
)
|
||||
{
|
||||
var ret = new Dictionary<string, bool>();
|
||||
|
||||
foreach (var session in Sessions)
|
||||
{
|
||||
if (_state != ServerState.Start)
|
||||
{
|
||||
Logger.Error("The service is shutting down.");
|
||||
break;
|
||||
}
|
||||
|
||||
var res = session.Context.WebSocket.Ping(frameAsBytes, timeout);
|
||||
ret.Add(session.ID, res);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
internal bool Remove(string id)
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
return _sessions.Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Start()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
_sweepTimer.Enabled = _clean;
|
||||
_state = ServerState.Start;
|
||||
}
|
||||
}
|
||||
|
||||
internal void Stop(ushort code, string reason)
|
||||
{
|
||||
if (code == (ushort)CloseStatusCode.NoStatus)
|
||||
{ // == no status
|
||||
stop(PayloadData.Empty, true);
|
||||
return;
|
||||
}
|
||||
|
||||
stop(new PayloadData(code, reason), !code.IsReserved());
|
||||
}
|
||||
|
||||
private static string createID()
|
||||
{
|
||||
return Guid.NewGuid().ToString("N");
|
||||
}
|
||||
|
||||
private void broadcast(OperationCode opcode, byte[] data, Action completed)
|
||||
{
|
||||
var cache = new Dictionary<CompressionMethod, byte[]>();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var session in Sessions)
|
||||
{
|
||||
if (_state != ServerState.Start)
|
||||
{
|
||||
Logger.Error("The service is shutting down.");
|
||||
break;
|
||||
}
|
||||
|
||||
session.Context.WebSocket.Send(opcode, data, cache);
|
||||
}
|
||||
|
||||
if (completed != null)
|
||||
{
|
||||
completed();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex.Message);
|
||||
Logger.Debug(ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
cache.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcast(OperationCode opcode, Stream stream, Action completed)
|
||||
{
|
||||
var cache = new Dictionary<CompressionMethod, Stream>();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var session in Sessions)
|
||||
{
|
||||
if (_state != ServerState.Start)
|
||||
{
|
||||
Logger.Error("The service is shutting down.");
|
||||
break;
|
||||
}
|
||||
|
||||
session.Context.WebSocket.Send(opcode, stream, cache);
|
||||
}
|
||||
|
||||
if (completed != null)
|
||||
{
|
||||
completed();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex.Message);
|
||||
Logger.Debug(ex.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var cached in cache.Values)
|
||||
{
|
||||
cached.Dispose();
|
||||
}
|
||||
|
||||
cache.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastAsync(OperationCode opcode, byte[] data, Action completed)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(
|
||||
state => broadcast(opcode, data, completed)
|
||||
);
|
||||
}
|
||||
|
||||
private void broadcastAsync(OperationCode opcode, Stream stream, Action completed)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(
|
||||
state => broadcast(opcode, stream, completed)
|
||||
);
|
||||
}
|
||||
|
||||
private Dictionary<string, bool> broadping(byte[] frameAsBytes)
|
||||
{
|
||||
var ret = new Dictionary<string, bool>();
|
||||
|
||||
foreach (var session in Sessions)
|
||||
{
|
||||
if (_state != ServerState.Start)
|
||||
{
|
||||
Logger.Error("The service is shutting down.");
|
||||
break;
|
||||
}
|
||||
|
||||
var res = session.Context.WebSocket.Ping(frameAsBytes, _waitTime);
|
||||
ret.Add(session.ID, res);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private bool canSet(out string message)
|
||||
{
|
||||
message = null;
|
||||
|
||||
if (_state == ServerState.Start)
|
||||
{
|
||||
message = "The service has already started.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_state == ServerState.ShuttingDown)
|
||||
{
|
||||
message = "The service is shutting down.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
private void setSweepTimer(double interval)
|
||||
{
|
||||
_sweepTimer = new System.Timers.Timer(interval);
|
||||
_sweepTimer.Elapsed += (sender, e) => Sweep();
|
||||
}
|
||||
|
||||
private void stop(PayloadData payloadData, bool send)
|
||||
{
|
||||
var bytes = send
|
||||
? WSFrame.CreateCloseFrame(payloadData, false).ToArray()
|
||||
: null;
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
_state = ServerState.ShuttingDown;
|
||||
|
||||
_sweepTimer.Enabled = false;
|
||||
foreach (var session in _sessions.Values.ToList())
|
||||
{
|
||||
session.Context.WebSocket.Close(payloadData, bytes);
|
||||
}
|
||||
|
||||
_state = ServerState.Stop;
|
||||
}
|
||||
}
|
||||
|
||||
private bool tryGetSession(string id, out IWSSession session)
|
||||
{
|
||||
session = null;
|
||||
|
||||
if (_state != ServerState.Start)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
if (_state != ServerState.Start)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _sessions.TryGetValue(id, out session);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,6 +29,11 @@ namespace EonaCat.Network
|
|||
|
||||
public override Type EndpointType => typeof(TEndpoint);
|
||||
|
||||
protected override WSEndpoint CreateSession()
|
||||
{
|
||||
return _creator();
|
||||
}
|
||||
|
||||
private Func<TEndpoint> createCreator(
|
||||
Func<TEndpoint> creator, Action<TEndpoint> initializer
|
||||
)
|
||||
|
@ -46,10 +51,5 @@ namespace EonaCat.Network
|
|||
return ret;
|
||||
};
|
||||
}
|
||||
|
||||
protected override WSEndpoint CreateSession()
|
||||
{
|
||||
return _creator();
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -11,19 +11,13 @@ namespace EonaCat.Network
|
|||
{
|
||||
internal class WSFrame : IEnumerable<byte>
|
||||
{
|
||||
private const int BUFFER_SIZE = 1024;
|
||||
|
||||
internal static readonly byte[] EmptyPingBytes;
|
||||
|
||||
private const int BUFFER_SIZE = 1024;
|
||||
static WSFrame()
|
||||
{
|
||||
EmptyPingBytes = CreatePingFrame(false).ToArray();
|
||||
}
|
||||
|
||||
private WSFrame()
|
||||
{
|
||||
}
|
||||
|
||||
internal WSFrame(OperationCode opcode, PayloadData payloadData, bool mask)
|
||||
: this(FinalFrame.Final, opcode, payloadData, false, mask)
|
||||
{
|
||||
|
@ -42,8 +36,9 @@ namespace EonaCat.Network
|
|||
Rsv2 = Rsv.Off;
|
||||
Rsv3 = Rsv.Off;
|
||||
Opcode = opcode;
|
||||
PayloadData = new PayloadData(payloadData);
|
||||
|
||||
var len = payloadData.Length;
|
||||
var len = PayloadData.Length;
|
||||
if (len < 126)
|
||||
{
|
||||
PayloadLength = (byte)len;
|
||||
|
@ -64,17 +59,41 @@ namespace EonaCat.Network
|
|||
{
|
||||
Mask = Mask.On;
|
||||
MaskingKey = createMaskingKey();
|
||||
payloadData.Mask(MaskingKey);
|
||||
PayloadData.Mask(MaskingKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
Mask = Mask.Off;
|
||||
MaskingKey = WSClient.EmptyBytes;
|
||||
}
|
||||
|
||||
PayloadData = payloadData;
|
||||
}
|
||||
|
||||
private WSFrame()
|
||||
{
|
||||
}
|
||||
public byte[] ExtendedPayloadLength { get; private set; }
|
||||
public FinalFrame Fin { get; private set; }
|
||||
public bool IsBinary => Opcode == OperationCode.Binary;
|
||||
public bool IsClose => Opcode == OperationCode.Close;
|
||||
public bool IsCompressed => Rsv1 == Rsv.On;
|
||||
public bool IsContinuation => Opcode == OperationCode.Continue;
|
||||
public bool IsControl => Opcode >= OperationCode.Close;
|
||||
public bool IsData => Opcode == OperationCode.Text || Opcode == OperationCode.Binary;
|
||||
public bool IsFinal => Fin == FinalFrame.Final;
|
||||
public bool IsFragment => Fin == FinalFrame.More || Opcode == OperationCode.Continue;
|
||||
public bool IsMasked => Mask == Mask.On;
|
||||
public bool IsPing => Opcode == OperationCode.Ping;
|
||||
public bool IsPong => Opcode == OperationCode.Pong;
|
||||
public bool IsText => Opcode == OperationCode.Text;
|
||||
public ulong Length => 2 + (ulong)(ExtendedPayloadLength.Length + MaskingKey.Length) + PayloadData.Length;
|
||||
public Mask Mask { get; private set; }
|
||||
public byte[] MaskingKey { get; private set; }
|
||||
public OperationCode Opcode { get; private set; }
|
||||
public PayloadData PayloadData { get; private set; }
|
||||
public byte PayloadLength { get; private set; }
|
||||
public Rsv Rsv1 { get; private set; }
|
||||
public Rsv Rsv2 { get; private set; }
|
||||
public Rsv Rsv3 { get; private set; }
|
||||
internal int ExtendedPayloadLengthCount => PayloadLength < 126 ? 0 : (PayloadLength == 126 ? 2 : 8);
|
||||
|
||||
internal ulong FullPayloadLength => PayloadLength < 126
|
||||
|
@ -82,52 +101,173 @@ namespace EonaCat.Network
|
|||
: PayloadLength == 126
|
||||
? ExtendedPayloadLength.ToUInt16(ByteOrder.Big)
|
||||
: ExtendedPayloadLength.ToUInt64(ByteOrder.Big);
|
||||
public IEnumerator<byte> GetEnumerator()
|
||||
{
|
||||
foreach (var b in ToArray())
|
||||
{
|
||||
yield return b;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] ExtendedPayloadLength { get; private set; }
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public FinalFrame Fin { get; private set; }
|
||||
public void Print(bool dumped)
|
||||
{
|
||||
Console.WriteLine(dumped ? dump(this) : print(this));
|
||||
}
|
||||
|
||||
public bool IsBinary => Opcode == OperationCode.Binary;
|
||||
public string PrintToString(bool dumped)
|
||||
{
|
||||
return dumped ? dump(this) : print(this);
|
||||
}
|
||||
|
||||
public bool IsClose => Opcode == OperationCode.Close;
|
||||
public byte[] ToArray()
|
||||
{
|
||||
using (var buff = new MemoryStream())
|
||||
{
|
||||
var header = (int)Fin;
|
||||
header = (header << 1) + (int)Rsv1;
|
||||
header = (header << 1) + (int)Rsv2;
|
||||
header = (header << 1) + (int)Rsv3;
|
||||
header = (header << 4) + (int)Opcode;
|
||||
header = (header << 1) + (int)Mask;
|
||||
header = (header << 7) + PayloadLength;
|
||||
buff.Write(((ushort)header).InternalToByteArray(ByteOrder.Big), 0, 2);
|
||||
|
||||
public bool IsCompressed => Rsv1 == Rsv.On;
|
||||
if (PayloadLength > 125)
|
||||
{
|
||||
buff.Write(ExtendedPayloadLength, 0, PayloadLength == 126 ? 2 : 8);
|
||||
}
|
||||
|
||||
public bool IsContinuation => Opcode == OperationCode.Cont;
|
||||
if (Mask == Mask.On)
|
||||
{
|
||||
buff.Write(MaskingKey, 0, 4);
|
||||
}
|
||||
|
||||
public bool IsControl => Opcode >= OperationCode.Close;
|
||||
if (PayloadLength > 0)
|
||||
{
|
||||
var bytes = PayloadData.ToArray();
|
||||
if (PayloadLength < 127)
|
||||
{
|
||||
buff.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
buff.WriteBytes(bytes, BUFFER_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsData => Opcode == OperationCode.Text || Opcode == OperationCode.Binary;
|
||||
buff.Close();
|
||||
return buff.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsFinal => Fin == FinalFrame.Final;
|
||||
public override string ToString()
|
||||
{
|
||||
return BitConverter.ToString(ToArray());
|
||||
}
|
||||
|
||||
public bool IsFragment => Fin == FinalFrame.More || Opcode == OperationCode.Cont;
|
||||
internal static WSFrame CreateCloseFrame(
|
||||
PayloadData payloadData, bool mask
|
||||
)
|
||||
{
|
||||
return new WSFrame(
|
||||
FinalFrame.Final, OperationCode.Close, payloadData, false, mask
|
||||
);
|
||||
}
|
||||
|
||||
public bool IsMasked => Mask == Mask.On;
|
||||
internal static WSFrame CreatePingFrame(bool mask)
|
||||
{
|
||||
return new WSFrame(
|
||||
FinalFrame.Final, OperationCode.Ping, PayloadData.Empty, false, mask
|
||||
);
|
||||
}
|
||||
|
||||
public bool IsPing => Opcode == OperationCode.Ping;
|
||||
internal static WSFrame CreatePingFrame(byte[] data, bool mask)
|
||||
{
|
||||
return new WSFrame(
|
||||
FinalFrame.Final, OperationCode.Ping, new PayloadData(data), false, mask
|
||||
);
|
||||
}
|
||||
|
||||
public bool IsPong => Opcode == OperationCode.Pong;
|
||||
internal static WSFrame CreatePongFrame(
|
||||
PayloadData payloadData, bool mask
|
||||
)
|
||||
{
|
||||
return new WSFrame(
|
||||
FinalFrame.Final, OperationCode.Pong, payloadData, false, mask
|
||||
);
|
||||
}
|
||||
|
||||
public bool IsText => Opcode == OperationCode.Text;
|
||||
internal static WSFrame ReadFrame(Stream stream, bool unmask)
|
||||
{
|
||||
var frame = readHeader(stream);
|
||||
readExtendedPayloadLength(stream, frame);
|
||||
readMaskingKey(stream, frame);
|
||||
readPayloadData(stream, frame);
|
||||
|
||||
public ulong Length => 2 + (ulong)(ExtendedPayloadLength.Length + MaskingKey.Length) + PayloadData.Length;
|
||||
if (unmask)
|
||||
{
|
||||
frame.Unmask();
|
||||
}
|
||||
|
||||
public Mask Mask { get; private set; }
|
||||
return frame;
|
||||
}
|
||||
|
||||
public byte[] MaskingKey { get; private set; }
|
||||
internal static void ReadFrameAsync(
|
||||
Stream stream,
|
||||
bool unmask,
|
||||
Action<WSFrame> completed,
|
||||
Action<Exception> error
|
||||
)
|
||||
{
|
||||
readHeaderAsync(
|
||||
stream,
|
||||
frame =>
|
||||
readExtendedPayloadLengthAsync(
|
||||
stream,
|
||||
frame,
|
||||
frame1 =>
|
||||
readMaskingKeyAsync(
|
||||
stream,
|
||||
frame1,
|
||||
frame2 =>
|
||||
readPayloadDataAsync(
|
||||
stream,
|
||||
frame2,
|
||||
frame3 =>
|
||||
{
|
||||
if (unmask)
|
||||
{
|
||||
frame3.Unmask();
|
||||
}
|
||||
|
||||
public OperationCode Opcode { get; private set; }
|
||||
completed(frame3);
|
||||
},
|
||||
error
|
||||
),
|
||||
error
|
||||
),
|
||||
error
|
||||
),
|
||||
error
|
||||
);
|
||||
}
|
||||
|
||||
public PayloadData PayloadData { get; private set; }
|
||||
internal void Unmask()
|
||||
{
|
||||
if (Mask == Mask.Off)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
public byte PayloadLength { get; private set; }
|
||||
|
||||
public Rsv Rsv1 { get; private set; }
|
||||
|
||||
public Rsv Rsv2 { get; private set; }
|
||||
|
||||
public Rsv Rsv3 { get; private set; }
|
||||
Mask = Mask.Off;
|
||||
PayloadData.Mask(MaskingKey);
|
||||
MaskingKey = WSClient.EmptyBytes;
|
||||
}
|
||||
|
||||
private static byte[] createMaskingKey()
|
||||
{
|
||||
|
@ -496,173 +636,5 @@ Extended Payload Length: {7}
|
|||
|
||||
stream.ReadBytesAsync(llen, BUFFER_SIZE, compl, error);
|
||||
}
|
||||
|
||||
internal static WSFrame CreateCloseFrame(
|
||||
PayloadData payloadData, bool mask
|
||||
)
|
||||
{
|
||||
return new WSFrame(
|
||||
FinalFrame.Final, OperationCode.Close, payloadData, false, mask
|
||||
);
|
||||
}
|
||||
|
||||
internal static WSFrame CreatePingFrame(bool mask)
|
||||
{
|
||||
return new WSFrame(
|
||||
FinalFrame.Final, OperationCode.Ping, PayloadData.Empty, false, mask
|
||||
);
|
||||
}
|
||||
|
||||
internal static WSFrame CreatePingFrame(byte[] data, bool mask)
|
||||
{
|
||||
return new WSFrame(
|
||||
FinalFrame.Final, OperationCode.Ping, new PayloadData(data), false, mask
|
||||
);
|
||||
}
|
||||
|
||||
internal static WSFrame CreatePongFrame(
|
||||
PayloadData payloadData, bool mask
|
||||
)
|
||||
{
|
||||
return new WSFrame(
|
||||
FinalFrame.Final, OperationCode.Pong, payloadData, false, mask
|
||||
);
|
||||
}
|
||||
|
||||
internal static WSFrame ReadFrame(Stream stream, bool unmask)
|
||||
{
|
||||
var frame = readHeader(stream);
|
||||
readExtendedPayloadLength(stream, frame);
|
||||
readMaskingKey(stream, frame);
|
||||
readPayloadData(stream, frame);
|
||||
|
||||
if (unmask)
|
||||
{
|
||||
frame.Unmask();
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
internal static void ReadFrameAsync(
|
||||
Stream stream,
|
||||
bool unmask,
|
||||
Action<WSFrame> completed,
|
||||
Action<Exception> error
|
||||
)
|
||||
{
|
||||
readHeaderAsync(
|
||||
stream,
|
||||
frame =>
|
||||
readExtendedPayloadLengthAsync(
|
||||
stream,
|
||||
frame,
|
||||
frame1 =>
|
||||
readMaskingKeyAsync(
|
||||
stream,
|
||||
frame1,
|
||||
frame2 =>
|
||||
readPayloadDataAsync(
|
||||
stream,
|
||||
frame2,
|
||||
frame3 =>
|
||||
{
|
||||
if (unmask)
|
||||
{
|
||||
frame3.Unmask();
|
||||
}
|
||||
|
||||
completed(frame3);
|
||||
},
|
||||
error
|
||||
),
|
||||
error
|
||||
),
|
||||
error
|
||||
),
|
||||
error
|
||||
);
|
||||
}
|
||||
|
||||
internal void Unmask()
|
||||
{
|
||||
if (Mask == Mask.Off)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Mask = Mask.Off;
|
||||
PayloadData.Mask(MaskingKey);
|
||||
MaskingKey = WSClient.EmptyBytes;
|
||||
}
|
||||
|
||||
public IEnumerator<byte> GetEnumerator()
|
||||
{
|
||||
foreach (var b in ToArray())
|
||||
{
|
||||
yield return b;
|
||||
}
|
||||
}
|
||||
|
||||
public void Print(bool dumped)
|
||||
{
|
||||
Console.WriteLine(dumped ? dump(this) : print(this));
|
||||
}
|
||||
|
||||
public string PrintToString(bool dumped)
|
||||
{
|
||||
return dumped ? dump(this) : print(this);
|
||||
}
|
||||
|
||||
public byte[] ToArray()
|
||||
{
|
||||
using (var buff = new MemoryStream())
|
||||
{
|
||||
var header = (int)Fin;
|
||||
header = (header << 1) + (int)Rsv1;
|
||||
header = (header << 1) + (int)Rsv2;
|
||||
header = (header << 1) + (int)Rsv3;
|
||||
header = (header << 4) + (int)Opcode;
|
||||
header = (header << 1) + (int)Mask;
|
||||
header = (header << 7) + PayloadLength;
|
||||
buff.Write(((ushort)header).InternalToByteArray(ByteOrder.Big), 0, 2);
|
||||
|
||||
if (PayloadLength > 125)
|
||||
{
|
||||
buff.Write(ExtendedPayloadLength, 0, PayloadLength == 126 ? 2 : 8);
|
||||
}
|
||||
|
||||
if (Mask == Mask.On)
|
||||
{
|
||||
buff.Write(MaskingKey, 0, 4);
|
||||
}
|
||||
|
||||
if (PayloadLength > 0)
|
||||
{
|
||||
var bytes = PayloadData.ToArray();
|
||||
if (PayloadLength < 127)
|
||||
{
|
||||
buff.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
buff.WriteBytes(bytes, BUFFER_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
buff.Close();
|
||||
return buff.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return BitConverter.ToString(ToArray());
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,11 +12,9 @@ namespace EonaCat.Network
|
|||
{
|
||||
internal abstract class WebBase
|
||||
{
|
||||
private const int _headersMaxLength = 8192;
|
||||
internal byte[] EntityBodyData;
|
||||
|
||||
protected const string CrLf = "\r\n";
|
||||
|
||||
private const int _headersMaxLength = 8192;
|
||||
protected WebBase(Version version, NameValueCollection headers)
|
||||
{
|
||||
ProtocolVersion = version;
|
||||
|
@ -48,6 +46,60 @@ namespace EonaCat.Network
|
|||
|
||||
public Version ProtocolVersion { get; }
|
||||
|
||||
public byte[] ToByteArray()
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(ToString());
|
||||
}
|
||||
|
||||
protected static T Read<T>(Stream stream, Func<string[], T> parser, int millisecondsTimeout)
|
||||
where T : WebBase
|
||||
{
|
||||
var timeout = false;
|
||||
var timer = new Timer(
|
||||
state =>
|
||||
{
|
||||
timeout = true;
|
||||
stream.Close();
|
||||
},
|
||||
null,
|
||||
millisecondsTimeout,
|
||||
-1);
|
||||
|
||||
T http = null;
|
||||
Exception exception = null;
|
||||
try
|
||||
{
|
||||
http = parser(readHeaders(stream, _headersMaxLength));
|
||||
var contentLen = http.Headers["Content-Length"];
|
||||
if (contentLen != null && contentLen.Length > 0)
|
||||
{
|
||||
http.EntityBodyData = readEntityBody(stream, contentLen);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
finally
|
||||
{
|
||||
timer.Change(-1, -1);
|
||||
timer.Dispose();
|
||||
}
|
||||
|
||||
var message = timeout
|
||||
? "A timeout has occurred while reading an HTTP request/response."
|
||||
: exception != null
|
||||
? "An exception has occurred while reading an HTTP request/response."
|
||||
: null;
|
||||
|
||||
if (message != null)
|
||||
{
|
||||
throw new WSException(message, exception);
|
||||
}
|
||||
|
||||
return http;
|
||||
}
|
||||
|
||||
private static byte[] readEntityBody(Stream stream, string length)
|
||||
{
|
||||
if (!long.TryParse(length, out long len))
|
||||
|
@ -105,59 +157,5 @@ namespace EonaCat.Network
|
|||
.Replace(CrLf + "\t", " ")
|
||||
.Split(new[] { CrLf }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
protected static T Read<T>(Stream stream, Func<string[], T> parser, int millisecondsTimeout)
|
||||
where T : WebBase
|
||||
{
|
||||
var timeout = false;
|
||||
var timer = new Timer(
|
||||
state =>
|
||||
{
|
||||
timeout = true;
|
||||
stream.Close();
|
||||
},
|
||||
null,
|
||||
millisecondsTimeout,
|
||||
-1);
|
||||
|
||||
T http = null;
|
||||
Exception exception = null;
|
||||
try
|
||||
{
|
||||
http = parser(readHeaders(stream, _headersMaxLength));
|
||||
var contentLen = http.Headers["Content-Length"];
|
||||
if (contentLen != null && contentLen.Length > 0)
|
||||
{
|
||||
http.EntityBodyData = readEntityBody(stream, contentLen);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
finally
|
||||
{
|
||||
timer.Change(-1, -1);
|
||||
timer.Dispose();
|
||||
}
|
||||
|
||||
var message = timeout
|
||||
? "A timeout has occurred while reading an HTTP request/response."
|
||||
: exception != null
|
||||
? "An exception has occurred while reading an HTTP request/response."
|
||||
: null;
|
||||
|
||||
if (message != null)
|
||||
{
|
||||
throw new WSException(message, exception);
|
||||
}
|
||||
|
||||
return http;
|
||||
}
|
||||
|
||||
public byte[] ToByteArray()
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(ToString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,19 +14,18 @@ namespace EonaCat.Network
|
|||
private bool _websocketRequest;
|
||||
private bool _websocketRequestSet;
|
||||
|
||||
private WebRequest(string method, string uri, Version version, NameValueCollection headers)
|
||||
: base(version, headers)
|
||||
{
|
||||
HttpMethod = method;
|
||||
RequestUri = uri;
|
||||
}
|
||||
|
||||
internal WebRequest(string method, string uri)
|
||||
: this(method, uri, HttpVersion.Version11, new NameValueCollection())
|
||||
{
|
||||
Headers["User-Agent"] = $"EonaCat.Network/{Constants.Version}";
|
||||
}
|
||||
|
||||
private WebRequest(string method, string uri, Version version, NameValueCollection headers)
|
||||
: base(version, headers)
|
||||
{
|
||||
HttpMethod = method;
|
||||
RequestUri = uri;
|
||||
}
|
||||
public AuthenticationResponse AuthenticationResponse
|
||||
{
|
||||
get
|
||||
|
@ -63,67 +62,6 @@ namespace EonaCat.Network
|
|||
|
||||
public string RequestUri { get; }
|
||||
|
||||
internal static WebRequest CreateConnectRequest(Uri uri)
|
||||
{
|
||||
var host = uri.DnsSafeHost;
|
||||
var port = uri.Port;
|
||||
var authority = string.Format("{0}:{1}", host, port);
|
||||
var req = new WebRequest("CONNECT", authority);
|
||||
req.Headers["Host"] = port == 80 ? host : authority;
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
internal static WebRequest CreateWebSocketRequest(Uri uri)
|
||||
{
|
||||
var req = new WebRequest("GET", uri.PathAndQuery);
|
||||
var headers = req.Headers;
|
||||
|
||||
// Only includes a port number in the Host header value if it's non-default.
|
||||
// See: https://tools.ietf.org/html/rfc6455#page-17
|
||||
var port = uri.Port;
|
||||
var schm = uri.Scheme;
|
||||
headers["Host"] = (port == 80 && schm == "ws") || (port == 443 && schm == "wss")
|
||||
? uri.DnsSafeHost
|
||||
: uri.Authority;
|
||||
|
||||
headers["Upgrade"] = "websocket";
|
||||
headers["Connection"] = "Upgrade";
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
internal WebResponse GetResponse(Stream stream, int millisecondsTimeout)
|
||||
{
|
||||
var buff = ToByteArray();
|
||||
stream.Write(buff, 0, buff.Length);
|
||||
|
||||
return Read(stream, WebResponse.Parse, millisecondsTimeout);
|
||||
}
|
||||
|
||||
internal static WebRequest Parse(string[] headerParts)
|
||||
{
|
||||
var requestLine = headerParts[0].Split(new[] { ' ' }, 3);
|
||||
if (requestLine.Length != 3)
|
||||
{
|
||||
throw new ArgumentException("Invalid request line: " + headerParts[0]);
|
||||
}
|
||||
|
||||
var headers = new WebHeaderCollection();
|
||||
for (int i = 1; i < headerParts.Length; i++)
|
||||
{
|
||||
headers.InternalSet(headerParts[i], false);
|
||||
}
|
||||
|
||||
return new WebRequest(
|
||||
requestLine[0], requestLine[1], new Version(requestLine[2].Substring(5)), headers);
|
||||
}
|
||||
|
||||
internal static WebRequest Read(Stream stream, int millisecondsTimeout)
|
||||
{
|
||||
return Read(stream, Parse, millisecondsTimeout);
|
||||
}
|
||||
|
||||
public void SetCookies(CookieCollection cookies)
|
||||
{
|
||||
if (cookies == null || cookies.Count == 0)
|
||||
|
@ -169,5 +107,66 @@ namespace EonaCat.Network
|
|||
|
||||
return output.ToString();
|
||||
}
|
||||
|
||||
internal static WebRequest CreateConnectRequest(Uri uri)
|
||||
{
|
||||
var host = uri.DnsSafeHost;
|
||||
var port = uri.Port;
|
||||
var authority = string.Format("{0}:{1}", host, port);
|
||||
var req = new WebRequest("CONNECT", authority);
|
||||
req.Headers["Host"] = port == 80 ? host : authority;
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
internal static WebRequest CreateWebSocketRequest(Uri uri)
|
||||
{
|
||||
var req = new WebRequest("GET", uri.PathAndQuery);
|
||||
var headers = req.Headers;
|
||||
|
||||
// Only includes a port number in the Host header value if it's non-default.
|
||||
// See: https://tools.ietf.org/html/rfc6455#page-17
|
||||
var port = uri.Port;
|
||||
var schm = uri.Scheme;
|
||||
headers["Host"] = (port == 80 && schm == "ws") || (port == 443 && schm == "wss")
|
||||
? uri.DnsSafeHost
|
||||
: uri.Authority;
|
||||
|
||||
headers["Upgrade"] = "websocket";
|
||||
headers["Connection"] = "Upgrade";
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
internal static WebRequest Parse(string[] headerParts)
|
||||
{
|
||||
var requestLine = headerParts[0].Split(new[] { ' ' }, 3);
|
||||
if (requestLine.Length != 3)
|
||||
{
|
||||
throw new ArgumentException("Invalid request line: " + headerParts[0]);
|
||||
}
|
||||
|
||||
var headers = new WebHeaderCollection();
|
||||
for (int i = 1; i < headerParts.Length; i++)
|
||||
{
|
||||
headers.InternalSet(headerParts[i], false);
|
||||
}
|
||||
|
||||
return new WebRequest(
|
||||
requestLine[0], requestLine[1], new Version(requestLine[2].Substring(5)), headers);
|
||||
}
|
||||
|
||||
internal static WebRequest Read(Stream stream, int millisecondsTimeout)
|
||||
{
|
||||
return Read(stream, Parse, millisecondsTimeout);
|
||||
}
|
||||
|
||||
internal WebResponse GetResponse(Stream stream, int millisecondsTimeout)
|
||||
{
|
||||
var buff = ToByteArray();
|
||||
stream.Write(buff, 0, buff.Length);
|
||||
|
||||
return Read(stream, WebResponse.Parse, millisecondsTimeout);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,13 +11,6 @@ namespace EonaCat.Network
|
|||
{
|
||||
internal class WebResponse : WebBase
|
||||
{
|
||||
private WebResponse(string code, string reason, Version version, NameValueCollection headers)
|
||||
: base(version, headers)
|
||||
{
|
||||
StatusCode = code;
|
||||
Reason = reason;
|
||||
}
|
||||
|
||||
internal WebResponse(HttpStatusCode code)
|
||||
: this(code, code.GetDescription())
|
||||
{
|
||||
|
@ -29,6 +22,12 @@ namespace EonaCat.Network
|
|||
Headers["Server"] = $"EonaCat.Network/{Constants.Version}";
|
||||
}
|
||||
|
||||
private WebResponse(string code, string reason, Version version, NameValueCollection headers)
|
||||
: base(version, headers)
|
||||
{
|
||||
StatusCode = code;
|
||||
Reason = reason;
|
||||
}
|
||||
public CookieCollection Cookies => Headers.GetCookies(true);
|
||||
|
||||
public bool HasConnectionClose => Headers.Contains("Connection", "close");
|
||||
|
@ -55,6 +54,42 @@ namespace EonaCat.Network
|
|||
|
||||
public string StatusCode { get; }
|
||||
|
||||
public void SetCookies(CookieCollection cookies)
|
||||
{
|
||||
if (cookies == null || cookies.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var headers = Headers;
|
||||
foreach (var cookie in cookies.Sorted)
|
||||
{
|
||||
headers.Add("Set-Cookie", cookie.ToResponseString());
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var output = new StringBuilder(64);
|
||||
output.AppendFormat("HTTP/{0} {1} {2}{3}", ProtocolVersion, StatusCode, Reason, CrLf);
|
||||
|
||||
var headers = Headers;
|
||||
foreach (var key in headers.AllKeys)
|
||||
{
|
||||
output.AppendFormat("{0}: {1}{2}", key, headers[key], CrLf);
|
||||
}
|
||||
|
||||
output.Append(CrLf);
|
||||
|
||||
var entity = EntityBody;
|
||||
if (entity.Length > 0)
|
||||
{
|
||||
output.Append(entity);
|
||||
}
|
||||
|
||||
return output.ToString();
|
||||
}
|
||||
|
||||
internal static WebResponse CreateCloseResponse(HttpStatusCode code)
|
||||
{
|
||||
var res = new WebResponse(code);
|
||||
|
@ -104,41 +139,5 @@ namespace EonaCat.Network
|
|||
{
|
||||
return Read(stream, Parse, millisecondsTimeout);
|
||||
}
|
||||
|
||||
public void SetCookies(CookieCollection cookies)
|
||||
{
|
||||
if (cookies == null || cookies.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var headers = Headers;
|
||||
foreach (var cookie in cookies.Sorted)
|
||||
{
|
||||
headers.Add("Set-Cookie", cookie.ToResponseString());
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var output = new StringBuilder(64);
|
||||
output.AppendFormat("HTTP/{0} {1} {2}{3}", ProtocolVersion, StatusCode, Reason, CrLf);
|
||||
|
||||
var headers = Headers;
|
||||
foreach (var key in headers.AllKeys)
|
||||
{
|
||||
output.AppendFormat("{0}: {1}{2}", key, headers[key], CrLf);
|
||||
}
|
||||
|
||||
output.Append(CrLf);
|
||||
|
||||
var entity = EntityBody;
|
||||
if (entity.Length > 0)
|
||||
{
|
||||
output.Append(entity);
|
||||
}
|
||||
|
||||
return output.ToString();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue