Skip to content

Commit cd854ff

Browse files
authored
Use HttpMessageHandler on HttpConnection (#3648)
* Use HttpMessageHandler Fixes #3415 * Use MemoryStreamFactory This commit uses the MemoryStreamFactory to create a Memory Stream when the response is an Elasticsearch.Net response type * Use StringBuilder to build query string This commit replaces the use of string.join and the capturing delegate to instead use a StringBuilder and for loop. It also uses a simple heuristic to intialize the StringBuilder capacity. * Avoid allocating Headers if unused This commit avoids allocating a NameValueCollection for Headers if there are no headers to send
1 parent fd38b58 commit cd854ff

File tree

4 files changed

+40
-19
lines changed

4 files changed

+40
-19
lines changed

src/Elasticsearch.Net/Connection/HttpConnection.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public virtual TResponse Request<TResponse>(RequestData requestData)
7474
}
7575
responseStream = responseStream ?? Stream.Null;
7676
var response = ResponseBuilder.ToResponse<TResponse>(requestData, ex, statusCode, warnings, responseStream, mimeType);
77-
//var response = builder.ToResponse();
77+
7878
//explicit dispose of response not needed (as documented on MSDN) on desktop CLR
7979
//but we can not guarantee this is true for all HttpMessageHandler implementations
8080
if (typeof(TResponse) != typeof(ElasticsearchResponse<Stream>)) responseMessage?.Dispose();
@@ -150,7 +150,7 @@ private HttpClient GetClient(RequestData requestData)
150150
return client;
151151
}
152152

153-
protected virtual HttpClientHandler CreateHttpClientHandler(RequestData requestData)
153+
protected virtual HttpMessageHandler CreateHttpClientHandler(RequestData requestData)
154154
{
155155
var handler = new HttpClientHandler
156156
{
@@ -212,7 +212,12 @@ protected virtual HttpRequestMessage CreateRequestMessage(RequestData requestDat
212212
var method = ConvertHttpMethod(requestData.Method);
213213
var requestMessage = new HttpRequestMessage(method, requestData.Uri);
214214

215-
foreach (string key in requestData.Headers) requestMessage.Headers.TryAddWithoutValidation(key, requestData.Headers.GetValues(key));
215+
if (requestData.Headers != null)
216+
{
217+
foreach (string key in requestData.Headers)
218+
requestMessage.Headers.TryAddWithoutValidation(key, requestData.Headers.GetValues(key));
219+
}
220+
216221
requestMessage.Headers.Connection.Clear();
217222
requestMessage.Headers.ConnectionClose = false;
218223
requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(requestData.Accept));

src/Elasticsearch.Net/Transport/Pipeline/RequestData.cs

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.IO;
55
using System.Linq;
66
using System.Security.Cryptography.X509Certificates;
7+
using System.Text;
78

89
namespace Elasticsearch.Net
910
{
@@ -42,10 +43,17 @@ IMemoryStreamFactory memoryStreamFactory
4243
HttpCompression = global.EnableHttpCompression;
4344
RequestMimeType = local?.ContentType ?? MimeType;
4445
Accept = local?.Accept ?? MimeType;
45-
Headers = global.Headers != null ? new NameValueCollection(global.Headers) : new NameValueCollection();
46+
47+
if (global.Headers != null)
48+
Headers = new NameValueCollection(global.Headers);
4649

4750
if (!string.IsNullOrEmpty(local?.OpaqueId))
51+
{
52+
if (Headers == null)
53+
Headers = new NameValueCollection();
54+
4855
Headers.Add(OpaqueIdHeader, local.OpaqueId);
56+
}
4957

5058
RunAs = local?.RunAs;
5159
SkipDeserializationForStatusCodes = global?.SkipDeserializationForStatusCodes;
@@ -54,7 +62,7 @@ IMemoryStreamFactory memoryStreamFactory
5462
RequestTimeout = local?.RequestTimeout ?? global.RequestTimeout;
5563
PingTimeout =
5664
local?.PingTimeout
57-
?? global?.PingTimeout
65+
?? global.PingTimeout
5866
?? (global.ConnectionPool.UsingSsl ? ConnectionConfiguration.DefaultPingTimeoutOnSSL : ConnectionConfiguration.DefaultPingTimeout);
5967

6068
KeepAliveInterval = (int)(global.KeepAliveInterval?.TotalMilliseconds ?? 2000);
@@ -86,7 +94,7 @@ IMemoryStreamFactory memoryStreamFactory
8694
public bool MadeItToResponse { get; set; }
8795
public IMemoryStreamFactory MemoryStreamFactory { get; }
8896

89-
public HttpMethod Method { get; private set; }
97+
public HttpMethod Method { get; }
9098

9199
public Node Node { get; set; }
92100
public AuditEvent OnFailureAuditEvent => MadeItToResponse ? AuditEvent.BadResponse : AuditEvent.BadRequest;
@@ -111,11 +119,12 @@ private string CreatePathWithQueryStrings(string path, IConnectionConfigurationV
111119
{
112120
path = path ?? string.Empty;
113121
if (path.Contains("?"))
114-
throw new ArgumentException($"{nameof(path)} can not contain querystring parmeters and needs to be already escaped");
122+
throw new ArgumentException($"{nameof(path)} can not contain querystring parameters and needs to be already escaped");
115123

116124
var g = global.QueryStringParameters;
117125
var l = request?.QueryString;
118-
if (g?.Count == 0 && l?.Count == 0) return path;
126+
127+
if ((g == null || g.Count == 0) && (l == null || l.Count == 0)) return path;
119128

120129
//create a copy of the global query string collection if needed.
121130
var nv = g == null ? new NameValueCollection() : new NameValueCollection(g);
@@ -138,15 +147,22 @@ internal static class NameValueCollectionExtensions
138147
{
139148
internal static string ToQueryString(this NameValueCollection nv)
140149
{
141-
if (nv == null) return string.Empty;
142-
if (nv.AllKeys.Length == 0) return string.Empty;
150+
if (nv == null || nv.AllKeys.Length == 0) return string.Empty;
143151

144-
string E(string v)
152+
// initialize with capacity for number of key/values with length 5 each
153+
var builder = new StringBuilder("?", nv.AllKeys.Length * 2 * 5);
154+
for (int i = 0; i < nv.AllKeys.Length; i++)
145155
{
146-
return Uri.EscapeDataString(v);
156+
if (i != 0)
157+
builder.Append("&");
158+
159+
var key = nv.AllKeys[i];
160+
builder.Append(Uri.EscapeDataString(key));
161+
builder.Append("=");
162+
builder.Append(Uri.EscapeDataString(nv[key]));
147163
}
148164

149-
return "?" + string.Join("&", nv.AllKeys.Select(key => $"{E(key)}={E(nv[key])}"));
165+
return builder.ToString();
150166
}
151167

152168
internal static void UpdateFromDictionary(this NameValueCollection queryString, Dictionary<string, object> queryStringUpdates,

src/Elasticsearch.Net/Transport/Pipeline/ResponseBuilder.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ private static TResponse SetBody<TResponse>(ApiCallDetails details, RequestData
9090

9191
using (responseStream)
9292
{
93-
if (SetSpecialTypes<TResponse>(bytes, out var r))
93+
if (SetSpecialTypes<TResponse>(requestData, bytes, out var r))
9494
return r;
9595

9696
if (details.HttpStatusCode.HasValue && requestData.SkipDeserializationForStatusCodes.Contains(details.HttpStatusCode.Value))
@@ -121,7 +121,7 @@ private static async Task<TResponse> SetBodyAsync<TResponse>(
121121

122122
using (responseStream)
123123
{
124-
if (SetSpecialTypes<TResponse>(bytes, out var r)) return r;
124+
if (SetSpecialTypes<TResponse>(requestData, bytes, out var r)) return r;
125125

126126
if (details.HttpStatusCode.HasValue && requestData.SkipDeserializationForStatusCodes.Contains(details.HttpStatusCode.Value))
127127
return null;
@@ -136,7 +136,7 @@ private static async Task<TResponse> SetBodyAsync<TResponse>(
136136
}
137137
}
138138

139-
private static bool SetSpecialTypes<TResponse>(byte[] bytes, out TResponse cs)
139+
private static bool SetSpecialTypes<TResponse>(RequestData requestData, byte[] bytes, out TResponse cs)
140140
where TResponse : class, IElasticsearchResponse, new()
141141
{
142142
cs = null;
@@ -151,7 +151,7 @@ private static bool SetSpecialTypes<TResponse>(byte[] bytes, out TResponse cs)
151151
cs = new VoidResponse() as TResponse;
152152
else if (responseType == typeof(DynamicResponse))
153153
{
154-
using (var ms = new MemoryStream(bytes))
154+
using (var ms = requestData.MemoryStreamFactory.Create(bytes))
155155
{
156156
var body = LowLevelRequestResponseSerializer.Instance.Deserialize<DynamicBody>(ms);
157157
cs = new DynamicResponse(body) as TResponse;

src/Tests/Tests/ClientConcepts/Connection/HttpConnectionTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,9 @@ public override Task<TResponse> RequestAsync<TResponse>(RequestData requestData,
127127
return base.RequestAsync<TResponse>(requestData, cancellationToken);
128128
}
129129

130-
protected override HttpClientHandler CreateHttpClientHandler(RequestData requestData)
130+
protected override HttpMessageHandler CreateHttpClientHandler(RequestData requestData)
131131
{
132-
LastUsedHttpClientHandler = base.CreateHttpClientHandler(requestData);
132+
LastUsedHttpClientHandler = (HttpClientHandler)base.CreateHttpClientHandler(requestData);
133133
return LastUsedHttpClientHandler;
134134
}
135135
}

0 commit comments

Comments
 (0)