diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f88d3e7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# Default setting +* auto + +# Do nothing +*.sln -text diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6413b38..57a69a5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,8 +17,8 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: | + 8.0 6.0.102 - 3.1.101 - name: Install dependencies run: dotnet restore - name: Build diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml index 96ba277..b30ed37 100644 --- a/.github/workflows/nuget.yml +++ b/.github/workflows/nuget.yml @@ -10,17 +10,17 @@ jobs: steps: - uses: actions/checkout@v2 - + - name: Init git submodules run: git submodule init && git submodule update - + - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: 6.0.102 + dotnet-version: '8.0' - name: Install dependencies run: dotnet restore - + - name: Publish MaxLib.WebServer NuGet uses: brandedoutcast/publish-nuget@v2.5.5 with: diff --git a/MaxLib.WebServer.Test/MaxLib.WebServer.Test.csproj b/MaxLib.WebServer.Test/MaxLib.WebServer.Test.csproj deleted file mode 100644 index 18e63ed..0000000 --- a/MaxLib.WebServer.Test/MaxLib.WebServer.Test.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - netcoreapp3.1 - - false - - - - - - - - - - - - - - diff --git a/MaxLib.WebServer.sln b/MaxLib.WebServer.sln index d610ccd..4533fcc 100644 --- a/MaxLib.WebServer.sln +++ b/MaxLib.WebServer.sln @@ -5,13 +5,13 @@ VisualStudioVersion = 16.0.30717.126 MinimumVisualStudioVersion = 15.0.26124.0 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MaxLib.WebServer", "MaxLib.WebServer\MaxLib.WebServer.csproj", "{595DB1BF-246A-4A42-99EB-5DAB7709E21E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MaxLib.WebServer.Test", "MaxLib.WebServer.Test\MaxLib.WebServer.Test.csproj", "{60225D92-5742-4BC0-A3A5-206123F2129D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MaxLib.WebServer.Test", "tests\MaxLib.WebServer.Test\MaxLib.WebServer.Test.csproj", "{60225D92-5742-4BC0-A3A5-206123F2129D}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MaxLib.WebServer.Example", "example\MaxLib.WebServer.Example\MaxLib.WebServer.Example.csproj", "{6616448A-1AD7-4897-9124-F1560FB12461}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Example", "Example", "{39740782-6F07-470C-92B8-C0A07A5C0DFB}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MaxLib.WebServer.WebSocket.Echo", "MaxLib.WebServer.WebSocket.Echo\MaxLib.WebServer.WebSocket.Echo.csproj", "{01C302FF-8E99-4D54-8234-D4BA59862176}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MaxLib.WebServer.WebSocket.Echo", "example\MaxLib.WebServer.WebSocket.Echo\MaxLib.WebServer.WebSocket.Echo.csproj", "{01C302FF-8E99-4D54-8234-D4BA59862176}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{29467014-E04B-477A-82B7-16A3BAE7B5A6}" ProjectSection(SolutionItems) = preProject diff --git a/MaxLib.WebServer/Api/ApiService.cs b/MaxLib.WebServer/Api/ApiService.cs index 027dcb9..3c727fb 100644 --- a/MaxLib.WebServer/Api/ApiService.cs +++ b/MaxLib.WebServer/Api/ApiService.cs @@ -5,6 +5,7 @@ namespace MaxLib.WebServer.Api { + [Obsolete("The ApiService and the RestApiService classes are no longer maintained and will be removed in a future update. Use the Builder system instead.")] public abstract class ApiService : WebService { public ApiService(params string[] endpoint) diff --git a/MaxLib.WebServer/Api/Rest/ApiGetRule.cs b/MaxLib.WebServer/Api/Rest/ApiGetRule.cs index 2c933af..08ee9d4 100644 --- a/MaxLib.WebServer/Api/Rest/ApiGetRule.cs +++ b/MaxLib.WebServer/Api/Rest/ApiGetRule.cs @@ -4,6 +4,7 @@ namespace MaxLib.WebServer.Api.Rest { + [Obsolete("The ApiService and the RestApiService classes are no longer maintained and will be removed in a future update. Use the Builder system instead.")] public abstract class ApiGetRule : ApiRule { private string? key; diff --git a/MaxLib.WebServer/Api/Rest/ApiLocationRule.cs b/MaxLib.WebServer/Api/Rest/ApiLocationRule.cs index 19a99b4..55990e5 100644 --- a/MaxLib.WebServer/Api/Rest/ApiLocationRule.cs +++ b/MaxLib.WebServer/Api/Rest/ApiLocationRule.cs @@ -4,6 +4,7 @@ namespace MaxLib.WebServer.Api.Rest { + [Obsolete("The ApiService and the RestApiService classes are no longer maintained and will be removed in a future update. Use the Builder system instead.")] public abstract class ApiLocationRule : ApiRule { private int index = 0; diff --git a/MaxLib.WebServer/Api/Rest/ApiRule.cs b/MaxLib.WebServer/Api/Rest/ApiRule.cs index f89bd21..b10341d 100644 --- a/MaxLib.WebServer/Api/Rest/ApiRule.cs +++ b/MaxLib.WebServer/Api/Rest/ApiRule.cs @@ -1,7 +1,10 @@ #nullable enable +using System; + namespace MaxLib.WebServer.Api.Rest { + [Obsolete("The ApiService and the RestApiService classes are no longer maintained and will be removed in a future update. Use the Builder system instead.")] public abstract class ApiRule { public bool Required { get; set; } = true; diff --git a/MaxLib.WebServer/Api/Rest/ApiRuleFactory.cs b/MaxLib.WebServer/Api/Rest/ApiRuleFactory.cs index 83f4871..dff097e 100644 --- a/MaxLib.WebServer/Api/Rest/ApiRuleFactory.cs +++ b/MaxLib.WebServer/Api/Rest/ApiRuleFactory.cs @@ -6,6 +6,7 @@ namespace MaxLib.WebServer.Api.Rest { + [Obsolete("The ApiService and the RestApiService classes are no longer maintained and will be removed in a future update. Use the Builder system instead.")] public class ApiRuleFactory { public class HostRule : ApiRule diff --git a/MaxLib.WebServer/Api/Rest/RestActionEndpoint.cs b/MaxLib.WebServer/Api/Rest/RestActionEndpoint.cs index 268e6dc..c52e9a4 100644 --- a/MaxLib.WebServer/Api/Rest/RestActionEndpoint.cs +++ b/MaxLib.WebServer/Api/Rest/RestActionEndpoint.cs @@ -9,6 +9,7 @@ namespace MaxLib.WebServer.Api.Rest { + [Obsolete("The ApiService and the RestApiService classes are no longer maintained and will be removed in a future update. Use the Builder system instead.")] public class RestActionEndpoint : RestEndpoint { public Func, Task> HandleRequest { get; set; } diff --git a/MaxLib.WebServer/Api/Rest/RestApiService.cs b/MaxLib.WebServer/Api/Rest/RestApiService.cs index 870f56e..b4e5e98 100644 --- a/MaxLib.WebServer/Api/Rest/RestApiService.cs +++ b/MaxLib.WebServer/Api/Rest/RestApiService.cs @@ -6,6 +6,7 @@ namespace MaxLib.WebServer.Api.Rest { + [Obsolete("The ApiService and the RestApiService classes are no longer maintained and will be removed in a future update. Use the Builder system instead.")] public class RestApiService : ApiService { public List RestEndpoints { get; } = new List(); diff --git a/MaxLib.WebServer/Api/Rest/RestEndpoint.cs b/MaxLib.WebServer/Api/Rest/RestEndpoint.cs index b6fd58f..d6d2d43 100644 --- a/MaxLib.WebServer/Api/Rest/RestEndpoint.cs +++ b/MaxLib.WebServer/Api/Rest/RestEndpoint.cs @@ -8,6 +8,7 @@ namespace MaxLib.WebServer.Api.Rest { + [Obsolete("The ApiService and the RestApiService classes are no longer maintained and will be removed in a future update. Use the Builder system instead.")] public class RestEndpoint { readonly private ApiRuleFactory.GroupRule rules = new ApiRuleFactory.GroupRule(); diff --git a/MaxLib.WebServer/Api/Rest/RestQueryArgs.cs b/MaxLib.WebServer/Api/Rest/RestQueryArgs.cs index 306f21d..5755c38 100644 --- a/MaxLib.WebServer/Api/Rest/RestQueryArgs.cs +++ b/MaxLib.WebServer/Api/Rest/RestQueryArgs.cs @@ -5,6 +5,7 @@ namespace MaxLib.WebServer.Api.Rest { + [Obsolete("The ApiService and the RestApiService classes are no longer maintained and will be removed in a future update. Use the Builder system instead.")] public class RestQueryArgs { public string[] Location { get; } diff --git a/MaxLib.WebServer/Chunked/HttpChunkedStream.cs b/MaxLib.WebServer/Chunked/HttpChunkedStream.cs index b79581a..00daf83 100644 --- a/MaxLib.WebServer/Chunked/HttpChunkedStream.cs +++ b/MaxLib.WebServer/Chunked/HttpChunkedStream.cs @@ -23,11 +23,6 @@ public HttpChunkedStream(Stream baseStream, int readBufferLength = 0x8000) public int ReadBufferLength { get; } - [Obsolete] - public override bool CanAcceptData => BaseStream.CanWrite; - - public override bool CanProvideData => BaseStream.CanRead; - public override long? Length() => null; public override void Dispose() @@ -35,41 +30,26 @@ public override void Dispose() BaseStream.Dispose(); } - protected override async Task WriteStreamInternal(Stream stream, long start, long? stop) + protected override async Task WriteStreamInternal(Stream stream) { long total = 0; - int readed; - byte[] buffer = new byte[ReadBufferLength]; - do - { - readed = await BaseStream.ReadAsync( - buffer, - 0, - (int)Math.Min(buffer.Length, start - total) - ).ConfigureAwait(false); - total += readed; - } - while (total < start && readed > 0); - if (readed == 0 && start > 0) - return 0; + int read; + Memory buffer = new byte[ReadBufferLength]; var ascii = Encoding.ASCII; - var nl = ascii.GetBytes("\r\n"); + ReadOnlyMemory nl = ascii.GetBytes("\r\n"); do { - var read = stop == null - ? buffer.Length - : (int)Math.Min(buffer.Length, stop.Value - total); - readed = await BaseStream.ReadAsync(buffer, 0, read).ConfigureAwait(false); - if (readed <= 0) - return total - start; - var length = ascii.GetBytes(readed.ToString("X")); + read = await BaseStream.ReadAsync(buffer).ConfigureAwait(false); + if (read <= 0) + return total; + ReadOnlyMemory length = ascii.GetBytes(read.ToString("X")); try { - await stream.WriteAsync(length, 0, length.Length).ConfigureAwait(false); - await stream.WriteAsync(nl, 0, nl.Length).ConfigureAwait(false); - await stream.WriteAsync(buffer, 0, readed).ConfigureAwait(false); - await stream.WriteAsync(nl, 0, nl.Length).ConfigureAwait(false); - total += readed; + await stream.WriteAsync(length).ConfigureAwait(false); + await stream.WriteAsync(nl).ConfigureAwait(false); + await stream.WriteAsync(buffer[0..read]).ConfigureAwait(false); + await stream.WriteAsync(nl).ConfigureAwait(false); + total += read; await stream.FlushAsync().ConfigureAwait(false); } catch (IOException) @@ -78,87 +58,8 @@ protected override async Task WriteStreamInternal(Stream stream, long star return total; } } - while (readed > 0); - return total - start; - } - - [Obsolete] - protected override async Task ReadStreamInternal(Stream stream, long? length) - { - long total = 0; - int readed, numberLength; - var ascii = Encoding.ASCII; - var buffer = new byte[0x10000]; - while (true) - { - try { numberLength = await ReadNumber(stream, buffer).ConfigureAwait(false); } - catch (IOException) - { - WebServerLog.Add(ServerLogType.Information, GetType(), "read", "connection closed"); - return total; - } - if (numberLength == 0) - return total; - var numberString = ascii.GetString(buffer, 0, numberLength); - if (!long.TryParse(numberString, - NumberStyles.HexNumber, - CultureInfo.InvariantCulture.NumberFormat, - out long number) || number < 0) - { - WebServerLog.Add(ServerLogType.Information, GetType(), "read", "invalid number of bytes indicator"); - return total; - } - while (number > 0) - { - try { readed = await stream.ReadAsync(buffer, 0, (int)Math.Min(number, buffer.Length)).ConfigureAwait(false); } - catch (IOException) - { - WebServerLog.Add(ServerLogType.Information, GetType(), "read", "connection closed"); - return total; - } - if (readed == 0) - { - WebServerLog.Add(ServerLogType.Information, GetType(), "read", "could not read the block completly"); - return total; - } - await BaseStream.WriteAsync(buffer, 0, readed).ConfigureAwait(false); - total += readed; - number -= readed; - } - try - { - readed = await stream.ReadAsync(buffer, 0, 1).ConfigureAwait(false); - if (readed > 0 && buffer[0] == '\r') - readed = await stream.ReadAsync(buffer, 0, 1).ConfigureAwait(false); - if (readed == 0) - return total; - } - catch (IOException) - { - WebServerLog.Add(ServerLogType.Information, GetType(), "read", "connection closed"); - return total; - } - } - } - - private async Task ReadNumber(Stream stream, byte[] buffer) - { - int offset = 0; - var byteBuffer = new byte[1]; - while (true) - { - int readed = await stream.ReadAsync(byteBuffer, 0, 1).ConfigureAwait(false); - if (readed == 0) - return offset; - if (byteBuffer[0] == '\r' || byteBuffer[0] == '\n') - { - if (byteBuffer[0] == '\r') - await stream.ReadAsync(byteBuffer, 0, 1).ConfigureAwait(false); - return offset; - } - buffer[offset] = byteBuffer[0]; - offset++; - } + while (read > 0); + return total; } } } diff --git a/MaxLib.WebServer/HttpDataSource.cs b/MaxLib.WebServer/HttpDataSource.cs index 6698d52..e8c45b7 100644 --- a/MaxLib.WebServer/HttpDataSource.cs +++ b/MaxLib.WebServer/HttpDataSource.cs @@ -26,111 +26,18 @@ public virtual string MimeType } } - [Obsolete("HttpDataSource become readonly in a future release")] - public abstract bool CanAcceptData { get; } - - public abstract bool CanProvideData { get; } - - protected abstract Task WriteStreamInternal(Stream stream, long start, long? stop); - - - [Obsolete("HttpDataSource become readonly in a future release")] - protected abstract Task ReadStreamInternal(Stream stream, long? length); + protected abstract Task WriteStreamInternal(Stream stream); /// - /// Write its content to . - /// - /// the stream to write the data into - /// the first own byte (inclusive) that should have been written - /// the last own byte (excklusive) that should have been written or null to write all bytes till the end - /// the effective number of bytes written to the stream - public async Task WriteStream(Stream stream, long start, long? stop) - { - _ = stream ?? throw new ArgumentNullException(nameof(stream)); - if (start < 0) throw new ArgumentOutOfRangeException(nameof(start)); - var length = Length(); - if (length != null && start > length.Value) - throw new ArgumentOutOfRangeException(nameof(start)); - if (stop != null && stop < start) throw new ArgumentOutOfRangeException(nameof(stop)); - return await WriteStreamInternal(stream, start, stop).ConfigureAwait(false); - } - - /// - /// Write its content to . It will start at - /// (inclusive) and write until (exclusive). If - /// is null it will write all bytes till the end. + /// Write the whole content to . If you want to have a partial + /// content use as a wrapper. /// /// the stream to write the data into /// the effective number of bytes written to the stream public async Task WriteStream(Stream stream) -#pragma warning disable CS0618 - => await WriteStream(stream ?? throw new ArgumentNullException(nameof(stream)), RangeStart, RangeEnd).ConfigureAwait(false); -#pragma warning restore CS0618 - - /// - /// Read the data of and replace its own data with it. - /// - /// the stream to read the data from - /// the number of bytes that should been readed. null to read all bytes. - /// the number of bytes readed from the stream - [Obsolete("HttpDataSource become readonly in a future release")] - public async Task ReadStream(Stream stream, long? length = null) { _ = stream ?? throw new ArgumentNullException(nameof(stream)); - if (length != null && length < 0) throw new ArgumentOutOfRangeException(nameof(length)); - return await ReadStreamInternal(stream, length).ConfigureAwait(false); - } - - private long rangeStart = 0; - [Obsolete("HttpDataSource will change its Range behaviour")] - public virtual long RangeStart - { - get => rangeStart; - set - { - if (value < 0) throw new ArgumentOutOfRangeException(nameof(RangeStart)); - if (value > 0) TransferCompleteData = false; - else if (rangeEnd == null) TransferCompleteData = true; - rangeStart = value; - } - } - - private long? rangeEnd = null; - [Obsolete("HttpDataSource will change its Range behaviour")] - public virtual long? RangeEnd - { - get => rangeEnd; - set - { - if (Length() == null && value != null) - throw new ArgumentOutOfRangeException(nameof(RangeEnd)); - if (value != null && value < 0) throw new ArgumentOutOfRangeException(nameof(RangeEnd)); - if (value != null) - TransferCompleteData = false; - else if (rangeStart == 0) - TransferCompleteData = true; - rangeEnd = value; - } - } - - private bool transferCompleteData = true; - [Obsolete("In a future release HttpDataSource will always transfer the complete data")] - public virtual bool TransferCompleteData - { - get => transferCompleteData; - set - { - if (transferCompleteData = value) - { - rangeStart = 0; - rangeEnd = null; - } - else - { - if (rangeEnd == null) - rangeEnd = Length(); - } - } + return await WriteStreamInternal(stream).ConfigureAwait(false); } public static Stream TransformToStream(HttpDataSource dataSource) diff --git a/MaxLib.WebServer/HttpException.cs b/MaxLib.WebServer/HttpException.cs index 62e4f30..cea9a32 100644 --- a/MaxLib.WebServer/HttpException.cs +++ b/MaxLib.WebServer/HttpException.cs @@ -14,13 +14,13 @@ namespace MaxLib.WebServer public class HttpException : System.Exception { public HttpException() { } - public HttpException(string message) : base(message) + public HttpException(string message) : base(message) { DataSource = new HttpStringDataSource(message); } - public HttpException(string message, System.Exception inner) : base(message, inner) - { + public HttpException(string message, System.Exception inner) : base(message, inner) + { DataSource = new HttpStringDataSource(message); } @@ -40,12 +40,8 @@ public HttpException(HttpDataSource dataSource) DataSource = dataSource; } - protected HttpException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } - public HttpStateCode StateCode { get; set; } = HttpStateCode.InternalServerError; public HttpDataSource? DataSource { get; set; } } -} \ No newline at end of file +} diff --git a/MaxLib.WebServer/HttpFileDataSource.cs b/MaxLib.WebServer/HttpFileDataSource.cs index 697392e..0db795b 100644 --- a/MaxLib.WebServer/HttpFileDataSource.cs +++ b/MaxLib.WebServer/HttpFileDataSource.cs @@ -1,113 +1,29 @@ -using MaxLib.IO; -using System; +using System; using System.IO; -using System.Threading.Tasks; #nullable enable namespace MaxLib.WebServer { [Serializable] - public class HttpFileDataSource : HttpDataSource + public class HttpFileDataSource : HttpStreamDataSource { - public FileStream? File { get; private set; } + public FileStream File => (FileStream)Stream; - private string? path = null; - public virtual string? Path - { - get => path; - set - { - if (path == value) return; - if (File != null) File.Dispose(); - if (value == null) File = null; - else - { - var fi = new FileInfo(value); - if (!fi.Directory!.Exists) fi.Directory.Create(); - File = new FileStream(value, FileMode.OpenOrCreate, -#pragma warning disable CS0612 - ReadOnly ? FileAccess.Read : FileAccess.ReadWrite, -#pragma warning restore CS0612 - FileShare.ReadWrite); - } - path = value; - } - } - - [Obsolete] - public bool ReadOnly { get; } - - [Obsolete] - public override bool CanAcceptData => !ReadOnly; - - public override bool CanProvideData => true; - - public HttpFileDataSource(string? path) - { - Path = path; - } + public string Path { get; } - [Obsolete] - public HttpFileDataSource(string? path, bool readOnly = true) + public HttpFileDataSource(string path) + : base(GetStream(path)) { - ReadOnly = readOnly; Path = path; } - public override void Dispose() - { - Path = null; - } - - public override long? Length() - => File?.Length; - - protected override async Task WriteStreamInternal(Stream stream, long start, long? stop) - { - await Task.CompletedTask.ConfigureAwait(false); - if (File == null) - return 0; - File.Position = start; - using (var skip = new SkipableStream(File, 0)) - { - try - { - return skip.WriteToStream(stream, - stop == null ? null : (long?)(stop.Value - start)); - } - catch (IOException) - { - WebServerLog.Add(ServerLogType.Information, GetType(), "Send", "Connection closed by remote Host"); - return File.Position - start; - } - } - } - - [Obsolete] - protected override async Task ReadStreamInternal(Stream stream, long? length) + private static FileStream GetStream(string path) { - await Task.CompletedTask.ConfigureAwait(false); - if (ReadOnly) - throw new NotSupportedException(); - if (File == null) - return 0; - File.Position = 0; - using (var skip = new SkipableStream(File, 0)) - { - long readed; - try - { - readed = skip.ReadFromStream(stream, length); - } - catch (IOException) - { - WebServerLog.Add(ServerLogType.Information, GetType(), "Send", "Connection closed by remote Host"); - readed = File.Position; - } - File.SetLength(readed); - return readed; - } + _ = path ?? throw new ArgumentNullException(nameof(path)); + if (!System.IO.File.Exists(path)) + throw new FileNotFoundException("required file not found", path); + return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); } } } diff --git a/MaxLib.WebServer/HttpHeader.cs b/MaxLib.WebServer/HttpHeader.cs index 4f209da..f6e9507 100644 --- a/MaxLib.WebServer/HttpHeader.cs +++ b/MaxLib.WebServer/HttpHeader.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using MaxLib.Common.Collections; #nullable enable @@ -8,6 +9,35 @@ namespace MaxLib.WebServer [Serializable] public abstract class HttpHeader { + private bool lockReset = false; + + public HttpHeader() + { + var param = new ObservableDictionary(StringComparer.InvariantCultureIgnoreCase); + param.CollectionChanged += (_, __) => + { + if (!lockReset) + ResetCache(); + }; + HeaderParameter = param; + } + + /// + /// Set the current state of the reset lock. If the reset is locked all changes to + /// won't call and therefore + /// reset the cached state. + /// + /// the new state of the reset lock + protected internal void SetResetLock(bool lockReset) + { + this.lockReset = lockReset; + } + + protected virtual void ResetCache() + { + + } + private string httpProtocol = HttpProtocolDefinition.HttpVersion1_1; public string HttpProtocol { @@ -20,8 +50,7 @@ public string HttpProtocol } } - public Dictionary HeaderParameter { get; } - = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + public ObservableDictionary HeaderParameter { get; } public string? GetHeader(string key) { @@ -32,10 +61,13 @@ public string HttpProtocol public void SetHeader(IEnumerable<(string, string?)> headers) { _ = headers ?? throw new ArgumentNullException(nameof(headers)); + SetResetLock(true); foreach (var (key, value) in headers) if (value != null) HeaderParameter[key] = value; else HeaderParameter.Remove(key); + SetResetLock(false); + ResetCache(); } public void SetHeader(params (string, string?)[] header) diff --git a/MaxLib.WebServer/HttpPartialSource.cs b/MaxLib.WebServer/HttpPartialSource.cs index 50bfbb8..01e1a8c 100644 --- a/MaxLib.WebServer/HttpPartialSource.cs +++ b/MaxLib.WebServer/HttpPartialSource.cs @@ -39,12 +39,20 @@ public HttpPartialSource(HttpDataSource dataSource, long start, long? count) BaseSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource)); Start = start; Count = count; - } - - [Obsolete] - public override bool CanAcceptData => false; - public override bool CanProvideData => true; + // optimize this constructor if you use nested partial sources + if (dataSource is HttpPartialSource partial) + { + BaseSource = partial.BaseSource; + Start += partial.Start; + if (Count != null && partial.Count != null) + Count = Math.Min(Count.Value, partial.Count.Value - Start); + else + { + Count ??= partial.Count - Start; + } + } + } public override void Dispose() { @@ -59,74 +67,90 @@ public override void Dispose() availableBaseLength == null ? Count : Math.Min(Count.Value, availableBaseLength.Value); } - [Obsolete] - protected override Task ReadStreamInternal(Stream stream, long? length) + protected override async Task WriteStreamInternal(Stream stream) { - throw new NotSupportedException(); + // optimize if stream based + if (BaseSource is HttpStreamDataSource streamDataSource) + { + var window = new StreamWindow(stream, 0, Count); + return await streamDataSource.WriteStream(window, Start, Count); + } + else + { + var window = new StreamWindow(stream, Start, Count); + return await BaseSource.WriteStream(window); + } } - protected override async Task WriteStreamInternal(Stream stream, long start, long? stop) + private class StreamWindow : Stream { - // start and stop will further restrict the limitations of this implementation. - // In a future release this method will be simplified. - - var firstOutputByte = Start + start; - var clientWindow = stop - start; - var localWindow = Count - start; - var window = localWindow == null ? clientWindow : - clientWindow == null ? localWindow : Math.Min(localWindow.Value, clientWindow.Value); - - // throw first bytes away - using var bin = new StreamBin(); - long total = await BaseSource.WriteStream(bin, 0, firstOutputByte) - .ConfigureAwait(false); - if (total < firstOutputByte) - return 0; - - // read the rest of the source. This depends if the BaseSource is something we can seek - // on. A future release will make the start-stop sequence obsolete therefore we no - // longer need these special checks. - if (BaseSource is HttpStreamDataSource sds && !sds.Stream.CanSeek) - total = await BaseSource.WriteStream(stream, 0, window) - .ConfigureAwait(false); - else total = await BaseSource.WriteStream(stream, firstOutputByte, firstOutputByte + window); - - // we are finished with reading the data - return total; - } + public Stream Target { get; } - private class StreamBin : Stream - { - public override bool CanRead => true; + public long Start { get; private set; } + + public long? Count { get; private set; } - public override bool CanSeek => true; + public StreamWindow(Stream target, long start, long? count) + { + Target = target; + Start = start; + Count = count; + } + + public override bool CanRead => false; + + public override bool CanSeek => false; public override bool CanWrite => true; - public override long Length => 0; + public override long Length => throw new NotSupportedException(); - public override long Position { get => 0; set {} } + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } public override void Flush() { + Target.Flush(); } public override int Read(byte[] buffer, int offset, int count) { - return 0; + throw new NotSupportedException(); } public override long Seek(long offset, SeekOrigin origin) { - return 0; + throw new NotSupportedException(); } public override void SetLength(long value) { + throw new NotSupportedException(); } public override void Write(byte[] buffer, int offset, int count) { + // skip bytes if available + if (Start > 0) + { + var skip = (int)Math.Min(Start, count); + Start -= skip; + offset += skip; + count -= skip; + } + // trim count if necessary + if (Start == 0 && Count != null) + { + var usable = (int)Math.Min(Count.Value, count); + count = usable; + Count = Count.Value - usable; + } + // perform write operation + if (count > 0) + Target.Write(buffer, offset, count); } } } diff --git a/MaxLib.WebServer/HttpRequestHeader.cs b/MaxLib.WebServer/HttpRequestHeader.cs index 21e8475..a0bcc31 100644 --- a/MaxLib.WebServer/HttpRequestHeader.cs +++ b/MaxLib.WebServer/HttpRequestHeader.cs @@ -1,5 +1,5 @@ -using System; -using System.Collections.Generic; +using System.Collections.ObjectModel; +using System; #nullable enable @@ -8,6 +8,17 @@ namespace MaxLib.WebServer [Serializable] public class HttpRequestHeader : HttpHeader { + protected override void ResetCache() + { + base.ResetCache(); + fieldAccept = null; + fieldAcceptCharset = null; + fieldAcceptEncoding = null; + fieldConnection = null; + host = null; + cookie = null; + } + public string Url { get => Location.Url; @@ -16,23 +27,172 @@ public string Url public HttpLocation Location { get; } = new HttpLocation("/"); - private string host = ""; + private Lazy? host = null; public string Host { - get => host; - set => host = value ?? throw new ArgumentNullException(nameof(Host)); + get + { + if (host != null) + return host.Value; + return (host = new Lazy( + () => HeaderParameter.TryGetValue("Host", out string value) ? + value : "" + )).Value; + } + set + { + _ = host ?? throw new ArgumentNullException(nameof(value)); + SetResetLock(true); + HeaderParameter["Host"] = value; + host = new Lazy(value); + SetResetLock(false); + } } + public HttpPost Post { get; } = new HttpPost(); - public List FieldAccept { get; } = new List(); - public List FieldAcceptCharset { get; } = new List(); - public List FieldAcceptEncoding { get; } = new List(); - public HttpConnectionType FieldConnection { get; set; } = HttpConnectionType.Close; - public HttpCookie Cookie { get; } = new HttpCookie(""); + + private Lazy>? fieldAccept = null; + public ReadOnlyCollection FieldAccept + { + get + { + if (fieldAccept != null) + return fieldAccept.Value; + return (fieldAccept = new Lazy>( + () => new ReadOnlyCollection( + HeaderParameter.TryGetValue("Accept", out string value) ? + value.Split( + new[] { ',', ' ' }, + StringSplitOptions.RemoveEmptyEntries + ) : new string[0] + ) + )).Value; + } + set + { + _ = value ?? throw new ArgumentNullException(nameof(value)); + var header = string.Join(", ", value); + SetResetLock(true); + HeaderParameter["Accept"] = header; + fieldAccept = new Lazy>(value); + SetResetLock(false); + } + } + + private Lazy>? fieldAcceptCharset = null; + public ReadOnlyCollection FieldAcceptCharset + { + get + { + if (fieldAcceptCharset != null) + return fieldAcceptCharset.Value; + return (fieldAcceptCharset = new Lazy>( + () => new ReadOnlyCollection( + HeaderParameter.TryGetValue("Accept-Charset", out string value) ? + value.Split( + new[] { ',', ' ' }, + StringSplitOptions.RemoveEmptyEntries + ) : new string[0] + ) + )).Value; + } + set + { + _ = value ?? throw new ArgumentNullException(nameof(value)); + var header = string.Join(", ", value); + SetResetLock(true); + HeaderParameter["Accept-Charset"] = header; + fieldAcceptCharset = new Lazy>(value); + SetResetLock(false); + } + } + + private Lazy>? fieldAcceptEncoding = null; + public ReadOnlyCollection FieldAcceptEncoding + { + get + { + if (fieldAcceptEncoding != null) + return fieldAcceptEncoding.Value; + return (fieldAcceptEncoding = new Lazy>( + () => new ReadOnlyCollection( + HeaderParameter.TryGetValue("Accept-Encoding", out string value) ? + value.Split( + new[] { ',', ' ' }, + StringSplitOptions.RemoveEmptyEntries + ) : new string[0] + ) + )).Value; + } + set + { + _ = value ?? throw new ArgumentNullException(nameof(value)); + var header = string.Join(", ", value); + SetResetLock(true); + HeaderParameter["Accept-Encoding"] = header; + fieldAcceptEncoding = new Lazy>(value); + SetResetLock(false); + } + } + + private Lazy? fieldConnection = null; + public HttpConnectionType FieldConnection + { + get + { + if (fieldConnection != null) + return fieldConnection.Value; + return (fieldConnection = new Lazy( + () => HeaderParameter.TryGetValue("Connection", out string value) + && value.ToLower() == "keep-alive" ? + HttpConnectionType.KeepAlive : + HttpConnectionType.Close + )).Value; + } + set + { + SetResetLock(true); + switch (value) + { + case HttpConnectionType.KeepAlive: + HeaderParameter["Connection"] = "keep-alive"; + break; + case HttpConnectionType.Close: + HeaderParameter["Connection"] = "close"; + break; + default: + SetResetLock(false); + throw new NotSupportedException($"Unsupported connection type {value}"); + } + fieldConnection = new Lazy(value); + SetResetLock(false); + } + } + + private Lazy? cookie = null; + public HttpCookie Cookie + { + get + { + if (cookie != null) + return cookie.Value; + return (cookie = new Lazy( + () => HeaderParameter.TryGetValue("Cookie", out string value) ? + new HttpCookie(value) : + new HttpCookie("") + )).Value; + } + } public string? FieldUserAgent { get => GetHeader("User-Agent"); - set => SetHeader("User-Agent", value); + set + { + SetResetLock(true); + SetHeader("User-Agent", value); + SetResetLock(false); + } } } } diff --git a/MaxLib.WebServer/HttpStreamDataSource.cs b/MaxLib.WebServer/HttpStreamDataSource.cs index 436ae29..2ea7d91 100644 --- a/MaxLib.WebServer/HttpStreamDataSource.cs +++ b/MaxLib.WebServer/HttpStreamDataSource.cs @@ -12,14 +12,6 @@ public class HttpStreamDataSource : HttpDataSource { public Stream Stream { get; } - [Obsolete] - public bool ReadOnly { get; } - - [Obsolete] - public override bool CanAcceptData => !ReadOnly && Stream.CanWrite; - - public override bool CanProvideData => Stream.CanRead; - public HttpStreamDataSource(Stream stream) { Stream = stream ?? throw new ArgumentNullException(nameof(stream)); @@ -30,60 +22,38 @@ public HttpStreamDataSource(Stream stream) ); } - [Obsolete] - public HttpStreamDataSource(Stream stream, bool readOnly = true) - : this(stream) - { - ReadOnly = readOnly; - } - public override void Dispose() => Stream.Dispose(); public override long? Length() => Stream.Length; - protected override async Task WriteStreamInternal(Stream stream, long start, long? stop) + protected override Task WriteStreamInternal(Stream stream) { - await Task.CompletedTask; - Stream.Position = start; - using (var skip = new SkipableStream(Stream, 0)) - { - try - { - return skip.WriteToStream(stream, - stop == null ? null : (long?)(stop.Value - start)); - } - catch (IOException) - { - WebServerLog.Add(ServerLogType.Information, GetType(), "Send", "Connection closed by remote Host"); - return Stream.Position - start; - } - } + return WriteStream(stream, 0, null); } - [Obsolete] - protected override async Task ReadStreamInternal(Stream stream, long? length) + public async Task WriteStream(Stream stream, long offset, long? count) { - await Task.CompletedTask.ConfigureAwait(false); - if (ReadOnly) - throw new NotSupportedException(); - Stream.Position = 0; - using (var skip = new SkipableStream(Stream, 0)) + if (Stream.CanSeek) + Stream.Position = offset; + long total = 0; + Memory buffer = new byte[0x8000]; + try { - long readed; - try + int read; + int job = count == null ? buffer.Length : (int)Math.Min(buffer.Length, count.Value - total); + while ((read = await Stream.ReadAsync(buffer[..job])) > 0) { - readed = skip.ReadFromStream(stream, length); + await stream.WriteAsync(buffer[0..read]); + total += read; } - catch (IOException) - { - WebServerLog.Add(ServerLogType.Information, GetType(), "Send", "Connection closed by remote Host"); - readed = Stream.Position; - } - Stream.SetLength(readed); - return readed; } + catch (IOException) + { + WebServerLog.Add(ServerLogType.Information, GetType(), "Send", "Connection closed by remote Host"); + } + return total; } } } diff --git a/MaxLib.WebServer/HttpStringDataSource.cs b/MaxLib.WebServer/HttpStringDataSource.cs index 6f2fd4f..c6ecb4b 100644 --- a/MaxLib.WebServer/HttpStringDataSource.cs +++ b/MaxLib.WebServer/HttpStringDataSource.cs @@ -29,11 +29,6 @@ public string TextEncoding } } - [Obsolete] - public override bool CanAcceptData => true; - - public override bool CanProvideData => true; - Encoding Encoder; public HttpStringDataSource(string data) @@ -50,38 +45,17 @@ public override void Dispose() public override long? Length() => Encoder.GetByteCount(Data); - protected override async Task WriteStreamInternal(Stream stream, long start, long? stop) - { - await Task.CompletedTask.ConfigureAwait(false); - using (var m = new MemoryStream(Encoder.GetBytes(Data))) - using (var skip = new SkipableStream(m, start)) - { - try { return skip.WriteToStream(stream, stop); } - catch (IOException) - { - WebServerLog.Add(ServerLogType.Information, GetType(), "Send", "Connection closed by remote Host"); - return m.Position; - } - } - } - - [Obsolete] - protected override async Task ReadStreamInternal(Stream stream, long? length) + protected override async Task WriteStreamInternal(Stream stream) { await Task.CompletedTask.ConfigureAwait(false); - using (var m = new MemoryStream()) - using (var skip = new SkipableStream(m, 0)) + using var m = new MemoryStream(Encoder.GetBytes(Data)); + try { await m.CopyToAsync(stream).ConfigureAwait(false); } + catch (IOException) { - long total; - try { total = skip.ReadFromStream(stream, length); } - catch (IOException) - { - WebServerLog.Add(ServerLogType.Information, GetType(), "Receive", "Connection closed by remote Host"); - return m.Position; - } - Data = Encoder.GetString(m.ToArray()); - return total; + WebServerLog.Add(ServerLogType.Information, GetType(), "Send", "Connection closed by remote Host"); + return m.Position; } + return m.Length; } } } diff --git a/MaxLib.WebServer/IO/ReadLineOverflowException.cs b/MaxLib.WebServer/IO/ReadLineOverflowException.cs index fbb8cd3..788e4f0 100644 --- a/MaxLib.WebServer/IO/ReadLineOverflowException.cs +++ b/MaxLib.WebServer/IO/ReadLineOverflowException.cs @@ -14,12 +14,5 @@ public ReadLineOverflowException(HttpStateCode state, string message) : base(mes { State = state; } - public ReadLineOverflowException(HttpStateCode state, string message, System.Exception inner) : base(message, inner) - { - State = state; - } - protected ReadLineOverflowException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } } -} \ No newline at end of file +} diff --git a/MaxLib.WebServer/Lazy/LazySource.cs b/MaxLib.WebServer/Lazy/LazySource.cs index 3b8d22f..5649ee8 100644 --- a/MaxLib.WebServer/Lazy/LazySource.cs +++ b/MaxLib.WebServer/Lazy/LazySource.cs @@ -50,65 +50,14 @@ public override void Dispose() s.Dispose(); } - protected override async Task WriteStreamInternal(Stream stream, long start, long? stop) + protected override async Task WriteStreamInternal(Stream stream) { - using (var skip = new SkipableStream(stream, start)) + long total = 0; + foreach (var s in GetAllSources()) { - long total = 0; - foreach (var s in GetAllSources()) - { - if (stop != null && total >= stop.Value) - return total; - var end = stop == null ? null : (long?)(stop.Value - total); - var size = s.Length(); - if (size == null) - { - total += await s.WriteStream(skip, 0, end).ConfigureAwait(false); - } - else - { - if (size.Value < skip.SkipBytes) - { - skip.Skip(size.Value); - continue; - } - var leftSkip = skip.SkipBytes; - skip.Skip(skip.SkipBytes); - total += await s.WriteStream(skip, leftSkip, end).ConfigureAwait(false); - } - } - return total; + total += await s.WriteStream(stream).ConfigureAwait(false); } + return total; } - - [Obsolete] - protected override Task ReadStreamInternal(Stream stream, long? length) - => throw new NotSupportedException(); - - [Obsolete] - public override long RangeStart - { - get => 0; - set => throw new NotSupportedException(); - } - - [Obsolete] - public override long? RangeEnd - { - get => null; - set => throw new NotSupportedException(); - } - - [Obsolete] - public override bool TransferCompleteData - { - get => true; - set => throw new NotSupportedException(); - } - - [Obsolete] - public override bool CanAcceptData => false; - - public override bool CanProvideData => true; } } diff --git a/MaxLib.WebServer/MaxLib.WebServer.csproj b/MaxLib.WebServer/MaxLib.WebServer.csproj index 61e144f..1d6f970 100644 --- a/MaxLib.WebServer/MaxLib.WebServer.csproj +++ b/MaxLib.WebServer/MaxLib.WebServer.csproj @@ -3,13 +3,11 @@ - net6.0;netstandard2.1 + net8.0;net6.0 MaxLib.WebServer garados007 Max Brauer - - MaxLib.Ini is a full web server written in C# that can easily integrated in your project. - + README.md MIT https://github.com/Garados007/MaxLib.WebServer @@ -24,9 +22,8 @@ - - - + + @@ -36,6 +33,7 @@ + diff --git a/MaxLib.WebServer/MaxLib.WebServer.csproj.include b/MaxLib.WebServer/MaxLib.WebServer.csproj.include index c12b6cb..88dfa8e 100644 --- a/MaxLib.WebServer/MaxLib.WebServer.csproj.include +++ b/MaxLib.WebServer/MaxLib.WebServer.csproj.include @@ -2,7 +2,7 @@ - 3.6.0 + 4.0.0 $(Version).0 $(Version).0 diff --git a/MaxLib.WebServer/MultipartRanges.cs b/MaxLib.WebServer/MultipartRanges.cs index 453ab8c..7283e29 100644 --- a/MaxLib.WebServer/MultipartRanges.cs +++ b/MaxLib.WebServer/MultipartRanges.cs @@ -28,11 +28,6 @@ public static long JoinGap } } - [Obsolete] - public override bool CanAcceptData => false; - - public override bool CanProvideData => true; - [Serializable] struct Range { @@ -172,14 +167,11 @@ void SinglePart() response.StatusCode = HttpStateCode.PartialContent; var h = response.HeaderParameter; h["Content-Range"] = ranges[0].ToString(baseStream.Length); - streams.Add(new HttpStreamDataSource(baseStream) - { -#pragma warning disable CS0618 - RangeStart = ranges[0].From, - RangeEnd = ranges[0].To, - TransferCompleteData = false -#pragma warning restore CS0618 - }); + streams.Add(new HttpPartialSource( + new HttpStreamDataSource(baseStream), + ranges[0].From, + ranges[0].To - ranges[0].From + )); } void MultiPart() @@ -202,31 +194,18 @@ void MultiPart() sb.Append("Content-Range: "); sb.AppendLine(r.ToString(baseStream.Length)); sb.AppendLine(); - streams.Add(new HttpStringDataSource(sb.ToString()) - { -#pragma warning disable CS0618 - TransferCompleteData = true -#pragma warning restore CS0618 - }); - streams.Add(new HttpStreamDataSource(baseStream) - { -#pragma warning disable CS0618 - RangeStart = r.From, - RangeEnd = r.To, - TransferCompleteData = false -#pragma warning restore CS0618 - }); + streams.Add(new HttpStringDataSource(sb.ToString())); + streams.Add(new HttpPartialSource( + new HttpStreamDataSource(baseStream), + r.From, + r.To - r.From + )); sb.Clear(); } sb.Append("--"); sb.Append(boundary); sb.Append("--"); - streams.Add(new HttpStringDataSource(sb.ToString()) - { -#pragma warning disable CS0618 - TransferCompleteData = true -#pragma warning restore CS0618 - }); + streams.Add(new HttpStringDataSource(sb.ToString())); } public override long? Length() @@ -248,39 +227,14 @@ public override void Dispose() foreach (var s in streams) s.Dispose(); } - protected override async Task WriteStreamInternal(Stream stream, long start, long? stop) + protected override async Task WriteStreamInternal(Stream stream) { - using (var skip = new SkipableStream(stream, start)) + long total = 0; + foreach (var s in streams) { - long total = 0; - foreach (var s in streams) - { - if (stop != null && total >= stop.Value) - return total; - var end = stop == null ? null : (long?)(stop.Value - total); - var size = s.Length(); - if (size == null) - { - total += await s.WriteStream(skip, 0, end).ConfigureAwait(false); - } - else - { - if (size.Value < skip.SkipBytes) - { - skip.Skip(size.Value); - continue; - } - var leftSkip = skip.SkipBytes; - skip.Skip(skip.SkipBytes); - total += await s.WriteStream(skip, leftSkip, end).ConfigureAwait(false); - } - } - return total; + total += await s.WriteStream(stream).ConfigureAwait(false); } + return total; } - - [Obsolete] - protected override Task ReadStreamInternal(Stream stream, long? length) - => throw new NotSupportedException(); } } diff --git a/MaxLib.WebServer/Remote/MarshalContainer.cs b/MaxLib.WebServer/Remote/MarshalContainer.cs index a67f760..21a2f97 100644 --- a/MaxLib.WebServer/Remote/MarshalContainer.cs +++ b/MaxLib.WebServer/Remote/MarshalContainer.cs @@ -16,19 +16,6 @@ public void SetOrigin(HttpDataSource origin) Origin = origin ?? throw new ArgumentNullException(nameof(origin)); } - [Obsolete] - public bool CanAcceptData() - { - _ = Origin ?? throw new InvalidOperationException("Origin is not set"); - return Origin.CanAcceptData; - } - - public bool CanProvideData() - { - _ = Origin ?? throw new InvalidOperationException("Origin is not set"); - return Origin.CanProvideData; - } - public long? Length() { _ = Origin ?? throw new InvalidOperationException("Origin is not set"); @@ -41,59 +28,10 @@ public void Dispose() Origin.Dispose(); } - public Task WriteStream(Stream stream, long start, long? stop) - { - _ = Origin ?? throw new InvalidOperationException("Origin is not set"); - return Origin.WriteStream(stream, start, stop); - } - - [Obsolete] - public Task ReadStream(Stream stream, long? length) - { - _ = Origin ?? throw new InvalidOperationException("Origin is not set"); - return Origin.ReadStream(stream, length); - } - - [Obsolete] - public long? RangeEnd() - { - _ = Origin ?? throw new InvalidOperationException("Origin is not set"); - return Origin.RangeEnd; - } - - [Obsolete] - public void RangeEnd(long? value) - { - _ = Origin ?? throw new InvalidOperationException("Origin is not set"); - Origin.RangeEnd = value; - } - - [Obsolete] - public long RangeStart() - { - _ = Origin ?? throw new InvalidOperationException("Origin is not set"); - return Origin.RangeStart; - } - - [Obsolete] - public void RangeStart(long value) - { - _ = Origin ?? throw new InvalidOperationException("Origin is not set"); - Origin.RangeStart = value; - } - - [Obsolete] - public bool TransferCompleteData() - { - _ = Origin ?? throw new InvalidOperationException("Origin is not set"); - return Origin.TransferCompleteData; - } - - [Obsolete] - public void TransferCompleteData(bool value) + public Task WriteStream(Stream stream) { _ = Origin ?? throw new InvalidOperationException("Origin is not set"); - Origin.TransferCompleteData = value; + return Origin.WriteStream(stream); } public string MimeType() diff --git a/MaxLib.WebServer/Remote/MarshalSource.cs b/MaxLib.WebServer/Remote/MarshalSource.cs index 9befe74..e608e12 100644 --- a/MaxLib.WebServer/Remote/MarshalSource.cs +++ b/MaxLib.WebServer/Remote/MarshalSource.cs @@ -25,33 +25,8 @@ public MarshalSource(HttpDataSource source) public override void Dispose() => Container.Dispose(); - protected override Task WriteStreamInternal(Stream stream, long start, long? stop) - => Container.WriteStream(stream, start, stop); - - [Obsolete] - protected override Task ReadStreamInternal(Stream stream, long? length) - => Container.ReadStream(stream, length); - - [Obsolete] - public override long? RangeEnd - { - get => Container.RangeEnd(); - set => Container.RangeEnd(value); - } - - [Obsolete] - public override long RangeStart - { - get => Container.RangeStart(); - set => Container.RangeStart(value); - } - - [Obsolete] - public override bool TransferCompleteData - { - get => Container.TransferCompleteData(); - set => Container.TransferCompleteData(value); - } + protected override Task WriteStreamInternal(Stream stream) + => Container.WriteStream(stream); public override string MimeType { @@ -59,11 +34,6 @@ public override string MimeType set => Container.MimeType(value); } - [Obsolete] - public override bool CanAcceptData => Container.CanAcceptData(); - - public override bool CanProvideData => Container.CanProvideData(); - public Collections.MarshalEnumerable? GetAllSources() => Container.Sources(); } diff --git a/MaxLib.WebServer/SSL/DualSecureWebServer.cs b/MaxLib.WebServer/SSL/DualSecureWebServer.cs index 185e64d..7513632 100644 --- a/MaxLib.WebServer/SSL/DualSecureWebServer.cs +++ b/MaxLib.WebServer/SSL/DualSecureWebServer.cs @@ -20,9 +20,7 @@ public DualSecureWebServer(DualSecureWebServerSettings settings) : base(settings protected override async Task ClientStartListen(HttpConnection connection) { - if (connection.NetworkStream == null && DualSettings.Certificate != null - && connection.NetworkClient != null - ) + if (connection.NetworkStream == null && connection.NetworkClient != null) { var peaker = new StreamPeaker(connection.NetworkClient.GetStream()); var mark = peaker.FirstByte; diff --git a/MaxLib.WebServer/SSL/DualSecureWebServerSettings.cs b/MaxLib.WebServer/SSL/DualSecureWebServerSettings.cs index 2aee070..5f9c6a3 100644 --- a/MaxLib.WebServer/SSL/DualSecureWebServerSettings.cs +++ b/MaxLib.WebServer/SSL/DualSecureWebServerSettings.cs @@ -6,13 +6,7 @@ namespace MaxLib.WebServer.SSL { public class DualSecureWebServerSettings : WebServerSettings { - public X509Certificate? Certificate { get; set; } - - public DualSecureWebServerSettings(string settingFolderPath) - : base(settingFolderPath) - { - - } + public X509Certificate Certificate { get; set; } public DualSecureWebServerSettings(int port, int connectionTimeout, X509Certificate certificate) : base(port, connectionTimeout) diff --git a/MaxLib.WebServer/SSL/SecureWebServerSettings.cs b/MaxLib.WebServer/SSL/SecureWebServerSettings.cs index dde4ff7..ec66dd1 100644 --- a/MaxLib.WebServer/SSL/SecureWebServerSettings.cs +++ b/MaxLib.WebServer/SSL/SecureWebServerSettings.cs @@ -1,5 +1,4 @@ -using MaxLib.Ini; -using System.Security.Cryptography.X509Certificates; +using System.Security.Cryptography.X509Certificates; #nullable enable @@ -12,11 +11,6 @@ public class SecureWebServerSettings : WebServerSettings public X509Certificate? Certificate { get; set; } - public SecureWebServerSettings(string settingFolderPath) - : base(settingFolderPath) - { - } - public SecureWebServerSettings(int port, int securePort, int connectionTimeout) : base(port, connectionTimeout) { @@ -29,13 +23,5 @@ public SecureWebServerSettings(int securePort, int connectionTimeout) SecurePort = securePort; EnableUnsafePort = false; } - - protected override void Load_Server(IniFile set) - { - base.Load_Server(set); - var server = set.GetGroup("Server"); - SecurePort = server.GetInt32("SecurePort", 443); - EnableUnsafePort = server.GetBool("EnableUnsafePort", true); - } } } diff --git a/MaxLib.WebServer/Server.cs b/MaxLib.WebServer/Server.cs index e45b15d..c34c8b5 100644 --- a/MaxLib.WebServer/Server.cs +++ b/MaxLib.WebServer/Server.cs @@ -67,18 +67,16 @@ public Server(WebServerSettings settings) /// /// Initialize the server with a basic set of web services. These are: , , , , and .
- /// With these you have basic functionality and a working web server that can deliver 404 - /// answers for every request. + /// cref="Services.HttpRequestParser" />, , + /// , and + /// .
With these you have basic functionality and a + /// working web server that can deliver 404 answers for every request. ///
public virtual void InitialDefault() { //Pre parse request AddWebService(new Services.HttpRequestParser()); //post parse request - AddWebService(new Services.HttpHeaderPostParser()); AddWebService(new Services.HttpHeaderSpecialAction()); //pre create document AddWebService(new Services.Http404Service()); diff --git a/MaxLib.WebServer/Services/HttpHeaderPostParser.cs b/MaxLib.WebServer/Services/HttpHeaderPostParser.cs deleted file mode 100644 index 018d2d1..0000000 --- a/MaxLib.WebServer/Services/HttpHeaderPostParser.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Threading.Tasks; - -#nullable enable - -namespace MaxLib.WebServer.Services -{ - /// - /// WebServiceType.PostParseRequest: Ließt alle Informationen aus dem Header aus und analysiert diese. - /// Die Headerklasse wird für die weitere Verwendung vorbereitet. - /// - public class HttpHeaderPostParser : WebService - { - /// - /// WebServiceType.PostParseRequest: Ließt alle Informationen aus dem Header aus und analysiert diese. - /// Die Headerklasse wird für die weitere Verwendung vorbereitet. - /// - public HttpHeaderPostParser() - : base(ServerStage.ParseRequest) - { - Priority = WebServicePriority.VeryHigh; - } - - public override async Task ProgressTask(WebProgressTask task) - { - _ = task ?? throw new ArgumentNullException(nameof(task)); - - var header = task.Request; - //Accept - if (header.HeaderParameter.TryGetValue("Accept", out string? value)) - { - header.FieldAccept.AddRange(value.Split( - new[] { ',', ' ', ';' }, StringSplitOptions.RemoveEmptyEntries)); - } - //Accept-Encoding - if (header.HeaderParameter.TryGetValue("Accept-Encoding", out value)) - { - header.FieldAcceptEncoding.AddRange(value.Split( - new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)); - } - //Connection - if (header.HeaderParameter.TryGetValue("Connection", out value)) - { - if (value.ToLower() == "keep-alive") - header.FieldConnection = HttpConnectionType.KeepAlive; - } - //Host - if (header.HeaderParameter.TryGetValue("Host", out value)) - { - header.Host = value; - } - //Cookie - if (header.HeaderParameter.TryGetValue("Cookie", out value)) - { - header.Cookie.SetRequestCookieString(value); - } - - await Task.CompletedTask.ConfigureAwait(false); - } - - public override bool CanWorkWith(WebProgressTask task) - => true; - } -} diff --git a/MaxLib.WebServer/Services/StandardDocumentLoader.cs b/MaxLib.WebServer/Services/StandardDocumentLoader.cs index 4ddfa2e..c9eb835 100644 --- a/MaxLib.WebServer/Services/StandardDocumentLoader.cs +++ b/MaxLib.WebServer/Services/StandardDocumentLoader.cs @@ -9,6 +9,7 @@ namespace MaxLib.WebServer.Services /// WebServiceType.PreCreateDocument: Stellt ein festdefiniertes Dokument bereit. Dies ist unabhängig vom /// angeforderten Pfad. /// + [Obsolete("Define your own endpoint or use Http404Service")] public class StandardDocumentLoader : WebService { /// diff --git a/MaxLib.WebServer/WebServerSettings.cs b/MaxLib.WebServer/WebServerSettings.cs index 187876b..5ec0d79 100644 --- a/MaxLib.WebServer/WebServerSettings.cs +++ b/MaxLib.WebServer/WebServerSettings.cs @@ -1,7 +1,4 @@ -using MaxLib.Ini; -using System; -using System.Collections.Generic; -using System.IO; +using System; using System.Net; #nullable enable @@ -50,71 +47,6 @@ public TimeSpan ConnectionDelay public bool Debug_WriteRequests = false; public bool Debug_LogConnections = false; - public Dictionary DefaultFileMimeAssociation { get; } - = new Dictionary(); - - protected enum SettingTypes - { - MimeAssociation, - ServerSettings - } - - public string? SettingsPath { get; private set; } - - public virtual void LoadSettingFromData(string data) - { - _ = data ?? throw new ArgumentNullException(nameof(data)); - var sf = new Ini.Parser.IniParser().ParseFromString(data); - if (sf.GetGroup("Mime") != null) - Load_Mime(sf); - if (sf.GetGroup("Server") != null) - Load_Server(sf); - } - - public virtual void LoadSetting(string path) - { - _ = path ?? throw new ArgumentNullException(nameof(path)); - SettingsPath = path; - var sf = new Ini.Parser.IniParser().Parse(path); - if (sf.GetGroup("Mime") != null) - Load_Mime(sf); - if (sf.GetGroup("Server") != null) - Load_Server(sf); - } - - protected virtual void Load_Mime(IniFile set) - { - DefaultFileMimeAssociation.Clear(); - var gr = set.GetGroup("Mime").GetAll(); - foreach (IniOption keypair in gr) - DefaultFileMimeAssociation[keypair.Name] = keypair.String; - } - - protected virtual void Load_Server(IniFile set) - { - var server = set.GetGroup("Server"); - Port = server.GetInt32("Port", 80); - if (Port <= 0 || Port >= 0xffff) - Port = 80; - ConnectionTimeout = server.GetInt32("ConnectionTimeout", 2000); - if (ConnectionTimeout < 0) - ConnectionTimeout = 2000; - } - - public WebServerSettings(string settingFolderPath) - { - _ = settingFolderPath ?? throw new ArgumentNullException(nameof(settingFolderPath)); - if (Directory.Exists(settingFolderPath)) - foreach (var file in Directory.GetFiles(settingFolderPath)) - { - if (file.EndsWith(".ini")) - LoadSetting(file); - } - else if (File.Exists(settingFolderPath)) - LoadSetting(settingFolderPath); - else throw new DirectoryNotFoundException(); - } - public WebServerSettings(int port, int connectionTimeout) { if (port <= 0 || port >= 0xffff) diff --git a/MaxLib.WebServer/WebServerTaskCreator.cs b/MaxLib.WebServer/WebServerTaskCreator.cs index d027580..b462439 100644 --- a/MaxLib.WebServer/WebServerTaskCreator.cs +++ b/MaxLib.WebServer/WebServerTaskCreator.cs @@ -1,4 +1,5 @@ -using System; +using System.Collections.ObjectModel; +using System; using System.IO; using System.Threading.Tasks; @@ -58,8 +59,10 @@ public void SetPost(WebProgressTask task, ReadOnlyMemory post, string mime public void SetAccept(string[]? acceptTypes = null, string[]? encoding = null) { - if (acceptTypes != null) Task.Request.FieldAccept.AddRange(acceptTypes); - if (encoding != null) Task.Request.FieldAcceptEncoding.AddRange(encoding); + if (acceptTypes != null) + Task.Request.FieldAccept = new ReadOnlyCollection(acceptTypes); + if (encoding != null) + Task.Request.FieldAcceptEncoding = new ReadOnlyCollection(encoding); } public void SetHost(string host) diff --git a/MaxLib.WebServer/WebSocket/TooLargePayloadException.cs b/MaxLib.WebServer/WebSocket/TooLargePayloadException.cs index 63ccf52..2a8105f 100644 --- a/MaxLib.WebServer/WebSocket/TooLargePayloadException.cs +++ b/MaxLib.WebServer/WebSocket/TooLargePayloadException.cs @@ -10,8 +10,5 @@ public class TooLargePayloadException : Exception public TooLargePayloadException() { } public TooLargePayloadException(string message) : base(message) { } public TooLargePayloadException(string message, Exception inner) : base(message, inner) { } - protected TooLargePayloadException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } } } diff --git a/example/MaxLib.WebServer.Example/MaxLib.WebServer.Example.csproj b/example/MaxLib.WebServer.Example/MaxLib.WebServer.Example.csproj index c3861a3..5c7ceec 100644 --- a/example/MaxLib.WebServer.Example/MaxLib.WebServer.Example.csproj +++ b/example/MaxLib.WebServer.Example/MaxLib.WebServer.Example.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net8.0 diff --git a/example/MaxLib.WebServer.Example/Program.cs b/example/MaxLib.WebServer.Example/Program.cs index 1ee7b8d..143aa0f 100644 --- a/example/MaxLib.WebServer.Example/Program.cs +++ b/example/MaxLib.WebServer.Example/Program.cs @@ -1,23 +1,23 @@ using System; +using System.Threading.Tasks; using MaxLib.WebServer.Services; namespace MaxLib.WebServer.Example { class Program { - static void Main(string[] args) + static async Task Main(string[] args) { WebServerLog.LogAdded += WebServerLog_LogAdded; - var server = new Server(new WebServerSettings(8000, 5000)); + using var server = new Server(new WebServerSettings(8000, 5000)); // add services server.AddWebService(new HttpRequestParser()); - server.AddWebService(new HttpHeaderPostParser()); server.AddWebService(new HttpHeaderSpecialAction()); - server.AddWebService(new StandardDocumentLoader() { Document = "Hello World!" }); + server.AddWebService(new Http404Service()); server.AddWebService(new HttpResponseCreator()); server.AddWebService(new HttpSender()); - // start server - server.Start(); + // run server until cancel received + await server.RunAsync(); } private static void WebServerLog_LogAdded(ServerLogItem item) diff --git a/MaxLib.WebServer.WebSocket.Echo/EchoConnection.cs b/example/MaxLib.WebServer.WebSocket.Echo/EchoConnection.cs similarity index 100% rename from MaxLib.WebServer.WebSocket.Echo/EchoConnection.cs rename to example/MaxLib.WebServer.WebSocket.Echo/EchoConnection.cs diff --git a/MaxLib.WebServer.WebSocket.Echo/EchoEndpoint.cs b/example/MaxLib.WebServer.WebSocket.Echo/EchoEndpoint.cs similarity index 100% rename from MaxLib.WebServer.WebSocket.Echo/EchoEndpoint.cs rename to example/MaxLib.WebServer.WebSocket.Echo/EchoEndpoint.cs diff --git a/MaxLib.WebServer.WebSocket.Echo/MaxLib.WebServer.WebSocket.Echo.csproj b/example/MaxLib.WebServer.WebSocket.Echo/MaxLib.WebServer.WebSocket.Echo.csproj similarity index 52% rename from MaxLib.WebServer.WebSocket.Echo/MaxLib.WebServer.WebSocket.Echo.csproj rename to example/MaxLib.WebServer.WebSocket.Echo/MaxLib.WebServer.WebSocket.Echo.csproj index fc78de2..5c7ceec 100644 --- a/MaxLib.WebServer.WebSocket.Echo/MaxLib.WebServer.WebSocket.Echo.csproj +++ b/example/MaxLib.WebServer.WebSocket.Echo/MaxLib.WebServer.WebSocket.Echo.csproj @@ -2,11 +2,11 @@ Exe - netcoreapp3.1 + net8.0 - + diff --git a/MaxLib.WebServer.WebSocket.Echo/Program.cs b/example/MaxLib.WebServer.WebSocket.Echo/Program.cs similarity index 64% rename from MaxLib.WebServer.WebSocket.Echo/Program.cs rename to example/MaxLib.WebServer.WebSocket.Echo/Program.cs index 39987d1..95184d1 100644 --- a/MaxLib.WebServer.WebSocket.Echo/Program.cs +++ b/example/MaxLib.WebServer.WebSocket.Echo/Program.cs @@ -1,5 +1,6 @@ using MaxLib.WebServer.Services; using System; +using System.Threading.Tasks; #nullable enable @@ -7,27 +8,21 @@ namespace MaxLib.WebServer.WebSocket.Echo { class Program { - static void Main() + static async Task Main() { WebServerLog.LogAdded += WebServerLog_LogAdded; - var server = new Server(new WebServerSettings(8000, 5000)); + using var server = new Server(new WebServerSettings(8000, 5000)); // add services server.AddWebService(new HttpRequestParser()); - server.AddWebService(new HttpHeaderPostParser()); server.AddWebService(new HttpHeaderSpecialAction()); server.AddWebService(new HttpResponseCreator()); server.AddWebService(new HttpSender()); // setup web socket - var websocket = new WebSocketService(); + using var websocket = new WebSocketService(); websocket.Add(new EchoEndpoint()); server.AddWebService(websocket); - // start server - server.Start(); - // wait for console quit - while (Console.ReadKey().Key != ConsoleKey.Q) ; - // close - server.Stop(); - websocket.Dispose(); + // run server until cancel received + await server.RunAsync(); } private static void WebServerLog_LogAdded(ServerLogItem item) diff --git a/MaxLib.WebServer.Test/Api/Rest/TestRestActionEndpoint.cs b/tests/MaxLib.WebServer.Test/Api/Rest/TestRestActionEndpoint.cs similarity index 99% rename from MaxLib.WebServer.Test/Api/Rest/TestRestActionEndpoint.cs rename to tests/MaxLib.WebServer.Test/Api/Rest/TestRestActionEndpoint.cs index be8a640..33b738b 100644 --- a/MaxLib.WebServer.Test/Api/Rest/TestRestActionEndpoint.cs +++ b/tests/MaxLib.WebServer.Test/Api/Rest/TestRestActionEndpoint.cs @@ -6,6 +6,7 @@ namespace MaxLib.WebServer.Test.Api.Rest { + [Obsolete] [TestClass] public class TestRestActionEndpoint { diff --git a/MaxLib.WebServer.Test/Api/Rest/TestRestApiFactory.cs b/tests/MaxLib.WebServer.Test/Api/Rest/TestRestApiFactory.cs similarity index 99% rename from MaxLib.WebServer.Test/Api/Rest/TestRestApiFactory.cs rename to tests/MaxLib.WebServer.Test/Api/Rest/TestRestApiFactory.cs index 6866be4..46003a8 100644 --- a/MaxLib.WebServer.Test/Api/Rest/TestRestApiFactory.cs +++ b/tests/MaxLib.WebServer.Test/Api/Rest/TestRestApiFactory.cs @@ -2,9 +2,11 @@ using MaxLib.WebServer.Api.Rest; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Collections.Generic; +using System; namespace MaxLib.WebServer.Test.Api.Rest { + [Obsolete] [TestClass] public class TestRestApiFactory { diff --git a/MaxLib.WebServer.Test/Builder/SingleService.cs b/tests/MaxLib.WebServer.Test/Builder/SingleService.cs similarity index 100% rename from MaxLib.WebServer.Test/Builder/SingleService.cs rename to tests/MaxLib.WebServer.Test/Builder/SingleService.cs diff --git a/MaxLib.WebServer.Test/Builder/TestSingleService.cs b/tests/MaxLib.WebServer.Test/Builder/TestSingleService.cs similarity index 100% rename from MaxLib.WebServer.Test/Builder/TestSingleService.cs rename to tests/MaxLib.WebServer.Test/Builder/TestSingleService.cs diff --git a/MaxLib.WebServer.Test/IO/TestNetworkReader.cs b/tests/MaxLib.WebServer.Test/IO/TestNetworkReader.cs similarity index 100% rename from MaxLib.WebServer.Test/IO/TestNetworkReader.cs rename to tests/MaxLib.WebServer.Test/IO/TestNetworkReader.cs diff --git a/tests/MaxLib.WebServer.Test/MaxLib.WebServer.Test.csproj b/tests/MaxLib.WebServer.Test/MaxLib.WebServer.Test.csproj new file mode 100644 index 0000000..ff5d126 --- /dev/null +++ b/tests/MaxLib.WebServer.Test/MaxLib.WebServer.Test.csproj @@ -0,0 +1,22 @@ + + + + + + net8.0 + + false + + + + + + + + + + + + + + diff --git a/MaxLib.WebServer.Test/Services/TestHttpHeaderSpecialAction.cs b/tests/MaxLib.WebServer.Test/Services/TestHttpHeaderSpecialAction.cs similarity index 100% rename from MaxLib.WebServer.Test/Services/TestHttpHeaderSpecialAction.cs rename to tests/MaxLib.WebServer.Test/Services/TestHttpHeaderSpecialAction.cs diff --git a/MaxLib.WebServer.Test/Services/TestHttpRequestParser.cs b/tests/MaxLib.WebServer.Test/Services/TestHttpRequestParser.cs similarity index 100% rename from MaxLib.WebServer.Test/Services/TestHttpRequestParser.cs rename to tests/MaxLib.WebServer.Test/Services/TestHttpRequestParser.cs diff --git a/MaxLib.WebServer.Test/Services/TestHttpResponseCreator.cs b/tests/MaxLib.WebServer.Test/Services/TestHttpResponseCreator.cs similarity index 100% rename from MaxLib.WebServer.Test/Services/TestHttpResponseCreator.cs rename to tests/MaxLib.WebServer.Test/Services/TestHttpResponseCreator.cs diff --git a/MaxLib.WebServer.Test/Services/TestHttpSender.cs b/tests/MaxLib.WebServer.Test/Services/TestHttpSender.cs similarity index 100% rename from MaxLib.WebServer.Test/Services/TestHttpSender.cs rename to tests/MaxLib.WebServer.Test/Services/TestHttpSender.cs diff --git a/MaxLib.WebServer.Test/Services/TestHttpHeaderPostParser.cs b/tests/MaxLib.WebServer.Test/TestHttpRequestHeader.cs similarity index 64% rename from MaxLib.WebServer.Test/Services/TestHttpHeaderPostParser.cs rename to tests/MaxLib.WebServer.Test/TestHttpRequestHeader.cs index 3f39bba..6020380 100644 --- a/MaxLib.WebServer.Test/Services/TestHttpHeaderPostParser.cs +++ b/tests/MaxLib.WebServer.Test/TestHttpRequestHeader.cs @@ -1,12 +1,13 @@ using MaxLib.WebServer.Services; using MaxLib.WebServer.Testing; using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; using System.Threading.Tasks; namespace MaxLib.WebServer.Test.Services { [TestClass] - public class TestHttpHeaderPostParser + public class TestHttpRequestHeader { TestWebServer server; TestTask test; @@ -15,7 +16,6 @@ public class TestHttpHeaderPostParser public void Init() { server = new TestWebServer(); - server.AddWebService(new HttpHeaderPostParser()); test = new TestTask(server) { CurrentStage = ServerStage.ParseRequest, @@ -24,21 +24,18 @@ public void Init() } [TestMethod] - public async Task TestAccept() + public void TestAccept() { test.Request.HeaderParameter.Add("Accept", "text/css,*/*;q=0.1"); - await new HttpHeaderPostParser().ProgressTask(test.Task).ConfigureAwait(false); - Assert.AreEqual(3, test.Request.FieldAccept.Count); + Assert.AreEqual(2, test.Request.FieldAccept.Count); Assert.AreEqual("text/css", test.Request.FieldAccept[0]); - Assert.AreEqual("*/*", test.Request.FieldAccept[1]); - Assert.AreEqual("q=0.1", test.Request.FieldAccept[2]); + Assert.AreEqual("*/*;q=0.1", test.Request.FieldAccept[1]); } [TestMethod] - public async Task TestAcceptEncoding() + public void TestAcceptEncoding() { test.Request.HeaderParameter.Add("Accept-Encoding", "gzip, deflate, br"); - await new HttpHeaderPostParser().ProgressTask(test.Task).ConfigureAwait(false); Assert.AreEqual(3, test.Request.FieldAcceptEncoding.Count); Assert.AreEqual("gzip", test.Request.FieldAcceptEncoding[0]); Assert.AreEqual("deflate", test.Request.FieldAcceptEncoding[1]); @@ -46,34 +43,30 @@ public async Task TestAcceptEncoding() } [TestMethod] - public async Task TestConnection() + public void TestConnection() { test.Request.HeaderParameter.Add("Connection", "keep-alive"); - await new HttpHeaderPostParser().ProgressTask(test.Task).ConfigureAwait(false); Assert.AreEqual(HttpConnectionType.KeepAlive, test.Request.FieldConnection); } [TestMethod] - public async Task TestConnectionClose() + public void TestConnectionClose() { test.Request.HeaderParameter.Add("Connection", "close"); - await new HttpHeaderPostParser().ProgressTask(test.Task).ConfigureAwait(false); Assert.AreEqual(HttpConnectionType.Close, test.Request.FieldConnection); } [TestMethod] - public async Task TestHost() + public void TestHost() { test.Request.HeaderParameter.Add("Host", "test.domain"); - await new HttpHeaderPostParser().ProgressTask(test.Task).ConfigureAwait(false); Assert.AreEqual("test.domain", test.Request.Host); } [TestMethod] - public async Task TestCookie() + public void TestCookie() { test.Request.HeaderParameter.Add("Cookie", "key1=value1; key2= value2;"); - await new HttpHeaderPostParser().ProgressTask(test.Task).ConfigureAwait(false); Assert.AreEqual("key1=value1; key2= value2;", test.Request.Cookie.CompleteRequestCookie); } } diff --git a/MaxLib.WebServer.Test/Testing/TestTestTask.cs b/tests/MaxLib.WebServer.Test/Testing/TestTestTask.cs similarity index 100% rename from MaxLib.WebServer.Test/Testing/TestTestTask.cs rename to tests/MaxLib.WebServer.Test/Testing/TestTestTask.cs diff --git a/MaxLib.WebServer.Test/WebSocket/FrameParsing.cs b/tests/MaxLib.WebServer.Test/WebSocket/FrameParsing.cs similarity index 100% rename from MaxLib.WebServer.Test/WebSocket/FrameParsing.cs rename to tests/MaxLib.WebServer.Test/WebSocket/FrameParsing.cs