Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace Kestrel with Ceen #729

Merged
merged 7 commits into from
Aug 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,24 @@
// </copyright>
//-----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Akka.Cluster;
using Akka.Configuration;
using Akka.Http.Dsl.Model;
using Akka.Http.Dsl.Server;
using Akka.Http.Extensions;
using Akka.Management.Cluster.Bootstrap.ContactPoint;
using Ceen;
using FluentAssertions;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using Xunit;
using Xunit.Abstractions;
Expand Down Expand Up @@ -46,8 +54,8 @@ public HttpContactPointRoutesSpec(ITestOutputHelper helper)
public async Task EmptyListIfNotPartOfCluster()
{
var context = new DefaultHttpContext();
context.Request.Method = HttpMethods.Get;
context.Request.Path = ClusterBootstrapRequests.BootstrapSeedNodes("").ToString();
context.FakeRequest.Method = "GET";
context.FakeRequest.Path = ClusterBootstrapRequests.BootstrapSeedNodes("").ToString();

var requestContext = new RequestContext(await HttpRequest.CreateAsync(context.Request), Sys);
var response = (RouteResult.Complete) await _httpBootstrap.Routes.Concat()(requestContext);
Expand All @@ -72,8 +80,8 @@ public async Task IncludeSeedsWhenPartOfCluster()
up.Member.Should().Be(cluster.SelfMember);

var context = new DefaultHttpContext();
context.Request.Method = HttpMethods.Get;
context.Request.Path = ClusterBootstrapRequests.BootstrapSeedNodes("");
context.FakeRequest.Method = "GET";
context.FakeRequest.Path = ClusterBootstrapRequests.BootstrapSeedNodes("");

var requestContext = new RequestContext(await HttpRequest.CreateAsync(context.Request), Sys);
var response = (RouteResult.Complete) await _httpBootstrap.Routes.Concat()(requestContext);
Expand All @@ -90,4 +98,167 @@ public async Task IncludeSeedsWhenPartOfCluster()
responseString);
}
}

internal class DefaultHttpContext : IHttpContext
{
public Task LogMessageAsync(LogLevel level, string message, Exception ex)
{
throw new NotImplementedException();
}

public FakeRequest FakeRequest { get; } = new FakeRequest();
public IHttpRequest Request => FakeRequest;
public IHttpResponse Response { get; }
public IStorageCreator Storage { get; }
public IDictionary<string, string> Session { get; set; }
public IDictionary<string, string> LogData { get; }
public ILoadedModuleInfo LoadedModules { get; }
}

internal class FakeRequest : IHttpRequest
{
public void PushHandlerOnStack(IHttpModule handler)
{
throw new NotImplementedException();
}

public void RequireHandler(IEnumerable<RequireHandlerAttribute> attributes)
{
throw new NotImplementedException();
}

public void RequireHandler(Type handler, bool allowderived = true)
{
throw new NotImplementedException();
}

public void ResetProcessingTimeout()
{
throw new NotImplementedException();
}

public void ThrowIfTimeout()
{
throw new NotImplementedException();
}

public string RawHttpRequestLine { get; }
public string Method { get; set; }
public string Path { get; set; }
public string OriginalPath { get; }
public string RawQueryString { get; }
public IDictionary<string, string> QueryString { get; }
public IDictionary<string, string> Headers { get; }
public IDictionary<string, string> Form { get; }
public IDictionary<string, string> Cookies { get; }
public IList<IMultipartItem> Files { get; }
public string HttpVersion { get; set; }
public string UserID { get; set; }
public string SessionID { get; set; }
public SslProtocols SslProtocol { get; }
public EndPoint RemoteEndPoint { get; }
public X509Certificate ClientCertificate { get; }
public string LogConnectionID { get; }
public string LogRequestID { get; }
public Stream Body { get; set; }
public string ContentType { get; }
public int ContentLength { get; }
public string Hostname { get; }
public IDictionary<string, object> RequestState { get; }
public IEnumerable<IHttpModule> HandlerStack { get; }
public CancellationToken TimeoutCancellationToken { get; }
public bool IsConnected { get; }
public DateTime RequestProcessingStarted { get; }
}

internal class FakeResponse : IHttpResponse
{
public IResponseCookie AddCookie(string name, string value, string path = null, string domain = null, DateTime? expires = null,
long maxage = -1, bool secure = false, bool httponly = false, string samesite = null)
{
throw new NotImplementedException();
}

public IResponseCookie AddCookie(string name, string value, string path = null, string domain = null, DateTime? expires = null,
long maxage = -1, bool secure = false, bool httponly = false)
{
throw new NotImplementedException();
}

public void AddHeader(string key, string value)
{
throw new NotImplementedException();
}

public void InternalRedirect(string path)
{
throw new NotImplementedException();
}

public Task FlushHeadersAsync()
{
throw new NotImplementedException();
}

public Task WriteAllAsync(Stream data, string contenttype = null)
{
throw new NotImplementedException();
}

public Task WriteAllAsync(byte[] data, string contenttype = null)
{
throw new NotImplementedException();
}

public Task WriteAllAsync(string data, string contenttype = null)
{
throw new NotImplementedException();
}

public Task WriteAllAsync(string data, Encoding encoding, string contenttype = null)
{
throw new NotImplementedException();
}

public Task WriteAllJsonAsync(string data)
{
throw new NotImplementedException();
}

public void Redirect(string newurl)
{
throw new NotImplementedException();
}

public void SetNonCacheable()
{
throw new NotImplementedException();
}

public void SetExpires(TimeSpan duration, bool isPublic = true)
{
throw new NotImplementedException();
}

public void SetExpires(DateTime until, bool isPublic = true)
{
throw new NotImplementedException();
}

public Stream GetResponseStream()
{
throw new NotImplementedException();
}

public string HttpVersion { get; set; }
public Ceen.HttpStatusCode StatusCode { get; set; }
public string StatusMessage { get; set; }
public bool HasSentHeaders { get; }
public IDictionary<string, string> Headers { get; }
public IList<IResponseCookie> Cookies { get; }
public bool IsRedirectingInternally { get; }
public string ContentType { get; set; }
public long ContentLength { get; set; }
public bool KeepAlive { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
using Akka.Http.Dsl.Model;
using Akka.Http.Dsl.Server;
using Akka.IO;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using static Akka.Management.Cluster.Bootstrap.ContactPoint.HttpBootstrapJsonProtocol;
using HttpResponse = Akka.Http.Dsl.Model.HttpResponse;
Expand All @@ -36,7 +35,7 @@ public Route[] Routes
{
return new Route[]{async context =>
{
if (context.Request.Method == HttpMethods.Get && context.Request.Path == "/bootstrap/seed-nodes")
if (context.Request.Method == "GET" && context.Request.Path == "/bootstrap/seed-nodes")
{
return await GetSeedNodes()(context);
}
Expand Down
9 changes: 3 additions & 6 deletions src/management/Akka.Http.Shim.Tests/HttpSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
using Akka.Http.Dsl.Settings;
using Akka.Http.Extensions;
using FluentAssertions;
using Microsoft.AspNetCore.Http;
using Xunit;
using Xunit.Abstractions;
using HttpResponse = Akka.Http.Dsl.Model.HttpResponse;
Expand All @@ -40,9 +39,7 @@ public async Task Should_Bind_Properly()

var serverBinding = await baseBuilder.Bind(new []{RouteOne(), RouteTwo()}.Concat()).ConfigureAwait(false);

var boundHost = ((DnsEndPoint) serverBinding.LocalAddress).Host;
var boundPort = ((DnsEndPoint)serverBinding.LocalAddress).Port;
Log.Info($"Bound Akka Management (HTTP) endpoint to: {boundHost}:{boundPort}");
Log.Info($"Bound Akka Management (HTTP) endpoint to: {serverBinding.LocalAddress}");

await AssertServerIsRunning();

Expand Down Expand Up @@ -76,7 +73,7 @@ private Route RouteOne()
return context =>
{
var request = context.Request;
if (request.Method != HttpMethods.Get || request.Path != "/test/one")
if (request.Method != "GET" || request.Path != "/test/one")
return Task.FromResult<IRouteResult>(null);

return Task.FromResult<IRouteResult>(new Complete(HttpResponse.Create()));
Expand All @@ -88,7 +85,7 @@ private Route RouteTwo()
return context =>
{
var request = context.Request;
if (request.Method != HttpMethods.Get || request.Path != "/test/two")
if (request.Method != "GET" || request.Path != "/test/two")
return Task.FromResult<IRouteResult>(null);

return Task.FromResult<IRouteResult>(new Complete(HttpResponse.Create()));
Expand Down
3 changes: 1 addition & 2 deletions src/management/Akka.Http.Shim/Akka.Http.Shim.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@

<ItemGroup>
<PackageReference Include="Akka" Version="$(AkkaVersion)" />
<PackageReference Condition="'$(TargetFramework)' == '$(LibraryFramework)'" Include="Microsoft.AspNetCore" Version="2.2.0" />
<FrameworkReference Condition="'$(TargetFramework)' == '$(NetFramework)'" Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Ceen.Httpd" Version="0.9.9" />
</ItemGroup>

</Project>
70 changes: 70 additions & 0 deletions src/management/Akka.Http.Shim/AkkaRouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// -----------------------------------------------------------------------
// <copyright file="AkkaRouter.cs" company="Akka.NET Project">
// Copyright (C) 2009-2022 Lightbend Inc. <http://www.lightbend.com>
// Copyright (C) 2013-2022 .NET Foundation <https://github.com/akkadotnet/akka.net>
// </copyright>
// -----------------------------------------------------------------------

using System;
using System.Threading.Tasks;
using Akka.Actor;
using Akka.Event;
using Akka.Http.Dsl;
using Akka.Http.Dsl.Model;
using Akka.Http.Dsl.Server;
using Ceen;

namespace Akka.Http
{
public class AkkaRouter : IRouter
{
private readonly ActorSystem _system;
private readonly Route _routes;
private readonly ILoggingAdapter _log;

public AkkaRouter(ActorSystem system, Route routes)
{
_system = system;
_routes = routes;
_log = Logging.GetLogger(system, typeof(AkkaRouter));
}

public async Task<bool> Process(IHttpContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}

var requestContext = new RequestContext(await HttpRequest.CreateAsync(context.Request), _system);

var response = await _routes(requestContext);
switch (response)
{
case null:
context.Response.StatusCode = HttpStatusCode.NotFound;
_log.Info($"Request to path {context.Request.Path} rejected: [{HttpStatusCode.NotFound}]");
break;
case RouteResult.Rejected reject:
// TODO: Do response error code conversion
switch (reject.Rejection)
{
default:
context.Response.StatusCode = HttpStatusCode.BadRequest;
break;
}
_log.Info($"Request to path {context.Request.Path} rejected: [{reject}]");
break;
case RouteResult.Complete complete:
var r = complete.Response;
context.Response.StatusCode = r.Status;
context.Response.ContentType = r.Entity.ContentType;
await context.Response.WriteAllAsync(r.Entity.DataBytes.ToArray());
_log.Debug($"Request to path {context.Request.Path} completed successfully.");
break;
}

return true;
}
}
}