6
6
using System . Collections . Generic ;
7
7
using System . Diagnostics ;
8
8
using System . Linq ;
9
- using System . Runtime . CompilerServices ;
10
- using System . Text . Json ;
11
9
using System . Threading . Tasks ;
12
10
using System . Threading ;
13
11
using Elastic . Transport ;
14
- using Elastic . Transport . Diagnostics ;
15
12
16
13
using Elastic . Clients . Elasticsearch . Requests ;
17
14
@@ -28,18 +25,23 @@ public partial class ElasticsearchClient
28
25
private const string OpenTelemetrySchemaVersion = "https://opentelemetry.io/schemas/1.21.0" ;
29
26
30
27
private readonly ITransport < IElasticsearchClientSettings > _transport ;
31
- internal static ConditionalWeakTable < JsonSerializerOptions , IElasticsearchClientSettings > SettingsTable { get ; } = new ( ) ;
32
28
33
29
/// <summary>
34
30
/// Creates a client configured to connect to http://localhost:9200.
35
31
/// </summary>
36
- public ElasticsearchClient ( ) : this ( new ElasticsearchClientSettings ( new Uri ( "http://localhost:9200" ) ) ) { }
32
+ public ElasticsearchClient ( ) :
33
+ this ( new ElasticsearchClientSettings ( new Uri ( "http://localhost:9200" ) ) )
34
+ {
35
+ }
37
36
38
37
/// <summary>
39
38
/// Creates a client configured to connect to a node reachable at the provided <paramref name="uri" />.
40
39
/// </summary>
41
40
/// <param name="uri">The <see cref="Uri" /> to connect to.</param>
42
- public ElasticsearchClient ( Uri uri ) : this ( new ElasticsearchClientSettings ( uri ) ) { }
41
+ public ElasticsearchClient ( Uri uri ) :
42
+ this ( new ElasticsearchClientSettings ( uri ) )
43
+ {
44
+ }
43
45
44
46
/// <summary>
45
47
/// Creates a client configured to communicate with Elastic Cloud using the provided <paramref name="cloudId" />.
@@ -51,8 +53,8 @@ public ElasticsearchClient(Uri uri) : this(new ElasticsearchClientSettings(uri))
51
53
/// </summary>
52
54
/// <param name="cloudId">The Cloud ID of an Elastic Cloud deployment.</param>
53
55
/// <param name="credentials">The credentials to use for the connection.</param>
54
- public ElasticsearchClient ( string cloudId , AuthorizationHeader credentials ) : this (
55
- new ElasticsearchClientSettings ( cloudId , credentials ) )
56
+ public ElasticsearchClient ( string cloudId , AuthorizationHeader credentials ) :
57
+ this ( new ElasticsearchClientSettings ( cloudId , credentials ) )
56
58
{
57
59
}
58
60
@@ -69,8 +71,7 @@ internal ElasticsearchClient(ITransport<IElasticsearchClientSettings> transport)
69
71
{
70
72
transport . ThrowIfNull ( nameof ( transport ) ) ;
71
73
transport . Configuration . ThrowIfNull ( nameof ( transport . Configuration ) ) ;
72
- transport . Configuration . RequestResponseSerializer . ThrowIfNull (
73
- nameof ( transport . Configuration . RequestResponseSerializer ) ) ;
74
+ transport . Configuration . RequestResponseSerializer . ThrowIfNull ( nameof ( transport . Configuration . RequestResponseSerializer ) ) ;
74
75
transport . Configuration . Inferrer . ThrowIfNull ( nameof ( transport . Configuration . Inferrer ) ) ;
75
76
76
77
_transport = transport ;
@@ -96,47 +97,38 @@ private enum ProductCheckStatus
96
97
97
98
private partial void SetupNamespaces ( ) ;
98
99
99
- internal TResponse DoRequest < TRequest , TResponse , TRequestParameters > ( TRequest request )
100
- where TRequest : Request < TRequestParameters >
101
- where TResponse : TransportResponse , new ( )
102
- where TRequestParameters : RequestParameters , new ( ) =>
103
- DoRequest < TRequest , TResponse , TRequestParameters > ( request , null ) ;
104
-
105
100
internal TResponse DoRequest < TRequest , TResponse , TRequestParameters > (
106
- TRequest request ,
107
- Action < IRequestConfiguration > ? forceConfiguration )
101
+ TRequest request )
108
102
where TRequest : Request < TRequestParameters >
109
103
where TResponse : TransportResponse , new ( )
110
104
where TRequestParameters : RequestParameters , new ( )
111
- => DoRequestCoreAsync < TRequest , TResponse , TRequestParameters > ( false , request , forceConfiguration ) . EnsureCompleted ( ) ;
112
-
113
- internal Task < TResponse > DoRequestAsync < TRequest , TResponse , TRequestParameters > (
114
- TRequest request ,
115
- CancellationToken cancellationToken = default )
116
- where TRequest : Request < TRequestParameters >
117
- where TResponse : TransportResponse , new ( )
118
- where TRequestParameters : RequestParameters , new ( )
119
- => DoRequestAsync < TRequest , TResponse , TRequestParameters > ( request , null , cancellationToken ) ;
105
+ {
106
+ return DoRequestCoreAsync < TRequest , TResponse , TRequestParameters > ( false , request ) . EnsureCompleted ( ) ;
107
+ }
120
108
121
109
internal Task < TResponse > DoRequestAsync < TRequest , TResponse , TRequestParameters > (
122
110
TRequest request ,
123
- Action < IRequestConfiguration > ? forceConfiguration ,
124
111
CancellationToken cancellationToken = default )
125
112
where TRequest : Request < TRequestParameters >
126
113
where TResponse : TransportResponse , new ( )
127
114
where TRequestParameters : RequestParameters , new ( )
128
- => DoRequestCoreAsync < TRequest , TResponse , TRequestParameters > ( true , request , forceConfiguration , cancellationToken ) . AsTask ( ) ;
115
+ {
116
+ return DoRequestCoreAsync < TRequest , TResponse , TRequestParameters > ( true , request , cancellationToken ) . AsTask ( ) ;
117
+ }
129
118
130
119
private ValueTask < TResponse > DoRequestCoreAsync < TRequest , TResponse , TRequestParameters > (
131
120
bool isAsync ,
132
121
TRequest request ,
133
- Action < IRequestConfiguration > ? forceConfiguration ,
134
122
CancellationToken cancellationToken = default )
135
123
where TRequest : Request < TRequestParameters >
136
124
where TResponse : TransportResponse , new ( )
137
125
where TRequestParameters : RequestParameters , new ( )
138
126
{
139
- // The product check modifies request parameters and therefore must not be executed concurrently.
127
+ if ( request is null )
128
+ {
129
+ throw new ArgumentNullException ( nameof ( request ) ) ;
130
+ }
131
+
140
132
// We use a lockless CAS approach to make sure that only a single product check request is executed at a time.
141
133
// We do not guarantee that the product check is always performed on the first request.
142
134
@@ -157,12 +149,12 @@ private ValueTask<TResponse> DoRequestCoreAsync<TRequest, TResponse, TRequestPar
157
149
158
150
ValueTask < TResponse > SendRequest ( )
159
151
{
160
- var ( endpointPath , resolvedRouteValues , postData ) = PrepareRequest < TRequest , TRequestParameters > ( request ) ;
161
- var openTelemetryDataMutator = GetOpenTelemetryDataMutator < TRequest , TRequestParameters > ( request , resolvedRouteValues ) ;
152
+ PrepareRequest < TRequest , TRequestParameters > ( request , out var endpointPath , out var postData , out var requestConfiguration , out var routeValues ) ;
153
+ var openTelemetryDataMutator = GetOpenTelemetryDataMutator < TRequest , TRequestParameters > ( request , routeValues ) ;
162
154
163
155
return isAsync
164
- ? new ValueTask < TResponse > ( _transport . RequestAsync < TResponse > ( endpointPath , postData , openTelemetryDataMutator , request . RequestConfiguration , cancellationToken ) )
165
- : new ValueTask < TResponse > ( _transport . Request < TResponse > ( endpointPath , postData , openTelemetryDataMutator , request . RequestConfiguration ) ) ;
156
+ ? new ValueTask < TResponse > ( _transport . RequestAsync < TResponse > ( endpointPath , postData , openTelemetryDataMutator , requestConfiguration , cancellationToken ) )
157
+ : new ValueTask < TResponse > ( _transport . Request < TResponse > ( endpointPath , postData , openTelemetryDataMutator , requestConfiguration ) ) ;
166
158
}
167
159
168
160
async ValueTask < TResponse > SendRequestWithProductCheck ( )
@@ -178,34 +170,35 @@ async ValueTask<TResponse> SendRequestWithProductCheck()
178
170
// 32-bit read/write operations are atomic and due to the initial memory barrier, we can be sure that
179
171
// no other thread executes the product check at the same time. Locked access is not required here.
180
172
if ( _productCheckStatus is ( int ) ProductCheckStatus . InProgress )
173
+ {
181
174
_productCheckStatus = ( int ) ProductCheckStatus . NotChecked ;
175
+ }
182
176
183
177
throw ;
184
178
}
185
179
}
186
180
187
181
async ValueTask < TResponse > SendRequestWithProductCheckCore ( )
188
182
{
183
+ PrepareRequest < TRequest , TRequestParameters > ( request , out var endpointPath , out var postData , out var requestConfiguration , out var routeValues ) ;
184
+ var openTelemetryDataMutator = GetOpenTelemetryDataMutator < TRequest , TRequestParameters > ( request , routeValues ) ;
185
+
189
186
// Attach product check header
190
187
191
- // TODO: The copy constructor should accept null values
192
- var requestConfig = ( request . RequestConfiguration is null )
193
- ? new RequestConfiguration ( )
188
+ var requestConfig = ( requestConfiguration is null )
189
+ ? new RequestConfiguration
194
190
{
195
191
ResponseHeadersToParse = new HeadersList ( "x-elastic-product" )
196
192
}
197
- : new RequestConfiguration ( request . RequestConfiguration )
193
+ : new RequestConfiguration ( requestConfiguration )
198
194
{
199
- ResponseHeadersToParse = ( request . RequestConfiguration . ResponseHeadersToParse is { Count : > 0 } )
200
- ? new HeadersList ( request . RequestConfiguration . ResponseHeadersToParse , "x-elastic-product" )
195
+ ResponseHeadersToParse = ( requestConfiguration . ResponseHeadersToParse is { Count : > 0 } )
196
+ ? new HeadersList ( requestConfiguration . ResponseHeadersToParse , "x-elastic-product" )
201
197
: new HeadersList ( "x-elastic-product" )
202
198
} ;
203
199
204
200
// Send request
205
201
206
- var ( endpointPath , resolvedRouteValues , postData ) = PrepareRequest < TRequest , TRequestParameters > ( request ) ;
207
- var openTelemetryDataMutator = GetOpenTelemetryDataMutator < TRequest , TRequestParameters > ( request , resolvedRouteValues ) ;
208
-
209
202
TResponse response ;
210
203
211
204
if ( isAsync )
@@ -239,7 +232,9 @@ async ValueTask<TResponse> SendRequestWithProductCheckCore()
239
232
: ( int ) ProductCheckStatus . Failed ;
240
233
241
234
if ( _productCheckStatus == ( int ) ProductCheckStatus . Failed )
235
+ {
242
236
throw new UnsupportedProductException ( UnsupportedProductException . InvalidProductError ) ;
237
+ }
243
238
244
239
return response ;
245
240
}
@@ -249,15 +244,17 @@ async ValueTask<TResponse> SendRequestWithProductCheckCore()
249
244
where TRequest : Request < TRequestParameters >
250
245
where TRequestParameters : RequestParameters , new ( )
251
246
{
252
- // If there are no subscribed listeners, we avoid some work and allocations
247
+ // If there are no subscribed listeners, we avoid some work and allocations.
253
248
if ( ! Elastic . Transport . Diagnostics . OpenTelemetry . ElasticTransportActivitySourceHasListeners )
249
+ {
254
250
return null ;
251
+ }
255
252
256
253
return OpenTelemetryDataMutator ;
257
254
258
255
void OpenTelemetryDataMutator ( Activity activity )
259
256
{
260
- // We fall back to a general operation name in cases where the derived request fails to override the property
257
+ // We fall back to a general operation name in cases where the derived request fails to override the property.
261
258
var operationName = ! string . IsNullOrEmpty ( request . OperationName ) ? request . OperationName : request . HttpMethod . GetStringValue ( ) ;
262
259
263
260
// TODO: Optimisation: We should consider caching these, either for cases where resolvedRouteValues is null, or
@@ -267,7 +264,7 @@ void OpenTelemetryDataMutator(Activity activity)
267
264
// The latter may bloat the cache as some combinations of path parts may rarely re-occur.
268
265
269
266
activity . DisplayName = operationName ;
270
-
267
+
271
268
activity . SetTag ( OpenTelemetry . SemanticConventions . DbOperation , ! string . IsNullOrEmpty ( request . OperationName ) ? request . OperationName : "unknown" ) ;
272
269
activity . SetTag ( $ "{ OpenTelemetrySpanAttributePrefix } schema_url", OpenTelemetrySchemaVersion ) ;
273
270
@@ -282,21 +279,26 @@ void OpenTelemetryDataMutator(Activity activity)
282
279
}
283
280
}
284
281
285
- private ( EndpointPath endpointPath , Dictionary < string , string > ? resolvedRouteValues , PostData data ) PrepareRequest < TRequest , TRequestParameters > ( TRequest request )
282
+ private void PrepareRequest < TRequest , TRequestParameters > (
283
+ TRequest request ,
284
+ out EndpointPath endpointPath ,
285
+ out PostData ? postData ,
286
+ out IRequestConfiguration ? requestConfiguration ,
287
+ out Dictionary < string , string > ? routeValues )
286
288
where TRequest : Request < TRequestParameters >
287
289
where TRequestParameters : RequestParameters , new ( )
288
290
{
289
- request . ThrowIfNull ( nameof ( request ) , "A request is required." ) ;
290
-
291
- var ( resolvedUrl , _, routeValues ) = request . GetUrl ( ElasticsearchClientSettings ) ;
291
+ var ( resolvedUrl , _, resolvedRouteValues ) = request . GetUrl ( ElasticsearchClientSettings ) ;
292
292
var pathAndQuery = request . RequestParameters . CreatePathWithQueryStrings ( resolvedUrl , ElasticsearchClientSettings ) ;
293
293
294
- var postData =
295
- request . HttpMethod == HttpMethod . GET ||
296
- request . HttpMethod == HttpMethod . HEAD || ! request . SupportsBody
294
+ routeValues = resolvedRouteValues ;
295
+ endpointPath = new EndpointPath ( request . HttpMethod , pathAndQuery ) ;
296
+ postData =
297
+ request . HttpMethod is HttpMethod . GET or HttpMethod . HEAD || ! request . SupportsBody
297
298
? null
298
299
: PostData . Serializable ( request ) ;
299
300
300
- return ( new EndpointPath ( request . HttpMethod , pathAndQuery ) , routeValues , postData ) ;
301
+ requestConfiguration = request . RequestConfiguration ;
302
+ ElasticsearchClientSettings . OnBeforeRequest ? . Invoke ( this , request , endpointPath , ref postData , ref requestConfiguration ) ;
301
303
}
302
304
}
0 commit comments