Giter VIP home page Giter VIP logo

http.fs's Introduction

Http.fs logo Http.fs

A gloriously functional HTTP client library for F#! NuGet name: Http.fs.

.Net build (AppVeyor): AppVeyor Build status Mono build (Travis CI): Travis Build Status NuGet package: NuGet

How do I use it?

In it's simplest form, this will get you a web page:

open Hopac
open HttpFs.Client

let body =
  Request.createUrl Get "http://somesite.com"
  |> Request.responseAsString
  |> run

printfn "Here's the body: %s" body

To get into the details a bit more, there are two or three steps to getting what you want from a web page/HTTP response.

1 - A Request (an immutable record type) is built up in a Fluent Builder style as follows:

open System.IO
open System.Text
open Hopac
open HttpFs.Client

let pathOf relativePath =
  let here = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)
  Path.Combine(here, relativePath)

let firstCt, secondCt, thirdCt, fourthCt =
    ContentType.parse "text/plain" |> Option.get,
    ContentType.parse "text/plain" |> Option.get,
    ContentType.create("application", "octet-stream"),
    ContentType.create("image", "gif")

let httpClientWithNoRedirects () =
    let handler = new HttpClientHandler(UseCookies = false)
    handler.AllowAutoRedirect <- false
    let client = new HttpClient(handler)
    client.DefaultRequestHeaders.Clear()
    client

// we can trivially extend request to add convenience functions for common operations
module Request =
    let autoFollowRedirectsDisabled h = 
        { h with httpClient = httpClientWithNoRedirects () }

let request =
    Request.createUrl Post "https://example.com"
    |> Request.queryStringItem "search" "jeebus"
    |> Request.basicAuthentication "myUsername" "myPassword" // UTF8-encoded
    |> Request.setHeader (UserAgent "Chrome or summat")
    |> Request.setHeader (Custom ("X-My-Header", "hi mum"))
    |> Request.autoFollowRedirectsDisabled
    |> Request.cookie (Cookie.create("session", "123", path="/"))
    |> Request.bodyString "This body will make heads turn"
    |> Request.bodyStringEncoded "Check out my sexy foreign body" (Encoding.UTF8)
    |> Request.body (BodyRaw [| 1uy; 2uy; 3uy |])
    |> Request.body (BodyString "this is a greeting from Santa")

    // if you submit a BodyForm, then Http.fs will also set the correct Content-Type, so you don't have to
    |> Request.body (BodyForm 
        [
            // if you only have this in your form, it will be submitted as application/x-www-form-urlencoded
            NameValue ("submit", "Hit Me!")

            // a single file form control, selecting two files from browser
            FormFile ("file", ("file1.txt", ContentType.create("text", "plain"), Plain "Hello World"))
            FormFile ("file", ("file2.txt", ContentType.create("text", "plain"), Binary [|1uy; 2uy; 3uy|]))

            // you can also use MultipartMixed for servers supporting it (this is not the browser-default)
            MultipartMixed ("files",
              [ "file1.txt", firstCt, Plain "Hello World" // => plain
                "file2.gif", secondCt, Plain "Loopy" // => plain
                "file3.gif", thirdCt, Plain "Thus" // => base64
                "cute-cat.gif", fourthCt, Binary (File.ReadAllBytes (pathOf "cat-stare.gif")) // => binary
          ])
    ])
    |> Request.responseCharacterEncoding Encoding.UTF8    
    |> Request.proxy {
          Address = "proxy.com";
          Port = 8080;
          Credentials = Credentials.Custom { username = "Tim"; password = "Password1" } }

(with everything after createRequest being optional)

2 - The Http response (or just the response code/body) is retrieved using one of the following:

job {
  use! response = getResponse request // disposed at the end of async, don't
                                      // fetch outside async body
  // the above doesn't download the response, so you'll have to do that:
  let! bodyStr = Response.readBodyAsString response
  // OR:
  //let! bodyBs = Response.readBodyAsBytes

  // remember HttpFs doesn't buffer the stream (how would we know if we're
  // downloading 3GiB?), so once you use one of the above methods, you can't do it
  // again, but have to buffer/stash it yourself somewhere.
  return bodyStr
}

3 - If you get the full response (another record), you can get things from it like so:

response.StatusCode
response.Body // but prefer the above helper functions
response.ContentLength
response.Cookies.["cookie1"]
response.Headers.[ContentEncoding]
response.Headers.[NonStandard("X-New-Fangled-Header")]

So you can do the old download-multiple-sites-in-parallel thing:

[ "http://news.bbc.co.uk"
  "http://www.wikipedia.com"
  "http://www.stackoverflow.com"]
|> List.map (createRequestSimple Get)
| > List.map (Request.responseAsString) // this takes care to dispose (req, body)
|> Job.conCollect
|> Job.map (printfn "%s")
|> start

If you need direct access to the response stream for some reason (for example to download a large file), you need to write yourself a function and pass it to getResponseStream like so:

open Hopac
open System.IO
open HttpFs.Client

job {
  use! resp = Request.createUrl Get "http://fsharp.org/img/logo.png" |> getResponse
  use fileStream = new FileStream("c:\\bigImage.png", FileMode.Create)
  do! resp.Body.CopyToAsync fileStream
}

Note because some of the request and response headers have the same names, to prevent name clashes, the response versions have 'Response' stuck on the end, e.g.

response.Headers.[ContentTypeResponse]

Building

  1. Download the source code
  2. Execute the build.sh (linux & macos) or build.cmd (windows)

Examples

Check out HttpClient.SampleApplication, which contains a program demonstrating the various functions of the library being used and (to a limited extent) unit tested.

SamplePostApplication shows how you can create a post with a body containing forms.

Version History

Http.fs attempts to follow Semantic Versioning, which defines what the different parts of the version number mean and how they relate to backwards compatability of the API. In a nutshell, as long as the major version doesn't change, everything should still work.

  • 0.X.X - Various. Thanks for code and suggestions from Sergeeeek, rodrigodival, ovatsus and more
  • 1.0.0 - First stable API release. Changed how 'duplicated' DUs were named between request/response.
  • 1.1.0 - Added withProxy, thanks to vasily-kirichenko
  • 1.1.1 - Handles response encoding secified as 'utf8' (.net encoder only likes 'utf-8')
  • 1.1.2 - Added utf16 to response encoding map
  • 1.1.3 - Added XML comments to public functions, made a couple of things private which should always have been (technically a breaking change, but I doubt anybody was using them)
  • 1.2.0 - Added withKeepAlive
  • 1.3.0 - Added getResponseBytes, thanks to Sergeeeek
  • 1.3.1 - Added project logo, thanks to sergey-tihon
  • 1.4.0 - Added getResponseStream, with thanks to xkrt
  • 1.5.0 - Added support for Patch method with help from haf, and xkrt fixed an issue with an empty response.CharacterSet
  • 1.5.1 - Corrected the assembly version
  • 2.0.0 - Production hardened, major release, major improvements
  • 3.0.3 - Async -> Job, withXX -> Request.withXX

FAQ

  • How does it work?

Http.fs currently uses HttpClient under the hood.

  • Does it support proxies?

Yes. By default it uses the proxy settings defined in IE, and as of 1.1.0 you can specify basic proxy settings separately using withProxy.

  • Can I set KeepAlive?

Yes, as of version 1.2.0. This actually sets the Connection header (to 'Keep-Alive' or 'Close'). Note that if this is set to true (which is the default), the Connection header will only be set on the first request, not subsequent ones.

Why?

Simplicity for F# programmers. An abstract, immutable API that you can build better abstractions beneath (if needed).

What other kick-ass open source libraries are involved?

The only thing that's used in the HttpClient module itself is AsyncStreamReader.fs, a source file taken directly from the Fsharpx library.

However, for testing a couple of other things are used:

  • Suave to create a web server for integration testing
  • FsUnit for unit testing
  • NancyFX to create a web server for integration testing

That's about it. Happy requesting!

Henrik Feldt โ€“ @haf

Originally built by Grant Crofton.

Post Scriptum

http.fs's People

Contributors

barbeque avatar baronfel avatar cnd avatar dlidstrom avatar gusty avatar haf avatar horacegonzalez avatar igor-leshkevych avatar ivpadim avatar marknuzz avatar matthewhartz avatar mnie avatar muhomorik avatar neoeinstein avatar object avatar relentless avatar sergeeeek avatar sergey-tihon avatar symphonym avatar xkrt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

http.fs's Issues

"Close" <> "close", casing difference in integration test

4) Test Failure : HttpClient_IntegrationTests+Integration tests.if KeepAlive is false, Connection set to 'Close' on every request
     String lengths are both 5. Strings differ at index 0.
  Expected: "Close"
  But was:  "close"
  -----------^

at FsUnit.TopLevelOperators.should[String,EqualConstraint] (Microsoft.FSharp.Core.FSharpFunc`2 f, System.String x, System.Object y) [0x00000] in <filename unknown>:0
at HttpClient_IntegrationTests+Integration tests.if KeepAlive is false, Connection set to 'Close' on every request () [0x00000] in <filename unknown>:0

Add Appveyor "Build Status" badge to the readme

On Appveyor go to project Settings > Badges and copy the "Branch sample markdown code", then add this to the readme.

I could do this, but I don't have permissions on Appveyor. Could you add me to the team?

Consider renaming assembly to match project name?

Currently the assembly is named 'HttpClient' but the project 'Http.fs', which causes some friction when one is new to the project.

Possibly consider renaming the DLL to match the name of the project, or otherwise close this issue.

Add Basic Authorization helper

which might look like
|> withHeader (AuthorizationBasic username password)
or
|> withBasicAuthorization username password

`Transfer-Encoding: chunked,chunked`

2) Test Failure : HttpClient_IntegrationTests+Integration tests.all of the response headers are available after a call to getResponse
     Expected string length 7 but was 15. Strings differ at index 7.
  Expected: "chunked"
  But was:  "chunked,chunked"
  ------------------^

Remove dependency on FSharpX to simplify things

Http.fs uses a source file from fsharpx for the Async stuff (AsyncStreamReader.fs). This isn't a problem functionality-wise, but it does have a couple of down sides:

  • Makes licensing more complicated. FSharpX isn't the only 3rd party library used, but it does have the most restrictive licence (Apache 2.0).
  • Makes code more complicated (two files instead of one).
  • Means we have to ignore code in coverage, or get low coverage results.

Map utf8 to UTF-8

Some pages come back with encoding set to utf8, which the .net encoder doesn't seem to like. One fix would be to replace it with UTF-8. There might be other useful mappings too.

As a workaround, you can use withResponseEncoding "UTF-8" on those pages.

Read response encoding from meta tag?

Some HTML (and XML) pages specify the character encoding in the page and not in the header, e.g.

or

At the moment, the user would have to specify these manually using 'withResponseCharacterEncoding' (assuming the default encoding didn't work), but it should be possible to parse this information and use it to specify the proper encoding. It involves decoding the header, which should be US-ASCII, finding the encoding (if specified), then decoding the whole thing using the encoding.

However this will add a fair bit of complexity and will slow the processing down somewhat, so it's not clear if it's a good idea to add it.

Project needs a logo

Let's create logo for http.fs
Option 1:
image
Option 2:
image
Option 3:
image
Option 4:
image
Option 5:
image
Option 6:
image
Option 7:
image

Build is very verbose

...is it possible to default to a less verbose output? It basically cleans out my terminal history every build.

[Body] Support the different sorts of bodies

Description

As a part of making Http.fs a nice general-purpose Http client for .Net, we should support multipart/form-data bodies. They are needed to avoid the exploding size of application/x-www-form-urlencoded. They are needed to name file uploads and to upload files in general. By using them, we can set a MIME type for each body entity.

Implementation

Suggesting that we implement a discriminated union for each of:

  • multipart/form-data bodies
    • multipart/form-data -- DONE
    • multipart/mixed inside form-data -- DONE
    • plain encoding of files -- DONE
    • base64 encoding of files -- DONE
    • binary encoding of files -- this could be done when we have the 'socket' type, by composing it nicely DONE
  • application/x-www-form-urlencoded -- DONE
  • raw bytes
    • base64 encoded bytes
    • binary write-straight-to-stream bytes *DONE
  • socket -- awaiting discussion

The raw-bytes body is well put to use for intermediate uploads, from 30MB to half a gig, depending on your machines' memory configuration.

The socket body can be used to stream data to the target web server, and if we choose to do #64, will fit well together with it. It would allow plugins like a websocket client, SSE client, webrtc client etc.

The form-data body type will contain randomized boundaries, so there should be a way to generate bodies deterministically.

Handling data structure

We have these sorts of data; binary blob, streamed (both of which are easy and low on abstraction), form-data (key-value like), file-data (key-key-mime-base64 like). Both form-data and file-data can be formatted for multipart/form-data and application/x-www-form-urlencoded so I suggest a typed model for key-value and key-key-mime-base64 typed values, with can then be written using formatters. RawBytes bodies could be a specialisation of Socket (/cc @ademar - couldn't we do this for the new 'writePreamble' in Suave, too? Composing writers, one of which would be responsible for writing the preamble, into the SocketTask?).

Let's see what I can cook; right now I have a need for form-data and urlencoded, so I will focus on that.

Provide access to response stream

Hi, please, provide access to raw response stream. I want to download large binary files with your library. Now binary data can not be downloaded with current API.

FSharpList[String] vs String[]

2) Test Failure : HttpClient_IntegrationTests+Integration tests.all of the manually-set request headers get sent to the server
     Expected is <Microsoft.FSharp.Collections.FSharpList`1[System.String]>, actual is <System.String[0]>
  Values differ at index [0]

at FsUnit.TopLevelOperators.should[FSharpList`1,EqualConstraint] (Microsoft.FSharp.Core.FSharpFunc`2 f, Microsoft.FSharp.Collections.FSharpList`1 x, System.Object y) [0x00000] in <filename unknown>:0
at HttpClient_IntegrationTests+Integration tests.all of the manually-set request headers get sent to the server () [0x00000] in <filename unknown>:0

Doesn't compile

$ fsharpi build.fsx


/Users/henrik.feldt/dev/haf/Http.fs/build.fsx(2,1): error FS0078: Unable to find the file 'packages\FAKE.3.4.0\tools\FakeLib.dll' in any of
 /Library/Frameworks/Mono.framework/Versions/3.10.0/lib/mono/4.5
 /Users/henrik.feldt/dev/haf/Http.fs
 /Library/Frameworks/Mono.framework/Versions/3.10.0/lib/mono/4.0/

SetDefaultCachePolicy not implemented on Mono

Errors and Failures:
1) SetUp Error : HttpClient_IntegrationTests+Integration tests
   SetUp : System.NotImplementedException : The requested feature is not implemented.
  at System.Net.HttpWebRequest.set_DefaultCachePolicy (System.Net.Cache.RequestCachePolicy value) [0x00000] in <filename unknown>:0
  at HttpClient_IntegrationTests+Integration tests.fixtureSetup () [0x00000] in <filename unknown>:0
  at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&)
  at System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00000] in <filename unknown>:0

Either implement it here or drop the caching feature?

Or alternatively; an option I've been thinking more and more about; use the socket-pieces of Suave to create a F#-only WebRequest implementation. It would be much easier to reason about and have fewer quirks and be fully immutable.

Make same-named headers work better

As some request and response headers have the same names, you have to quality the duplicated response headers with Resp.

There's probably a better way of doing it.

Add extra cookie details

Currently cookies only contain the name and value - it might be useful to also have the expiry date, domain and path

'Keep-Alive' vs 'keep-alive' mono<>windows casing error in integration test

1) Test Failure : HttpClient_IntegrationTests+Integration tests._if KeepAlive is true, Connection set to 'Keep-Alive' on the first request, but not subsequent ones
     String lengths are both 10. Strings differ at index 0.
  Expected: "Keep-Alive"
  But was:  "keep-alive"
  -----------^

at FsUnit.TopLevelOperators.should[String,EqualConstraint] (Microsoft.FSharp.Core.FSharpFunc`2 f, System.String x, System.Object y) [0x00000] in <filename unknown>:0
at HttpClient_IntegrationTests+Integration tests._if KeepAlive is true, Connection set to 'Keep-Alive' on the first request, but not subsequent ones () [0x00000] in <filename unknown>:0

Don't version releases together with source code

This is an anti pattern because:

  • it dramatically increases the checkout time for CI and human builds of the project
  • you already have the history on NuGet and MyGet
  • it's worth more having reproducible builds across version than to save binary artifacts, see also https://bugzilla.xamarin.com/show_bug.cgi?id=26842
  • you can save those binary artifacts in github releases
  • git is not adapted primarily for binary large artifacts
  • it's basically a copy of the code, slightly modified, so viewing diffs of them doesn't make much sense and this disturbs the human review-workflow

Allow download of raw bytes?

At the moment, the response body can only be read as a string. However, it might not be a string (an image, for example), so it would be good to have the option of returning the body as bytes. However, it would need to be done in such a way as to keep the API simple.

The HTTP functionality in FSharp.Data does something similar.

Access response stream does not throw ArgumentException

5) Test Failure : HttpClient_IntegrationTests+Integration tests.Trying to access the response stream after getResponseStream causes an ArgumentException
     Expected: <System.ArgumentException>
  But was:  no exception thrown

at FsUnit.TopLevelOperators.should[Type,ExactTypeConstraint] (Microsoft.FSharp.Core.FSharpFunc`2 f, System.Type x, System.Object y) [0x00000] in <filename unknown>:0
at HttpClient_IntegrationTests+Integration tests.Trying to access the response stream after getResponseStream causes an ArgumentException () [0x00000] in <filename unknown>:0

Response headers for HttpListener hard-coded to Windows in integration test

3) Test Failure : HttpClient_IntegrationTests+Integration tests.all of the response headers are available after a call to getResponse
     Expected string length 21 but was 16. Strings differ at index 1.
  Expected: "Microsoft-HTTPAPI/2.0"
  But was:  "Mono-HTTPAPI/1.0"
  ------------^

at FsUnit.TopLevelOperators.should[String,EqualConstraint] (Microsoft.FSharp.Core.FSharpFunc`2 f, System.String x, System.Object y) [0x00000] in <filename unknown>:0
at HttpClient_IntegrationTests+Integration tests.all of the response headers are available after a call to getResponse () [0x00000] in <filename unknown>:0

Allow specific character encoding for the request body

The character encoding used when reading the response can be specified, but the body of the request always gets encoded with windows-1252. The user should be able to specify which encoding they want to use, either with an optional parameter on the withBody function or a new function like 'withRequestCharacterEncoding'

Setting header multiple times now allowed?

There was the 'chunked,chunked' issue; and now I found a unit-test for setting a header multiple times;

If the same header is added multiple times, throws an exception

Should this be the case? Isn't it more in line with HTTP to comma-separate the header values? See e.g. how the Set-Cookie header from the server works.

Suggestion: create a socket implementation for Http.fs

This would allow us much more granular testing of web servers and would allow the F# code to be written in an exception-free style, supporting exhaustive case analysis/static analysis. In the future, that socket implementation could form the basis of Suave and any other TCP lib such as the Kafka F# client I'm writing. In the future, such a socket lib could optionally make use of F* to prove the state machine, all the way up the abstraction stack; from TCP state machine, to HTTP as a protocol, to HTTP as an application protocol, to entities/resources, to 'webmachine'/Freya-style APIs, to server-sent-events as pushed versions of eventually consistent read models, to CRDTs (but now I'm getting ahead of myself) over HTTP.

It would allow specific timeouts to be controlled and the state to be passed around rather than wrapped in the Socket object -- and that state to be inspected, as opposed to the exception-driven model of .Net's Socket (and it's also an implicit model, e.g. the Close() call, which modifies internal state)

Consider switching to a non-reflection based testing framework

I would recomend Fuchu. The upsides are many:

  • you don't have to have a specific runner for unit tests; just run the console project
  • you can compose test-values like any other value, which means you can create awesome test DSLs (I have a proprietary one that's not yet open sourced that makes it very easy to test event-sourced read models and domains for example)
  • fast moving than NUnit, which seems to be very stalled in terms of features
  • much better code inside testing framework, if you venture into it, clean F# as opposed to huge C# methods with mutability
  • XamarinStudio doesn't lock up when running it
  • parallel test runner
  • supports F# modules

Drawbacks:

  • less community
  • fewer 'nice' Asserts - but you can go pretty far with what's in there
  • another API
  • doesn't support TestCase (but that's not used in this code-base) - but has a nice integration with FsCheck so it's stronger on the random-testing side than NUnit is

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.