Updated
This commit is contained in:
parent
e6517ad662
commit
08a2be3221
|
@ -84,7 +84,7 @@ namespace EonaCat.Network
|
||||||
/// <returns>A collection of authentication parameters.</returns>
|
/// <returns>A collection of authentication parameters.</returns>
|
||||||
internal static NameValueCollection ParseParameters(string value)
|
internal static NameValueCollection ParseParameters(string value)
|
||||||
{
|
{
|
||||||
var res = new NameValueCollection();
|
var result = new NameValueCollection();
|
||||||
foreach (var param in value.SplitHeaderValue(','))
|
foreach (var param in value.SplitHeaderValue(','))
|
||||||
{
|
{
|
||||||
var i = param.IndexOf('=');
|
var i = param.IndexOf('=');
|
||||||
|
@ -95,10 +95,10 @@ namespace EonaCat.Network
|
||||||
? param.Substring(i + 1).Trim().Trim('"')
|
? param.Substring(i + 1).Trim().Trim('"')
|
||||||
: string.Empty;
|
: string.Empty;
|
||||||
|
|
||||||
res.Add(name, val);
|
result.Add(name, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -15,16 +15,6 @@ namespace EonaCat.Network
|
||||||
private const string DIGEST = "digest";
|
private const string DIGEST = "digest";
|
||||||
private const int DIGEST_SIZE = 128;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AuthenticationChallenge"/> class for Basic or Digest authentication.
|
/// Initializes a new instance of the <see cref="AuthenticationChallenge"/> class for Basic or Digest authentication.
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Gets the domain for Digest authentication.
|
/// Gets the domain for Digest authentication.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -18,16 +18,6 @@ namespace EonaCat.Network
|
||||||
private const string DIGEST = "digest";
|
private const string DIGEST = "digest";
|
||||||
private uint _nonceCount;
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AuthenticationResponse"/> class for Basic authentication.
|
/// Initializes a new instance of the <see cref="AuthenticationResponse"/> class for Basic authentication.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -74,12 +64,14 @@ namespace EonaCat.Network
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the nonce count.
|
/// Initializes a new instance of the <see cref="AuthenticationResponse"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal uint NonceCount => _nonceCount < uint.MaxValue
|
/// <param name="scheme">The authentication scheme.</param>
|
||||||
? _nonceCount
|
/// <param name="parameters">The collection of authentication parameters.</param>
|
||||||
: 0;
|
private AuthenticationResponse(AuthenticationSchemes scheme, NameValueCollection parameters)
|
||||||
|
: base(scheme, parameters)
|
||||||
|
{
|
||||||
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the cnonce value for Digest authentication.
|
/// Gets the cnonce value for Digest authentication.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -111,97 +103,23 @@ namespace EonaCat.Network
|
||||||
public string UserName => Parameters["username"];
|
public string UserName => Parameters["username"];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the A1 value for Digest authentication.
|
/// Gets the nonce count.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="username">The username.</param>
|
internal uint NonceCount => _nonceCount < uint.MaxValue
|
||||||
/// <param name="password">The password.</param>
|
? _nonceCount
|
||||||
/// <param name="realm">The realm.</param>
|
: 0;
|
||||||
/// <returns>The A1 value.</returns>
|
|
||||||
private static string CreateA1(string username, string password, string realm)
|
|
||||||
{
|
|
||||||
return $"{username}:{realm}:{password}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the A1 value for Digest authentication with cnonce and nonce.
|
/// Converts the authentication response to an identity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="username">The username.</param>
|
/// <returns>An instance of <see cref="IIdentity"/>.</returns>
|
||||||
/// <param name="password">The password.</param>
|
public IIdentity ToIdentity()
|
||||||
/// <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}";
|
var scheme = Scheme;
|
||||||
}
|
return scheme == AuthenticationSchemes.Basic
|
||||||
|
? new HttpBasicIdentity(Parameters["username"], Parameters["password"])
|
||||||
/// <summary>
|
: scheme == AuthenticationSchemes.Digest
|
||||||
/// Creates the A2 value for Digest authentication.
|
? new HttpDigestIdentity(Parameters)
|
||||||
/// </summary>
|
: null;
|
||||||
/// <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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -343,17 +261,97 @@ namespace EonaCat.Network
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts the authentication response to an identity.
|
/// Creates the A1 value for Digest authentication.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>An instance of <see cref="IIdentity"/>.</returns>
|
/// <param name="username">The username.</param>
|
||||||
public IIdentity ToIdentity()
|
/// <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 $"{username}:{realm}:{password}";
|
||||||
return scheme == AuthenticationSchemes.Basic
|
}
|
||||||
? new HttpBasicIdentity(Parameters["username"], Parameters["password"])
|
|
||||||
: scheme == AuthenticationSchemes.Digest
|
/// <summary>
|
||||||
? new HttpDigestIdentity(Parameters)
|
/// Creates the A1 value for Digest authentication with cnonce and nonce.
|
||||||
: null;
|
/// </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>
|
/// </summary>
|
||||||
public class NetworkCredential
|
public class NetworkCredential
|
||||||
{
|
{
|
||||||
private string _domain;
|
|
||||||
private static readonly string[] _noRoles;
|
private static readonly string[] _noRoles;
|
||||||
|
private string _domain;
|
||||||
private string _password;
|
private string _password;
|
||||||
private string[] _roles;
|
private string[] _roles;
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,11 @@ namespace EonaCat.Network
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class ChunkStream
|
internal class ChunkStream
|
||||||
{
|
{
|
||||||
|
private readonly List<WebChunk> _chunks;
|
||||||
|
private readonly StringBuilder _saved;
|
||||||
private int _chunkRead;
|
private int _chunkRead;
|
||||||
private int _chunkSize;
|
private int _chunkSize;
|
||||||
private readonly List<WebChunk> _chunks;
|
|
||||||
private bool _foundSPCode;
|
private bool _foundSPCode;
|
||||||
private readonly StringBuilder _saved;
|
|
||||||
private bool _gotChunck;
|
private bool _gotChunck;
|
||||||
private InputChunkState _state;
|
private InputChunkState _state;
|
||||||
private int _trailerState;
|
private int _trailerState;
|
||||||
|
@ -49,11 +49,6 @@ namespace EonaCat.Network
|
||||||
Write(buffer, offset, count);
|
Write(buffer, offset, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the web headers associated with the chunk stream.
|
|
||||||
/// </summary>
|
|
||||||
internal WebHeaderCollection Headers { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the number of bytes left in the current chunk.
|
/// Gets the number of bytes left in the current chunk.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -64,6 +59,78 @@ namespace EonaCat.Network
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool WantMore => _state != InputChunkState.End;
|
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)
|
private int read(byte[] buffer, int offset, int count)
|
||||||
{
|
{
|
||||||
var nread = 0;
|
var nread = 0;
|
||||||
|
@ -92,13 +159,6 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
return nread;
|
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)
|
private InputChunkState SeekCrLf(byte[] buffer, ref int offset, int length)
|
||||||
{
|
{
|
||||||
if (!_gotChunck)
|
if (!_gotChunck)
|
||||||
|
@ -256,12 +316,6 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
return InputChunkState.End;
|
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)
|
private void Write(byte[] buffer, ref int offset, int length)
|
||||||
{
|
{
|
||||||
if (_state == InputChunkState.End)
|
if (_state == InputChunkState.End)
|
||||||
|
@ -337,62 +391,5 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
return _chunkRead == _chunkSize ? InputChunkState.DataEnded : InputChunkState.Data;
|
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>
|
/// </summary>
|
||||||
internal ChunkStream Decoder { get; set; }
|
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>
|
/// <summary>
|
||||||
/// Begins an asynchronous read operation from the stream.
|
/// Begins an asynchronous read operation from the stream.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -204,5 +173,36 @@ namespace EonaCat.Network
|
||||||
var result = BeginRead(buffer, offset, count, null, null);
|
var result = BeginRead(buffer, offset, count, null, null);
|
||||||
return EndRead(result);
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the WebHeaderCollection class with the specified parameters.
|
/// Initializes a new instance of the WebHeaderCollection class with the specified parameters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -132,18 +136,10 @@ namespace EonaCat.Network
|
||||||
throw new ArgumentException(ex.Message, nameof(serializationInfo), ex);
|
throw new ArgumentException(ex.Message, nameof(serializationInfo), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public WebHeaderCollection()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
internal HttpHeaderType State => _state;
|
|
||||||
|
|
||||||
public override string[] AllKeys => base.AllKeys;
|
public override string[] AllKeys => base.AllKeys;
|
||||||
|
|
||||||
public override int Count => base.Count;
|
public override int Count => base.Count;
|
||||||
|
public override KeysCollection Keys => base.Keys;
|
||||||
|
internal HttpHeaderType State => _state;
|
||||||
public string this[HttpRequestHeader header]
|
public string this[HttpRequestHeader header]
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -169,272 +165,14 @@ namespace EonaCat.Network
|
||||||
Add(header, value);
|
Add(header, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public static bool IsRestricted(string headerName)
|
||||||
public override KeysCollection Keys => base.Keys;
|
|
||||||
|
|
||||||
private void add(string name, string value, bool ignoreRestricted)
|
|
||||||
{
|
{
|
||||||
var act = ignoreRestricted
|
return isRestricted(CheckName(headerName), false);
|
||||||
? (Action<string, string>)addWithoutCheckingNameAndRestricted
|
|
||||||
: addWithoutCheckingName;
|
|
||||||
|
|
||||||
DoWithCheckingState(act, CheckName(name), value, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addWithoutCheckingName(string name, string value)
|
public static bool IsRestricted(string headerName, bool response)
|
||||||
{
|
{
|
||||||
DoWithoutCheckingName(base.Add, name, value);
|
return isRestricted(CheckName(headerName), response);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Add(string header)
|
public void Add(string header)
|
||||||
|
@ -489,18 +227,6 @@ namespace EonaCat.Network
|
||||||
return base.GetKey(index);
|
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(
|
[SecurityPermission(
|
||||||
SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
|
SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
|
||||||
public override void GetObjectData(
|
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)
|
public override void OnDeserialization(object sender)
|
||||||
|
@ -589,14 +327,266 @@ namespace EonaCat.Network
|
||||||
return buff.Append("\r\n").ToString();
|
return buff.Append("\r\n").ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
[SecurityPermission(
|
/// <summary>
|
||||||
SecurityAction.LinkDemand,
|
/// Converts the specified HttpRequestHeader to a string.
|
||||||
Flags = SecurityPermissionFlag.SerializationFormatter,
|
/// </summary>
|
||||||
SerializationFormatter = true)]
|
/// <param name="header">The HttpRequestHeader to convert.</param>
|
||||||
void ISerializable.GetObjectData(
|
/// <returns>A string representing the converted HttpRequestHeader.</returns>
|
||||||
SerializationInfo serializationInfo, StreamingContext streamingContext)
|
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);
|
_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 CookieCollection CookieCollection => _context.Request.Cookies;
|
||||||
|
|
||||||
public override NameValueCollection Headers => _context.Request.Headers;
|
public override NameValueCollection Headers => _context.Request.Headers;
|
||||||
|
@ -85,6 +80,16 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
public override WSClient WebSocket => _websocket;
|
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>
|
/// <summary>
|
||||||
/// Closes the WebSocket connection.
|
/// Closes the WebSocket connection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -101,11 +106,5 @@ namespace EonaCat.Network
|
||||||
{
|
{
|
||||||
_context.Response.Close(code);
|
_context.Response.Close(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return _context.Request.ToString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -21,15 +21,14 @@ namespace EonaCat.Network
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
internal class TcpListenerWSContext : WSContext
|
internal class TcpListenerWSContext : WSContext
|
||||||
{
|
{
|
||||||
private CookieCollection _cookies;
|
|
||||||
private NameValueCollection _queryString;
|
|
||||||
private WebRequest _request;
|
|
||||||
private readonly bool _secure;
|
private readonly bool _secure;
|
||||||
private readonly TcpClient _tcpClient;
|
private readonly TcpClient _tcpClient;
|
||||||
private readonly Uri _uri;
|
private readonly Uri _uri;
|
||||||
private IPrincipal _user;
|
|
||||||
private readonly WSClient _websocket;
|
private readonly WSClient _websocket;
|
||||||
|
private CookieCollection _cookies;
|
||||||
|
private NameValueCollection _queryString;
|
||||||
|
private WebRequest _request;
|
||||||
|
private IPrincipal _user;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="TcpListenerWSContext"/> class.
|
/// Initializes a new instance of the <see cref="TcpListenerWSContext"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -76,11 +75,6 @@ namespace EonaCat.Network
|
||||||
_websocket = new WSClient(this, protocol);
|
_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 CookieCollection CookieCollection => _cookies ??= _request.Cookies;
|
||||||
|
|
||||||
public override NameValueCollection Headers => _request.Headers;
|
public override NameValueCollection Headers => _request.Headers;
|
||||||
|
@ -132,6 +126,16 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
public override WSClient WebSocket => _websocket;
|
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>
|
/// <summary>
|
||||||
/// Authenticates the WebSocket connection based on the specified authentication scheme.
|
/// Authenticates the WebSocket connection based on the specified authentication scheme.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -220,11 +224,5 @@ namespace EonaCat.Network
|
||||||
Stream.Write(buff, 0, buff.Length);
|
Stream.Write(buff, 0, buff.Length);
|
||||||
_request = WebRequest.Read(Stream, 15000);
|
_request = WebRequest.Read(Stream, 15000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return _request.ToString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,6 +13,9 @@ namespace EonaCat.Network
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public sealed class Cookie
|
public sealed class Cookie
|
||||||
{
|
{
|
||||||
|
private static readonly char[] _reservedCharsForName;
|
||||||
|
private static readonly char[] _reservedCharsForValue;
|
||||||
|
private readonly DateTime _timestamp;
|
||||||
private string _comment;
|
private string _comment;
|
||||||
private Uri _commentUri;
|
private Uri _commentUri;
|
||||||
private bool _discard;
|
private bool _discard;
|
||||||
|
@ -23,10 +26,7 @@ namespace EonaCat.Network
|
||||||
private string _path;
|
private string _path;
|
||||||
private string _port;
|
private string _port;
|
||||||
private int[] _ports;
|
private int[] _ports;
|
||||||
private static readonly char[] _reservedCharsForName;
|
|
||||||
private static readonly char[] _reservedCharsForValue;
|
|
||||||
private bool _secure;
|
private bool _secure;
|
||||||
private readonly DateTime _timestamp;
|
|
||||||
private string _value;
|
private string _value;
|
||||||
private int _version;
|
private int _version;
|
||||||
|
|
||||||
|
@ -90,33 +90,6 @@ namespace EonaCat.Network
|
||||||
Domain = domain;
|
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
|
public string Comment
|
||||||
{
|
{
|
||||||
get
|
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)
|
private static bool canSetName(string name, out string message)
|
||||||
{
|
{
|
||||||
if (name.IsNullOrEmpty())
|
if (name.IsNullOrEmpty())
|
||||||
|
@ -378,6 +463,36 @@ namespace EonaCat.Network
|
||||||
(m << 20 | m >> 12);
|
(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()
|
private string toResponseStringVersion0()
|
||||||
{
|
{
|
||||||
var output = new StringBuilder(64);
|
var output = new StringBuilder(64);
|
||||||
|
@ -470,121 +585,5 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
return output.ToString();
|
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>
|
/// <summary>
|
||||||
/// Gets the number of cookies in the collection.
|
/// Gets the number of cookies in the collection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -58,6 +42,26 @@ namespace EonaCat.Network
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsSynchronized => false;
|
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>
|
/// <summary>
|
||||||
/// Gets or sets the cookie at the specified index.
|
/// Gets or sets the cookie at the specified index.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -101,11 +105,164 @@ namespace EonaCat.Network
|
||||||
return null;
|
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>
|
/// <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>
|
/// </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)
|
private static int compareCookieWithinSort(Cookie x, Cookie y)
|
||||||
{
|
{
|
||||||
|
@ -357,6 +514,11 @@ namespace EonaCat.Network
|
||||||
return cookies;
|
return cookies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string[] splitCookieHeaderValue(string value)
|
||||||
|
{
|
||||||
|
return new List<string>(value.SplitHeaderValue(',', ';')).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
private int searchCookie(Cookie cookie)
|
private int searchCookie(Cookie cookie)
|
||||||
{
|
{
|
||||||
var name = cookie.Name;
|
var name = cookie.Name;
|
||||||
|
@ -378,169 +540,5 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
return -1;
|
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]
|
[Serializable]
|
||||||
public class CookieException : FormatException, ISerializable
|
public class CookieException : FormatException, ISerializable
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CookieException"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public CookieException()
|
||||||
|
: base()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="CookieException"/> class with a specified error message.
|
/// Initializes a new instance of the <see cref="CookieException"/> class with a specified error message.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -41,15 +49,6 @@ namespace EonaCat.Network
|
||||||
: base(serializationInfo, streamingContext)
|
: base(serializationInfo, streamingContext)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="CookieException"/> class.
|
|
||||||
/// </summary>
|
|
||||||
public CookieException()
|
|
||||||
: base()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Populates a <see cref="SerializationInfo"/> with the data needed to serialize the exception.
|
/// Populates a <see cref="SerializationInfo"/> with the data needed to serialize the exception.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -18,15 +18,14 @@ namespace EonaCat.Network
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class EndPointListener
|
internal sealed class EndPointListener
|
||||||
{
|
{
|
||||||
private List<HttpListenerPrefix> _all; // host == '+'
|
|
||||||
private static readonly string _defaultCertFolderPath;
|
private static readonly string _defaultCertFolderPath;
|
||||||
private readonly IPEndPoint _endpoint;
|
private readonly IPEndPoint _endpoint;
|
||||||
private Dictionary<HttpListenerPrefix, HttpListener> _prefixes;
|
|
||||||
private readonly Socket _socket;
|
private readonly Socket _socket;
|
||||||
private List<HttpListenerPrefix> _unhandled; // host == '*'
|
|
||||||
private readonly Dictionary<HttpConnection, HttpConnection> _unregistered;
|
private readonly Dictionary<HttpConnection, HttpConnection> _unregistered;
|
||||||
private readonly object _unregisteredSync;
|
private readonly object _unregisteredSync;
|
||||||
|
private List<HttpListenerPrefix> _all; // host == '+'
|
||||||
|
private Dictionary<HttpListenerPrefix, HttpListener> _prefixes;
|
||||||
|
private List<HttpListenerPrefix> _unhandled; // host == '*'
|
||||||
static EndPointListener()
|
static EndPointListener()
|
||||||
{
|
{
|
||||||
_defaultCertFolderPath =
|
_defaultCertFolderPath =
|
||||||
|
@ -93,293 +92,6 @@ namespace EonaCat.Network
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SSLConfigServer SSL { get; }
|
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)
|
public void AddPrefix(HttpListenerPrefix prefix, HttpListener listener)
|
||||||
{
|
{
|
||||||
List<HttpListenerPrefix> current, future;
|
List<HttpListenerPrefix> current, future;
|
||||||
|
@ -528,5 +240,292 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
leaveIfNoPrefix();
|
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)
|
private static void addPrefix(string uriPrefix, HttpListener listener)
|
||||||
{
|
{
|
||||||
var pref = new HttpListenerPrefix(uriPrefix);
|
var pref = new HttpListenerPrefix(uriPrefix);
|
||||||
|
@ -137,96 +228,5 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
lsnr.RemovePrefix(pref, listener);
|
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>
|
/// </summary>
|
||||||
internal sealed class HttpConnection
|
internal sealed class HttpConnection
|
||||||
{
|
{
|
||||||
private byte[] _buffer;
|
|
||||||
private const int _bufferLength = 8192;
|
private const int _bufferLength = 8192;
|
||||||
private const int TIMEOUT_CONTINUE = 15000;
|
private const int TIMEOUT_CONTINUE = 15000;
|
||||||
private const int TIMEOUT_INITIAL = 90000;
|
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 HttpListenerContext _context;
|
||||||
private bool _contextRegistered;
|
private bool _contextRegistered;
|
||||||
private StringBuilder _currentLine;
|
private StringBuilder _currentLine;
|
||||||
|
@ -28,14 +31,11 @@ namespace EonaCat.Network
|
||||||
private RequestStream _inputStream;
|
private RequestStream _inputStream;
|
||||||
private HttpListener _lastListener;
|
private HttpListener _lastListener;
|
||||||
private LineState _lineState;
|
private LineState _lineState;
|
||||||
private readonly EndPointListener _listener;
|
|
||||||
private ResponseStream _outputStream;
|
private ResponseStream _outputStream;
|
||||||
private int _position;
|
private int _position;
|
||||||
private MemoryStream _requestBuffer;
|
private MemoryStream _requestBuffer;
|
||||||
private Socket _socket;
|
private Socket _socket;
|
||||||
private readonly object _lock;
|
|
||||||
private int _timeout;
|
private int _timeout;
|
||||||
private readonly Dictionary<int, bool> _timeoutCanceled;
|
|
||||||
private Timer _timer;
|
private Timer _timer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -106,375 +106,6 @@ namespace EonaCat.Network
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Stream Stream { get; private set; }
|
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>
|
/// <summary>
|
||||||
/// Initiates reading the request.
|
/// Initiates reading the request.
|
||||||
/// </summary>
|
/// </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;
|
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>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the header is for a request.
|
/// Gets a value indicating whether the header is for a request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -49,6 +39,15 @@ namespace EonaCat.Network
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public HttpHeaderType Type { get; }
|
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>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the header is multi-value.
|
/// Gets a value indicating whether the header is multi-value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -13,25 +13,25 @@ namespace EonaCat.Network
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class HttpListener : IDisposable
|
public sealed class HttpListener : IDisposable
|
||||||
{
|
{
|
||||||
private AuthenticationSchemes _authSchemes;
|
private static readonly string _defaultRealm;
|
||||||
private Func<HttpListenerRequest, AuthenticationSchemes> _authSchemeSelector;
|
|
||||||
private string _certFolderPath;
|
|
||||||
private readonly Dictionary<HttpConnection, HttpConnection> _connections;
|
private readonly Dictionary<HttpConnection, HttpConnection> _connections;
|
||||||
private readonly object _connectionsSync;
|
private readonly object _connectionsSync;
|
||||||
private readonly List<HttpListenerContext> contextQueue;
|
|
||||||
private readonly object _contextQueueLock;
|
private readonly object _contextQueueLock;
|
||||||
private readonly Dictionary<HttpListenerContext, HttpListenerContext> _contextRegistry;
|
private readonly Dictionary<HttpListenerContext, HttpListenerContext> _contextRegistry;
|
||||||
private readonly object _contextRegistryLock;
|
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 bool _ignoreWriteExceptions;
|
||||||
private volatile bool _listening;
|
private volatile bool _listening;
|
||||||
private readonly HttpListenerPrefixCollection _prefixes;
|
|
||||||
private string _realm;
|
private string _realm;
|
||||||
private SSLConfigServer _sslConfig;
|
private SSLConfigServer _sslConfig;
|
||||||
private Func<IIdentity, NetworkCredential> _userCredFinder;
|
private Func<IIdentity, NetworkCredential> _userCredFinder;
|
||||||
private readonly List<HttpListenerAsyncResult> _waitQueue;
|
|
||||||
private readonly object _waitQueueLock;
|
|
||||||
|
|
||||||
static HttpListener()
|
static HttpListener()
|
||||||
{
|
{
|
||||||
_defaultRealm = "SECRET AREA";
|
_defaultRealm = "SECRET AREA";
|
||||||
|
@ -59,9 +59,29 @@ namespace EonaCat.Network
|
||||||
_waitQueueLock = ((ICollection)_waitQueue).SyncRoot;
|
_waitQueueLock = ((ICollection)_waitQueue).SyncRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool IsDisposed { get; private set; }
|
public static bool IsSupported => true;
|
||||||
|
/// <summary>
|
||||||
internal bool ReuseAddress { get; set; }
|
/// 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>
|
/// <summary>
|
||||||
/// Gets or sets the authentication schemes used by this listener.
|
/// Gets or sets the authentication schemes used by this listener.
|
||||||
|
@ -127,9 +147,6 @@ namespace EonaCat.Network
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsListening => _listening;
|
public bool IsListening => _listening;
|
||||||
|
|
||||||
public static bool IsSupported => true;
|
|
||||||
|
|
||||||
public HttpListenerPrefixCollection Prefixes
|
public HttpListenerPrefixCollection Prefixes
|
||||||
{
|
{
|
||||||
get
|
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()
|
private void CleanupConnections()
|
||||||
{
|
{
|
||||||
HttpConnection[] httpConnections = null;
|
HttpConnection[] httpConnections = null;
|
||||||
|
@ -335,285 +635,5 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
return ctx;
|
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
|
internal class HttpListenerAsyncResult : IAsyncResult
|
||||||
{
|
{
|
||||||
private readonly AsyncCallback _callback;
|
private readonly AsyncCallback _callback;
|
||||||
|
private readonly object _sync;
|
||||||
private bool _completed;
|
private bool _completed;
|
||||||
private HttpListenerContext _context;
|
private HttpListenerContext _context;
|
||||||
private Exception _exception;
|
private Exception _exception;
|
||||||
private readonly object _sync;
|
|
||||||
private ManualResetEvent _waitHandle;
|
private ManualResetEvent _waitHandle;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -30,16 +30,6 @@ namespace EonaCat.Network
|
||||||
_sync = new object();
|
_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>
|
/// <summary>
|
||||||
/// Gets the user-defined object that contains information about the asynchronous operation.
|
/// Gets the user-defined object that contains information about the asynchronous operation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -78,38 +68,15 @@ namespace EonaCat.Network
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private method to complete the asynchronous operation
|
/// <summary>
|
||||||
private static void complete(HttpListenerAsyncResult asyncResult)
|
/// Gets or sets a value indicating whether the <see cref="EndGetContext"/> method has been called.
|
||||||
{
|
/// </summary>
|
||||||
lock (asyncResult._sync)
|
internal bool EndCalled { get; set; }
|
||||||
{
|
|
||||||
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 asynchronous operation is in progress.
|
||||||
|
/// </summary>
|
||||||
|
internal bool InGet { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Completes the asynchronous operation with the specified exception.
|
/// Completes the asynchronous operation with the specified exception.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -158,5 +125,37 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
return _context;
|
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);
|
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>
|
/// <summary>
|
||||||
/// Gets the underlying <see cref="HttpConnection"/> for the context.
|
/// Gets the underlying <see cref="HttpConnection"/> for the context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -49,21 +64,34 @@ namespace EonaCat.Network
|
||||||
/// Gets or sets the <see cref="HttpListener"/> associated with the context.
|
/// Gets or sets the <see cref="HttpListener"/> associated with the context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal HttpListener Listener { get; set; }
|
internal HttpListener Listener { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the <see cref="HttpListenerRequest"/> associated with the context.
|
/// Accepts a WebSocket connection with the specified protocol.
|
||||||
/// </summary>
|
/// </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>
|
if (protocol != null)
|
||||||
/// Gets the <see cref="HttpListenerResponse"/> associated with the context.
|
{
|
||||||
/// </summary>
|
if (protocol.Length == 0)
|
||||||
public HttpListenerResponse Response { get; }
|
{
|
||||||
|
throw new ArgumentException("Empty string.", nameof(protocol));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
if (!protocol.IsToken())
|
||||||
/// Gets or sets the <see cref="IPrincipal"/> associated with the user.
|
{
|
||||||
/// </summary>
|
throw new ArgumentException("Contains invalid characters", nameof(protocol));
|
||||||
public IPrincipal User { get; private set; }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_websocketContext = new HttpListenerWSContext(this, protocol);
|
||||||
|
return _websocketContext;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Authenticates the user based on the specified authentication scheme.
|
/// Authenticates the user based on the specified authentication scheme.
|
||||||
|
@ -119,34 +147,5 @@ namespace EonaCat.Network
|
||||||
{
|
{
|
||||||
Listener.UnregisterContext(this);
|
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]
|
[Serializable]
|
||||||
public class HttpListenerException : Win32Exception
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="HttpListenerException"/> class with no error message.
|
/// Initializes a new instance of the <see cref="HttpListenerException"/> class with no error message.
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Gets the Win32 error code associated with this exception.
|
/// Gets the Win32 error code associated with this exception.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -52,35 +52,6 @@ namespace EonaCat.Network
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Port { get; private set; }
|
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>
|
/// <summary>
|
||||||
/// Checks if the specified URI prefix is valid.
|
/// Checks if the specified URI prefix is valid.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -163,5 +134,34 @@ namespace EonaCat.Network
|
||||||
{
|
{
|
||||||
return _prefix;
|
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();
|
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>
|
/// <summary>
|
||||||
/// Removes the specified URI prefix from the collection.
|
/// Removes the specified URI prefix from the collection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -141,14 +150,5 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
return ret;
|
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.Collections.Specialized;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace EonaCat.Network
|
namespace EonaCat.Network
|
||||||
|
@ -17,12 +16,12 @@ namespace EonaCat.Network
|
||||||
public sealed class HttpListenerRequest
|
public sealed class HttpListenerRequest
|
||||||
{
|
{
|
||||||
private static readonly byte[] _100continue;
|
private static readonly byte[] _100continue;
|
||||||
|
private readonly HttpListenerContext _context;
|
||||||
|
private readonly WebHeaderCollection _headers;
|
||||||
private bool _chunked;
|
private bool _chunked;
|
||||||
private Encoding _contentEncoding;
|
private Encoding _contentEncoding;
|
||||||
private bool _contentLengthSet;
|
private bool _contentLengthSet;
|
||||||
private readonly HttpListenerContext _context;
|
|
||||||
private CookieCollection _cookies;
|
private CookieCollection _cookies;
|
||||||
private readonly WebHeaderCollection _headers;
|
|
||||||
private Stream _inputStream;
|
private Stream _inputStream;
|
||||||
private bool _keepAlive;
|
private bool _keepAlive;
|
||||||
private bool _keepAliveSet;
|
private bool _keepAliveSet;
|
||||||
|
@ -162,7 +161,7 @@ namespace EonaCat.Network
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the query string in the request.
|
/// Gets the query string in the request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public NameValueCollection QueryString => _queryString ??= HttpUtility.InternalParseQueryString(Url.Query, Encoding.UTF8);
|
public NameValueCollection QueryString => _queryString ??= HttpUtility.InternalParseQueryString(Url.Query, ContentEncoding);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the raw URL of the request.
|
/// Gets the raw URL of the request.
|
||||||
|
@ -209,18 +208,13 @@ namespace EonaCat.Network
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string[] UserLanguages { get; private set; }
|
public string[] UserLanguages { get; private set; }
|
||||||
|
|
||||||
private static bool tryCreateVersion(string version, out Version result)
|
public override string ToString()
|
||||||
{
|
{
|
||||||
try
|
var buff = new StringBuilder(64);
|
||||||
{
|
buff.AppendFormat("{0} {1} HTTP/{2}\r\n", HttpMethod, _uri, _version);
|
||||||
result = new Version(version);
|
buff.Append(_headers.ToString());
|
||||||
return true;
|
|
||||||
}
|
return buff.ToString();
|
||||||
catch
|
|
||||||
{
|
|
||||||
result = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void AddHeader(string header)
|
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);
|
try
|
||||||
buff.AppendFormat("{0} {1} HTTP/{2}\r\n", HttpMethod, _uri, _version);
|
{
|
||||||
buff.Append(_headers.ToString());
|
result = new Version(version);
|
||||||
|
return true;
|
||||||
return buff.ToString();
|
}
|
||||||
|
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.
|
// 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.
|
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
|
||||||
|
|
||||||
using EonaCat.Logger;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace EonaCat.Network
|
namespace EonaCat.Network
|
||||||
|
@ -16,10 +14,10 @@ namespace EonaCat.Network
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class HttpListenerResponse : IDisposable
|
public sealed class HttpListenerResponse : IDisposable
|
||||||
{
|
{
|
||||||
|
private readonly HttpListenerContext _context;
|
||||||
private Encoding _contentEncoding;
|
private Encoding _contentEncoding;
|
||||||
private long _contentLength;
|
private long _contentLength;
|
||||||
private string _contentType;
|
private string _contentType;
|
||||||
private readonly HttpListenerContext _context;
|
|
||||||
private CookieCollection _cookies;
|
private CookieCollection _cookies;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
private WebHeaderCollection _headers;
|
private WebHeaderCollection _headers;
|
||||||
|
|
|
@ -9,8 +9,8 @@ namespace EonaCat.Network
|
||||||
internal class HttpStreamAsyncResult : IAsyncResult
|
internal class HttpStreamAsyncResult : IAsyncResult
|
||||||
{
|
{
|
||||||
private readonly AsyncCallback _callback;
|
private readonly AsyncCallback _callback;
|
||||||
private bool _isCompleted;
|
|
||||||
private readonly object _sync;
|
private readonly object _sync;
|
||||||
|
private bool _isCompleted;
|
||||||
private ManualResetEvent _waitHandle;
|
private ManualResetEvent _waitHandle;
|
||||||
|
|
||||||
internal HttpStreamAsyncResult(AsyncCallback callback, object state)
|
internal HttpStreamAsyncResult(AsyncCallback callback, object state)
|
||||||
|
@ -20,6 +20,30 @@ namespace EonaCat.Network
|
||||||
_sync = new object();
|
_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 byte[] Buffer { get; set; }
|
||||||
|
|
||||||
internal int Count { get; set; }
|
internal int Count { get; set; }
|
||||||
|
@ -31,33 +55,6 @@ namespace EonaCat.Network
|
||||||
internal int Offset { get; set; }
|
internal int Offset { get; set; }
|
||||||
|
|
||||||
internal int SyncRead { 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()
|
internal void Complete()
|
||||||
{
|
{
|
||||||
lock (_sync)
|
lock (_sync)
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -25,62 +25,23 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
internal class Logger
|
internal class Logger
|
||||||
{
|
{
|
||||||
internal static string LoggingDirectory { get; private set; }
|
|
||||||
private static readonly LogManager _logManager;
|
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()
|
static Logger()
|
||||||
{
|
{
|
||||||
_logManager = new LogManager(new LoggerSettings { RemoveMessagePrefix = true });
|
_logManager = new LogManager(new LoggerSettings { RemoveMessagePrefix = true });
|
||||||
_logManager.OnException += _logManager_OnException;
|
_logManager.OnException += _logManager_OnException;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void Setup(string loggingDirectory = null)
|
internal static bool DisableConsole { get; set; }
|
||||||
{
|
internal static bool IsLoggingDirectorySet => !string.IsNullOrWhiteSpace(LoggingDirectory);
|
||||||
LoggingDirectory = loggingDirectory;
|
internal static bool IsLoggingEnabled { get; set; }
|
||||||
_logManager.Settings.FileLoggerOptions.FileNamePrefix = "EonaCat.Network";
|
internal static string LoggingDirectory { get; private set; }
|
||||||
|
private static bool HasBeenSetup { get; set; }
|
||||||
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 void AddGrayLogServer(string hostname, int port)
|
internal static void AddGrayLogServer(string hostname, int port)
|
||||||
{
|
{
|
||||||
_logManager.Settings.GrayLogServers.Add(new GrayLogServer(hostname, 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)
|
internal static void AddSplunkServer(string splunkHecUrl, string splunkHecToken, bool disableSSL = false)
|
||||||
{
|
{
|
||||||
var splunkServer = new SplunkServer(splunkHecUrl, splunkHecToken);
|
var splunkServer = new SplunkServer(splunkHecUrl, splunkHecToken);
|
||||||
|
@ -91,56 +52,14 @@ namespace EonaCat.Network
|
||||||
_logManager.Settings.SplunkServers.Add(splunkServer);
|
_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)
|
internal static void AddSyslogServer(string ipAddress, int port)
|
||||||
{
|
{
|
||||||
_logManager.Settings.SysLogServers.Add(new SyslogServer(ipAddress, 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);
|
Write(message, ELogType.CRITICAL, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void Debug(string message, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
|
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)
|
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);
|
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
|
public class SSLConfigClient
|
||||||
{
|
{
|
||||||
private LocalCertificateSelectionCallback _clientCertSelectionCallback;
|
|
||||||
private X509CertificateCollection _clientCertificates;
|
private X509CertificateCollection _clientCertificates;
|
||||||
|
private LocalCertificateSelectionCallback _clientCertSelectionCallback;
|
||||||
private RemoteCertificateValidationCallback _serverCertValidationCallback;
|
private RemoteCertificateValidationCallback _serverCertValidationCallback;
|
||||||
|
|
||||||
public SSLConfigClient()
|
public SSLConfigClient()
|
||||||
{
|
{
|
||||||
SslProtocols = SslProtocols.Tls12;
|
SslProtocols = SslProtocols.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SSLConfigClient(string targetHost)
|
public SSLConfigClient(string targetHost)
|
||||||
{
|
{
|
||||||
TargetHost = targetHost;
|
TargetHost = targetHost;
|
||||||
SslProtocols = SslProtocols.Tls12;
|
SslProtocols = SslProtocols.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SSLConfigClient(SSLConfigClient sslConfig)
|
public SSLConfigClient(SSLConfigClient sslConfig)
|
||||||
|
@ -40,8 +40,6 @@ namespace EonaCat.Network
|
||||||
TargetHost = sslConfig.TargetHost;
|
TargetHost = sslConfig.TargetHost;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CheckForCertificateRevocation { get; set; }
|
|
||||||
|
|
||||||
public X509CertificateCollection Certificates
|
public X509CertificateCollection Certificates
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -56,6 +54,7 @@ namespace EonaCat.Network
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CheckForCertificateRevocation { get; set; }
|
||||||
public LocalCertificateSelectionCallback ClientCertificateSelectionCallback
|
public LocalCertificateSelectionCallback ClientCertificateSelectionCallback
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -71,8 +70,6 @@ namespace EonaCat.Network
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public SslProtocols SslProtocols { get; set; }
|
|
||||||
|
|
||||||
public RemoteCertificateValidationCallback ServerCertificateValidationCallback
|
public RemoteCertificateValidationCallback ServerCertificateValidationCallback
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -88,6 +85,7 @@ namespace EonaCat.Network
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SslProtocols SslProtocols { get; set; }
|
||||||
public string TargetHost { get; set; }
|
public string TargetHost { get; set; }
|
||||||
|
|
||||||
private static X509Certificate SelectClientCertificate(
|
private static X509Certificate SelectClientCertificate(
|
||||||
|
|
|
@ -14,13 +14,13 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
public SSLConfigServer()
|
public SSLConfigServer()
|
||||||
{
|
{
|
||||||
SslProtocols = SslProtocols.Tls12;
|
SslProtocols = SslProtocols.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SSLConfigServer(X509Certificate2 certificate)
|
public SSLConfigServer(X509Certificate2 certificate)
|
||||||
{
|
{
|
||||||
Certificate = certificate;
|
Certificate = certificate;
|
||||||
SslProtocols = SslProtocols.Tls12;
|
SslProtocols = SslProtocols.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SSLConfigServer(SSLConfigServer sslConfig)
|
public SSLConfigServer(SSLConfigServer sslConfig)
|
||||||
|
@ -37,10 +37,9 @@ namespace EonaCat.Network
|
||||||
Certificate = sslConfig.Certificate;
|
Certificate = sslConfig.Certificate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public X509Certificate2 Certificate { get; set; }
|
||||||
public bool CheckForCertificateRevocation { get; set; }
|
public bool CheckForCertificateRevocation { get; set; }
|
||||||
|
|
||||||
public bool IsClientCertificateRequired { get; set; }
|
|
||||||
|
|
||||||
public RemoteCertificateValidationCallback ClientCertificateValidationCallback
|
public RemoteCertificateValidationCallback ClientCertificateValidationCallback
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -56,10 +55,8 @@ namespace EonaCat.Network
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsClientCertificateRequired { get; set; }
|
||||||
public SslProtocols SslProtocols { get; set; }
|
public SslProtocols SslProtocols { get; set; }
|
||||||
|
|
||||||
public X509Certificate2 Certificate { get; set; }
|
|
||||||
|
|
||||||
private static bool ValidateClientCertificate(
|
private static bool ValidateClientCertificate(
|
||||||
object sender,
|
object sender,
|
||||||
X509Certificate certificate,
|
X509Certificate certificate,
|
||||||
|
|
|
@ -8,13 +8,12 @@ namespace EonaCat.Network
|
||||||
{
|
{
|
||||||
internal class RequestStream : Stream
|
internal class RequestStream : Stream
|
||||||
{
|
{
|
||||||
private long _bodyLeft;
|
|
||||||
private readonly byte[] _buffer;
|
private readonly byte[] _buffer;
|
||||||
|
private readonly Stream _stream;
|
||||||
|
private long _bodyLeft;
|
||||||
private int _count;
|
private int _count;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
private int _offset;
|
private int _offset;
|
||||||
private readonly Stream _stream;
|
|
||||||
|
|
||||||
internal RequestStream(Stream stream, byte[] buffer, int offset, int count)
|
internal RequestStream(Stream stream, byte[] buffer, int offset, int count)
|
||||||
: this(stream, buffer, offset, count, -1)
|
: 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(
|
public override IAsyncResult BeginRead(
|
||||||
byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||||
{
|
{
|
||||||
|
@ -233,5 +174,63 @@ namespace EonaCat.Network
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
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
|
internal class ResponseStream : Stream
|
||||||
{
|
{
|
||||||
private MemoryStream _body;
|
|
||||||
private static readonly byte[] _crlf = new byte[] { 13, 10 };
|
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 bool _disposed;
|
||||||
private HttpListenerResponse _response;
|
private HttpListenerResponse _response;
|
||||||
private bool _sendChunked;
|
private bool _sendChunked;
|
||||||
private Stream _stream;
|
private Stream _stream;
|
||||||
private readonly Action<byte[], int, int> _write;
|
|
||||||
private Action<byte[], int, int> _writeBody;
|
private Action<byte[], int, int> _writeBody;
|
||||||
private readonly Action<byte[], int, int> _writeChunked;
|
|
||||||
|
|
||||||
internal ResponseStream(
|
internal ResponseStream(
|
||||||
Stream stream, HttpListenerResponse response, bool ignoreWriteExceptions)
|
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)
|
private bool flush(bool closing)
|
||||||
{
|
{
|
||||||
if (!_response.HeadersSent)
|
if (!_response.HeadersSent)
|
||||||
|
@ -137,12 +251,6 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
return true;
|
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)
|
private void writeChunked(byte[] buffer, int offset, int count)
|
||||||
{
|
{
|
||||||
var size = getChunkSizeBytes(count, false);
|
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 ushort Code => PayloadData.Code;
|
||||||
|
|
||||||
public string Reason => PayloadData.Reason ?? string.Empty;
|
public string Reason => PayloadData.Reason ?? string.Empty;
|
||||||
|
|
||||||
public bool WasClean { get; internal set; }
|
public bool WasClean { get; internal set; }
|
||||||
|
internal PayloadData PayloadData { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,10 +7,9 @@ namespace EonaCat.Network
|
||||||
{
|
{
|
||||||
public class MessageEventArgs : EventArgs
|
public class MessageEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
|
private readonly byte[] _rawData;
|
||||||
private string _data;
|
private string _data;
|
||||||
private bool _dataSet;
|
private bool _dataSet;
|
||||||
private readonly byte[] _rawData;
|
|
||||||
|
|
||||||
internal MessageEventArgs(WSFrame frame)
|
internal MessageEventArgs(WSFrame frame)
|
||||||
{
|
{
|
||||||
Opcode = frame.Opcode;
|
Opcode = frame.Opcode;
|
||||||
|
@ -28,8 +27,6 @@ namespace EonaCat.Network
|
||||||
_rawData = rawData;
|
_rawData = rawData;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal OperationCode Opcode { get; }
|
|
||||||
|
|
||||||
public string Data
|
public string Data
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -40,11 +37,8 @@ namespace EonaCat.Network
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsBinary => Opcode == OperationCode.Binary;
|
public bool IsBinary => Opcode == OperationCode.Binary;
|
||||||
|
|
||||||
public bool IsPing => Opcode == OperationCode.Ping;
|
public bool IsPing => Opcode == OperationCode.Ping;
|
||||||
|
|
||||||
public bool IsText => Opcode == OperationCode.Text;
|
public bool IsText => Opcode == OperationCode.Text;
|
||||||
|
|
||||||
public byte[] RawData
|
public byte[] RawData
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -54,6 +48,7 @@ namespace EonaCat.Network
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal OperationCode Opcode { get; }
|
||||||
private void setData()
|
private void setData()
|
||||||
{
|
{
|
||||||
if (_dataSet)
|
if (_dataSet)
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,7 +5,7 @@ namespace EonaCat.Network
|
||||||
{
|
{
|
||||||
internal enum OperationCode : byte
|
internal enum OperationCode : byte
|
||||||
{
|
{
|
||||||
Cont = 0x0,
|
Continue = 0x0,
|
||||||
|
|
||||||
Text = 0x1,
|
Text = 0x1,
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,21 @@ namespace EonaCat.Network
|
||||||
_reasonSet = true;
|
_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)
|
internal PayloadData(byte[] data)
|
||||||
: this(data, data.LongLength)
|
: this(data, data.LongLength)
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,36 +27,6 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
public IPrincipal User => _context.User;
|
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)
|
public byte[] ReadFile(string path)
|
||||||
{
|
{
|
||||||
if (path == null)
|
if (path == null)
|
||||||
|
@ -98,5 +68,35 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
return tryReadFile(createFilePath(path), out contents);
|
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
|
public class HttpServer
|
||||||
{
|
{
|
||||||
|
private bool _allowForwardedRequest;
|
||||||
private string _docRootPath;
|
private string _docRootPath;
|
||||||
private string _hostname;
|
private string _hostname;
|
||||||
private HttpListener _listener;
|
private HttpListener _listener;
|
||||||
private Thread _receiveThread;
|
private Thread _receiveThread;
|
||||||
private volatile ServerState _state;
|
private volatile ServerState _state;
|
||||||
private object _sync;
|
private object _sync;
|
||||||
|
|
||||||
public HttpServer()
|
public HttpServer()
|
||||||
{
|
{
|
||||||
init("*", System.Net.IPAddress.Any, 80, false);
|
init("*", System.Net.IPAddress.Any, 80, false);
|
||||||
|
@ -100,8 +100,49 @@ namespace EonaCat.Network
|
||||||
init(address.ToString(true), address, port, secure);
|
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; }
|
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
|
public AuthenticationSchemes AuthenticationSchemes
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -208,6 +249,7 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
public bool IsListening => _state == ServerState.Start;
|
public bool IsListening => _state == ServerState.Start;
|
||||||
|
|
||||||
|
public bool IsLoggingEnabled { get; private set; }
|
||||||
public bool IsSecure { get; private set; }
|
public bool IsSecure { get; private set; }
|
||||||
|
|
||||||
public bool KeepClean
|
public bool KeepClean
|
||||||
|
@ -222,11 +264,7 @@ namespace EonaCat.Network
|
||||||
WebSocketServices.AutoCleanSessions = value;
|
WebSocketServices.AutoCleanSessions = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsLoggingEnabled { get; private set; }
|
|
||||||
|
|
||||||
public int Port { get; private set; }
|
public int Port { get; private set; }
|
||||||
|
|
||||||
public string Realm
|
public string Realm
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -339,24 +377,174 @@ namespace EonaCat.Network
|
||||||
}
|
}
|
||||||
|
|
||||||
public WSEndpointManager WebSocketServices { get; private set; }
|
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()
|
private void abort()
|
||||||
{
|
{
|
||||||
|
@ -432,20 +620,6 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
return true;
|
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(
|
private void init(
|
||||||
string hostname, System.Net.IPAddress address, int port, bool secure
|
string hostname, System.Net.IPAddress address, int port, bool secure
|
||||||
)
|
)
|
||||||
|
@ -703,161 +877,5 @@ namespace EonaCat.Network
|
||||||
_listener.Stop();
|
_listener.Stop();
|
||||||
_receiveThread.Join(millisecondsTimeout);
|
_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;
|
StartTime = DateTime.MaxValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected WSSessionManager Sessions { get; private set; }
|
|
||||||
|
|
||||||
public WSContext Context { get; private set; }
|
public WSContext Context { get; private set; }
|
||||||
|
|
||||||
public Func<CookieCollection, CookieCollection, bool> CookiesValidator { get; set; }
|
public Func<CookieCollection, CookieCollection, bool> CookiesValidator { get; set; }
|
||||||
|
|
||||||
public bool EmitOnPing
|
public bool EmitOnPing
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -43,11 +39,8 @@ namespace EonaCat.Network
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ID { get; private set; }
|
public string ID { get; private set; }
|
||||||
|
|
||||||
public bool IgnoreExtensions { get; set; }
|
public bool IgnoreExtensions { get; set; }
|
||||||
|
|
||||||
public Func<string, bool> OriginValidator { get; set; }
|
public Func<string, bool> OriginValidator { get; set; }
|
||||||
|
|
||||||
public string Protocol
|
public string Protocol
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -72,53 +65,8 @@ namespace EonaCat.Network
|
||||||
}
|
}
|
||||||
|
|
||||||
public DateTime StartTime { get; private set; }
|
public DateTime StartTime { get; private set; }
|
||||||
|
|
||||||
public WSState State => _websocket != null ? _websocket.ReadyState : WSState.Connecting;
|
public WSState State => _websocket != null ? _websocket.ReadyState : WSState.Connecting;
|
||||||
|
protected WSSessionManager Sessions { get; private set; }
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Start(WSContext context, WSSessionManager sessions)
|
internal void Start(WSContext context, WSSessionManager sessions)
|
||||||
{
|
{
|
||||||
if (_websocket != null)
|
if (_websocket != null)
|
||||||
|
@ -210,5 +158,49 @@ namespace EonaCat.Network
|
||||||
{
|
{
|
||||||
_websocket?.SendAsync(stream, length, completed);
|
_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();
|
Sessions = new WSSessionManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ServerState State => Sessions.State;
|
public abstract Type EndpointType { get; }
|
||||||
|
|
||||||
public bool KeepClean
|
public bool KeepClean
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -29,11 +28,7 @@ namespace EonaCat.Network
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Path { get; }
|
public string Path { get; }
|
||||||
|
|
||||||
public WSSessionManager Sessions { get; }
|
public WSSessionManager Sessions { get; }
|
||||||
|
|
||||||
public abstract Type EndpointType { get; }
|
|
||||||
|
|
||||||
public TimeSpan WaitTime
|
public TimeSpan WaitTime
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -47,6 +42,7 @@ namespace EonaCat.Network
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal ServerState State => Sessions.State;
|
||||||
internal void Start()
|
internal void Start()
|
||||||
{
|
{
|
||||||
Sessions.Start();
|
Sessions.Start();
|
||||||
|
|
|
@ -11,10 +11,10 @@ namespace EonaCat.Network
|
||||||
{
|
{
|
||||||
public class WSEndpointManager
|
public class WSEndpointManager
|
||||||
{
|
{
|
||||||
private volatile bool _clean;
|
|
||||||
private readonly Dictionary<string, WSEndpointHost> _hosts;
|
private readonly Dictionary<string, WSEndpointHost> _hosts;
|
||||||
private volatile ServerState _state;
|
|
||||||
private readonly object _sync;
|
private readonly object _sync;
|
||||||
|
private volatile bool _clean;
|
||||||
|
private volatile ServerState _state;
|
||||||
private TimeSpan _waitTime;
|
private TimeSpan _waitTime;
|
||||||
|
|
||||||
internal WSEndpointManager()
|
internal WSEndpointManager()
|
||||||
|
@ -26,59 +26,6 @@ namespace EonaCat.Network
|
||||||
_waitTime = TimeSpan.FromSeconds(1);
|
_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
|
public bool AutoCleanSessions
|
||||||
{
|
{
|
||||||
get
|
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
|
public IEnumerable<string> Paths
|
||||||
{
|
{
|
||||||
get
|
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[]>();
|
get
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
foreach (var host in Hosts)
|
if (path == null)
|
||||||
{
|
{
|
||||||
if (_state != ServerState.Start)
|
throw new ArgumentNullException(nameof(path));
|
||||||
{
|
|
||||||
Logger.Error("The server is shutting down.");
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
throw new ArgumentException("Not an absolute path.", nameof(path));
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.Error(ex, "Could not broadcast");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
cache.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void broadcast(OperationCode opcode, Stream stream, Action completed)
|
if (path.IndexOfAny(new[] { '?', '#' }) > -1)
|
||||||
{
|
{
|
||||||
var cache = new Dictionary<CompressionMethod, Stream>();
|
var message = "It includes either or both query and fragment components.";
|
||||||
|
throw new ArgumentException(message, nameof(path));
|
||||||
try
|
|
||||||
{
|
|
||||||
foreach (var host in Hosts)
|
|
||||||
{
|
|
||||||
if (_state != ServerState.Start)
|
|
||||||
{
|
|
||||||
Logger.Error("The server is shutting down.");
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
host.Sessions.Broadcast(opcode, stream, cache);
|
InternalTryGetServiceHost(path, out WSEndpointHost host);
|
||||||
}
|
|
||||||
|
|
||||||
if (completed != null)
|
return host;
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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>(
|
public void AddService<TEndpoint>(
|
||||||
string path, Action<TEndpoint> initializer
|
string path, Action<TEndpoint> initializer
|
||||||
)
|
)
|
||||||
|
@ -470,5 +306,168 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
return InternalTryGetServiceHost(path, out host);
|
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 class WSServer
|
||||||
{
|
{
|
||||||
public bool IsConsoleLoggingEnabled { get; set; }
|
private static readonly string _defaultRealm;
|
||||||
public bool IsLoggingEnabled { get; set; }
|
|
||||||
private bool _allowForwardedRequest;
|
private bool _allowForwardedRequest;
|
||||||
private AuthenticationSchemes _authSchemes;
|
private AuthenticationSchemes _authSchemes;
|
||||||
private static readonly string _defaultRealm;
|
|
||||||
private bool _dnsStyle;
|
private bool _dnsStyle;
|
||||||
private string _hostname;
|
private string _hostname;
|
||||||
private TcpListener _listener;
|
private TcpListener _listener;
|
||||||
|
@ -27,7 +25,6 @@ namespace EonaCat.Network
|
||||||
private volatile ServerState _state;
|
private volatile ServerState _state;
|
||||||
private object _sync;
|
private object _sync;
|
||||||
private Func<IIdentity, NetworkCredential> _userCredentialsFinder;
|
private Func<IIdentity, NetworkCredential> _userCredentialsFinder;
|
||||||
|
|
||||||
static WSServer()
|
static WSServer()
|
||||||
{
|
{
|
||||||
_defaultRealm = "SECRET AREA";
|
_defaultRealm = "SECRET AREA";
|
||||||
|
@ -35,8 +32,8 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
public WSServer()
|
public WSServer()
|
||||||
{
|
{
|
||||||
var addr = System.Net.IPAddress.Any;
|
var address = System.Net.IPAddress.Any;
|
||||||
init(addr.ToString(), addr, 80, false);
|
init(address.ToString(), address, 80, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public WSServer(int port)
|
public WSServer(int port)
|
||||||
|
@ -118,7 +115,6 @@ namespace EonaCat.Network
|
||||||
}
|
}
|
||||||
|
|
||||||
public System.Net.IPAddress Address { get; private set; }
|
public System.Net.IPAddress Address { get; private set; }
|
||||||
|
|
||||||
public bool AllowForwardedRequest
|
public bool AllowForwardedRequest
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -175,10 +171,6 @@ namespace EonaCat.Network
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsListening => _state == ServerState.Start;
|
|
||||||
|
|
||||||
public bool IsSecure { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines if sessions need to be removed automatically
|
/// Determines if sessions need to be removed automatically
|
||||||
/// </summary>
|
/// </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 int Port { get; private set; }
|
||||||
|
|
||||||
public string Realm
|
public string Realm
|
||||||
|
@ -266,35 +291,6 @@ namespace EonaCat.Network
|
||||||
return GetSSLConfig();
|
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
|
public TimeSpan WaitTime
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -307,8 +303,146 @@ namespace EonaCat.Network
|
||||||
Endpoints.WaitTime = value;
|
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()
|
private void abort()
|
||||||
{
|
{
|
||||||
|
@ -365,21 +499,6 @@ namespace EonaCat.Network
|
||||||
|| Uri.CheckHostName(name) != UriHostNameType.Dns
|
|| Uri.CheckHostName(name) != UriHostNameType.Dns
|
||||||
|| name == _hostname;
|
|| 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()
|
private string GetRealm()
|
||||||
{
|
{
|
||||||
var realm = _realm;
|
var realm = _realm;
|
||||||
|
@ -662,132 +781,5 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
_receiveThread.Join(millisecondsTimeout);
|
_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
|
public class WSSessionManager
|
||||||
{
|
{
|
||||||
private volatile bool _clean;
|
|
||||||
private readonly object _forSweep;
|
private readonly object _forSweep;
|
||||||
private readonly Dictionary<string, IWSSession> _sessions;
|
private readonly Dictionary<string, IWSSession> _sessions;
|
||||||
|
private readonly object _sync;
|
||||||
|
private volatile bool _clean;
|
||||||
private volatile ServerState _state;
|
private volatile ServerState _state;
|
||||||
private volatile bool _sweeping;
|
private volatile bool _sweeping;
|
||||||
private System.Timers.Timer _sweepTimer;
|
private System.Timers.Timer _sweepTimer;
|
||||||
private readonly object _sync;
|
|
||||||
private TimeSpan _waitTime;
|
private TimeSpan _waitTime;
|
||||||
|
|
||||||
internal WSSessionManager()
|
internal WSSessionManager()
|
||||||
|
@ -33,8 +33,6 @@ namespace EonaCat.Network
|
||||||
setSweepTimer(60000);
|
setSweepTimer(60000);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ServerState State => _state;
|
|
||||||
|
|
||||||
public IEnumerable<string> ActiveIDs
|
public IEnumerable<string> ActiveIDs
|
||||||
{
|
{
|
||||||
get
|
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
|
public bool KeepClean
|
||||||
{
|
{
|
||||||
get
|
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[]>();
|
get
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
foreach (var session in Sessions)
|
if (id == null)
|
||||||
{
|
{
|
||||||
if (_state != ServerState.Start)
|
throw new ArgumentNullException(nameof(id));
|
||||||
{
|
|
||||||
Logger.Error("The service is shutting down.");
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
session.Context.WebSocket.Send(opcode, data, cache);
|
if (id.Length == 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("An empty string.", nameof(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (completed != null)
|
tryGetSession(id, out IWSSession session);
|
||||||
{
|
|
||||||
completed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.Error(ex.Message);
|
|
||||||
Logger.Debug(ex.ToString());
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
cache.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void broadcast(OperationCode opcode, Stream stream, Action completed)
|
return session;
|
||||||
{
|
|
||||||
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 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)
|
public void Broadcast(byte[] data)
|
||||||
{
|
{
|
||||||
if (_state != ServerState.Start)
|
if (_state != ServerState.Start)
|
||||||
|
@ -872,5 +599,275 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
return tryGetSession(id, out session);
|
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);
|
public override Type EndpointType => typeof(TEndpoint);
|
||||||
|
|
||||||
|
protected override WSEndpoint CreateSession()
|
||||||
|
{
|
||||||
|
return _creator();
|
||||||
|
}
|
||||||
|
|
||||||
private Func<TEndpoint> createCreator(
|
private Func<TEndpoint> createCreator(
|
||||||
Func<TEndpoint> creator, Action<TEndpoint> initializer
|
Func<TEndpoint> creator, Action<TEndpoint> initializer
|
||||||
)
|
)
|
||||||
|
@ -46,10 +51,5 @@ namespace EonaCat.Network
|
||||||
return ret;
|
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>
|
internal class WSFrame : IEnumerable<byte>
|
||||||
{
|
{
|
||||||
private const int BUFFER_SIZE = 1024;
|
|
||||||
|
|
||||||
internal static readonly byte[] EmptyPingBytes;
|
internal static readonly byte[] EmptyPingBytes;
|
||||||
|
private const int BUFFER_SIZE = 1024;
|
||||||
static WSFrame()
|
static WSFrame()
|
||||||
{
|
{
|
||||||
EmptyPingBytes = CreatePingFrame(false).ToArray();
|
EmptyPingBytes = CreatePingFrame(false).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private WSFrame()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
internal WSFrame(OperationCode opcode, PayloadData payloadData, bool mask)
|
internal WSFrame(OperationCode opcode, PayloadData payloadData, bool mask)
|
||||||
: this(FinalFrame.Final, opcode, payloadData, false, mask)
|
: this(FinalFrame.Final, opcode, payloadData, false, mask)
|
||||||
{
|
{
|
||||||
|
@ -42,8 +36,9 @@ namespace EonaCat.Network
|
||||||
Rsv2 = Rsv.Off;
|
Rsv2 = Rsv.Off;
|
||||||
Rsv3 = Rsv.Off;
|
Rsv3 = Rsv.Off;
|
||||||
Opcode = opcode;
|
Opcode = opcode;
|
||||||
|
PayloadData = new PayloadData(payloadData);
|
||||||
|
|
||||||
var len = payloadData.Length;
|
var len = PayloadData.Length;
|
||||||
if (len < 126)
|
if (len < 126)
|
||||||
{
|
{
|
||||||
PayloadLength = (byte)len;
|
PayloadLength = (byte)len;
|
||||||
|
@ -64,17 +59,41 @@ namespace EonaCat.Network
|
||||||
{
|
{
|
||||||
Mask = Mask.On;
|
Mask = Mask.On;
|
||||||
MaskingKey = createMaskingKey();
|
MaskingKey = createMaskingKey();
|
||||||
payloadData.Mask(MaskingKey);
|
PayloadData.Mask(MaskingKey);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Mask = Mask.Off;
|
Mask = Mask.Off;
|
||||||
MaskingKey = WSClient.EmptyBytes;
|
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 int ExtendedPayloadLengthCount => PayloadLength < 126 ? 0 : (PayloadLength == 126 ? 2 : 8);
|
||||||
|
|
||||||
internal ulong FullPayloadLength => PayloadLength < 126
|
internal ulong FullPayloadLength => PayloadLength < 126
|
||||||
|
@ -82,52 +101,173 @@ namespace EonaCat.Network
|
||||||
: PayloadLength == 126
|
: PayloadLength == 126
|
||||||
? ExtendedPayloadLength.ToUInt16(ByteOrder.Big)
|
? ExtendedPayloadLength.ToUInt16(ByteOrder.Big)
|
||||||
: ExtendedPayloadLength.ToUInt64(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; }
|
Mask = Mask.Off;
|
||||||
|
PayloadData.Mask(MaskingKey);
|
||||||
public Rsv Rsv1 { get; private set; }
|
MaskingKey = WSClient.EmptyBytes;
|
||||||
|
}
|
||||||
public Rsv Rsv2 { get; private set; }
|
|
||||||
|
|
||||||
public Rsv Rsv3 { get; private set; }
|
|
||||||
|
|
||||||
private static byte[] createMaskingKey()
|
private static byte[] createMaskingKey()
|
||||||
{
|
{
|
||||||
|
@ -496,173 +636,5 @@ Extended Payload Length: {7}
|
||||||
|
|
||||||
stream.ReadBytesAsync(llen, BUFFER_SIZE, compl, error);
|
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
|
internal abstract class WebBase
|
||||||
{
|
{
|
||||||
private const int _headersMaxLength = 8192;
|
|
||||||
internal byte[] EntityBodyData;
|
internal byte[] EntityBodyData;
|
||||||
|
|
||||||
protected const string CrLf = "\r\n";
|
protected const string CrLf = "\r\n";
|
||||||
|
private const int _headersMaxLength = 8192;
|
||||||
protected WebBase(Version version, NameValueCollection headers)
|
protected WebBase(Version version, NameValueCollection headers)
|
||||||
{
|
{
|
||||||
ProtocolVersion = version;
|
ProtocolVersion = version;
|
||||||
|
@ -48,6 +46,60 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
public Version ProtocolVersion { get; }
|
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)
|
private static byte[] readEntityBody(Stream stream, string length)
|
||||||
{
|
{
|
||||||
if (!long.TryParse(length, out long len))
|
if (!long.TryParse(length, out long len))
|
||||||
|
@ -105,59 +157,5 @@ namespace EonaCat.Network
|
||||||
.Replace(CrLf + "\t", " ")
|
.Replace(CrLf + "\t", " ")
|
||||||
.Split(new[] { CrLf }, StringSplitOptions.RemoveEmptyEntries);
|
.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 _websocketRequest;
|
||||||
private bool _websocketRequestSet;
|
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)
|
internal WebRequest(string method, string uri)
|
||||||
: this(method, uri, HttpVersion.Version11, new NameValueCollection())
|
: this(method, uri, HttpVersion.Version11, new NameValueCollection())
|
||||||
{
|
{
|
||||||
Headers["User-Agent"] = $"EonaCat.Network/{Constants.Version}";
|
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
|
public AuthenticationResponse AuthenticationResponse
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -63,67 +62,6 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
public string RequestUri { get; }
|
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)
|
public void SetCookies(CookieCollection cookies)
|
||||||
{
|
{
|
||||||
if (cookies == null || cookies.Count == 0)
|
if (cookies == null || cookies.Count == 0)
|
||||||
|
@ -169,5 +107,66 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
return output.ToString();
|
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
|
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)
|
internal WebResponse(HttpStatusCode code)
|
||||||
: this(code, code.GetDescription())
|
: this(code, code.GetDescription())
|
||||||
{
|
{
|
||||||
|
@ -29,6 +22,12 @@ namespace EonaCat.Network
|
||||||
Headers["Server"] = $"EonaCat.Network/{Constants.Version}";
|
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 CookieCollection Cookies => Headers.GetCookies(true);
|
||||||
|
|
||||||
public bool HasConnectionClose => Headers.Contains("Connection", "close");
|
public bool HasConnectionClose => Headers.Contains("Connection", "close");
|
||||||
|
@ -55,6 +54,42 @@ namespace EonaCat.Network
|
||||||
|
|
||||||
public string StatusCode { get; }
|
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)
|
internal static WebResponse CreateCloseResponse(HttpStatusCode code)
|
||||||
{
|
{
|
||||||
var res = new WebResponse(code);
|
var res = new WebResponse(code);
|
||||||
|
@ -104,41 +139,5 @@ namespace EonaCat.Network
|
||||||
{
|
{
|
||||||
return Read(stream, Parse, millisecondsTimeout);
|
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