-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathExtensions.Modifier.cs
More file actions
226 lines (211 loc) · 10.5 KB
/
Extensions.Modifier.cs
File metadata and controls
226 lines (211 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
namespace StackExchange.Utils
{
public static partial class ExtensionsForHttp
{
/// <summary>
/// Sets a proxy for this request.
/// </summary>
/// <param name="builder">The builder we're working on.</param>
/// <param name="proxy">The proxy to use on this request.</param>
/// <returns>The request builder for chaining.</returns>
/// <remarks>
/// This isn't *really* per request since it's global on <see cref="System.Net.Http.HttpClient"/>,
/// so in reality we grab a different client from the pool.
/// </remarks>
public static IRequestBuilder WithProxy(this IRequestBuilder builder, IWebProxy proxy)
{
builder.Proxy = proxy;
return builder;
}
/// <summary>
/// Sets an <see cref="IHttpClientPool"/> to get a client from for this request.
/// </summary>
/// <param name="builder">The builder we're working on.</param>
/// <param name="pool">The pool to use on this request (defaults to global settings otherwise).</param>
/// <returns>The request builder for chaining.</returns>
public static IRequestBuilder WithClientPool(this IRequestBuilder builder, IHttpClientPool pool)
{
builder.ClientPool = pool;
return builder;
}
/// <summary>
/// Sets a timeout for this request.
/// </summary>
/// <param name="builder">The builder we're working on.</param>
/// <param name="timeout">The timeout to use on this request.</param>
/// <returns>The request builder for chaining.</returns>
/// <remarks>
/// This isn't *really* per request since it's global on <see cref="System.Net.Http.HttpClient"/>,
/// so in reality we grab a different client from the pool.
/// </remarks>
public static IRequestBuilder WithTimeout(this IRequestBuilder builder, TimeSpan timeout)
{
builder.Timeout = timeout;
return builder;
}
/// <summary>
/// Disables logging errors to the exceptional log on this request.
/// </summary>
/// <param name="builder">The builder we're working on.</param>
/// <returns>The request builder for chaining.</returns>
public static IRequestBuilder WithoutErrorLogging(this IRequestBuilder builder)
{
builder.LogErrors = false;
return builder;
}
/// <summary>
/// Doesn't log an error when the response's HTTP status code is any of the <paramref name="ignoredStatusCodes"/>.
/// </summary>
/// <param name="builder">The builder we're working on.</param>
/// <param name="ignoredStatusCodes">HTTP status codes to ignore.</param>
/// <returns>The request builder for chaining.</returns>
public static IRequestBuilder WithoutLogging(this IRequestBuilder builder, IEnumerable<HttpStatusCode> ignoredStatusCodes)
{
builder.IgnoredResponseStatuses = ignoredStatusCodes;
return builder;
}
private static readonly ConcurrentDictionary<HttpStatusCode, ImmutableHashSet<HttpStatusCode>> _ignoreCache = new ConcurrentDictionary<HttpStatusCode, ImmutableHashSet<HttpStatusCode>>();
/// <summary>
/// Doesn't log an error when the response's HTTP status code is <paramref name="ignoredStatusCode"/>.
/// </summary>
/// <param name="builder">The builder we're working on.</param>
/// <param name="ignoredStatusCode">HTTP status code to ignore.</param>
/// <returns>The request builder for chaining.</returns>
public static IRequestBuilder WithoutLogging(this IRequestBuilder builder, HttpStatusCode ignoredStatusCode)
{
builder.IgnoredResponseStatuses = _ignoreCache.GetOrAdd(ignoredStatusCode, k => ImmutableHashSet.Create(k));
return builder;
}
/// <summary>
/// Logs error response body as part of the httpClientException data when the response's HTTP status code is any of the <paramref name="statusCodes"/>.
/// </summary>
/// <param name="builder">The builder we're working on.</param>
/// <param name="statusCodes">HTTP error status codes to log for.</param>
/// <returns>The request builder for chaining.</returns>
public static IRequestBuilder WithErrorResponseBodyLogging(this IRequestBuilder builder, params HttpStatusCode[] statusCodes)
{
builder.LogErrorResponseBodyStatuses = statusCodes;
return builder;
}
/// <summary>
/// Adds an event handler for this request, for appending additional information to the logged exception for example.
/// </summary>
/// <param name="builder">The builder we're working on.</param>
/// <param name="beforeLogHandler">The exception handler to run before logging</param>
/// <returns>The request builder for chaining.</returns>
public static IRequestBuilder OnException(this IRequestBuilder builder, EventHandler<HttpExceptionArgs> beforeLogHandler)
{
builder.BeforeExceptionLog += beforeLogHandler;
return builder;
}
/// <summary>
/// Add a header to this request.
/// </summary>
/// <param name="builder">The builder we're working on.</param>
/// <param name="name">The header name to add to this request.</param>
/// <param name="value">The header value (for <paramref name="name"/>) to add to this request.</param>
/// <returns>The request builder for chaining.</returns>
public static IRequestBuilder AddHeader(this IRequestBuilder builder, string name, string value)
{
if (!string.IsNullOrEmpty(name))
{
try
{
builder.Message.Headers.Add(name, value);
}
catch (Exception e)
{
var wrapper = new HttpClientException("Unable to set header: " + name + " to '" + value + "'", builder.Message.RequestUri, e);
builder.GetSettings().OnException(builder, new HttpExceptionArgs(builder, wrapper));
}
}
return builder;
}
/// <summary>
/// Adds a single header without Validation against known Header types.
/// (ideal if you have different interpretation to the spec for any known types)
/// </summary>
/// <param name="builder">The builder we're working on.</param>
/// <param name="name">The auth scheme to add to this request.</param>
/// <param name="value">The key value (for <paramref name="name"/>) to add to this request.</param>
/// <returns>The request builder for chaining.</returns>
public static IRequestBuilder AddHeaderWithoutValidation(this IRequestBuilder builder, string name, string value)
{
if (!string.IsNullOrEmpty(name))
{
try
{
builder.Message.Headers.TryAddWithoutValidation(name, value);
}
catch (Exception e)
{
var wrapper = new HttpClientException("Unable to set header using: " + name + " to '" + value + "'", builder.Message.RequestUri, e);
builder.GetSettings().OnException(builder, new HttpExceptionArgs(builder, wrapper));
}
}
return builder;
}
/// <summary>
/// Adds headers to this request.
/// </summary>
/// <param name="builder">The builder we're working on.</param>
/// <param name="headers">The headers to add to this request.</param>
/// <returns>The request builder for chaining.</returns>
public static IRequestBuilder AddHeaders(this IRequestBuilder builder, IDictionary<string, string> headers)
{
if (headers == null) return builder;
var pHeaders = builder.Message.Headers;
foreach (var kv in headers)
{
try
{
//pHeaders.Add(kv.Key, kv.Value);
switch (kv.Key)
{
// certain headers must be accessed via the named property on the WebRequest
case "Accept": pHeaders.Accept.ParseAdd(kv.Value); break;
// case "Connection": break;
// case "proxy-connection": break;
// case "Proxy-Connection": break;
// case "Content-Length": break;
case "Content-Type": builder.Message.Content.Headers.ContentType = new MediaTypeHeaderValue(kv.Value); break;
// case "Host": break;
// case "If-Modified-Since": pHeaders.IfModifiedSince = DateTime.ParseExact(kv.Value, "R", CultureInfo.InvariantCulture); break;
// case "Referer": pHeaders.Referrer = new Uri(kv.Value); break;
// case "User-Agent": pHeaders.UserAgent.ParseAdd("Stack Exchange (Proxy)"); break;
default: pHeaders.Add(kv.Key, kv.Value); break;
}
}
catch (Exception e)
{
var wrapper = new HttpClientException("Unable to set header: " + kv.Key + " to '" + kv.Value + "'", builder.Message.RequestUri, e);
builder.GetSettings().OnException(builder, new HttpExceptionArgs(builder, wrapper));
}
}
return builder;
}
/// <summary>
/// Specifies the HTTP version to use for this request
/// </summary>
public static IRequestBuilder WithProtocolVersion(this IRequestBuilder builder, Version version)
{
builder.Message.Version = version;
return builder;
}
/// <summary>
/// Indicates that the response's content shouldn't be buffered, setting the HttpCompletionOption accordingly.
/// </summary>
public static IRequestBuilder WithoutResponseBuffering(this IRequestBuilder builder)
{
builder.BufferResponse = false;
return builder;
}
}
}