Created
April 22, 2015 15:23
-
-
Save cbilson/924a579dd0a5994ebb08 to your computer and use it in GitHub Desktop.
Migrating C# OWIN app to ClojureCLR
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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