3 people like it.
Like the snippet!
REST For Free
HttpListener + FParsec + Pattern Matching = Simple REST Endpoints
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
|
// EXAMPLES:
//
// http://localhost:8888/people/chefs/Jimbo?eggs=2&cheese=4
// http://localhost:8888/people/chefs/Jimbo?cheese=4&eggs=2
// http://localhost:8888/people/chefs/Jimbo?cheese=4&eggs=2&bacon=5
//
// USAGE:
// C:\RestForFree>fsi restForFree.fsx
//
// SEE ALSO:
// https://www.branded3.com/blog/creating-a-simple-http-server-with-f
#I @"C:\RestForFree"
#r @"FParsec.1.0.2\lib\net40-client\FParsecCS.dll"
#r @"FParsec.1.0.2\lib\net40-client\FParsec.dll"
////////////////////////////////////////////////////////////////////////////////
//Naive, but its just a pedantic example
module parser =
open System; open FParsec
//Just give me my result, parsec!
let (-=>) input someParser =
run someParser input
|> function
| Success(result,_,_) -> result
| _ -> Unchecked.defaultof<'T> //Equivalent to C#'s default(T),
//both match paths need same return type
type url = {
pathString: string
queryString: string }
let not = (<>) //Just eliminating parens
let psplitBy char =
let keep = manySatisfy(not char)
let toss = pstring(string(char))
sepBy(keep)(toss)
let pathParser = psplitBy('/')
let keyOrVal = many(noneOf "&=") |>> String.Concat
let keyValuePair = keyOrVal .>> pstring "=" .>>. keyOrVal
let queryParser = sepBy(keyValuePair)(pstring "&")
let preamble = parse {
do! pstring "http" |>> ignore //Proto
do! skipString "://" //
do! many(noneOf ":/") |>> String.Concat |>> ignore //host
do! pstring(":") >>. manySatisfy(Char.IsDigit) <|>% "" |>> ignore } //port?
let urlParser = parse {
do! preamble
let! pathString = pstring("/") >>. manySatisfy(not '?') <|>% ""
let! queryString = pstring("?") >>. restOfLine(true) <|>% ""
return {
pathString = pathString
queryString = queryString } }
////////////////////////////////////////////////////////////////////////////////
module listener =
open System; open System.Net; open System.Text
//Eliminate .NET line noise,
type HttpListenerResponse with
member me.WriteAsync str =
Encoding.ASCII.GetBytes(s=str)
|> me.OutputStream.AsyncWrite
//Config
let root,host = @"C:\RestForFree","http://localhost:8888/"
//.NET init stuff
let initListener =
let l = new HttpListener()
l.Prefixes.Add host
l.Start()
l
//As it turns out, Async.* was recently, to accomodate futures/deferreds
//However, HttpListener uses 'the IAsyncResult design pattern [sic]',
//which is older/more-lower-level
//https://msdn.microsoft.com/en-us/library/system.iasyncresult(v=vs.110).aspx
let listen =
let l = initListener //Called once upon definition of 'listen'
async { //Is accessed repeatedly, however
let b,e = l.BeginGetContext, l.EndGetContext
let! context = Async.FromBeginEnd(b, e)
return context.Request,context.Response }
let listening requestHandler =
Async.Start <| async { //Non-blocking
while true do
let! request, response = listen //Get the context asynchronously
do! requestHandler request response } //Handle the context async
Console.ReadLine() |> ignore //Retain console for printfn
////////////////////////////////////////////////////////////////////////////////
module http =
open System.Net; open listener
//Parens for sake of type inferencer confused by partial application
let httpWrapper (restHandler)(request)(response:HttpListenerResponse) =
async {
response.StatusCode <- int HttpStatusCode.OK //Response packaging
response.ContentType <- "text/html" //Response packaging
let! mainOutput = restHandler request response
do! response.WriteAsync mainOutput
response.Close() } //Resource management :-)
//TODO: Find IDisposables
// and 'use' them.
////////////////////////////////////////////////////////////////////////////////
open parser; open listener; open http
//Have to sort tuples by key alphabetically, lest there be no match! :-(
let (|ATOZ|) = ATOZ List.sortBy fst
let pretty s = sprintf "%A" s
listening(httpWrapper(fun request response ->
//Thank you Microsoft, but I will parse the url myself
request.Url.AbsoluteUri -=> urlParser
|> function
| { url.pathString = pathString; url.queryString = queryString } ->
//'Routes'
pathString -=> pathParser
|> function
//"href=/people/chefs/Jimbo"
| [ "people"; "chefs"; chef ] ->
//Remote Procedure Call (RPC)-style 'rest' endpoints
queryString -=> queryParser
|> function
| ATOZ[ "cheese",b ; "eggs",a ] -> async {
return
chef + " cannot make omlettes :-( ... <br />" +
"But, Dexter can make " + string(int a + int b + 1000) +
" omlettes du fromage!! <br />" +
"because he is a boy-genious, with a very large laboratory." }
//b, then c, then e, here, but not in the url!
| ATOZ[ "bacon",b ; "cheese",c ; "eggs",a ] -> async {
return
chef + " can make " + string(int a + int b + int c) + " omlets!" }
//b, then c, then e, here, but not in the url!
| ATOZ[ "bacon",b ; "cheese",c ; "eggs",a ; "mushrooms",m ] -> async {
return "Mushrooms...Disgusting!" }
| ATOZ query -> async {
return "NO QUERY MATCH FOR: " + pretty query }
| path -> async { return "NO PATH MATCH FOR: " + pretty path }
))
|
namespace System
namespace FParsec
val input : string
val someParser : Parser<'T,unit>
val run : Parser<'Result,unit> -> string -> ParserResult<'Result,unit>
Full name: FParsec.CharParsers.run
union case ParserResult.Success: 'Result * 'UserState * Position -> ParserResult<'Result,'UserState>
val result : 'T
module Unchecked
from Microsoft.FSharp.Core.Operators
val defaultof<'T> : 'T
Full name: Microsoft.FSharp.Core.Operators.Unchecked.defaultof
type url =
{pathString: string;
queryString: string;}
Full name: Script.parser.url
url.pathString: string
Multiple items
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = String
Full name: Microsoft.FSharp.Core.string
url.queryString: string
val not : ('a -> 'a -> bool) (requires equality)
Full name: Script.parser.not
val psplitBy : char:char -> Parser<string list,'a>
Full name: Script.parser.psplitBy
Multiple items
val char : char
--------------------
type char = Char
Full name: Microsoft.FSharp.Core.char
val keep : Parser<string,'a>
val manySatisfy : (char -> bool) -> Parser<string,'u>
Full name: FParsec.CharParsers.manySatisfy
val toss : Parser<string,'a>
val pstring : string -> Parser<string,'u>
Full name: FParsec.CharParsers.pstring
val sepBy : Parser<'a,'u> -> Parser<'b,'u> -> Parser<'a list,'u>
Full name: FParsec.Primitives.sepBy
val pathParser : Parser<string list,unit>
Full name: Script.parser.pathParser
val keyOrVal : Parser<string,unit>
Full name: Script.parser.keyOrVal
val many : Parser<'a,'u> -> Parser<'a list,'u>
Full name: FParsec.Primitives.many
val noneOf : seq<char> -> Parser<char,'u>
Full name: FParsec.CharParsers.noneOf
Multiple items
type String =
new : value:char -> string + 7 overloads
member Chars : int -> char
member Clone : unit -> obj
member CompareTo : value:obj -> int + 1 overload
member Contains : value:string -> bool
member CopyTo : sourceIndex:int * destination:char[] * destinationIndex:int * count:int -> unit
member EndsWith : value:string -> bool + 2 overloads
member Equals : obj:obj -> bool + 2 overloads
member GetEnumerator : unit -> CharEnumerator
member GetHashCode : unit -> int
...
Full name: System.String
--------------------
String(value: nativeptr<char>) : unit
String(value: nativeptr<sbyte>) : unit
String(value: char []) : unit
String(c: char, count: int) : unit
String(value: nativeptr<char>, startIndex: int, length: int) : unit
String(value: nativeptr<sbyte>, startIndex: int, length: int) : unit
String(value: char [], startIndex: int, length: int) : unit
String(value: nativeptr<sbyte>, startIndex: int, length: int, enc: Text.Encoding) : unit
String.Concat([<ParamArray>] values: string []) : string
(+0 other overloads)
String.Concat(values: Collections.Generic.IEnumerable<string>) : string
(+0 other overloads)
String.Concat<'T>(values: Collections.Generic.IEnumerable<'T>) : string
(+0 other overloads)
String.Concat([<ParamArray>] args: obj []) : string
(+0 other overloads)
String.Concat(arg0: obj) : string
(+0 other overloads)
String.Concat(str0: string, str1: string) : string
(+0 other overloads)
String.Concat(arg0: obj, arg1: obj) : string
(+0 other overloads)
String.Concat(str0: string, str1: string, str2: string) : string
(+0 other overloads)
String.Concat(arg0: obj, arg1: obj, arg2: obj) : string
(+0 other overloads)
String.Concat(str0: string, str1: string, str2: string, str3: string) : string
(+0 other overloads)
val keyValuePair : Parser<(string * string),unit>
Full name: Script.parser.keyValuePair
val queryParser : Parser<(string * string) list,unit>
Full name: Script.parser.queryParser
val preamble : Parser<unit,unit>
Full name: Script.parser.preamble
val parse : ParserCombinator
Full name: FParsec.Primitives.parse
val ignore : value:'T -> unit
Full name: Microsoft.FSharp.Core.Operators.ignore
val skipString : string -> Parser<unit,'u>
Full name: FParsec.CharParsers.skipString
type Char =
struct
member CompareTo : value:obj -> int + 1 overload
member Equals : obj:obj -> bool + 1 overload
member GetHashCode : unit -> int
member GetTypeCode : unit -> TypeCode
member ToString : unit -> string + 1 overload
static val MaxValue : char
static val MinValue : char
static member ConvertFromUtf32 : utf32:int -> string
static member ConvertToUtf32 : highSurrogate:char * lowSurrogate:char -> int + 1 overload
static member GetNumericValue : c:char -> float + 1 overload
...
end
Full name: System.Char
Char.IsDigit(c: char) : bool
Char.IsDigit(s: string, index: int) : bool
val urlParser : Parser<url,unit>
Full name: Script.parser.urlParser
val pathString : string
val queryString : string
val restOfLine : bool -> Parser<string,'u>
Full name: FParsec.CharParsers.restOfLine
namespace System.Net
namespace System.Text
type HttpListenerResponse =
member Abort : unit -> unit
member AddHeader : name:string * value:string -> unit
member AppendCookie : cookie:Cookie -> unit
member AppendHeader : name:string * value:string -> unit
member Close : unit -> unit + 1 overload
member ContentEncoding : Encoding with get, set
member ContentLength64 : int64 with get, set
member ContentType : string with get, set
member Cookies : CookieCollection with get, set
member CopyFrom : templateResponse:HttpListenerResponse -> unit
...
Full name: System.Net.HttpListenerResponse
val me : HttpListenerResponse
member HttpListenerResponse.WriteAsync : str:string -> Async<unit>
Full name: Script.listener.WriteAsync
val str : string
type Encoding =
member BodyName : string
member Clone : unit -> obj
member CodePage : int
member DecoderFallback : DecoderFallback with get, set
member EncoderFallback : EncoderFallback with get, set
member EncodingName : string
member Equals : value:obj -> bool
member GetByteCount : chars:char[] -> int + 3 overloads
member GetBytes : chars:char[] -> byte[] + 5 overloads
member GetCharCount : bytes:byte[] -> int + 2 overloads
...
Full name: System.Text.Encoding
property Encoding.ASCII: Encoding
Encoding.GetBytes(s: string) : byte []
Encoding.GetBytes(chars: char []) : byte []
Encoding.GetBytes(chars: char [], index: int, count: int) : byte []
Encoding.GetBytes(chars: nativeptr<char>, charCount: int, bytes: nativeptr<byte>, byteCount: int) : int
Encoding.GetBytes(s: string, charIndex: int, charCount: int, bytes: byte [], byteIndex: int) : int
Encoding.GetBytes(chars: char [], charIndex: int, charCount: int, bytes: byte [], byteIndex: int) : int
argument s : string
property HttpListenerResponse.OutputStream: IO.Stream
member IO.Stream.AsyncWrite : buffer:byte [] * ?offset:int * ?count:int -> Async<unit>
val root : string
Full name: Script.listener.root
val host : string
Full name: Script.listener.host
val initListener : HttpListener
Full name: Script.listener.initListener
val l : HttpListener
Multiple items
type HttpListener =
new : unit -> HttpListener
member Abort : unit -> unit
member AuthenticationSchemeSelectorDelegate : AuthenticationSchemeSelector with get, set
member AuthenticationSchemes : AuthenticationSchemes with get, set
member BeginGetContext : callback:AsyncCallback * state:obj -> IAsyncResult
member Close : unit -> unit
member DefaultServiceNames : ServiceNameCollection
member EndGetContext : asyncResult:IAsyncResult -> HttpListenerContext
member ExtendedProtectionPolicy : ExtendedProtectionPolicy with get, set
member ExtendedProtectionSelectorDelegate : ExtendedProtectionSelector with get, set
...
nested type ExtendedProtectionSelector
Full name: System.Net.HttpListener
--------------------
HttpListener() : unit
property HttpListener.Prefixes: HttpListenerPrefixCollection
HttpListenerPrefixCollection.Add(uriPrefix: string) : unit
HttpListener.Start() : unit
val listen : Async<HttpListenerRequest * HttpListenerResponse>
Full name: Script.listener.listen
val async : AsyncBuilder
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
val b : (AsyncCallback * 'a -> IAsyncResult)
val e : (#IAsyncResult -> HttpListenerContext)
HttpListener.BeginGetContext(callback: AsyncCallback, state: obj) : IAsyncResult
HttpListener.EndGetContext(asyncResult: IAsyncResult) : HttpListenerContext
val context : HttpListenerContext
Multiple items
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken
Full name: Microsoft.FSharp.Control.Async
--------------------
type Async<'T>
Full name: Microsoft.FSharp.Control.Async<_>
static member Async.FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member Async.FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member Async.FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member Async.FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
property HttpListenerContext.Request: HttpListenerRequest
property HttpListenerContext.Response: HttpListenerResponse
val listening : requestHandler:(HttpListenerRequest -> HttpListenerResponse -> Async<unit>) -> unit
Full name: Script.listener.listening
val requestHandler : (HttpListenerRequest -> HttpListenerResponse -> Async<unit>)
static member Async.Start : computation:Async<unit> * ?cancellationToken:Threading.CancellationToken -> unit
val request : HttpListenerRequest
val response : HttpListenerResponse
type Console =
static member BackgroundColor : ConsoleColor with get, set
static member Beep : unit -> unit + 1 overload
static member BufferHeight : int with get, set
static member BufferWidth : int with get, set
static member CapsLock : bool
static member Clear : unit -> unit
static member CursorLeft : int with get, set
static member CursorSize : int with get, set
static member CursorTop : int with get, set
static member CursorVisible : bool with get, set
...
Full name: System.Console
Console.ReadLine() : string
module listener
from Script
val httpWrapper : restHandler:('a -> HttpListenerResponse -> Async<string>) -> request:'a -> response:HttpListenerResponse -> Async<unit>
Full name: Script.http.httpWrapper
val restHandler : ('a -> HttpListenerResponse -> Async<string>)
val request : 'a
property HttpListenerResponse.StatusCode: int
Multiple items
val int : value:'T -> int (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.int
--------------------
type int = int32
Full name: Microsoft.FSharp.Core.int
--------------------
type int<'Measure> = int
Full name: Microsoft.FSharp.Core.int<_>
type HttpStatusCode =
| Continue = 100
| SwitchingProtocols = 101
| OK = 200
| Created = 201
| Accepted = 202
| NonAuthoritativeInformation = 203
| NoContent = 204
| ResetContent = 205
| PartialContent = 206
| MultipleChoices = 300
...
Full name: System.Net.HttpStatusCode
field HttpStatusCode.OK = 200
property HttpListenerResponse.ContentType: string
val mainOutput : string
member HttpListenerResponse.WriteAsync : str:string -> Async<unit>
HttpListenerResponse.Close() : unit
HttpListenerResponse.Close(responseEntity: byte [], willBlock: bool) : unit
module parser
from Script
module http
from Script
Multiple items
module List
from Microsoft.FSharp.Collections
--------------------
type List<'T> =
| ( [] )
| ( :: ) of Head: 'T * Tail: 'T list
interface IEnumerable
interface IEnumerable<'T>
member Head : 'T
member IsEmpty : bool
member Item : index:int -> 'T with get
member Length : int
member Tail : 'T list
static member Cons : head:'T * tail:'T list -> 'T list
static member Empty : 'T list
Full name: Microsoft.FSharp.Collections.List<_>
val sortBy : projection:('T -> 'Key) -> list:'T list -> 'T list (requires comparison)
Full name: Microsoft.FSharp.Collections.List.sortBy
val fst : tuple:('T1 * 'T2) -> 'T1
Full name: Microsoft.FSharp.Core.Operators.fst
val pretty : s:'a -> string
Full name: Script.pretty
val s : 'a
val sprintf : format:Printf.StringFormat<'T> -> 'T
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
val listening : requestHandler:(System.Net.HttpListenerRequest -> System.Net.HttpListenerResponse -> Async<unit>) -> unit
Full name: Script.listener.listening
val httpWrapper : restHandler:('a -> System.Net.HttpListenerResponse -> Async<string>) -> request:'a -> response:System.Net.HttpListenerResponse -> Async<unit>
Full name: Script.http.httpWrapper
val request : System.Net.HttpListenerRequest
val response : System.Net.HttpListenerResponse
property System.Net.HttpListenerRequest.Url: System.Uri
property System.Uri.AbsoluteUri: string
val urlParser : FParsec.Primitives.Parser<url,unit>
Full name: Script.parser.urlParser
val pathParser : FParsec.Primitives.Parser<string list,unit>
Full name: Script.parser.pathParser
val chef : string
val queryParser : FParsec.Primitives.Parser<(string * string) list,unit>
Full name: Script.parser.queryParser
active recognizer ATOZ: (string * string) list -> (string * string) list
Full name: Script.( |ATOZ| )
val b : string
val a : string
Multiple items
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = System.String
Full name: Microsoft.FSharp.Core.string
val c : string
val m : string
val query : (string * string) list
val path : string list
More information
Link: | http://fssnip.net/td |
Posted: | 9 years ago |
Author: | Ted Cackowski Jr |
Tags: |
rest
, restful
, fparsec
, parsec
, parse
, parser
, parsers
, httplistener
, pattern matching
, web
, script
, fsi
, url
, rpc
|