Skip to content

Commit a11168a

Browse files
committed
Refactored HttpClient usage into utility class
1 parent 4aa1fe7 commit a11168a

File tree

3 files changed

+111
-79
lines changed

3 files changed

+111
-79
lines changed

src/json-ld.net/Core/DocumentLoader.cs

Lines changed: 57 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,34 @@ namespace JsonLD.Core
1515
{
1616
public class DocumentLoader
1717
{
18-
const int MAX_REDIRECTS = 20;
18+
enum JsonLDContentType
19+
{
20+
JsonLD,
21+
PlainJson,
22+
Other
23+
}
1924

20-
/// <summary>An HTTP Accept header that prefers JSONLD.</summary>
21-
/// <remarks>An HTTP Accept header that prefers JSONLD.</remarks>
22-
public const string AcceptHeader = "application/ld+json, application/json;q=0.9, application/javascript;q=0.5, text/javascript;q=0.5, text/plain;q=0.2, */*;q=0.1";
25+
JsonLDContentType GetJsonLDContentType(string contentTypeStr)
26+
{
27+
JsonLDContentType contentType;
28+
29+
switch (contentTypeStr)
30+
{
31+
case "application/ld+json":
32+
contentType = JsonLDContentType.JsonLD;
33+
break;
34+
// From RFC 6839, it looks like plain JSON is content type application/json and any MediaType ending in "+json".
35+
case "application/json":
36+
case string type when type.EndsWith("+json"):
37+
contentType = JsonLDContentType.PlainJson;
38+
break;
39+
default:
40+
contentType = JsonLDContentType.Other;
41+
break;
42+
}
43+
44+
return contentType;
45+
}
2346

2447
/// <exception cref="JsonLDNet.Core.JsonLdError"></exception>
2548
public RemoteDocument LoadDocument(string url)
@@ -31,68 +54,50 @@ public RemoteDocument LoadDocument(string url)
3154
public async Task<RemoteDocument> LoadDocumentAsync(string url)
3255
{
3356
RemoteDocument doc = new RemoteDocument(url, null);
34-
3557
try
3658
{
37-
HttpResponseMessage httpResponseMessage;
59+
using (HttpResponseMessage response = await JsonLD.Util.LDHttpClient.FetchAsync(url))
60+
{
3861

39-
int redirects = 0;
40-
int code;
41-
string redirectedUrl = url;
62+
var code = (int)response.StatusCode;
4263

43-
// Manually follow redirects because .NET Core refuses to auto-follow HTTPS->HTTP redirects.
44-
do
45-
{
46-
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, redirectedUrl);
47-
httpRequestMessage.Headers.Add("Accept", AcceptHeader);
48-
httpResponseMessage = await JSONUtils._HttpClient.SendAsync(httpRequestMessage);
49-
if (httpResponseMessage.Headers.TryGetValues("Location", out var location))
64+
if (code >= 400)
5065
{
51-
redirectedUrl = location.First();
66+
throw new JsonLdError(JsonLdError.Error.LoadingDocumentFailed, $"HTTP {code} {url}");
5267
}
5368

54-
code = (int)httpResponseMessage.StatusCode;
55-
} while (redirects++ < MAX_REDIRECTS && code >= 300 && code < 400);
69+
var finalUrl = response.RequestMessage.RequestUri.ToString();
5670

57-
if (redirects >= MAX_REDIRECTS)
58-
{
59-
throw new JsonLdError(JsonLdError.Error.LoadingDocumentFailed, $"Too many redirects - {url}");
60-
}
71+
var contentType = GetJsonLDContentType(response.Content.Headers.ContentType.MediaType);
6172

62-
if (code >= 400)
63-
{
64-
throw new JsonLdError(JsonLdError.Error.LoadingDocumentFailed, $"HTTP {code} {url}");
65-
}
66-
67-
bool isJsonld = httpResponseMessage.Content.Headers.ContentType.MediaType == "application/ld+json";
68-
69-
// From RFC 6839, it looks like we should accept application/json and any MediaType ending in "+json".
70-
if (httpResponseMessage.Content.Headers.ContentType.MediaType != "application/json" && !httpResponseMessage.Content.Headers.ContentType.MediaType.EndsWith("+json"))
71-
{
72-
throw new JsonLdError(JsonLdError.Error.LoadingDocumentFailed, url);
73-
}
73+
if (contentType == JsonLDContentType.Other)
74+
{
75+
throw new JsonLdError(JsonLdError.Error.LoadingDocumentFailed, url);
76+
}
7477

75-
if (!isJsonld && httpResponseMessage.Headers.TryGetValues("Link", out var linkHeaders))
76-
{
77-
linkHeaders = linkHeaders.SelectMany((h) => h.Split(",".ToCharArray()))
78-
.Select(h => h.Trim()).ToArray();
79-
IEnumerable<string> linkedContexts = linkHeaders.Where(v => v.EndsWith("rel=\"http://www.w3.org/ns/json-ld#context\""));
80-
if (linkedContexts.Count() > 1)
78+
// For plain JSON, see if there's a context document linked in the HTTP response headers.
79+
if (contentType == JsonLDContentType.PlainJson && response.Headers.TryGetValues("Link", out var linkHeaders))
8180
{
82-
throw new JsonLdError(JsonLdError.Error.MultipleContextLinkHeaders);
81+
linkHeaders = linkHeaders.SelectMany((h) => h.Split(",".ToCharArray()))
82+
.Select(h => h.Trim()).ToArray();
83+
IEnumerable<string> linkedContexts = linkHeaders.Where(v => v.EndsWith("rel=\"http://www.w3.org/ns/json-ld#context\""));
84+
if (linkedContexts.Count() > 1)
85+
{
86+
throw new JsonLdError(JsonLdError.Error.MultipleContextLinkHeaders);
87+
}
88+
string header = linkedContexts.First();
89+
string linkedUrl = header.Substring(1, header.IndexOf(">") - 1);
90+
string resolvedUrl = URL.Resolve(finalUrl, linkedUrl);
91+
var remoteContext = this.LoadDocument(resolvedUrl);
92+
doc.contextUrl = remoteContext.documentUrl;
93+
doc.context = remoteContext.document;
8394
}
84-
string header = linkedContexts.First();
85-
string linkedUrl = header.Substring(1, header.IndexOf(">") - 1);
86-
string resolvedUrl = URL.Resolve(redirectedUrl, linkedUrl);
87-
var remoteContext = this.LoadDocument(resolvedUrl);
88-
doc.contextUrl = remoteContext.documentUrl;
89-
doc.context = remoteContext.document;
90-
}
9195

92-
Stream stream = await httpResponseMessage.Content.ReadAsStreamAsync();
96+
Stream stream = await response.Content.ReadAsStreamAsync();
9397

94-
doc.DocumentUrl = redirectedUrl;
95-
doc.Document = JSONUtils.FromInputStream(stream);
98+
doc.DocumentUrl = finalUrl;
99+
doc.Document = JSONUtils.FromInputStream(stream);
100+
}
96101
}
97102
catch (JsonLdError)
98103
{
@@ -104,6 +109,5 @@ public async Task<RemoteDocument> LoadDocumentAsync(string url)
104109
}
105110
return doc;
106111
}
107-
108112
}
109113
}

src/json-ld.net/Util/JSONUtils.cs

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,6 @@ namespace JsonLD.Util
1515
/// <author>tristan</author>
1616
public class JSONUtils
1717
{
18-
const int MAX_REDIRECTS = 20;
19-
static internal HttpClient _HttpClient = new HttpClient();
20-
21-
/// <summary>An HTTP Accept header that prefers JSONLD.</summary>
22-
protected internal const string AcceptHeader = "application/ld+json, application/json;q=0.9, application/javascript;q=0.5, text/javascript;q=0.5, text/plain;q=0.2, */*;q=0.1";
23-
2418
static JSONUtils()
2519
{
2620
}
@@ -145,27 +139,9 @@ public static JToken FromURL(Uri url)
145139
/// </exception>
146140
public static async Task<JToken> FromURLAsync(Uri url)
147141
{
148-
HttpResponseMessage httpResponseMessage = null;
149-
int redirects = 0;
150-
151-
// Manually follow redirects because .NET Core refuses to auto-follow HTTPS->HTTP redirects.
152-
do
153-
{
154-
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url);
155-
httpRequestMessage.Headers.Add("Accept", AcceptHeader);
156-
httpResponseMessage = await _HttpClient.SendAsync(httpRequestMessage);
157-
if (httpResponseMessage.Headers.TryGetValues("Location", out var location))
158-
{
159-
url = new Uri(location.First());
160-
}
161-
} while (redirects++ < MAX_REDIRECTS && (int)httpResponseMessage.StatusCode >= 300 && (int)httpResponseMessage.StatusCode < 400);
162-
163-
if (redirects >= MAX_REDIRECTS || (int)httpResponseMessage.StatusCode >= 400)
164-
{
165-
throw new InvalidOperationException("Couldn't load JSON from URL");
142+
using (var response = await LDHttpClient.FetchAsync(url.ToString())) {
143+
return FromInputStream(await response.Content.ReadAsStreamAsync());
166144
}
167-
168-
return FromInputStream(await httpResponseMessage.Content.ReadAsStreamAsync());
169145
}
170146
}
171147
}

src/json-ld.net/Util/LDHttpClient.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Net.Http;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace JsonLD.Util
9+
{
10+
internal static class LDHttpClient
11+
{
12+
const string ACCEPT_HEADER = "application/ld+json, application/json;q=0.9, application/javascript;q=0.5, text/javascript;q=0.5, text/plain;q=0.2, */*;q=0.1";
13+
const int MAX_REDIRECTS = 20;
14+
15+
static HttpClient _hc;
16+
17+
static LDHttpClient()
18+
{
19+
_hc = new HttpClient();
20+
_hc.DefaultRequestHeaders.Add("Accept", ACCEPT_HEADER);
21+
}
22+
23+
static public async Task<HttpResponseMessage> FetchAsync(string url)
24+
{
25+
int redirects = 0;
26+
int code;
27+
string redirectedUrl = url;
28+
29+
HttpResponseMessage response;
30+
31+
// Manually follow redirects because .NET Core refuses to auto-follow HTTPS->HTTP redirects.
32+
do
33+
{
34+
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, redirectedUrl);
35+
response = await _hc.SendAsync(httpRequestMessage);
36+
if (response.Headers.TryGetValues("Location", out var location))
37+
{
38+
redirectedUrl = location.First();
39+
}
40+
41+
code = (int)response.StatusCode;
42+
} while (redirects++ < MAX_REDIRECTS && code >= 300 && code < 400);
43+
44+
if (redirects >= MAX_REDIRECTS)
45+
{
46+
throw new HttpRequestException("Too many redirects");
47+
}
48+
49+
return response;
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)