Skip to content

Commit f0aa1fd

Browse files
committed
Merge branch 'feature/negotiate_handler' into develop
2 parents eba5b7f + 6ddcfa0 commit f0aa1fd

File tree

4 files changed

+560
-17
lines changed

4 files changed

+560
-17
lines changed

README.md

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ The old NuGet package has been unlisted and will not receive any updates any mor
5151
- [text](#text)
5252
- [json](#json)
5353
- [xml](#xml)
54+
- [negotiate](#negotiate)
55+
- [negotiateWith](#negotiatewith)
5456
- [htmlFile](#htmlfile)
5557
- [dotLiquid](#dotliquid)
5658
- [dotLiquidTemplate](#dotliquidtemplate)
@@ -481,7 +483,7 @@ let app =
481483

482484
### setBody
483485

484-
`setBody` sets or modifies the body of the `HttpResponse`. This http handler triggers the response being sent to the client and other http handlers afterwards will not be able to modify the HTTP headers anymore.
486+
`setBody` sets or modifies the body of the `HttpResponse`. This http handler triggers a response to the client and other http handlers will not be able to modify the HTTP headers afterwards any more.
485487

486488
#### Example:
487489

@@ -494,7 +496,7 @@ let app =
494496

495497
### setBodyAsString
496498

497-
`setBodyAsString` sets or modifies the body of the `HttpResponse`. This http handler triggers the response being sent to the client and other http handlers afterwards will not be able to modify the HTTP headers anymore.
499+
`setBodyAsString` sets or modifies the body of the `HttpResponse`. This http handler triggers a response to the client and other http handlers will not be able to modify the HTTP headers afterwards any more.
498500

499501
#### Example:
500502

@@ -507,7 +509,7 @@ let app =
507509

508510
### text
509511

510-
`text` sets or modifies the body of the `HttpResponse`. This http handler triggers the response being sent to the client and other http handlers afterwards will not be able to modify the HTTP headers anymore.
512+
`text` sets or modifies the body of the `HttpResponse` by sending a plain text value to the client.. This http handler triggers a response to the client and other http handlers will not be able to modify the HTTP headers afterwards any more.
511513

512514
The different between `text` and `setBodyAsString` is that this http handler also sets the `Content-Type` HTTP header to `text/plain`.
513515

@@ -522,7 +524,7 @@ let app =
522524

523525
### json
524526

525-
`json` sets or modifies the body of the `HttpResponse`. This http handler triggers the response being sent to the client and other http handlers afterwards will not be able to modify the HTTP headers anymore. It also sets the `Content-Type` HTTP header to `application/json`.
527+
`json` sets or modifies the body of the `HttpResponse` by sending a JSON serialized object to the client. This http handler triggers a response to the client and other http handlers will not be able to modify the HTTP headers afterwards any more. It also sets the `Content-Type` HTTP header to `application/json`.
526528

527529
#### Example:
528530

@@ -541,7 +543,7 @@ let app =
541543

542544
### xml
543545

544-
`xml` sets or modifies the body of the `HttpResponse`. This http handler triggers the response being sent to the client and other http handlers afterwards will not be able to modify the HTTP headers anymore. It also sets the `Content-Type` HTTP header to `application/xml`.
546+
`xml` sets or modifies the body of the `HttpResponse` by sending an XML serialized object to the client. This http handler triggers a response to the client and other http handlers will not be able to modify the HTTP headers afterwards any more. It also sets the `Content-Type` HTTP header to `application/xml`.
545547

546548
#### Example:
547549

@@ -559,9 +561,61 @@ let app =
559561
]
560562
```
561563

564+
### negotiate
565+
566+
`negotiate` sets or modifies the body of the `HttpResponse` by inspecting the `Accept` header of the HTTP request and deciding if the response should be sent in JSON or XML. If the client is indifferent then the default response will be sent in JSON.
567+
568+
This http handler triggers a response to the client and other http handlers will not be able to modify the HTTP headers afterwards any more.
569+
570+
#### Example:
571+
572+
```fsharp
573+
[<CLIMutable>]
574+
type Person =
575+
{
576+
FirstName : string
577+
LastName : string
578+
}
579+
580+
let app =
581+
choose [
582+
route "/foo" >=> negotiate { FirstName = "Foo"; LastName = "Bar" }
583+
]
584+
```
585+
586+
### negotiateWith
587+
588+
`negotiateWith` sets or modifies the body of the `HttpResponse` by inspecting the `Accept` header of the HTTP request and deciding in what mimeType the response should be sent. A dictionary of type `IDictionary<string, obj -> HttpHandler>` is used to determine which `obj -> HttpHandler` function should be used to convert an object into a `HttpHandler` for a given mime type.
589+
590+
This http handler triggers a response to the client and other http handlers will not be able to modify the HTTP headers afterwards any more.
591+
592+
#### Example:
593+
594+
```fsharp
595+
[<CLIMutable>]
596+
type Person =
597+
{
598+
FirstName : string
599+
LastName : string
600+
}
601+
602+
// xml and json are the two HttpHandler functions from above
603+
let rules =
604+
dict [
605+
"*/*" , xml
606+
"application/json", json
607+
"application/xml" , xml
608+
]
609+
610+
let app =
611+
choose [
612+
route "/foo" >=> negotiateWith rules { FirstName = "Foo"; LastName = "Bar" }
613+
]
614+
```
615+
562616
### htmlFile
563617

564-
`htmlFile` sets or modifies the body of the `HttpResponse` with the contents of a physical html file. This http handler triggers the response being sent to the client and other http handlers afterwards will not be able to modify the HTTP headers anymore.
618+
`htmlFile` sets or modifies the body of the `HttpResponse` with the contents of a physical html file. This http handler triggers a response to the client and other http handlers will not be able to modify the HTTP headers afterwards any more.
565619

566620
This http handler takes a relative path of a html file as input parameter and sets the HTTP header `Content-Type` to `text/html`.
567621

@@ -576,7 +630,7 @@ let app =
576630

577631
### dotLiquid
578632

579-
`dotLiquid` uses the [DotLiquid](http://dotliquidmarkup.org/) template engine to set or modify the body of the `HttpResponse`. This http handler triggers the response being sent to the client and other http handlers afterwards will not be able to modify the HTTP headers anymore.
633+
`dotLiquid` uses the [DotLiquid](http://dotliquidmarkup.org/) template engine to set or modify the body of the `HttpResponse`. This http handler triggers a response to the client and other http handlers will not be able to modify the HTTP headers afterwards any more.
580634

581635
The `dotLiquid` handler requires the content type and the actual template to be passed in as two string values together with an object model. This handler is supposed to be used as the base handler for other http handlers which want to utilize the DotLiquid template engine (e.g. you could create an SVG handler on top of it).
582636

@@ -599,7 +653,7 @@ let app =
599653

600654
### dotLiquidTemplate
601655

602-
`dotLiquidTemplate` uses the [DotLiquid](http://dotliquidmarkup.org/) template engine to set or modify the body of the `HttpResponse`. This http handler triggers the response being sent to the client and other http handlers afterwards will not be able to modify the HTTP headers anymore.
656+
`dotLiquidTemplate` uses the [DotLiquid](http://dotliquidmarkup.org/) template engine to set or modify the body of the `HttpResponse`. This http handler triggers a response to the client and other http handlers will not be able to modify the HTTP headers afterwards any more.
603657

604658
This http handler takes a relative path of a template file, an associated model and the contentType of the response as parameters.
605659

@@ -639,7 +693,7 @@ let app =
639693

640694
### razorView
641695

642-
`razorView` uses the official ASP.NET Core MVC Razor view engine to compile a page and set the body of the `HttpResponse`. This http handler triggers the response being sent to the client and other http handlers afterwards will not be able to modify the HTTP headers anymore.
696+
`razorView` uses the official ASP.NET Core MVC Razor view engine to compile a page and set the body of the `HttpResponse`. This http handler triggers a response to the client and other http handlers will not be able to modify the HTTP headers afterwards any more.
643697

644698
The `razorView` handler requires the view name, an object model and the contentType of the response to be passed in. It also requires to be enabled through the `AddRazorEngine` function during start-up.
645699

src/Giraffe/Giraffe.fsproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<AssemblyName>Giraffe</AssemblyName>
5-
<VersionPrefix>0.1.0-alpha009</VersionPrefix>
5+
<VersionPrefix>0.1.0-alpha010</VersionPrefix>
66
<Description>A native functional ASP.NET Core web framework for F# developers.</Description>
77
<Copyright>Copyright 2017 Dustin Moris Gorski</Copyright>
88
<NeutralLanguage>en-GB</NeutralLanguage>

src/Giraffe/HttpHandlers.fs

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module Giraffe.HttpHandlers
22

33
open System
44
open System.Text
5+
open System.Collections.Generic
56
open Microsoft.AspNetCore.Http
67
open Microsoft.AspNetCore.Hosting
78
open Microsoft.Extensions.Primitives
@@ -380,4 +381,70 @@ let renderHtml (document: HtmlNode) =
380381
document
381382
|> renderHtmlDocument
382383
|> setBodyAsString
383-
ctx |> (setHttpHeader "Content-Type" "text/html" >=> htmlHandler)
384+
ctx |> (setHttpHeader "Content-Type" "text/html" >=> htmlHandler)
385+
386+
/// ---------------------------
387+
/// Content negotioation handlers
388+
/// ---------------------------
389+
390+
let defaultNegotioationRules =
391+
dict [
392+
"*/*" , json
393+
"application/json", json
394+
"application/xml" , xml
395+
"text/xml" , xml
396+
]
397+
398+
type AcceptedMimeType =
399+
{
400+
OriginalValue : string
401+
MimeType : string
402+
Preference : float
403+
}
404+
static member FromString (value : string) =
405+
let values =
406+
value.Split([| "; q=" |], StringSplitOptions.RemoveEmptyEntries)
407+
|> Array.map (fun x -> x.Trim())
408+
409+
if values.Length > 2 then failwithf "Unexpected value in HTTP Accept header: %s" value
410+
else if values.Length = 2 then { OriginalValue = value; MimeType = values.[0]; Preference = float values.[1] }
411+
else { OriginalValue = value; MimeType = values.[0]; Preference = 1.0 }
412+
413+
let negotiateWith (rules : IDictionary<string, obj -> HttpHandler>) (responseObj : obj) =
414+
fun (ctx : HttpHandlerContext) ->
415+
let acceptHeaderValues =
416+
ctx.HttpContext.Request.GetTypedHeaders()
417+
|> fun headers -> headers.Accept
418+
419+
if isNull acceptHeaderValues || acceptHeaderValues.Count = 0
420+
then
421+
rules.Keys
422+
|> Seq.head
423+
|> fun key -> rules.[key]
424+
|> fun handler -> handler responseObj ctx
425+
else
426+
let acceptedTypes =
427+
acceptHeaderValues
428+
|> Seq.map (fun h -> h.ToString() |> AcceptedMimeType.FromString)
429+
430+
acceptedTypes
431+
|> Seq.map (fun t -> t.MimeType)
432+
|> Seq.exists rules.ContainsKey
433+
|> function
434+
| false ->
435+
setStatusCode 406
436+
>=> (acceptedTypes
437+
|> Seq.map (fun t -> t.OriginalValue)
438+
|> String.concat ", "
439+
|> sprintf "%s is unacceptable by the server."
440+
|> text)
441+
| true ->
442+
acceptedTypes
443+
|> Seq.sortByDescending (fun t -> t.Preference)
444+
|> Seq.find (fun t -> rules.ContainsKey t.MimeType)
445+
|> fun t -> rules.[t.MimeType]
446+
|> fun handler -> handler responseObj
447+
<| ctx
448+
449+
let negotiate (responseObj : obj) =
450+
negotiateWith defaultNegotioationRules responseObj

0 commit comments

Comments
 (0)