66using JsonLD . Util ;
77using System . Net ;
88using System . Collections . Generic ;
9+ using System . Net . Http ;
10+ using System . Net . Http . Headers ;
11+ using System . Threading . Tasks ;
12+ using System . Runtime . InteropServices ;
913
1014namespace JsonLD . Core
1115{
1216 public class DocumentLoader
1317 {
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+
1430 /// <exception cref="JsonLDNet.Core.JsonLdError"></exception>
15- public virtual RemoteDocument LoadDocument ( string url )
31+ public async Task < RemoteDocument > LoadDocumentAsync ( string url )
1632 {
17- #if ! PORTABLE && ! IS_CORECLR
1833 RemoteDocument doc = new RemoteDocument ( url , null ) ;
19- HttpWebResponse resp ;
2034
2135 try
2236 {
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" ) )
2871 {
2972 throw new JsonLdError ( JsonLdError . Error . LoadingDocumentFailed , url ) ;
3073 }
3174
32- string [ ] linkHeaders = resp . Headers . GetValues ( "Link" ) ;
33- if ( ! isJsonld && linkHeaders != null )
75+ if ( ! isJsonld && httpResponseMessage . Headers . TryGetValues ( "Link" , out var linkHeaders ) )
3476 {
3577 linkHeaders = linkHeaders . SelectMany ( ( h ) => h . Split ( "," . ToCharArray ( ) ) )
3678 . Select ( h => h . Trim ( ) ) . ToArray ( ) ;
@@ -41,189 +83,27 @@ public virtual RemoteDocument LoadDocument(string url)
4183 }
4284 string header = linkedContexts . First ( ) ;
4385 string linkedUrl = header . Substring ( 1 , header . IndexOf ( ">" ) - 1 ) ;
44- string resolvedUrl = URL . Resolve ( url , linkedUrl ) ;
86+ string resolvedUrl = URL . Resolve ( redirectedUrl , linkedUrl ) ;
4587 var remoteContext = this . LoadDocument ( resolvedUrl ) ;
4688 doc . contextUrl = remoteContext . documentUrl ;
4789 doc . context = remoteContext . document ;
4890 }
4991
50- Stream stream = resp . GetResponseStream ( ) ;
92+ Stream stream = await httpResponseMessage . Content . ReadAsStreamAsync ( ) ;
5193
52- doc . DocumentUrl = req . Address . ToString ( ) ;
94+ doc . DocumentUrl = redirectedUrl ;
5395 doc . Document = JSONUtils . FromInputStream ( stream ) ;
5496 }
5597 catch ( JsonLdError )
5698 {
5799 throw ;
58100 }
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- }
82101 catch ( Exception exception )
83102 {
84103 throw new JsonLdError ( JsonLdError . Error . LoadingDocumentFailed , url , exception ) ;
85104 }
86105 return doc ;
87- #else
88- throw new PlatformNotSupportedException ( ) ;
89- #endif
90106 }
91107
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- // }
228108 }
229109}
0 commit comments