Skip to content

Commit 48304e3

Browse files
committed
try/catch when attempting to deserialize ServerError from response
This commit adds a try/catch block around attempting to deserialize a ServerError from the body, since IElasticsearchResponse types withn Elasticsearch.Net set/attempt to set the response body, irrespective of the response status code. Move BytesResponseTests and StringResponseTests from Tests.Reproduce into main Tests project. Allow byte array to be passed to FixedResponseClient. Fixes #3378
1 parent b6add7d commit 48304e3

File tree

10 files changed

+207
-67
lines changed

10 files changed

+207
-67
lines changed

src/Elasticsearch.Net/Responses/ElasticsearchResponse.cs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ namespace Elasticsearch.Net
1010
/// </summary>
1111
public abstract class ElasticsearchResponseBase : IApiCallDetails, IElasticsearchResponse
1212
{
13+
/// <inheritdoc />
1314
public IApiCallDetails ApiCall { get; set; }
1415

1516
bool IElasticsearchResponse.TryGetServerErrorReason(out string reason) => this.TryGetServerErrorReason(out reason);
@@ -20,35 +21,52 @@ protected virtual bool TryGetServerErrorReason(out string reason)
2021
return false;
2122
}
2223

24+
// TODO: Remove?
2325
//ignored
2426
List<Audit> IApiCallDetails.AuditTrail { get; set; }
2527

28+
/// <inheritdoc cref="IApiCallDetails.Success"/>
2629
public bool Success => this.ApiCall.Success;
30+
31+
/// <inheritdoc cref="IApiCallDetails.ResponseMimeType"/>
2732
public string ResponseMimeType => this.ApiCall.ResponseMimeType;
33+
34+
/// <inheritdoc cref="IApiCallDetails.HttpMethod"/>
2835
public HttpMethod HttpMethod => this.ApiCall.HttpMethod;
36+
37+
/// <inheritdoc cref="IApiCallDetails.Uri"/>
2938
public Uri Uri => this.ApiCall.Uri;
39+
40+
/// <inheritdoc cref="IApiCallDetails.HttpStatusCode"/>
3041
public int? HttpStatusCode => this.ApiCall.HttpStatusCode;
42+
43+
/// <inheritdoc cref="IApiCallDetails.AuditTrail"/>
3144
public List<Audit> AuditTrail => this.ApiCall.AuditTrail;
45+
46+
/// <inheritdoc cref="IApiCallDetails.DeprecationWarnings"/>
3247
public IEnumerable<string> DeprecationWarnings => this.ApiCall.DeprecationWarnings;
48+
49+
/// <inheritdoc cref="IApiCallDetails.SuccessOrKnownError"/>
3350
public bool SuccessOrKnownError => this.ApiCall.SuccessOrKnownError;
51+
52+
/// <inheritdoc cref="IApiCallDetails.OriginalException"/>
3453
public Exception OriginalException => this.ApiCall.OriginalException;
3554

36-
/// <summary>The raw byte request message body, only set when DisableDirectStreaming() is set on Connection configuration</summary>
55+
/// <inheritdoc cref="IApiCallDetails.RequestBodyInBytes"/>
3756
public byte[] RequestBodyInBytes => this.ApiCall.RequestBodyInBytes;
3857

39-
/// <summary>The raw byte response message body, only set when DisableDirectStreaming() is set on Connection configuration</summary>
58+
/// <inheritdoc cref="IApiCallDetails.ResponseBodyInBytes"/>
4059
public byte[] ResponseBodyInBytes => this.ApiCall.ResponseBodyInBytes;
4160

61+
/// <inheritdoc cref="IApiCallDetails.DebugInformation"/>
4262
public string DebugInformation => this.ApiCall.DebugInformation;
4363

4464
public override string ToString() => this.ApiCall.ToString();
45-
46-
4765
}
4866

4967
/// <summary>
5068
/// A response from Elasticsearch including details about the request/response life cycle. Base class for the built in low level response
51-
/// types: <see cref="StringResponse"/> <see cref="BytesResponse"/> and <see cref="DynamicResponse"/>
69+
/// types, <see cref="StringResponse"/>, <see cref="BytesResponse"/>, <see cref="DynamicResponse"/> and <see cref="VoidResponse"/>
5270
/// </summary>
5371
public abstract class ElasticsearchResponse<T> : ElasticsearchResponseBase
5472
{

src/Elasticsearch.Net/Responses/HttpDetails/IApiCallDetails.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,22 @@
44

55
namespace Elasticsearch.Net
66
{
7+
/// <summary>
8+
/// Details about the API call
9+
/// </summary>
710
public interface IApiCallDetails
811
{
912
/// <summary>
1013
/// The response status code is in the 200 range or is in the allowed list of status codes set on the request.
1114
/// </summary>
1215
bool Success { get; }
1316

14-
/// <summary>The response mime type </summary>
17+
/// <summary>The response MIME type </summary>
1518
string ResponseMimeType { get; }
1619

1720
/// <summary>
1821
/// If <see cref="Success"/> is <c>false</c>, this will hold the original exception.
19-
/// This will be the orginating CLR exception in most cases.
22+
/// This will be the originating CLR exception in most cases.
2023
/// </summary>
2124
Exception OriginalException { get; }
2225

@@ -55,7 +58,7 @@ public interface IApiCallDetails
5558
[DebuggerDisplay("{RequestBodyInBytes != null ? System.Text.Encoding.UTF8.GetString(RequestBodyInBytes) : null,nq}")]
5659
byte[] RequestBodyInBytes { get; }
5760

58-
//TODO Get rid of setter
61+
//TODO: Get rid of setter
5962
/// <summary>
6063
/// An audit trail of requests made to nodes within the cluster
6164
/// </summary>

src/Elasticsearch.Net/Responses/Special/BytesResponse.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.IO;
1+
using System;
2+
using System.IO;
23
using System.Text;
34

45
namespace Elasticsearch.Net
@@ -12,9 +13,16 @@ public bool TryGetServerError(out ServerError serverError)
1213
{
1314
serverError = null;
1415
if (this.Body == null || this.Body.Length == 0) return false;
15-
using(var stream = new MemoryStream(this.Body))
16-
serverError = ServerError.Create(stream);
17-
return true;
16+
try
17+
{
18+
using(var stream = new MemoryStream(this.Body))
19+
serverError = ServerError.Create(stream);
20+
return true;
21+
}
22+
catch
23+
{
24+
return false;
25+
}
1826
}
1927

2028
protected override bool TryGetServerErrorReason(out string reason)

src/Elasticsearch.Net/Responses/Special/StringResponse.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.IO;
1+
using System;
2+
using System.IO;
23
using System.Text;
34

45
namespace Elasticsearch.Net
@@ -12,9 +13,16 @@ public bool TryGetServerError(out ServerError serverError)
1213
{
1314
serverError = null;
1415
if (string.IsNullOrEmpty(this.Body)) return false;
15-
using(var stream = new MemoryStream(Encoding.UTF8.GetBytes(this.Body)))
16-
serverError = ServerError.Create(stream);
17-
return true;
16+
try
17+
{
18+
using(var stream = new MemoryStream(Encoding.UTF8.GetBytes(this.Body)))
19+
serverError = ServerError.Create(stream);
20+
return true;
21+
}
22+
catch (Exception)
23+
{
24+
return false;
25+
}
1826
}
1927

2028
protected override bool TryGetServerErrorReason(out string reason)

src/Tests/Tests.Core/Client/FixedResponseClient.cs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,25 @@ public static ConnectionSettings CreateConnectionSettings(
2626
Exception exception = null)
2727
{
2828
var serializer = TestClient.Default.RequestResponseSerializer;
29-
byte[] fixedResult = null;
30-
if (response is string s) fixedResult = Encoding.UTF8.GetBytes(s);
31-
else if (contentType == RequestData.MimeType) fixedResult = serializer.SerializeToBytes(response);
32-
else fixedResult = Encoding.UTF8.GetBytes(response.ToString());
29+
byte[] responseBytes;
30+
switch (response)
31+
{
32+
case string s:
33+
responseBytes = Encoding.UTF8.GetBytes(s);
34+
break;
35+
case byte[] b:
36+
responseBytes = b;
37+
break;
38+
default:
39+
{
40+
responseBytes = contentType == RequestData.MimeType
41+
? serializer.SerializeToBytes(response)
42+
: Encoding.UTF8.GetBytes(response.ToString());
43+
break;
44+
}
45+
}
3346

34-
var connection = new InMemoryConnection(fixedResult, statusCode, exception, contentType);
47+
var connection = new InMemoryConnection(responseBytes, statusCode, exception, contentType);
3548
var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
3649
var defaultSettings = new ConnectionSettings(connectionPool, connection)
3750
.DefaultIndex("default-index");

src/Tests/Tests.Reproduce/BytesResponseTests.cs

Lines changed: 0 additions & 38 deletions
This file was deleted.

src/Tests/Tests/ClientConcepts/ConnectionPooling/Exceptions/UnrecoverableExceptions.doc.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Nest;
1010
using Tests.Domain;
1111
using Tests.Framework;
12+
using Tests.Framework.SerializationTests;
1213

1314
namespace Tests.ClientConcepts.ConnectionPooling.Exceptions
1415
{
@@ -99,14 +100,7 @@ [U] public async Task BadAuthenticationIsUnrecoverable()
99100
);
100101
}
101102

102-
private static byte[] HtmlNginx401Response = Encoding.UTF8.GetBytes(@"<html>
103-
<head><title>401 Authorization Required</title></head>
104-
<body bgcolor=""white"">
105-
<center><h1>401 Authorization Required</h1></center>
106-
<hr><center>nginx/1.4.6 (Ubuntu)</center>
107-
</body>
108-
</html>
109-
");
103+
private static byte[] HtmlNginx401Response = Encoding.UTF8.GetBytes(StubResponse.NginxHtml401Response);
110104

111105
/**
112106
* When a bad authentication response occurs, the client attempts to deserialize the response body returned;
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using System;
2+
using System.Linq;
3+
using System.Text;
4+
using Elastic.Xunit.XunitPlumbing;
5+
using Elasticsearch.Net;
6+
using FluentAssertions;
7+
using Tests.Core.Client;
8+
using Tests.Core.ManagedElasticsearch.Clusters;
9+
10+
namespace Tests.Framework.SerializationTests
11+
{
12+
public class BytesResponseTests : IClusterFixture<ReadOnlyCluster>
13+
{
14+
private readonly ReadOnlyCluster _cluster;
15+
16+
public BytesResponseTests(ReadOnlyCluster cluster) => _cluster = cluster;
17+
18+
[I] public void NonNullBytesResponse()
19+
{
20+
var client = _cluster.Client;
21+
22+
var bytesResponse = client.LowLevel.Search<BytesResponse>("project", "project", PostData.Serializable(new { }));
23+
bytesResponse.Body.Should().NotBeNull();
24+
bytesResponse.Body.Should().BeEquivalentTo(bytesResponse.ResponseBodyInBytes);
25+
}
26+
27+
[I] public void NonNullBytesLowLevelResponse()
28+
{
29+
var settings = new ConnectionConfiguration(new Uri($"http://localhost:{_cluster.Nodes.First().Port ?? 9200}"));
30+
var lowLevelClient = new ElasticLowLevelClient(settings);
31+
32+
var bytesResponse = lowLevelClient.Search<BytesResponse>("project", "project", PostData.Serializable(new { }));
33+
34+
bytesResponse.Body.Should().NotBeNull();
35+
bytesResponse.Body.Should().BeEquivalentTo(bytesResponse.ResponseBodyInBytes);
36+
}
37+
38+
[U]
39+
public void TryGetServerErrorDoesNotThrowException()
40+
{
41+
var responseBytes = Encoding.UTF8.GetBytes(StubResponse.NginxHtml401Response);
42+
43+
var client = FixedResponseClient.Create(responseBytes, 401,
44+
modifySettings: s => s.DisableDirectStreaming(),
45+
contentType: "text/html",
46+
exception: new Exception("problem with the request as a result of 401")
47+
);
48+
49+
var bytesResponse = client.LowLevel.Search<BytesResponse>("project", "project", PostData.Serializable(new { }));
50+
51+
bytesResponse.Body.Should().NotBeNull();
52+
bytesResponse.Body.Should().BeEquivalentTo(responseBytes);
53+
bytesResponse.TryGetServerError(out var serverError).Should().BeFalse();
54+
serverError.Should().BeNull();
55+
}
56+
57+
[U] public void SkipDeserializationForStatusCodesSetsBody()
58+
{
59+
var responseBytes = Encoding.UTF8.GetBytes(StubResponse.NginxHtml401Response);
60+
61+
var client = FixedResponseClient.Create(responseBytes, 401,
62+
modifySettings: s => s.DisableDirectStreaming().SkipDeserializationForStatusCodes(401),
63+
contentType: "text/html",
64+
exception: new Exception("problem with the request as a result of 401")
65+
);
66+
67+
var bytesResponse = client.LowLevel.Search<BytesResponse>("project", "project", PostData.Serializable(new { }));
68+
69+
bytesResponse.Body.Should().NotBeNull();
70+
bytesResponse.Body.Should().BeEquivalentTo(responseBytes);
71+
}
72+
}
73+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System;
2+
using System.Text;
3+
using Elastic.Xunit.XunitPlumbing;
4+
using Elasticsearch.Net;
5+
using FluentAssertions;
6+
using Tests.Core.Client;
7+
8+
namespace Tests.Framework.SerializationTests
9+
{
10+
public class StringResponseTests
11+
{
12+
[U] public void TryGetServerErrorDoesNotThrowException()
13+
{
14+
var client = FixedResponseClient.Create(StubResponse.NginxHtml401Response, 401,
15+
modifySettings: s => s.DisableDirectStreaming(),
16+
contentType: "text/html",
17+
exception: new Exception("problem with the request as a result of 401")
18+
);
19+
20+
var stringResponse = client.LowLevel.Search<StringResponse>("project", "project", PostData.Serializable(new { }));
21+
22+
stringResponse.Body.Should().NotBeNull();
23+
stringResponse.Body.Should().Be(StubResponse.NginxHtml401Response);
24+
stringResponse.TryGetServerError(out var serverError).Should().BeFalse();
25+
serverError.Should().BeNull();
26+
}
27+
28+
[U] public void SkipDeserializationForStatusCodesSetsBody()
29+
{
30+
var client = FixedResponseClient.Create(StubResponse.NginxHtml401Response, 401,
31+
modifySettings: s => s.DisableDirectStreaming().SkipDeserializationForStatusCodes(401),
32+
contentType: "text/html",
33+
exception: new Exception("problem with the request as a result of 401")
34+
);
35+
36+
var stringResponse = client.LowLevel.Search<StringResponse>("project", "project", PostData.Serializable(new { }));
37+
38+
stringResponse.Body.Should().NotBeNull();
39+
stringResponse.Body.Should().Be(StubResponse.NginxHtml401Response);
40+
}
41+
}
42+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace Tests.Framework.SerializationTests
2+
{
3+
public class StubResponse
4+
{
5+
public const string NginxHtml401Response = @"<html>
6+
<head><title>401 Authorization Required</title></head>
7+
<body bgcolor=""white"">
8+
<center><h1>401 Authorization Required</h1></center>
9+
<hr><center>nginx/1.4.6 (Ubuntu)</center>
10+
</body>
11+
</html>
12+
<!-- a padding to disable MSIE and Chrome friendly error page -->
13+
<!-- a padding to disable MSIE and Chrome friendly error page -->
14+
<!-- a padding to disable MSIE and Chrome friendly error page -->
15+
<!-- a padding to disable MSIE and Chrome friendly error page -->
16+
<!-- a padding to disable MSIE and Chrome friendly error page -->
17+
<!-- a padding to disable MSIE and Chrome friendly error page -->";
18+
}
19+
}

0 commit comments

Comments
 (0)