diff --git a/src/Http/HttpServerConfiguration.cs b/src/Http/HttpServerConfiguration.cs
index fe3d10d..5ee6bf0 100644
--- a/src/Http/HttpServerConfiguration.cs
+++ b/src/Http/HttpServerConfiguration.cs
@@ -8,7 +8,6 @@
// Repository: https://github.com/sisk-http/core
using System.Globalization;
-using System.Text;
namespace Sisk.Core.Http
{
@@ -61,12 +60,6 @@ public sealed class HttpServerConfiguration : IDisposable
///
public ForwardingResolver? ForwardingResolver { get; set; }
- ///
- /// Gets or sets the default encoding for sending and decoding messages.
- ///
- [Obsolete("This property is deprecated and will be removed in later Sisk versions.")]
- public Encoding DefaultEncoding { get; set; } = Encoding.UTF8;
-
///
/// Gets or sets the maximum size of a request body before it is closed by the socket.
///
diff --git a/src/Http/HttpServerFlags.cs b/src/Http/HttpServerFlags.cs
index ae9f9fb..921cc3c 100644
--- a/src/Http/HttpServerFlags.cs
+++ b/src/Http/HttpServerFlags.cs
@@ -128,14 +128,22 @@ public sealed class HttpServerFlags
public TimeSpan IdleConnectionTimeout = TimeSpan.FromSeconds(120);
///
- /// Determines if the new span-based multipart form reader should be used. This is an experimental
- /// feature and may not be stable for production usage.
+ /// Determines if the new span-based multipart form reader should be used.
///
/// Default value: true
///
///
public bool EnableNewMultipartFormReader = true;
+ ///
+ /// Determines if the HTTP server should convert object responses into
+ /// an blocking .
+ ///
+ /// Default value: true
+ ///
+ ///
+ public bool ConvertIAsyncEnumerableIntoEnumerable = true;
+
///
/// Creates an new instance with default flags values.
///
diff --git a/src/Http/HttpServer__Core.cs b/src/Http/HttpServer__Core.cs
index 3c9bcab..f22c171 100644
--- a/src/Http/HttpServer__Core.cs
+++ b/src/Http/HttpServer__Core.cs
@@ -283,6 +283,7 @@ private void ProcessRequest(HttpListenerContext context)
// get response
routerResult = matchedListeningHost.Router.Execute(srContext);
+ executionResult.ServerException = routerResult.Exception;
response = routerResult.Response;
bool routeAllowCors = routerResult.Route?.UseCors ?? true;
diff --git a/src/Http/LogStream.cs b/src/Http/LogStream.cs
index c28e319..724e7a0 100644
--- a/src/Http/LogStream.cs
+++ b/src/Http/LogStream.cs
@@ -258,10 +258,17 @@ private async void ProcessQueue()
/// Writes an exception description in the log.
///
/// The exception which will be written.
- public virtual void WriteException(Exception exp)
+ public virtual void WriteException(Exception exp) => this.WriteException(exp, null);
+
+ ///
+ /// Writes an exception description in the log.
+ ///
+ /// The exception which will be written.
+ /// Extra context message to append to the exception message.
+ public virtual void WriteException(Exception exp, string? extraContext = null)
{
StringBuilder excpStr = new StringBuilder();
- this.WriteExceptionInternal(excpStr, exp, 0);
+ this.WriteExceptionInternal(excpStr, exp, extraContext, 0);
this.WriteLineInternal(excpStr.ToString());
}
@@ -339,17 +346,20 @@ void EnqueueMessageLine(string message)
_ = this.channel.Writer.WriteAsync(message);
}
- void WriteExceptionInternal(StringBuilder exceptionSbuilder, Exception exp, int currentDepth = 0)
+ void WriteExceptionInternal(StringBuilder exceptionSbuilder, Exception exp, string? context = null, int currentDepth = 0)
{
if (currentDepth == 0)
- exceptionSbuilder.AppendLine(string.Format(SR.LogStream_ExceptionDump_Header, DateTime.Now.ToString("R")));
+ exceptionSbuilder.AppendLine(string.Format(SR.LogStream_ExceptionDump_Header,
+ context is null ? DateTime.Now.ToString("R") : $"{context}, {DateTime.Now:R}"));
+
exceptionSbuilder.AppendLine(exp.ToString());
if (exp.InnerException != null)
{
if (currentDepth <= 3)
{
- this.WriteExceptionInternal(exceptionSbuilder, exp.InnerException, currentDepth + 1);
+ exceptionSbuilder.AppendLine("+++ inner exception +++");
+ this.WriteExceptionInternal(exceptionSbuilder, exp.InnerException, null, currentDepth + 1);
}
else
{
diff --git a/src/Routing/Route.cs b/src/Routing/Route.cs
index 090db38..ae27144 100644
--- a/src/Routing/Route.cs
+++ b/src/Routing/Route.cs
@@ -22,7 +22,8 @@ public class Route : IEquatable
internal RouteAction? _singleParamCallback;
internal ParameterlessRouteAction? _parameterlessRouteAction;
- internal bool _isAsync;
+ internal bool _isAsyncEnumerable;
+ internal bool _isAsyncTask;
internal Regex? routeRegex;
private string path;
@@ -40,7 +41,7 @@ public class Route : IEquatable
///
/// Gets an boolean indicating if this action return is an asynchronous .
///
- public bool IsAsync { get => this._isAsync; }
+ public bool IsAsync { get => this._isAsyncTask; }
///
/// Gets or sets how this route can write messages to log files on the server.
@@ -109,7 +110,8 @@ public Delegate? Action
{
this._parameterlessRouteAction = null;
this._singleParamCallback = null;
- this._isAsync = false;
+ this._isAsyncTask = false;
+ this._isAsyncEnumerable = false;
return;
}
else if (!this.TrySetRouteAction(value.Method, value.Target, out Exception? ex))
@@ -137,6 +139,23 @@ internal bool TrySetRouteAction(MethodInfo method, object? target, [NotNullWhen(
return false;
}
+ Exception? CheckAsyncReturnParameters(Type asyncOutType)
+ {
+ if (asyncOutType.GenericTypeArguments.Length == 0)
+ {
+ return new InvalidOperationException(string.Format(SR.Route_Action_AsyncMissingGenericType, this));
+ }
+ else
+ {
+ Type genericAssignType = asyncOutType.GenericTypeArguments[0];
+ if (genericAssignType.IsValueType)
+ {
+ return new NotSupportedException(SR.Route_Action_ValueTypeSet);
+ }
+ }
+ return null;
+ }
+
var retType = method.ReturnType;
if (retType.IsValueType)
{
@@ -145,25 +164,26 @@ internal bool TrySetRouteAction(MethodInfo method, object? target, [NotNullWhen(
}
else if (retType.IsAssignableTo(typeof(Task)))
{
- this._isAsync = true;
- if (retType.GenericTypeArguments.Length == 0)
+ this._isAsyncTask = true;
+ if (CheckAsyncReturnParameters(retType) is Exception rex)
{
- ex = new InvalidOperationException(string.Format(SR.Route_Action_AsyncMissingGenericType, this));
+ ex = rex;
return false;
}
- else
+ }
+ else if (retType.GetGenericTypeDefinition() == typeof(IAsyncEnumerable<>))
+ {
+ this._isAsyncEnumerable = true;
+ if (CheckAsyncReturnParameters(retType) is Exception rex)
{
- Type genericAssignType = retType.GenericTypeArguments[0];
- if (genericAssignType.IsValueType)
- {
- ex = new NotSupportedException(SR.Route_Action_ValueTypeSet);
- return false;
- }
+ ex = rex;
+ return false;
}
}
else
{
- this._isAsync = false;
+ this._isAsyncTask = false;
+ this._isAsyncEnumerable = false;
}
ex = null;
@@ -223,7 +243,7 @@ public Route()
///
public override string ToString()
{
- return $"[{this.Method.ToString().ToUpper()} {this.path}] {this.Name ?? this.Action?.Method.Name}";
+ return $"[{this.Method.ToString().ToUpper()} {this.path}] {this.Name ?? this.Action?.Method.Name ?? ""}";
}
///
diff --git a/src/Routing/Router.cs b/src/Routing/Router.cs
index 55bdb49..c739f99 100644
--- a/src/Routing/Router.cs
+++ b/src/Routing/Router.cs
@@ -14,7 +14,17 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-record struct RouteDictItem(System.Type type, Delegate lambda);
+class ActionHandler
+{
+ public Type MatchingType { get; set; }
+ public Func