Fluent HTTP☍
NOTE: Everything beyond URL building and parsing requires installing Flurl.Http rather than the base Flurl package.
A pretty common way to think about interacting with an HTTP service is "I want to build a URL and then call it." Flurl.Http allows you to express that pretty concisely:
using Flurl;
using Flurl.Http;
var result = await baseUrl.AppendPathSegment("endpoint").GetAsync();
The above code sends an HTTP GET
request and returns an IFlurlResponse
, from which you can get properties such as StatusCode
, Headers
, and the body content via methods such as GetStringAsync
and GetJsonAsync<T>
.
But often you just want to jump straight to the body, and Flurl provides a variety of shortcuts to do that:
T poco = await "http://api.foo.com".GetJsonAsync<T>();
string text = await "http://site.com/readme.txt".GetStringAsync();
byte[] bytes = await "http://site.com/image.jpg".GetBytesAsync();
Stream stream = await "http://site.com/music.mp3".GetStreamAsync();
With JSON APIs, it's usually best define a class (T
above) that matches the shape of the expected JSON response. But if that feels like overkill, skip it and get a dynamic:
dynamic d = await "http://api.foo.com".GetJsonAsync();
Or get a list of dynamics from an API that returns a JSON array:
var list = await "http://api.foo.com".GetJsonListAsync();
Download a file:
// filename is optional here; it will default to the remote file name
var path = await "http://files.foo.com/image.jpg"
.DownloadFileAsync("c:\\downloads", filename);
Other "read" verbs:
var headResponse = await "http://api.foo.com".HeadAsync();
var optionsResponse = await "http://api.foo.com".OptionsAsync();
Then there's the "write" verbs:
await "http://api.foo.com".PostJsonAsync(new { a = 1, b = 2 });
await "http://api.foo.com/1".PatchJsonAsync(new { c = 3 });
await "http://api.foo.com/2".PutStringAsync("hello");
All of the methods above return a Task<IFlurlResponse>
. You may of course expect some data to be returned in the response body:
T poco = await url.PostAsync(content).ReceiveJson<T>();
dynamic d = await url.PutStringAsync(s).ReceiveJson();
string s = await url.PatchJsonAsync(partial).ReceiveString();
Weird verbs or content? Use one of the lower-level methods:
await url.PostAsync(content); // a System.Net.Http.HttpContent object
await url.SendJsonAsync(HttpMethod.Trace, data);
await url.SendAsync(
new HttpMethod("CONNECT"),
httpContent, // optional
cancellationToken, // optional
HttpCompletionOption.ResponseHeaderRead); // optional
Set request headers:
// one:
await url.WithHeader("Accept", "text/plain").GetJsonAsync();
// multiple:
await url.WithHeaders(new { Accept = "text/plain", User_Agent = "Flurl" }).GetJsonAsync();
In the second example above, User_Agent
will automatically render as User-Agent
in the header name. (Hyphens are very common in header names but not allowed in C# identifiers; underscores, just the opposite.)
Specify a timeout:
await url.WithTimeout(10).DownloadFileAsync(); // 10 seconds
await url.WithTimeout(TimeSpan.FromMinutes(2)).DownloadFileAsync();
Cancel a request:
var cts = new CancellationTokenSource();
var task = url.GetAsync(cts.Token);
...
cts.Cancel();
Authenticate using Basic authentication:
await url.WithBasicAuth("username", "password").GetJsonAsync();
Or an OAuth 2.0 bearer token:
await url.WithOAuthBearerToken("mytoken").GetJsonAsync();
Simulate an HTML form post:
await "http://site.com/login".PostUrlEncodedAsync(new {
user = "user",
pass = "pass"
});
Or a multipart/form-data
POST:
var resp = await "http://api.com".PostMultipartAsync(mp => mp
.AddString("name", "hello!") // individual string
.AddStringParts(new {a = 1, b = 2}) // multiple strings
.AddFile("file1", path1) // local file path
.AddFile("file2", stream, "foo.txt") // file stream
.AddJson("json", new { foo = "x" }) // json
.AddUrlEncoded("urlEnc", new { bar = "y" }) // URL-encoded
.Add(content)); // any HttpContent
Send some cookies with a request:
var resp = await "https://cookies.com"
.WithCookie("name", "value")
.WithCookies(new { cookie1 = "foo", cookie2 = "bar" })
.GetAsync();
Better yet, grab response cookies from the first request and let Flurl determine when to send them back (per RFC 6265):
await "https://cookies.com/login".WithCookies(out var jar).PostUrlEncodedAsync(credentials);
await "https://cookies.com/a".WithCookies(jar).GetAsync();
await "https://cookies.com/b".WithCookies(jar).GetAsync();
Or avoid all those WithCookies
calls and use a CookieSession
:
using (var session = new CookieSession("https://cookies.com")) {
// set any initial cookies on session.Cookies
await session.Request("a").GetAsync();
await session.Request("b").GetAsync();
// read cookies at any point using session.Cookies
}
A CookieJar
can also be created/modified explicitly, which may be useful in re-hydrating cookies that were persisted:
var jar = new CookieJar()
.AddOrUpdate("cookie1", "foo", "https://cookies.com") // you must specify the origin URL
.AddOrUpdate("cookie2", "bar", "https://cookies.com");
await "https://cookies.com/a".WithCookies(jar).GetAsync();
CookieJar
is Flurl's equivalent of CookieContainer
from the HttpClient
stack, but with one major advantage: it is not bound to an HttpMessageHandler
, hence you can simulate multiple cookie "sessions" on a single HttClient/Handler
instance.