6
6
using JsonLD . Util ;
7
7
using System . Net ;
8
8
using System . Collections . Generic ;
9
+ using System . Net . Http ;
10
+ using System . Net . Http . Headers ;
11
+ using System . Threading . Tasks ;
12
+ using System . Runtime . InteropServices ;
9
13
10
14
namespace JsonLD . Core
11
15
{
12
16
public class DocumentLoader
13
17
{
18
+ const int MAX_REDIRECTS = 20 ;
19
+
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" ;
23
+
24
+ /// <exception cref="JsonLDNet.Core.JsonLdError"></exception>
25
+ public RemoteDocument LoadDocument ( string url )
26
+ {
27
+ return LoadDocumentAsync ( url ) . GetAwaiter ( ) . GetResult ( ) ;
28
+ }
29
+
14
30
/// <exception cref="JsonLDNet.Core.JsonLdError"></exception>
15
- public virtual RemoteDocument LoadDocument ( string url )
31
+ public async Task < RemoteDocument > LoadDocumentAsync ( string url )
16
32
{
17
- #if ! PORTABLE && ! IS_CORECLR
18
33
RemoteDocument doc = new RemoteDocument ( url , null ) ;
19
- HttpWebResponse resp ;
20
34
21
35
try
22
36
{
23
- HttpWebRequest req = ( HttpWebRequest ) HttpWebRequest . Create ( url ) ;
24
- req . Accept = AcceptHeader ;
25
- resp = ( HttpWebResponse ) req . GetResponse ( ) ;
26
- bool isJsonld = resp . Headers [ HttpResponseHeader . ContentType ] == "application/ld+json" ;
27
- if ( ! resp . Headers [ HttpResponseHeader . ContentType ] . Contains ( "json" ) )
37
+ HttpResponseMessage httpResponseMessage ;
38
+
39
+ int redirects = 0 ;
40
+ int code ;
41
+ string redirectedUrl = url ;
42
+
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 ) )
50
+ {
51
+ redirectedUrl = location . First ( ) ;
52
+ }
53
+
54
+ code = ( int ) httpResponseMessage . StatusCode ;
55
+ } while ( redirects ++ < MAX_REDIRECTS && code >= 300 && code < 400 ) ;
56
+
57
+ if ( redirects >= MAX_REDIRECTS )
58
+ {
59
+ throw new JsonLdError ( JsonLdError . Error . LoadingDocumentFailed , $ "Too many redirects - { url } ") ;
60
+ }
61
+
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" ) )
28
71
{
29
72
throw new JsonLdError ( JsonLdError . Error . LoadingDocumentFailed , url ) ;
30
73
}
31
74
32
- string [ ] linkHeaders = resp . Headers . GetValues ( "Link" ) ;
33
- if ( ! isJsonld && linkHeaders != null )
75
+ if ( ! isJsonld && httpResponseMessage . Headers . TryGetValues ( "Link" , out var linkHeaders ) )
34
76
{
35
77
linkHeaders = linkHeaders . SelectMany ( ( h ) => h . Split ( "," . ToCharArray ( ) ) )
36
78
. Select ( h => h . Trim ( ) ) . ToArray ( ) ;
@@ -41,189 +83,27 @@ public virtual RemoteDocument LoadDocument(string url)
41
83
}
42
84
string header = linkedContexts . First ( ) ;
43
85
string linkedUrl = header . Substring ( 1 , header . IndexOf ( ">" ) - 1 ) ;
44
- string resolvedUrl = URL . Resolve ( url , linkedUrl ) ;
86
+ string resolvedUrl = URL . Resolve ( redirectedUrl , linkedUrl ) ;
45
87
var remoteContext = this . LoadDocument ( resolvedUrl ) ;
46
88
doc . contextUrl = remoteContext . documentUrl ;
47
89
doc . context = remoteContext . document ;
48
90
}
49
91
50
- Stream stream = resp . GetResponseStream ( ) ;
92
+ Stream stream = await httpResponseMessage . Content . ReadAsStreamAsync ( ) ;
51
93
52
- doc . DocumentUrl = req . Address . ToString ( ) ;
94
+ doc . DocumentUrl = redirectedUrl ;
53
95
doc . Document = JSONUtils . FromInputStream ( stream ) ;
54
96
}
55
97
catch ( JsonLdError )
56
98
{
57
99
throw ;
58
100
}
59
- catch ( WebException webException )
60
- {
61
- try
62
- {
63
- resp = ( HttpWebResponse ) webException . Response ;
64
- int baseStatusCode = ( int ) ( Math . Floor ( ( double ) resp . StatusCode / 100 ) ) * 100 ;
65
- if ( baseStatusCode == 300 )
66
- {
67
- string location = resp . Headers [ HttpResponseHeader . Location ] ;
68
- if ( ! string . IsNullOrWhiteSpace ( location ) )
69
- {
70
- // TODO: Add recursion break or simply switch to HttpClient so we don't have to recurse on HTTP redirects.
71
- return LoadDocument ( location ) ;
72
- }
73
- }
74
- }
75
- catch ( Exception innerException )
76
- {
77
- throw new JsonLdError ( JsonLdError . Error . LoadingDocumentFailed , url , innerException ) ;
78
- }
79
-
80
- throw new JsonLdError ( JsonLdError . Error . LoadingDocumentFailed , url , webException ) ;
81
- }
82
101
catch ( Exception exception )
83
102
{
84
103
throw new JsonLdError ( JsonLdError . Error . LoadingDocumentFailed , url , exception ) ;
85
104
}
86
105
return doc ;
87
- #else
88
- throw new PlatformNotSupportedException ( ) ;
89
- #endif
90
106
}
91
107
92
- /// <summary>An HTTP Accept header that prefers JSONLD.</summary>
93
- /// <remarks>An HTTP Accept header that prefers JSONLD.</remarks>
94
- 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" ;
95
-
96
- // private static volatile IHttpClient httpClient;
97
-
98
- // /// <summary>
99
- // /// Returns a Map, List, or String containing the contents of the JSON
100
- // /// resource resolved from the URL.
101
- // /// </summary>
102
- // /// <remarks>
103
- // /// Returns a Map, List, or String containing the contents of the JSON
104
- // /// resource resolved from the URL.
105
- // /// </remarks>
106
- // /// <param name="url">The URL to resolve</param>
107
- // /// <returns>
108
- // /// The Map, List, or String that represent the JSON resource
109
- // /// resolved from the URL
110
- // /// </returns>
111
- // /// <exception cref="Com.Fasterxml.Jackson.Core.JsonParseException">If the JSON was not valid.
112
- // /// </exception>
113
- // /// <exception cref="System.IO.IOException">If there was an error resolving the resource.
114
- // /// </exception>
115
- // public static object FromURL(URL url)
116
- // {
117
- // MappingJsonFactory jsonFactory = new MappingJsonFactory();
118
- // InputStream @in = OpenStreamFromURL(url);
119
- // try
120
- // {
121
- // JsonParser parser = jsonFactory.CreateParser(@in);
122
- // try
123
- // {
124
- // JsonToken token = parser.NextToken();
125
- // Type type;
126
- // if (token == JsonToken.StartObject)
127
- // {
128
- // type = typeof(IDictionary);
129
- // }
130
- // else
131
- // {
132
- // if (token == JsonToken.StartArray)
133
- // {
134
- // type = typeof(IList);
135
- // }
136
- // else
137
- // {
138
- // type = typeof(string);
139
- // }
140
- // }
141
- // return parser.ReadValueAs(type);
142
- // }
143
- // finally
144
- // {
145
- // parser.Close();
146
- // }
147
- // }
148
- // finally
149
- // {
150
- // @in.Close();
151
- // }
152
- // }
153
-
154
- // /// <summary>
155
- // /// Opens an
156
- // /// <see cref="Java.IO.InputStream">Java.IO.InputStream</see>
157
- // /// for the given
158
- // /// <see cref="Java.Net.URL">Java.Net.URL</see>
159
- // /// , including support
160
- // /// for http and https URLs that are requested using Content Negotiation with
161
- // /// application/ld+json as the preferred content type.
162
- // /// </summary>
163
- // /// <param name="url">The URL identifying the source.</param>
164
- // /// <returns>An InputStream containing the contents of the source.</returns>
165
- // /// <exception cref="System.IO.IOException">If there was an error resolving the URL.</exception>
166
- // public static InputStream OpenStreamFromURL(URL url)
167
- // {
168
- // string protocol = url.GetProtocol();
169
- // if (!JsonLDNet.Shims.EqualsIgnoreCase(protocol, "http") && !JsonLDNet.Shims.EqualsIgnoreCase
170
- // (protocol, "https"))
171
- // {
172
- // // Can't use the HTTP client for those!
173
- // // Fallback to Java's built-in URL handler. No need for
174
- // // Accept headers as it's likely to be file: or jar:
175
- // return url.OpenStream();
176
- // }
177
- // IHttpUriRequest request = new HttpGet(url.ToExternalForm());
178
- // // We prefer application/ld+json, but fallback to application/json
179
- // // or whatever is available
180
- // request.AddHeader("Accept", AcceptHeader);
181
- // IHttpResponse response = GetHttpClient().Execute(request);
182
- // int status = response.GetStatusLine().GetStatusCode();
183
- // if (status != 200 && status != 203)
184
- // {
185
- // throw new IOException("Can't retrieve " + url + ", status code: " + status);
186
- // }
187
- // return response.GetEntity().GetContent();
188
- // }
189
-
190
- // public static IHttpClient GetHttpClient()
191
- // {
192
- // IHttpClient result = httpClient;
193
- // if (result == null)
194
- // {
195
- // lock (typeof(JSONUtils))
196
- // {
197
- // result = httpClient;
198
- // if (result == null)
199
- // {
200
- // // Uses Apache SystemDefaultHttpClient rather than
201
- // // DefaultHttpClient, thus the normal proxy settings for the
202
- // // JVM will be used
203
- // DefaultHttpClient client = new SystemDefaultHttpClient();
204
- // // Support compressed data
205
- // // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/httpagent.html#d5e1238
206
- // client.AddRequestInterceptor(new RequestAcceptEncoding());
207
- // client.AddResponseInterceptor(new ResponseContentEncoding());
208
- // CacheConfig cacheConfig = new CacheConfig();
209
- // cacheConfig.SetMaxObjectSize(1024 * 128);
210
- // // 128 kB
211
- // cacheConfig.SetMaxCacheEntries(1000);
212
- // // and allow caching
213
- // httpClient = new CachingHttpClient(client, cacheConfig);
214
- // result = httpClient;
215
- // }
216
- // }
217
- // }
218
- // return result;
219
- // }
220
-
221
- // public static void SetHttpClient(IHttpClient nextHttpClient)
222
- // {
223
- // lock (typeof(JSONUtils))
224
- // {
225
- // httpClient = nextHttpClient;
226
- // }
227
- // }
228
108
}
229
109
}
0 commit comments