Skip to content

Instantly share code, notes, and snippets.

@haf
Last active December 18, 2015 03:19
Show Gist options
  • Save haf/5717121 to your computer and use it in GitHub Desktop.
Save haf/5717121 to your computer and use it in GitHub Desktop.
Hacking async APIs for the LULz
using System;
using System.Threading.Tasks;
using ConsoleApplication2.Authorization;
using ConsoleApplication2.ServiceStack;
using ConsoleApplication2.Sugar;
namespace ConsoleApplication2
{
internal class Program
{
static void Main(string[] args)
{
var p = new Program();
p.BootstrapServerWith(
new AuthorizationService(),
new HeartbeatSentinel());
}
RequestPipeline RequestPipeline
{
get { return new RequestPipeline(); }
}
void BootstrapServerWith(
AuthorizationService tenantUserSvc,
HeartbeatSentinel sentinel)
{
RequestPipeline.Add(async (req, resp, dto) =>
{
var decision = await tenantUserSvc.DecideOn(req.DeserializeAuthorCtx());
return decision.WriteToResponse(resp, "You have to provide a tenant AND and user");
});
RequestPipeline.Add(async (req, resp, dto) =>
{
var reply = await sentinel.RepliesWithin(5.Milliseconds());
if (reply.TimedOut)
return new ContinueWith(new FailoverPipeline()) as FilterDecision;
return new Continue();
});
}
}
internal class FailoverPipeline
: RequestPipeline
{
}
internal class Continue
: FilterDecision
{
}
internal class HeartbeatSentinel
{
public async Task<HeartbeatReply> RepliesWithin(TimeSpan milliseconds)
{
return new HeartbeatReply();
}
}
internal class HeartbeatReply
{
public bool TimedOut
{
get { return false; }
}
}
public class AuthorizationService
{
public async Task<AuthorizationDecision> DecideOn(AuthorizationContext ctx)
{
return ctx.UserId != Guid.Empty && ctx.TenantId != Guid.Empty
? new AllowDecision() as AuthorizationDecision
: new DenyDecision() as AuthorizationDecision;
}
}
namespace Authorization
{
public abstract class AuthorizationDecision
{
public FilterDecision WriteToResponse(HttpResp resp, string friendlyOuterMessage)
{
resp.ContentType = "text/plain";
resp.Write(friendlyOuterMessage); //Whatever you want in body
return HandleDecision(resp);
}
protected abstract FilterDecision HandleDecision(HttpResp resp);
}
public class AllowDecision
: AuthorizationDecision
{
protected override FilterDecision HandleDecision(HttpResp resp)
{
throw new NotImplementedException();
}
}
public interface AuthorizationContext
{
Guid UserId { get; }
Guid TenantId { get; }
}
public class DenyDecision
: AuthorizationDecision
{
protected override FilterDecision HandleDecision(HttpResp resp)
{
resp.RedirectTo("/login");
return new BreakPipeline();
}
}
public static class HttpReqEx
{
public static AuthorizationContext DeserializeAuthorCtx(this HttpReq req)
{
return new MissingContext();
}
}
internal class MissingContext : AuthorizationContext
{
public MissingContext()
{
UserId = Guid.Empty;
TenantId = Guid.Empty;
}
public Guid UserId { get; private set; }
public Guid TenantId { get; private set; }
}
}
namespace AppPipelines
{
}
namespace ServiceStack
{
public interface HttpReq
{
}
public class RequestPipeline
{
public void Add<T>(Func<HttpReq, HttpResp, RequestDto, Task<T>> action)
where T : FilterDecision
{
}
}
public interface FilterDecision
{
}
internal class BreakPipeline
: FilterDecision
{
}
internal class ContinueWith
: FilterDecision
{
public ContinueWith(RequestPipeline next)
{
Next = next;
}
RequestPipeline Next { get; set; }
}
public interface HttpResp
{
string ContentType { set; }
void Write(string content);
void RedirectTo(string siteAbsoluteUri);
}
public interface RequestDto
{
}
}
namespace Sugar
{
public static class IntegerEx
{
public static TimeSpan Milliseconds(this int input)
{
return new TimeSpan(0, 0, 0, 0, input);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment