流畅的URL构建方式
using Flurl;
var url = "https://www.so.com"
.AppendPathSegment("endpoint")
.SetQueryParams(new {
api_key = "sdfdsfsdfs",
max_results = 20,
q = "Don't worry, I'll get encoded!"
})
.SetFragment("after-hash");
string urlStr = url.toString();
// https://www.so.com/endpoint?api_key=sdfdsfsdfs&max_results=20&q=Don%27t%20worry%2C%20I%27ll%20get%20encoded%21#after-hash
上面例子(以及其他绝大多数示例)使用String扩展方法隐式地创建 Url 对象;
如果您愿意,你可以使用Flurl内置的Url实例显式构建类似的Url对象,如:
var url = new Url("https://www.so.com").AppendPathSegment(...
除了上面的对象表示法外,SetQueryParams 还接受任何 键值对集合、Tuples 或 Dictionary 对象;这些替代方案对于不是有效的c#标识符的参数名特别有用。如果你想逐个设置他们,也可以使用 SetQueryParam;
在任何情况下,这些方法都会覆盖以前设置的相同名称的值,但是您可以通过传递一个集合来设置多个相同名称的值:
var url = "http://www.mysite.com".SetQueryParam("x", new[] { 1, 2, 3 });
Assert.AreEqual("http://www.mysite.com?x=1&x=2&x=3", url); // true
在Flurl中,构建器方法及其重载是高度显式的、直观的,并且总是链式的,一些破坏性的方法如:RemoveQueryParam、 RemovePathSegment 和 ResetToRoot 也包括在内。
Parsing/URL解析
除了构建 URL 外, Flurl还可以有效地解析现有的URL,如下:
var url = new Url("https://user:pass@www.mysite.com:1234/with/path?x=1&y=2#foo");
Assert.AreEqual("https", url.Scheme);
Assert.AreEqual("user:pass", url.UserInfo);
Assert.AreEqual("www.mysite.com", url.Host);
Assert.AreEqual(1234, url.Port);
Assert.AreEqual("user:pass@www.mysite.com:1234", url.Authority);
Assert.AreEqual("https://user:pass@www.mysite.com:1234", url.Root);
Assert.AreEqual("/with/path", url.Path);
Assert.AreEqual("x=1&y=2", url.Query);
Assert.AreEqual("foo", url.Fragment);
虽然这些解析功能类似于C#内置的URL解析功能,但Flurl的目标是与RFC 3986更加兼容,并且更符合的实际字符串应用场景,因此有以下方面的不同之处:
- Uri.Query 方法包含 ?字符,而 Url.Query 方法不包含;
- Uri.Fragment 方法包含 # 字符,Url.Fragment 方法不包含;
- Uri.AbsolutePath 方法始终包含一个开头 / 字符,而 Url.Path 只有在它实际存在于原始字符串中时才会包含它,例如,对于
http://foo.com
,Url.Path 方法返回的是一个空字符串。
- Uri.Authority 方法不包含用户信息(如 user:pass@); Url.Authority 方法则会包含获取到的用户信息;
- Uri.Port 方法在未指定端口时会返回默认的端口值,而Url.Port方法在未指定端口时为nullable;
- Uri 不会尝试去解析相对 URL 地址;而 Url的类中,假设Url字符串不是以{scheme}://开头,那么它将以一个路径开始并相应地解析它。
Url.QueryParams 是一种维护了参数顺序的特殊集合类型,它允许重复名称,但针对唯一名称的典型情况进行了优化,如下:
var url = new Url("https://www.mysite.com?x=1&y=2&y=3");
Assert.AreEqual("1", url.QueryParams.FirstOrDefault("x")); // true
Assert.AreEqual(new[] { "2", "3" }, url.QueryParams.GetAll("y")); // true
Mutability/可变性
Url 实际上是隐式转换为字符串的可变生成器对象。
如果您需要一个不可变的 URL,比如将 基URL 作为类的一个成员变量,常见的模式是将其作为字符串输入,如:
public class MyServiceClass
{
private readonly string _baseUrl; // 使用了 readonly ,_baseUrl 是不可变的;
public Task CallServiceAsync(string endpoint, object data) {
return _baseUrl
.AppendPathSegment(endpoint) // 这里会生成一个 Url 对象
.PostAsync(data); // 请引用Flurl.Http命名空间
}
}
这里调用AppendPathSegment方法创建了一个新的Url对象。结果是_baseUrl没有被修改,与将其声明为Url相比,也没有添加额外的信息。
另一种避免 Url 可变性的方法是使用 Clone ()方法:
var url2 = url1.Clone().AppendPathSegment("next");
在这里,使用Clone()方法复制并获得了基于另一个Url对象的新Url对象,因此可以在不改变原始Url的情况下对复制的Url进行修改。
编码
Flurl负责对url中的字符进行编码,但对路径段(path segment)采用的方法与对查询字符串值采用的方法不同。
假设查询字符串值是高度可变的(例如来自用户输入) ,而路径段往往更“固定”(可能已经编码了),在这种情况下,您不希望进行双重编码。以下是 Flurl 遵循的规则:
- 查询字符串值 是完全 URL-encoded 的。
- 对于 路径段,不对
/
和 %
等保留字符进行编码。
- 对于 路径段,会对非法字符(如空格)进行编码。
- 对于路径段,
?
字符会被编码,因为 查询字符串 会进行特殊处理。
在某些情况下,你可能希望设置一个已经知道编码的查询参数。SetQueryParam 具有一个可选的 isEncode 参数来设置:
url.SetQueryParam("x", "don%27t%20touch%20me", true);
虽然空格字符的官方URL编码是 %20,但在查询参数中使用字符+是很常见的。您可以通过 Url.ToString 的可选 encodeSpaceAsPlus 参数来指定是否将空格编码成+号,示例如下:
var url = "http://foo.com".SetQueryParam("x", "hi there");
Assert.AreEqual("http://foo.com?x=hi%20there", url.ToString());
Assert.AreEqual("http://foo.com?x=hi+there", url.ToString(true));
其他一些实用方法
Url 还包含一些方便的静态方法,比如 Combine,它是一个用来组合URL地址地类似C#文件系统中的Path.Combine方法URL地址,确保各部分之间只有一个分隔符,如:
var url = Url.Combine(
"http://foo.com/",
"/too/", "/many/", "/slashes/",
"too", "few?",
"x=1", "y=2"
// http://www.foo.com/too/many/slashes/too/few?x=1&y=2
并且为了帮助您避免一些与.NET 中的各种 URL endoing/decoding 方法相关的臭名昭著的怪癖,Flurl 提供了一些“无怪癖”的替代方案:
Url.Encode(string s, bool encodeSpaceAsPlus); // 包含保留字符,如 / 和 ?
Url.EncodeIllegalCharacters(string s, bool encodeSpaceAsPlus); // 保留的字符不会被 touched
Url.Decode(string s, bool interpretPlusAsSpace);
Fluent HTTP
注意: 除了 URL building 和 parsing 之外的所有操作都需要安装 Flurl.Http
,而不是基本的 Flurl
包。
using Flurl;
using Flurl.Http;
// 发送一个 Get 请求,返回 IFlurlResponse 对象;
var result = await baseUrl.AppendPathSegment("endpoint").GetAsync();
返回的 IFlurlResponse 对象,可以通过 GetStringAsync
和 GetJsonAsync<T>
等 方法获得属性如 StatusCode、 Header 和 body 内容。
但是通常你只想获得 body 内容,Flurl 提供了更加便捷的方式:
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();
对于JSON API,通常最好定义一个与预期JSON响应数据类型匹配的类(T)。但如果你不想创建这个匹配的类,也可以换动态(dynamic)的对象,比如:
dynamic d = await "http://api.foo.com".GetJsonAsync();
或者,从返回JSON数组的API中获取一个动态列表,比如:
var list = await "http://api.foo.com".GetJsonListAsync();
下载一个文件:
// 文件名 在这里是可选的; 如果不指定,默认会使用远程文件的文件名
var path = await "http://files.foo.com/image.jpg"
.DownloadFileAsync("c:\\downloads", filename);
从请求中读取其他(如:头部信息等):
var headResponse = await "http://api.foo.com".HeadAsync(); // 获取Head参数
var optionsResponse = await "http://api.foo.com".OptionsAsync();
发送数据
await "http://api.foo.com".PostJsonAsync(new { a = 1, b = 2 }); // Post
await "http://api.foo.com/1".PatchJsonAsync(new { c = 3 }); // Patch
await "http://api.foo.com/2".PutStringAsync("hello"); // Put
上面的所有方法都返回 Task<IFurlResponse>
。当然,您可能希望在响应主体中返回一些数据:
T poco = await url.PostAsync(content).ReceiveJson<T>();
string s = await url.PatchJsonAsync(partial).ReceiveString();
或者一些奇特的HTTP请求需求,比如:
await url.PostAsync(content); // 这里的content为System.Net.Http.HttpContent对象
await url.SendJsonAsync(HttpMethod.Trace, data);
await url.SendAsync(
new HttpMethod("CONNECT"),
httpContent, // 可选的
cancellationToken, // 可选的
HttpCompletionOption.ResponseHeaderRead); // 可选的
设置请求头:
// 一个:
await url.WithHeader("Accept", "text/plain").GetJsonAsync();
// 多个:
await url.WithHeaders(new { Accept = "text/plain", User_Agent = "Flurl" }).GetJsonAsync();
User_Agent 将在 header 中自动呈现为 User-Agent。(连字符在 header names 中非常常见,但是在 C# 标识符中不允许; 而下划线恰恰相反)
指定超时时间:
await url.WithTimeout(10).DownloadFileAsync(); // 10 秒
await url.WithTimeout(TimeSpan.FromMinutes(2)).DownloadFileAsync(); // 超时时间设置成了2分钟
取消请求:
var cts = new CancellationTokenSource();
var task = url.GetAsync(cts.Token);
...
cts.Cancel();
使用 Basic authentication 进行身份验证:
await url.WithBasicAuth("username", "password").GetJsonAsync();
或者一个 OAuth 2.0 bearer token 身份认证:
await url.WithOAuthBearerToken("mytoken").GetJsonAsync();
模拟 HTML post表单:
await "http://site.com/login".PostUrlEncodedAsync(new {
user = "user",
pass = "pass"
});
或一个 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));
携带 cookie 的请求:
var resp = await "https://cookies.com"
.WithCookie("name", "value")
.WithCookies(new { cookie1 = "foo", cookie2 = "bar" })
.GetAsync();
更好的做法是,从第一个请求中获取响应 cookie,让 Flurl 决定何时将它们发送回去(根据 RFC6265) ,比如:
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();
或者使用CookieSession来代替WithCookies,如:
using (var session = new CookieSession("https://cookies.com")) {
// set any initial cookies on session.Cookies
// 在 sessin.Cookies 中设置初始cookies
await session.Request("a").GetAsync();
await session.Request("b").GetAsync();
// read cookies at any point using session.Cookies
// 在任何时候使用 session.Cookies 读取 cookies
}
CookieJar可以显式地被创建和修改,对需要长期保存的Cookies有可能会有用,比如:
var jar = new CookieJar()
.AddOrUpdate("cookie1", "foo", "https://cookies.com") // 必须指定原始的URL地址
.AddOrUpdate("cookie2", "bar", "https://cookies.com");
await "https://cookies.com/a".WithCookies(jar).GetAsync();
CookieJar是Flurl在HttpClient堆栈中的CookieContainer的对等物,它有一个主要优点: 它没有绑定到 HttpMessageHandler 对象,因此你可以在单个 HttClient/Handler 实例上模拟多个 cookie “sessions”。
Testable HTTP/可测试HTTP
Flurl.Http 提供了一组测试特性,这些特性让 arrange-act-assert 测试变得非常简单。
其核心是 HttpTest;
using Flurl.Http.Testing;
[Test]
public void Test_Some_Http_Calling_Method() {
using (var httpTest = new HttpTest()) {
// Flurl 现在进入测试模式
sut.CallThingThatUsesFlurlHttp(); // HTTP 调用是伪造的!
}
}
...
Configuration/配置
Flurl.Http 行为可以通过分层设置系统进行配置,每一层按如下配置顺序继承/覆盖前一层:
FlurlHttp.GlobalSettings
(静态的)
IFlurlClient.Settings
IFlurlRequest.Settings
HttpTest.Settings
(配置的测试参数总是有效性最高)
所有4个级别的可用属性大多相同,只有少数例外。下面是一个完整的列表,包括它们的位置和不支持的位置:
请注意,只有没有显式地设置值时,才表示要从层次结构的上层继承。null的意思是null,而不是继承。
配置设置
设置属性都是 可读/写 的,但是如果您希望一次(原子地)更改多个设置参数,你通常应该用一个 Configure*
方法之一,该方法使用 Action<Settings>
委托。
配置全局默认值:
// 在用户程序启动时调用一次即可
FlurlHttp.Configure(settings => ...);
您还可以配置用于调用给定URL的FlurlClient。需要注意的是,在默认情况下对同一主机的所有调用都使用相同的 FlurlClient,所以这可能会影响到的不仅仅是对提供的特定URL的调用:
// 在用户程序启动时调用一次即可
FlurlHttp.ConfigureClient(url, cli => ...);
或者,你也可以显式地配置FlurlClient:
flurlClient.Configure(settings => ...);
或者,链式地配置单个请求(通过字符串、Url或IFlurlRequest的扩展方法):
await url.ConfigureRequest(settings => ...).GetAsync();
还可以在测试中覆盖任何设置,无论这些设置在测试对象的哪个级别:
httpTest.Configure(settings => ...);
如果需要,您可以将任何级别的设置恢复为它们的默认值或继承值:
flurlClient.Settings.ResetDefaults();
FlurlHttp.GlobalSettings.ResetDefaults();
下面让我们来看看一些特定的设置。
HttpClientFactory
不要与.NET Core的 IHttpClientFactory 混淆;它们是非常不同的东西(Flurl的IHttpClientFactory比.NET Core的出现得早)。
对于高级场景,您可以自定义Flurl.Http构造HttpClient和HttpMessageHandler实例。尽管只需要实现Flurl.Http.Configuration.IHttpClientFactory,但建议从DefaultHttpClientFactory继承,并只在需要时扩展,如下:
public class MyCustomHttpClientFactory : DefaultHttpClientFactory
{
// 重写 CreateHttpClient 方法,用以自定义 HttpClient 如何创建和配置
public override HttpClient CreateHttpClient(HttpMessageHandler handler);
// 重写 CreateMessageHandler 方法,用以自定义 HttpMessageHandler 如何创建和配置
public override HttpMessageHandler CreateMessageHandler();
}
注册全局工厂:
FlurlHttp.Configure(settings => {
settings.HttpClientFactory = new MyCustomHttpClientFactory();
});
或者在单个FlurlClient上配置(不常见):
var cli = new FlurlClient(BASE_URL).Configure(settings => {
settings.HttpClientFactory = new MyCustomHttpClientFactory();
});
提醒你几句:
-
Overriding CreateMessageHandler can be very useful for configuring things like proxies and client certificates, but some features that Flurl has re-implemented, such as cookies and redirects, require that the related settings on HttpClientHandler remain disabled in order to function properly. It's a best practice to call base.CreateMessageHandler() and configure/return that, rather than creating a new one yourself. If in doubt, have a look at what Flurl does by default and avoid straying farther from that implementation than necessary.
- 重写 CreateMessageHandler 对于配置代理和客户端证书之类的东西非常有用,但是 Flurl 重新实现的一些特性,如 cookies 和重定向,要求HttpClientHandler上的相关设置保持禁用状态,以便正常工作。最好的做法是调用 base.CreateMessageHandler() 并配置/返回它,而不是自己创建一个新的。如果有疑问,看看 Flurl 在默认情况下做了什么,避免偏离实现太远。
-
A custom HttpClientFactory should be concerned only with creating these objects, not caching/reusing them. That's a concern of FlurlClientFactory.
- 自定义 HttpClientFactory 应该只关心创建这些对象,而不是缓存/重用它们。这是 FlurlClientFactory 所担心的。
FlurlClientFactory
IFlurlClientFactory
接口定义了一个方法Get(Url)
,该方法负责提供应该用于调用该Url的IFlurlClient
实例。在应用程序的生命周期内,默认实现了根据URL的主机/方案/端口的组合使用一个缓存的FlurlClient
实例。
要改变这种行为,你可以通过直接实现IFlurlClientFactory
来定义你自己的工厂,但是从FlurlClientFactoryBase
继承要容易得多。它允许您通过返回基于Url的缓存键来定义缓存策略,而不必实现缓存本身,如下:
public abstract class FlurlClientFactoryBase : IFlurlClientFactory
{
// 重写GetCacheKey方法
protected abstract string GetCacheKey(Url url);
// 重写Create方法(仅在需要时才调用此方法)
protected virtual IFlurlClient Create(Url url);
}
然FlurlClientFactory配置设置仅在全局级别可用,但IFlurlClientFactory在依赖注入模式时也很有用。
序列化
JsonSerializer 和 UrlEncodedSerializer 都实现了ISerializer,这是一个简单的接口,用于将对象与字符串进行序列化。
public interface ISerializer
{
string Serialize(object obj);
T Deserialize<T>(string s);
T Deserialize<T>(Stream stream);
}
两者都有一个全局注册的默认实现,你可以替换它们,但不是必要的。默认的JsonSerializer
实现是NewtonsoftJsonSerializer
,正如您可能猜到的那样,它使用了一直流行的Json.NET序列化类库。你可以使用NewtonsoftJsonSerializer
来自定义序列化配置,如下:
FlurlHttp.Configure(settings => {
var jsonSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ObjectCreationHandling = ObjectCreationHandling.Replace
};
settings.JsonSerializer = new NewtonsoftJsonSerializer(jsonSettings);
});
事件处理
将关注点(如日志记录和错误处理)与正常的逻辑流分离开,通常会使代码更加清晰。Flurl.Http 为这些场景提供了一个事件模型。BeforeCall
,AfterCall
, OnError
, OnRedirect
以及它们的*Async
同名异步方法都在全局或客户端级别被定义,但如果需要的话,也可以在每个请求中定义。这些方法都采用了Action<HttpCall>
委托,FlurlCall
提供了你可以利用的关于调用的丰富细节:
public class FlurlCall
{
public IFlurlRequest Request { get; set; }
public HttpRequestMessage HttpRequestMessage { get; set; }
public IFlurlResponse Response { get; set; }
public HttpResponseMessage HttpResponseMessage { get; set; }
public string RequestBody { get; }
public FlurlRedirect Redirect { get; set; }
public FlurlCall RedirectedFrom { get; set; }
public Exception Exception { get; set; }
public bool ExceptionHandled { get; set; }
public DateTime StartedUtc { get; set; }
public DateTime? EndedUtc { get; set; }
public TimeSpan? Duration { get; }
public bool Completed { get; }
public bool Succeeded { get; }
}
与响应有关的属性值在BeforeCall中都是null,AfterCall在请求成功或者失败时都会被触发,在OnError中将ExceptionHandled设置为true可以防止出现异常。下面是一个注册全局异步错误处理程序的示例:
private async Task HandleFlurlErrorAsync(HttpCall call) {
await LogErrorAsync(call.Exception.Message);
call.ExceptionHandled = true;
}
FlurlHttp.Configure(settings => settings.OnErrorAsync = HandleFlurlErrorAsync);
重定向(Redirects)
像HttpClient一样,Flurl默认自动跟随3xx重定向,但是Flurl公开的设置和钩子提供了更高级别的可配置性,如下:
FlurlHttp.Configure(settings => {
settings.Redirects.Enabled = true; // default true
settings.Redirects.AllowSecureToInsecure = true; // default false
settings.Redirects.ForwardAuthorizationHeader = true; // default false
settings.Redirects.MaxAutoRedirects = 5; // default 10 (consecutive)
});
你还可以使用事件处理程序在每次调用的基础上配置重定向行为:
flurlClient.OnRedirect(call => {
if (call.Redirect.Count > 5) {
call.Redirect.Follow = false;
}
else {
log.WriteInfo($"redirecting from {call.Request.Url} to {call.Redirect.Url}");
call.Redirect.ChangeVerbToGet = (call.Response.Status == 301);
call.Redirect.Follow = true;
}
});
如果你只需要启用/禁用单个调用的自动重定向,你可以内联执行:
await url.WithAutoRedirect(false).GetAsync();
Error Handling/错误处理
与 HttpClient 不同,Flurl.HTTP 在默认情况下会抛出任何非2XX 的 HTTP 状态:
- 非2xx情况往往是“异常的”,也就是说,在“正常”的环境和逻辑流中会是2xx状态,因此非2xx状态适合try/catch范式。
- 特别是在 JSON API 中,错误响应体倾向于采用与常规响应不同的形态,并且如果您使用如
url.GetJsonAsync<RegularShape>()
这样的快捷处理方法的话, Flurl的try/catch模式提供了一种反序列化到catch块中不同内容的方法,如下:
try {
var result = await url.PostJsonAsync(poco).ReceiveJson<T>();
}
catch (FlurlHttpException ex) {
var error = await ex.GetResponseJsonAsync<TError>();
logger.Write($"Error returned from {ex.Call.Request.Url}: {error.SomeDetails}");
}
以上的调用属性是事件处理程序使用的同一个FlurlCall对象的实例,它提供了关于调用的丰富细节。对于简单的日志和调试,FlurlHttpException.Message提供了错误的简易摘要,包括URL、HTTP谓词和接收到的状态代码等等。
FlurlHttpException还提供了一些反序列化body的快捷方式:
Task<string> GetResponseStringAsync();
Task<T> GetResponseJsonAsync<T>();
这些都是 FlurlHttpException.Call 上等效方法的简写,因此如果你需要获取例如流这样的不同数据,您可以使用该方式。
超时(Timeouts)
Flurl.Http为超时定义了一个特殊的异常类型:FlurlHttpTimeoutException,这种类型继承自FlurlHttpException,因此会在catch (FlurlHttpException)块中被捕获,同时,你也可以使用不同的方式来处理超时:
catch (FlurlHttpTimeoutException) {
// handle timeout
}
catch (FlurlHttpException) {
// handle error response
}
除了FlurlHttpTimeoutException之外没有其他属性,但因为超时意味着没有收到响应,所有与响应相关的属性都将始终为null。
默认超时是100秒(与HttpClient相同),但这可以在任何设置级别上配置,或者内联每个请求:
await url.WithTimeout(200).GetAsync(); // 200 seconds
await url.WithTimeout(TimeSpan.FromMinutes(10)).GetAsync();
允许非2xx响应
如果你不喜欢默认的抛出行为,你可以通过Settings.AllowedHttpStatusRange在任何设置级别更改它,这是一个基于字符串(除了通配符)的设置,所以如果你从来不想抛出,可将它设置为*。
你也可以在请求级别允许非2XX:
url.AllowHttpStatus("400-404,6xx").GetAsync();
url.AllowAnyHttpStatus().GetAsync();
第一个示例中的模式是显而易见的,AllowHttpStatus()方法允许的字符包括数字、逗号(分隔符)、连字符(范围)和通配符x或X或*。这些语法规则与Settings.AllowedHttpStatusRange是相同的,但是有一个细微的行为差异:上面的请求级别方法是可添加的,所以,例如如果每个设置都允许2xx,你就不必在请求级别模式中包含它。
在反序列化前检查响应
如果你喜欢把非2xx状态作为正常控制流的一部分来处理,这很简单:
var response = await url
.AllowAnyHttpStatus()
.GetAsync();
if (result.StatusCode < 300) {
var result = await response.GetJsonAsync<T>();
Console.WriteLine($"Success! {result}")
}
else if (result.StatusCode < 500) {
var error = await response.GetJsonAsync<UserErrorData>();
Console.WriteLine($"You did something wrong! {error}")
}
else {
var error = await response.GetJsonAsync<ServerErrorData>();
Console.WriteLine($"We did something wrong! {error}")
}
这里的response是IFlurlResponse的一个实例,它封装了System.Net.Http.HttpResponseMessage信息,除了StatusCode之外,你还可以通过多种方式检查头部、cookie以及获取正文内容:
Task<T> GetJsonAsync<T>();
Task<string> GetStringAsync();
Task<Stream> GetStreamAsync();
Task<byte[]> GetBytesAsync();
HttpClient 生命周期管理
Flurl.Http 是建立在 System.Net.Http 栈上的,如果您熟悉 HttpClient,您可能已经知道以下建议:
HttpClient 是实例化一次,并在应用程序的整个生命周期中复用。特别是在服务器应用程序中,为每个请求创建一个新的 HttpClient 实例将耗尽重负载下可用的 socket 数量。这将导致 SocketException 错误。
Flurl.Http 默认情况下遵守本指南。Fluent方法将创建一个 HttpClient,缓存它,并在对同一主机^*的每次调用中重用它:
var data = await "http://api.com/endpoint".GetJsonAsync();
- 在3.0中,Scheme 是端口,也是 cache 密钥的一部分。因此,例如,如果您同时访问同一主机的 http 和 https 端点,您将有两个可以独立配置的客户端实例。
显式管理实例
FlurlClient 是 HttpClient 的轻量级包装器,与其生命周期紧密绑定。它实现 IDisposable,当释放时也将释放 HttpClient。FlurlClient 包括 BaseUrl 属性,以及 Header、 Settings 和许多您可能已经熟悉的fluent方法。这些属性和方法大多用于设置可在请求级别重写的默认值。
您可以显式创建 FlurlClient 并(可选)流畅地配置它:
var cli = new FlurlClient("https://api.com")
.WithOAUthBearerToken(token))
.Configure(settings => ...);
Fluent 从 Request 方法开始调用 FlurlClient,该方法可以选择接受一个或多个 URL 路径段:
await cli.Request("path", "to", "endpoint") // Request().AppendPathSegments(...) 的简写
.SetQueryParams(args)
.PostJsonAsync(data)
.ReceiveJson<T>();
使用 IoC 容器的 Flurl
...
可扩展性(Extensibility)
由于Flurl的大部分功能是通过扩展方法提供的,所以使用Flurl本身使用的相同模式来扩展非常容易。
扩展URL构建器
链式的URL生成器方法通常有三种重载:一种是扩展Flurl.Url,一种是扩展System.Uri以及扩展String。所有这些扩展都应该返回修改后的Flurl.Url对象:
public static Url DoMyThing(this Url url) {
// do something interesting with url
return url;
}
// keep these overloads DRY by constructing a Url and deferring to the above method
public static Url DoMyThing(this Uri uri) => new Url(uri).DoMyThing();
public static Url DoMyThing(this string url) => new Url(url).DoMyThing();
扩展Flurl.Http
链式的Flurl.Http
扩展方法通常包含4个,分别为:Flurl.Url
,System.Uri
,String
以及IFlurlRequest
。所有的都应该返回当前的IFlurlRequest
,以便进一步的链式扩展,如下:
public static IFlurlRequest DoMyThing(this IFlurlRequest req) {
// 使用 req.Settings, req.Headers, req.Url 等做一些有趣的事情。
return req;
}
// keep these overloads DRY by constructing a Url and deferring to the above method
// 通过构造一个 Url 并遵从上面的方法来保持这些重载 DRY
public static IFlurlRequest DoMyThing(this Url url) => new FlurlRequest(url).DoMyThing();
public static IFlurlRequest DoMyThing(this Uri uri) => new FlurlRequest(uri).DoMyThing();
public static IFlurlRequest DoMyThing(this string url) => new FlurlRequest(url).DoMyThing();
现在,你可以使用如下的方式调用自定义扩展的链式方法:
result = await "http://api.com"
.DoMyThing() // string extension
.GetAsync();
result = "http://api.com"
.AppendPathSegment("endpoint")
.DoMyThing() // Url extension
.GetAsync();
result = "http://api.com"
.AppendPathSegment("endpoint")
.WithBasicAuth(u, p)
.DoMyThing() // IFlurlRequest extension
.GetAsync();
在某些情况下,你可能还需要第五个重载:一个IFlurlClient
扩展。 如果您的扩展仅与Settings
或Headers
交互,请记住,它们的默认值存在于客户端级别。因此,为完整起见,您的扩展也应该支持客户端级别的默认值。