├── Furl.fsx ├── README.md └── license /Furl.fsx: -------------------------------------------------------------------------------- 1 | #r "System.Net.Http" 2 | 3 | open System 4 | open System.Net.Http 5 | 6 | let private addHeader (headers : Headers.HttpHeaders) (name, value : string) = 7 | headers.Add (name, value) 8 | 9 | let private addBody (req : HttpRequestMessage) headers body = 10 | req.Content <- new StringContent (body) 11 | let contentTypeHeader = 12 | headers |> List.tryFind (fun (n, _) -> n = "Content-Type") 13 | contentTypeHeader 14 | |> Option.iter (fun (_, v) -> req.Content.Headers.ContentType.MediaType <- v) 15 | 16 | let result (t : System.Threading.Tasks.Task<_>) = t.Result 17 | 18 | let composeMessage meth (url : Uri) headers body = 19 | let req = new HttpRequestMessage (meth, url) 20 | Option.iter (addBody req headers) body 21 | 22 | headers 23 | |> List.partition (fun (n, _) -> n = "Content-Type") 24 | |> snd 25 | |> List.iter (addHeader req.Headers) 26 | req 27 | 28 | let get url headers = 29 | use client = new HttpClient () 30 | // HttpMethod is qualified to avoid collision with FSharp.Data.HttpMethod, 31 | // if FSharp.Data is imported in a script as well as Furl. 32 | composeMessage Net.Http.HttpMethod.Get (Uri url) headers None 33 | |> client.SendAsync 34 | |> result 35 | 36 | let post url headers body = 37 | use client = new HttpClient () 38 | // HttpMethod is qualified to avoid collision with FSharp.Data.HttpMethod, 39 | // if FSharp.Data is imported in a script as well as Furl. 40 | composeMessage Net.Http.HttpMethod.Post (Uri url) headers (Some body) 41 | |> client.SendAsync 42 | |> result 43 | 44 | let bodyText (resp : HttpResponseMessage) = 45 | resp.Content.ReadAsStringAsync().Result 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Furl 2 | Interact with HTTP resources using F# scripting 3 | 4 | When developing REST APIs, you often need to test or verify them in various ad hoc manners. You could use cURL, but if you're more familiar with F# than Bash scripting, you can use Furl instead. This will also enable you to leverage JSON or XML **type providers** in your scripts or exploratory tests. 5 | 6 | ## Example 7 | 8 | Imagine that you have an HTTP API that exposes resources for making restaurant reservations. For example, if you want to know how many free seats are available for a particular date, you can perform a simple `GET` request: 9 | 10 | ```fsharp 11 | > get "http://localhost:56268/availability/2017/3/17" [] |> bodyText;; 12 | > val it : string = "{"openings":[{"date":"2017-03-17","seats":10}]}" 13 | ``` 14 | 15 | ## Use 16 | 17 | Furl is a **single F# script file**, so you can load it into any FSI session and starting using it right away. 18 | 19 | ## Contributions 20 | 21 | Furl has exactly the features I've needed so far. For the last half a year, I've only needed to do `GET` and `POST` HTTP requests, so those are the only HTTP methods available. If you need more features, please send a pull request, or [open an issue](https://github.com/ploeh/Furl/issues). 22 | 23 | The philosophy behind Furl is to keep it simple and in a single file. Its purpose is to support exploratory testing, not to address every possible use case ever. 24 | 25 | # Using type providers 26 | 27 | Often, when interacting with REST APIs, you must either send JSON or XML, or you receive data in one of those formats. 28 | 29 | With [F# Data](http://fsharp.github.io/FSharp.Data), you can use XML or JSON type providers to create even complex data structures, or to parse the responses you receive. 30 | 31 | In order to use the XML or JSON type provider, you must first load *F# Data* in FSI: 32 | 33 | ```fsharp 34 | > #r @".\packages\FSharp.Data\lib\net40\FSharp.Data.dll";; 35 | ``` 36 | 37 | When I develop REST APIs, I often write integration tests in F#. In such tests, I define the data formats in a stand-alone file, so that it's easy to load into FSI. Here's an example: 38 | 39 | ```fsharp 40 | namespace Ploeh.Samples.BookingApi.BoundaryTests 41 | 42 | open FSharp.Data 43 | 44 | type ReservationJson = JsonProvider<""" 45 | { 46 | "date": "some date", 47 | "name": "Mark Seemann", 48 | "email": "mark@example.org", 49 | "quantity": 4 50 | }"""> 51 | 52 | type AvailabilityJson = JsonProvider<""" 53 | { 54 | "openings": [ 55 | { 56 | "date": "some date", 57 | "seats": 10 58 | } 59 | ] 60 | }"""> 61 | ``` 62 | 63 | If you have such a file, you can load it into your FSI session: 64 | 65 | ```fsharp 66 | > #load @".\BookingApi\BookingApi.BoundaryTests\ProvidedTypes.fs";; 67 | ``` 68 | 69 | You can now start your local development server and start interacting with your REST API: 70 | 71 | ```fsharp 72 | > let json = ReservationJson.Root ("2017-03-18", "Mark Seemann", "mark@example.com", 2) |> string;; 73 | > 74 | val json : string = 75 | "{ 76 | "date": "2017-03-18", 77 | "name": "Mark Seemann", 78 | "ema"+[44 chars] 79 | 80 | > post "http://localhost:56268/reservations" ["Content-Type", "application/json"] json;; 81 | > val it : HttpResponseMessage = 82 | StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.StreamContent, 83 | ``` 84 | 85 | In the above example, I've truncated the response, because it actually gives you a lot of data. 86 | 87 | If you want to query a resource and parse the response, that's easy to do as well: 88 | 89 | ```fsharp 90 | > get "http://localhost:56268/availability/2017/3/18" [] |> bodyText |> AvailabilityJson.Parse;; 91 | > val it : FSharp.Data.JsonProvider<...>.Root = 92 | { 93 | "openings": [ 94 | { 95 | "date": "2017-03-18", 96 | "seats": 8 97 | } 98 | ] 99 | } 100 | ``` 101 | 102 | These examples demonstrate how to interact with a local development server, but Furl can be used to interact with any HTTP-based API on your network, including the internet. -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Mark Seemann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | --------------------------------------------------------------------------------