Skip to content

Instantly share code, notes, and snippets.

@markhepburn
Forked from cbilson/Program.cs
Created January 5, 2016 11:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save markhepburn/4d0de5f574c7cec2ada9 to your computer and use it in GitHub Desktop.
Save markhepburn/4d0de5f574c7cec2ada9 to your computer and use it in GitHub Desktop.
Migrating C# OWIN app to ClojureCLR
(assembly-load "Microsoft.Owin")
(assembly-load "Microsoft.Owin.Host.HttpListener")
(assembly-load "Microsoft.Owin.Hosting")
(assembly-load "Microsoft.Owin.Security")
(assembly-load "Newtonsoft.Json")
(assembly-load "Owin")
(ns camp-katana.core
(:import [System.Text Encoding]
[System.Threading.Tasks Task]
[Owin IAppBuilder]
[Microsoft.Owin.Hosting StartOptions WebApp]))
(defn env->request [env]
(reduce (fn [m kvp]
(if-let [key
({"owin.RequestPath" :path
"owin.RequestHeaders" :headers
"owin.RequestBody" :body
"owin.RequestQueryString" :query }
(.Key kvp))]
(assoc m key (.Value kvp))
m))
{} env))
(defn get-env [env key]
(let [kvp (first (filter #(= key (.Key %)) env))]
(.Value kvp)))
(defn update-headers! [owin-env headers]
(when headers
(when-let [owin-headers (get-env owin-env "owin.ResponseHeaders")]
(doseq [[key value] headers]
(when-let [owin-key ({:content-type "Content-Type"
:content-length "Content-Length"} key)]
#_(.Add owin-headers owin-key value)))))
owin-env)
(defmulti update-body! (fn [owin-env body] (class body)))
(defmethod update-body! |System.Byte[]| [owin-env body]
(let [owin-body (get-env owin-env "owin.ResponseBody")
headers (get-env owin-env "owin.ResponseHeaders")
length (.Length body)]
(println "owin-body:" owin-body)
(println "body:" body)
(.Write owin-body body 0 length)
(update-headers! owin-env {:content-length length})))
(defmethod update-body! System.String [owin-env body]
(update-body! owin-env (.GetBytes Encoding/UTF8 body)))
(defn response->env! [response owin-env]
(update-headers! owin-env (:headers owin-env))
(doseq [[key value] response]
(when-let [owin-key ({:status "owin.ResponseStatusCode"} key)]
(println "adding owin key:" owin-key ", value:" value)
#_(.Add owin-env owin-key value)))
(update-body! owin-env (:body response))
owin-env)
(defn dump-env [env]
(doseq [kvp env]
(println (.Key kvp) ": " (.Value kvp))))
(defn matching-route [verb path [route-verb route-pattern & _ :as route]]
(cond
(not= verb route-verb)
nil
(instance? String route-pattern)
(when (= path route-pattern)
route)
(instance? System.Text.RegularExpressions.Regex route-pattern)
(when (re-find route-pattern path)
route)
:otherwise
nil))
(defn compile-route [[verb pattern bindings & body]]
`[~(str verb) ~pattern
(fn ~bindings ~@body)])
(defmacro defroutes [name & routes]
;; TODO: Special case if the bindings use destructuring for query args
`(defn ^Task ~name [owin-environment# next-handler#]
(let [path# (get-env owin-environment# "owin.RequestPath")
verb# (get-env owin-environment# "owin.RequestMethod")
routes# ~(vec (map compile-route routes))]
(if-let [[verb# pattern# handler#]
(some (partial matching-route verb# path#) routes#)]
(do
(println "Found handler:" verb# handler#)
(.StartNew Task/Factory
(gen-delegate Action []
(let [req# (env->request owin-environment#)]
(println "Request:" (pr-str req#))
(let [res# (handler# req#)]
(println "Response:" (pr-str res#))
(response->env! res# owin-environment#))))))
(next-handler# owin-environment#)))))
(defroutes routes
(GET "/" [req]
(println "Got to request handler.")
{:headers {:content-type "text/html"}
:status 200
:body "<html><body>ohai!</body></html>"}))
(defmacro defmiddleware [name bindings & body]
`(def ~name
(sys-action [IAppBuilder] ~bindings
~@body)))
(def environment-sig
(str "System.Collections.Generic.IDictionary`2["
"System.String,"
"System.Object"
"]"))
(def handler-sig
(str "System.Func`2["
environment-sig ","
"System.Threading.Tasks.Task"
"]"))
(defn ^IAppBuilder use-handler [handler ^IAppBuilder builder]
(println "Creating handler invoker for" (pr-str handler))
(let [handler-invoker
(sys-func [handler-sig handler-sig]
[next]
(sys-func [environment-sig]
[owin-environment]
(try
(println "Invoking app")
(handler owin-environment next)
(catch Exception ex
(println "Error:" ex)))))]
(.Use builder handler-invoker (into-array Object []))))
;; Stand alone web app.
(gen-class :name camp_katana.WebApp
:main true
:prefix "app-")
(defn app-main [& args]
(try
(let [start-options (StartOptions. "http://localhost:5000/")]
(set! (.AppStartup start-options) "camp-katana")
(println "About to start. Ready?")
(read-line)
(WebApp/Start start-options
(sys-action [IAppBuilder] [builder]
(use-handler routes builder)))
(println "Server running on" (seq (.Urls start-options)))
(println "Press [Enter] to stop the server.")
(read-line)
0)
(catch Exception ex
(println "error:" (.ToString ex))
-1)))
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using clojure.clr.api;
using clojure.lang;
using Microsoft.Ajax.Utilities;
using Microsoft.Owin.Hosting;
using Owin;
namespace WebApplication2
{
public static class App
{
public static int Main(string[] ags)
{
try
{
Action<IAppBuilder> startUp = StartUp;
//var require = Clojure.var("clojure.core", "require");
//require.invoke(Clojure.read("camp-katana.core"));
//var routes = Clojure.var("camp-katana.core", "routes");
//var useHandler = Clojure.var("camp-katana.core", "use-handler");
//Action<IAppBuilder> startUp = builder => useHandler.invoke(routes, builder);
var startOptions = new StartOptions("http://localhost:5000/") {AppStartup = "my app"};
using (WebApp.Start(startOptions, startUp))
{
Console.WriteLine("Server started. Press any key to stop.");
Console.ReadLine();
}
return 0;
}
catch (Exception ex)
{
Console.WriteLine(ex);
return -1;
}
}
public static void StartUp(IAppBuilder app)
{
//ConfigureAuth(app);
Func<Func<IDictionary<string, object>, Task>, Func<IDictionary<string,object>, Task>> f = GetUsed;
//app.Use(f);
Func<Func<IDictionary<string, object>, Task>, Func<IDictionary<string,object>, Task>> f2 = next =>
{
return env =>
{
try
{
var require = Clojure.var("clojure.core", "require");
require.invoke(Clojure.read("camp-katana.core"));
var handler = Clojure.var("camp-katana.core", "app");
var task = handler.invoke(env, next) as Task;
if (task != null)
return task;
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine("Error: {0}", ex.ToString());
}
return next(env);
};
};
app.Use(f2);
}
public static Func<IDictionary<string,object>, Task> GetUsed(Func<IDictionary<string, object>, Task> next)
{
Console.WriteLine("Getting used.");
return env =>
{
if (env["owin.RequestPath"].Equals("/"))
{
var response = "<html><body><h3>ohai!</h3></body></html>";
var responesBytes = Encoding.UTF8.GetBytes(response);
var responseLength = responesBytes.Length;
var headers = env["owin.ResponseHeaders"] as IDictionary<string, string[]>;
headers.Add("Content-Length", new []{responseLength.ToString()});
headers.Add("Content-Type", new[] {"text/html"});
var body = env["owin.ResponseBody"] as Stream;
return body.WriteAsync(responesBytes, 0, responseLength);
}
else
{
return next(env);
}
};
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment