Skip to content

Commit 7d1f543

Browse files
authored
Merge pull request #218 from graphql-dotnet/return-content-on-failed-http-requests
Return content on failed http requests
2 parents 7aac988 + 2ecf683 commit 7d1f543

File tree

5 files changed

+86
-50
lines changed

5 files changed

+86
-50
lines changed

src/GraphQL.Client/GraphQLHttpClient.cs

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
using System;
22
using System.Collections.Concurrent;
33
using System.Diagnostics;
4+
using System.IO;
45
using System.Net.Http;
5-
using System.Text;
66
using System.Threading;
77
using System.Threading.Tasks;
88
using GraphQL.Client.Abstractions;
@@ -73,7 +73,7 @@ public async Task<GraphQLResponse<TResponse>> SendQueryAsync<TResponse>(GraphQLR
7373
if (Options.UseWebSocketForQueriesAndMutations)
7474
return await _graphQlHttpWebSocket.SendRequest<TResponse>(request, cancellationToken);
7575

76-
return await SendHttpPostRequestAsync<TResponse>(request, cancellationToken);
76+
return await SendHttpRequestAsync<TResponse>(request, cancellationToken);
7777
}
7878

7979
/// <inheritdoc />
@@ -124,35 +124,33 @@ public IObservable<GraphQLResponse<TResponse>> CreateSubscriptionStream<TRespons
124124

125125
#region Private Methods
126126

127-
private async Task<GraphQLHttpResponse<TResponse>> SendHttpPostRequestAsync<TResponse>(GraphQLRequest request, CancellationToken cancellationToken = default)
127+
private async Task<GraphQLHttpResponse<TResponse>> SendHttpRequestAsync<TResponse>(GraphQLRequest request, CancellationToken cancellationToken = default)
128128
{
129129
var preprocessedRequest = await Options.PreprocessRequest(request, this);
130-
using var httpRequestMessage = GenerateHttpRequestMessage(preprocessedRequest);
131-
using var httpResponseMessage = await HttpClient.SendAsync(httpRequestMessage, cancellationToken);
132-
133-
httpResponseMessage.EnsureSuccessStatusCode();
134130

135-
var bodyStream = await httpResponseMessage.Content.ReadAsStreamAsync();
136-
var response = await JsonSerializer.DeserializeFromUtf8StreamAsync<TResponse>(bodyStream, cancellationToken);
137-
return response.ToGraphQLHttpResponse(httpResponseMessage.Headers, httpResponseMessage.StatusCode);
138-
}
131+
using var httpRequestMessage = preprocessedRequest.ToHttpRequestMessage(Options, JsonSerializer);
132+
using var httpResponseMessage = await HttpClient.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
139133

140-
private HttpRequestMessage GenerateHttpRequestMessage(GraphQLRequest request)
141-
{
142-
var message = new HttpRequestMessage(HttpMethod.Post, Options.EndPoint)
134+
var contentStream = await httpResponseMessage.Content.ReadAsStreamAsync();
135+
136+
if (httpResponseMessage.IsSuccessStatusCode)
143137
{
144-
Content = new StringContent(JsonSerializer.SerializeToString(request), Encoding.UTF8, Options.MediaType)
145-
};
138+
var graphQLResponse = await JsonSerializer.DeserializeFromUtf8StreamAsync<TResponse>(contentStream, cancellationToken);
139+
return graphQLResponse.ToGraphQLHttpResponse(httpResponseMessage.Headers, httpResponseMessage.StatusCode);
140+
}
146141

147-
if (request is GraphQLHttpRequest httpRequest)
148-
httpRequest.PreprocessHttpRequestMessage(message);
142+
// error handling
143+
string content = null;
144+
if (contentStream != null)
145+
using (var sr = new StreamReader(contentStream))
146+
content = await sr.ReadToEndAsync();
149147

150-
return message;
148+
throw new GraphQLHttpRequestException(httpResponseMessage.StatusCode, httpResponseMessage.Headers, content);
151149
}
152150

153151
private Uri GetWebSocketUri()
154152
{
155-
var webSocketSchema = Options.EndPoint.Scheme == "https" ? "wss" : "ws";
153+
string webSocketSchema = Options.EndPoint.Scheme == "https" ? "wss" : "ws";
156154
return new Uri($"{webSocketSchema}://{Options.EndPoint.Host}:{Options.EndPoint.Port}{Options.EndPoint.AbsolutePath}");
157155
}
158156

src/GraphQL.Client/GraphQLHttpClientOptions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ public class GraphQLHttpClientOptions
4545
/// <summary>
4646
/// Request preprocessing function. Can be used i.e. to inject authorization info into a GraphQL request payload.
4747
/// </summary>
48-
public Func<GraphQLRequest, GraphQLHttpClient, Task<GraphQLRequest>> PreprocessRequest { get; set; } = (request, client) => Task.FromResult(request);
48+
public Func<GraphQLRequest, GraphQLHttpClient, Task<GraphQLHttpRequest>> PreprocessRequest { get; set; } = (request, client) =>
49+
Task.FromResult(request is GraphQLHttpRequest graphQLHttpRequest ? graphQLHttpRequest : new GraphQLHttpRequest(request));
4950

5051
/// <summary>
5152
/// This callback is called after successfully establishing a websocket connection but before any regular request is made.

src/GraphQL.Client/GraphQLHttpException.cs

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

src/GraphQL.Client/GraphQLHttpRequest.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
22
using System.Net.Http;
33
using System.Runtime.Serialization;
4+
using System.Text;
5+
using GraphQL.Client.Abstractions;
46

57
namespace GraphQL.Client.Http
68
{
@@ -15,10 +17,33 @@ public GraphQLHttpRequest(string query, object? variables = null, string? operat
1517
{
1618
}
1719

20+
public GraphQLHttpRequest(GraphQLRequest other): base(other.Query, other.Variables, other.OperationName)
21+
{
22+
}
23+
1824
/// <summary>
1925
/// Allows to preprocess a <see cref="HttpRequestMessage"/> before it is sent, i.e. add custom headers
2026
/// </summary>
2127
[IgnoreDataMember]
28+
[Obsolete("Inherit from GraphQLHttpRequest and override ToHttpRequestMessage() to customize the HttpRequestMessage. Will be removed in v4.0.0.")]
2229
public Action<HttpRequestMessage> PreprocessHttpRequestMessage { get; set; } = message => { };
30+
31+
/// <summary>
32+
/// Creates a <see cref="HttpRequestMessage"/> from this <see cref="GraphQLHttpRequest"/>.
33+
/// Used by <see cref="GraphQLHttpClient"/> to convert GraphQL requests when sending them as regular HTTP requests.
34+
/// </summary>
35+
/// <param name="options">the <see cref="GraphQLHttpClientOptions"/> passed from <see cref="GraphQLHttpClient"/></param>
36+
/// <param name="serializer">the <see cref="IGraphQLJsonSerializer"/> passed from <see cref="GraphQLHttpClient"/></param>
37+
/// <returns></returns>
38+
public virtual HttpRequestMessage ToHttpRequestMessage(GraphQLHttpClientOptions options, IGraphQLJsonSerializer serializer)
39+
{
40+
var message = new HttpRequestMessage(HttpMethod.Post, options.EndPoint)
41+
{
42+
Content = new StringContent(serializer.SerializeToString(this), Encoding.UTF8, options.MediaType)
43+
};
44+
45+
PreprocessHttpRequestMessage(message);
46+
return message;
47+
}
2348
}
2449
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System;
2+
using System.Net;
3+
using System.Net.Http.Headers;
4+
5+
namespace GraphQL.Client.Http
6+
{
7+
8+
/// <summary>
9+
/// An exception thrown on unexpected <see cref="System.Net.Http.HttpResponseMessage"/>
10+
/// </summary>
11+
public class GraphQLHttpRequestException : Exception
12+
{
13+
/// <summary>
14+
/// The returned status code
15+
/// </summary>
16+
public HttpStatusCode StatusCode { get; }
17+
18+
/// <summary>
19+
/// the returned response headers
20+
/// </summary>
21+
public HttpResponseHeaders ResponseHeaders { get; }
22+
23+
/// <summary>
24+
/// the returned content
25+
/// </summary>
26+
public string? Content { get; }
27+
28+
/// <summary>
29+
/// Creates a new instance of <see cref="GraphQLHttpRequestException"/>
30+
/// </summary>
31+
/// <param name="statusCode"></param>
32+
/// <param name="responseHeaders"></param>
33+
/// <param name="content"></param>
34+
public GraphQLHttpRequestException(HttpStatusCode statusCode, HttpResponseHeaders responseHeaders, string? content) : base($"The HTTP request failed with status code {statusCode}")
35+
{
36+
StatusCode = statusCode;
37+
ResponseHeaders = responseHeaders;
38+
Content = content;
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)