Skip to content

Commit

Permalink
Merge all implementations into single file for easier paket reference #…
Browse files Browse the repository at this point in the history
  • Loading branch information
tachyus-ryan committed Oct 13, 2014
1 parent 2e31dc5 commit 4a123a3
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 93 deletions.
3 changes: 3 additions & 0 deletions RELEASE_NOTES.md
@@ -1,3 +1,6 @@
### New in 3.0.19 - (Released 2014/10/13)
* Merge all implementations into one file and add .fsi signature

### New in 3.0.18 - (Released 2014/10/12)
* Use Paket for package management
* FSharp.Core 4.3.1.0
Expand Down
92 changes: 90 additions & 2 deletions src/Frank/Frank.fs
Expand Up @@ -201,8 +201,9 @@ module Core =
|> respond HttpStatusCode.NotAcceptable ignore (Some(new StringContent("406 Not Acceptable")))
|> async.Return

let findFormatterFor mediaType =
Seq.find (fun (formatter: MediaTypeFormatter) ->
let findFormatterFor mediaType formatters =
formatters
|> Seq.find (fun (formatter: MediaTypeFormatter) ->
formatter.SupportedMediaTypes
|> Seq.map (fun value -> value.MediaType)
|> Seq.exists ((=) mediaType))
Expand Down Expand Up @@ -275,3 +276,90 @@ type AsyncHandler =
new (f) = { inherit DelegatingHandler(); AsyncSend = f }
override x.SendAsync(request, cancellationToken) =
Async.StartAsTask(x.AsyncSend request, cancellationToken = cancellationToken)


(**
* # F# Extensions to System.Web.Http
*)

namespace System.Web.Http

open System.Net
open System.Net.Http
open System.Web.Http
open Frank

// HTTP resources expose an resource handler function at a given uri.
// In the common MVC-style frameworks, this would roughly correspond
// to a `Controller`. Resources should represent a single entity type,
// and it is important to note that a `Foo` is not the same entity
// type as a `Foo list`, which is where most MVC approaches go wrong.
// The optional `uriMatcher` parameter allows the consumer to provide
// a more advanced uri matching algorithm, such as one using regular
// expressions.
//
// Additional notes:
// Should this type subclass HttpServer? If it did it could get
// it's own configration and have its own route table. I'm not
// convinced System.Web.Routing is worth it, but it's an option.
type HttpResource(template: string, methods, handler) =
inherit System.Web.Http.Routing.HttpRoute(routeTemplate = template.TrimStart([|'/'|]),
defaults = null,
constraints = null,
dataTokens = null,
handler = new AsyncHandler(resourceHandlerOrDefault methods handler))
with
member x.Name = template

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module HttpResource =
let makeHandler(httpMethod, handler) = function
| (request: HttpRequestMessage) when request.Method = httpMethod -> Some(handler request)
| _ -> None

// Helpers to more easily map `HttpApplication` functions to methods to be composed into `HttpResource`s.
let mapResourceHandler(httpMethod: HttpMethod, handler) = [httpMethod], makeHandler(httpMethod, handler)
let get handler = mapResourceHandler(HttpMethod.Get, handler)
let post handler = mapResourceHandler(HttpMethod.Post, handler)
let put handler = mapResourceHandler(HttpMethod.Put, handler)
let delete handler = mapResourceHandler(HttpMethod.Delete, handler)
let options handler = mapResourceHandler(HttpMethod.Options, handler)
let trace handler = mapResourceHandler(HttpMethod.Trace, handler)
let patch handler = mapResourceHandler(HttpMethod("PATCH"), handler)

// Helper to more easily access URL params
let getParam<'T> (request:HttpRequestMessage) key =
let values = request.GetRouteData().Values
if values.ContainsKey(key) then
Some(values.[key] :?> 'T)
else None

// We can use several methods to merge multiple handlers together into a single resource.
// Our chosen mechanism here is merging functions into a larger function of the same signature.
// This allows us to create resources as follows:
//
// let resource = get app1 <|> post app2 <|> put app3 <|> delete app4
//
// The intent here is to build a resource, with at most one handler per HTTP method. This goes
// against a lot of the "RESTful" approaches that just merge a bunch of method handlers at
// different URI addresses.
let orElse left right =
fst left @ fst right,
fun request ->
match snd left request with
| None -> snd right request
| result -> result

let inline (<|>) left right = orElse left right

let route uri handler = HttpResource(uri, fst handler, snd handler)

let routeResource uri handlers = route uri <| Seq.reduce orElse handlers

let ``404 Not Found`` (request: HttpRequestMessage) = async {
return request.CreateResponse(HttpStatusCode.NotFound)
}

let register (resources: seq<HttpResource>) (config: HttpConfiguration) =
for resource in resources do
config.Routes.Add(resource.Name, resource)
169 changes: 169 additions & 0 deletions src/Frank/Frank.fsi
@@ -0,0 +1,169 @@
(* # Frank
## License
Author: Ryan Riley <ryan.riley@panesofglass.org>
Copyright (c) 2011-2012, Ryan Riley.
Licensed under the Apache License, Version 2.0.
See LICENSE.txt for details.
*)
namespace Frank

open System
open System.Collections.Generic
open System.IO
open System.Net
open System.Net.Http
open System.Net.Http.Formatting
open System.Net.Http.Headers
open System.Text
open System.Threading.Tasks

/// `HttpApplication` defines the contract for processing any request.
/// An application takes an `HttpRequestMessage` and returns an `HttpRequestHandler` asynchronously.
type HttpApplication = HttpRequestMessage -> Async<HttpResponseMessage>

/// An empty `HttpContent` type.
type EmptyContent =
inherit HttpContent
/// Creates a new instance of `EmptyContent`
new : unit -> EmptyContent

[<AutoOpen>]
module Core =
type HttpContent with
/// Returns an `EmptyContent`.
member Empty : HttpContent

type HttpResponseHeadersBuilder = FSharpx.Reader.Reader<HttpResponseMessage, unit>

val respond : statusCode: HttpStatusCode -> headers: HttpResponseHeadersBuilder -> content: #HttpContent option -> request: HttpRequestMessage -> HttpResponseMessage

(* General Headers *)

val Date : x: DateTimeOffset -> HttpResponseHeadersBuilder
val Connection : x: string -> HttpResponseHeadersBuilder
val Trailer : x: string -> HttpResponseHeadersBuilder
val ``Transfer-Encoding`` : x: string -> HttpResponseHeadersBuilder
val Upgrade : x: string -> HttpResponseHeadersBuilder
val Via : x: string -> HttpResponseHeadersBuilder
val ``Cache-Control`` : x: string -> HttpResponseHeadersBuilder
val Pragma : x: string -> HttpResponseHeadersBuilder

(* Response Headers *)

val Age : x: TimeSpan -> HttpResponseHeadersBuilder
val ``Retry-After`` : x: string -> HttpResponseHeadersBuilder
val Server : x: string -> HttpResponseHeadersBuilder
val Warning : x: string -> HttpResponseHeadersBuilder
val ``Accept-Ranges`` : x: string -> HttpResponseHeadersBuilder
val Vary : x: string -> HttpResponseHeadersBuilder
val ``Proxy-Authenticate`` : x: string -> HttpResponseHeadersBuilder
val ``WWW-Authenticate`` : x: string -> HttpResponseHeadersBuilder

(* Entity Headers *)

val Allow : allowedMethods: #seq<HttpMethod> -> HttpResponseHeadersBuilder
val Location : x: Uri -> HttpResponseHeadersBuilder
val ``Content-Disposition`` : x: string -> HttpResponseHeadersBuilder
val ``Content-Encoding`` : x: seq<string> -> HttpResponseHeadersBuilder
val ``Content-Language`` : x: seq<string> -> HttpResponseHeadersBuilder
val ``Content-Length`` : x: int64 -> HttpResponseHeadersBuilder
val ``Content-Location`` : x: Uri -> HttpResponseHeadersBuilder
val ``Content-MD5`` : x: byte[] -> HttpResponseHeadersBuilder
val ``Content-Range`` : from: int64 -> _to: int64 -> length: int64 -> HttpResponseHeadersBuilder
val ``Content-Type`` : x: string -> HttpResponseHeadersBuilder
val ETag : tag: string -> isWeak: bool -> HttpResponseHeadersBuilder
val Expires : x: DateTimeOffset -> HttpResponseHeadersBuilder
val ``Last Modified`` : x: DateTimeOffset -> HttpResponseHeadersBuilder

/// Returns a response message with status code `200 OK`
val OK : headers: HttpResponseHeadersBuilder -> content: #HttpContent option -> (HttpRequestMessage -> HttpResponseMessage)

(* Allow Header Helpers *)

/// `OPTIONS` responses should return the allowed methods, and this helper facilitates method calls.
val options : allowedMethods: seq<HttpMethod> -> HttpApplication

/// In some instances, you need to respond with a `405 Message Not Allowed` response.
/// The HTTP spec requires that this message include an `Allow` header with the allowed
/// HTTP methods.
val ``405 Method Not Allowed`` : allowedMethods: seq<HttpMethod> -> HttpApplication

(* Content Negotiation Helpers *)

val ``406 Not Acceptable`` : HttpApplication

val findFormatterFor : mediaType: string -> formatters: seq<MediaTypeFormatter> -> MediaTypeFormatter

/// `formatWith` allows you to specify a specific `formatter` with which to render a representation
/// of your content body.
/// The `Web API` tries to do this for you at this time, so this function is likely to be clobbered,
/// or rather, wrapped again in another representation. Hopefully, this will get fixed in a future release.
val formatWith : mediaType: string -> formatter: MediaTypeFormatter -> body: 'a -> HttpContent

val IO : stream: Stream -> HttpContent
val Str : s: string -> HttpContent
val Formatted : s: string * encoding: Text.Encoding * mediaType: string -> HttpContent
val Form : pairs: seq<string * string> -> HttpContent
val Bytes : bytes: byte[] -> HttpContent
val Segment : segment: ArraySegment<byte> -> HttpContent

val negotiateMediaType : formatters: seq<MediaTypeFormatter> -> HttpRequestMessage -> string option

val runConneg : formatters: seq<MediaTypeFormatter> -> f: (HttpRequestMessage -> Async<_>) -> HttpApplication

/// Adds a default response of "405 Method Not Allowed" to a handler supporting the specified methods.
val resourceHandlerOrDefault : methods: seq<HttpMethod> -> handler: (HttpRequestMessage -> Async<HttpResponseMessage> option) -> HttpApplication

/// Adapts an `HttpApplication` function into a `System.Net.Http.DelegatingHandler`.
type AsyncHandler =
inherit DelegatingHandler
val AsyncSend : HttpRequestMessage -> Async<HttpResponseMessage>


(**
* # F# Extensions to System.Web.Http
*)

namespace System.Web.Http

open System.Net
open System.Net.Http
open System.Web.Http
open Frank

/// HTTP resources expose an resource handler function at a given uri.
/// In the common MVC-style frameworks, this would roughly correspond
/// to a `Controller`. Resources should represent a single entity type,
/// and it is important to note that a `Foo` is not the same entity
/// type as a `Foo list`, which is where most MVC approaches go wrong.
/// The optional `uriMatcher` parameter allows the consumer to provide
/// a more advanced uri matching algorithm, such as one using regular
/// expressions.
type HttpResource =
inherit System.Web.Http.Routing.HttpRoute
new : template: string * methods: seq<HttpMethod> * handler: (HttpRequestMessage -> Async<HttpResponseMessage> option) -> HttpResource
member Name : string

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module HttpResource =

val mapResourceHandler : httpMethod: HttpMethod * handler: (HttpRequestMessage -> 'b) -> HttpMethod list * (#HttpRequestMessage -> 'b option)
val get : handler: (HttpRequestMessage -> 'b) -> HttpMethod list * (#HttpRequestMessage -> 'b option)
val post : handler: (HttpRequestMessage -> 'b) -> HttpMethod list * (#HttpRequestMessage -> 'b option)
val put : handler: (HttpRequestMessage -> 'b) -> HttpMethod list * (#HttpRequestMessage -> 'b option)
val delete : handler: (HttpRequestMessage -> 'b) -> HttpMethod list * (#HttpRequestMessage -> 'b option)
val options : handler: (HttpRequestMessage -> 'b) -> HttpMethod list * (#HttpRequestMessage -> 'b option)
val trace : handler: (HttpRequestMessage -> 'b) -> HttpMethod list * (#HttpRequestMessage -> 'b option)
val patch : handler: (HttpRequestMessage -> 'b) -> HttpMethod list * (#HttpRequestMessage -> 'b option)

/// Helper to more easily access URL params
val getParam<'a> : request: HttpRequestMessage -> key: string -> 'a option
val orElse : left: 'a list * ('b -> 'c option) -> right: 'a list * ('b -> 'c option) -> 'a list * ('b -> 'c option)
val inline (<|>) : left: 'a list * ('b -> 'c option) -> right: 'a list * ('b -> 'c option) -> 'a list * ('b -> 'c option)
val route : uri: string -> handler: seq<HttpMethod> * (HttpRequestMessage -> Async<HttpResponseMessage> option) -> HttpResource
val routeResource : uri: string -> handlers: seq<HttpMethod list * (HttpRequestMessage -> Async<HttpResponseMessage> option)> -> HttpResource
val ``404 Not Found`` : HttpApplication
val register : resources: seq<HttpResource> -> config: HttpConfiguration -> unit
2 changes: 1 addition & 1 deletion src/Frank/Frank.fsproj
Expand Up @@ -53,8 +53,8 @@
<ItemGroup>
<None Include="App.config" />
<None Include="paket.references" />
<Compile Include="Frank.fsi" />
<Compile Include="Frank.fs" />
<Compile Include="System.Web.Http.fs" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
Expand Down
90 changes: 0 additions & 90 deletions src/Frank/System.Web.Http.fs

This file was deleted.

0 comments on commit 4a123a3

Please sign in to comment.