Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
HttpxGetter.cs
1using System;
3using System.IO;
4using System.Security.Cryptography.X509Certificates;
5using System.Threading;
6using System.Threading.Tasks;
7using Waher.Content;
8using Waher.Events;
13
15{
23 {
30 public HttpxGetter()
31 {
32 }
33
37 public const string HttpxUriScheme = "httpx";
38
42 public string[] UriSchemes => new string[] { HttpxUriScheme };
43
50 public bool CanGet(Uri Uri, out Grade Grade)
51 {
52 switch (Uri.Scheme)
53 {
54 case HttpxUriScheme:
55 Grade = Grade.Ok;
56 return true;
57
58 default:
59 Grade = Grade.NotAtAll;
60 return false;
61 }
62 }
63
72 public Task<ContentResponse> GetAsync(Uri Uri, X509Certificate Certificate, EventHandler<RemoteCertificateEventArgs> RemoteCertificateValidator,
73 params KeyValuePair<string, string>[] Headers)
74 {
75 return this.GetAsync(Uri, Certificate, RemoteCertificateValidator, InternetContent.DefaultTimeout, Headers);
76 }
77
96 public async Task<ContentResponse> GetAsync(Uri Uri, X509Certificate Certificate,
97 EventHandler<RemoteCertificateEventArgs> RemoteCertificateValidator, int TimeoutMs, params KeyValuePair<string, string>[] Headers)
98 {
99 using ContentStreamResponse Rec = await this.GetTempStreamAsync(Uri, Certificate, RemoteCertificateValidator, TimeoutMs, Headers);
100
101 if (Rec.HasError)
102 return new ContentResponse(Rec.Error);
103
104 Rec.Encoded.Position = 0;
105
106 if (Rec.Encoded.Length > int.MaxValue)
107 return new ContentResponse(new OutOfMemoryException("Resource too large."));
108
109 byte[] Bin = await Rec.Encoded.ReadAllAsync();
110
111 return await InternetContent.DecodeAsync(Rec.ContentType, Bin, Uri);
112 }
113
128 public Task<ContentStreamResponse> GetTempStreamAsync(Uri Uri, X509Certificate Certificate,
129 EventHandler<RemoteCertificateEventArgs> RemoteCertificateValidator, params KeyValuePair<string, string>[] Headers)
130 {
131 return this.GetTempStreamAsync(Uri, Certificate, RemoteCertificateValidator, InternetContent.DefaultTimeout, Headers);
132 }
133
143 public Task<ContentStreamResponse> GetTempStreamAsync(Uri Uri, X509Certificate Certificate,
144 EventHandler<RemoteCertificateEventArgs> RemoteCertificateValidator, TemporaryStream Destination, params KeyValuePair<string, string>[] Headers)
145 {
146 return this.GetTempStreamAsync(Uri, Certificate, RemoteCertificateValidator, InternetContent.DefaultTimeout, Destination, Headers);
147 }
148
164 public Task<ContentStreamResponse> GetTempStreamAsync(Uri Uri, X509Certificate Certificate,
165 EventHandler<RemoteCertificateEventArgs> RemoteCertificateValidator, int TimeoutMs, params KeyValuePair<string, string>[] Headers)
166 {
167 return this.GetTempStreamAsync(Uri, Certificate, RemoteCertificateValidator, TimeoutMs, null, Headers);
168 }
169
186 public async Task<ContentStreamResponse> GetTempStreamAsync(Uri Uri, X509Certificate Certificate,
187 EventHandler<RemoteCertificateEventArgs> RemoteCertificateValidator, int TimeoutMs, TemporaryStream Destination,
188 params KeyValuePair<string, string>[] Headers)
189 {
191 string BareJid = Uri.UserInfo + "@" + Uri.Authority;
192 string FullJid;
193 string LocalUrl;
194
195 if (Types.TryGetModuleParameter("HTTPX", out HttpxProxy Proxy))
196 {
197 if (Proxy.DefaultXmppClient.Disposed || Proxy.ServerlessMessaging.Disposed)
198 return new ContentStreamResponse(new InvalidOperationException("Service is being shut down."));
199
200 if (string.Compare(BareJid, Proxy.DefaultXmppClient.BareJID, true) == 0 &&
201 Proxy.DefaultXmppClient.TryGetExtension(out HttpxServer Server))
202 {
203 return await Server.GetLocalTempStreamAsync(Uri.PathAndQuery + Uri.Fragment, Destination);
204 }
205 else
206 {
207 GetClientResponse Rec = await Proxy.GetClientAsync(Uri);
208
209 BareJid = Rec.BareJid;
210 FullJid = Rec.FullJid;
212 LocalUrl = Rec.LocalUrl;
213 }
214 }
215 else if (Types.TryGetModuleParameter("XMPP", out XmppClient XmppClient))
216 {
218 return new ContentStreamResponse(new InvalidOperationException("Service is being shut down."));
219
220 if (string.Compare(BareJid, XmppClient.BareJID, true) == 0 &&
222 {
223 return await Server.GetLocalTempStreamAsync(Uri.PathAndQuery + Uri.Fragment, Destination);
224 }
225 else
226 {
227 if (!XmppClient.TryGetExtension(out HttpxClient HttpxClient2))
228 return new ContentStreamResponse(new InvalidOperationException("No HTTPX Extesion has been registered on the XMPP Client."));
229
230 HttpxClient = HttpxClient2;
231
232 if (string.IsNullOrEmpty(Uri.UserInfo))
233 FullJid = BareJid = Uri.Authority;
234 else
235 {
236 BareJid = Uri.UserInfo + "@" + Uri.Authority;
237
238 RosterItem Item = XmppClient.GetRosterItem(BareJid);
239
240 if (Item is null)
241 return new ContentStreamResponse(new ConflictException("No approved presence subscription with " + BareJid + "."));
242 else if (!Item.HasLastPresence || !Item.LastPresence.IsOnline)
243 return new ContentStreamResponse(new ServiceUnavailableException(BareJid + " is not online."));
244 else
245 FullJid = Item.LastPresenceFullJid;
246 }
247
248 LocalUrl = Uri.PathAndQuery + Uri.Fragment;
249 }
250 }
251 else
252 return new ContentStreamResponse(new InvalidOperationException("An HTTPX Proxy or XMPP Client Module Parameter has not been registered."));
253
254 List<HttpField> Headers2 = new List<HttpField>();
255 bool HasHost = false;
256
257 foreach (KeyValuePair<string, string> Header in Headers)
258 {
259 switch (Header.Key.ToLower())
260 {
261 case "host":
262 Headers2.Add(new HttpField("Host", BareJid));
263 HasHost = true;
264 break;
265
266 case "cookie":
267 case "set-cookie":
268 // Do not forward cookies.
269 break;
270
271 default:
272 Headers2.Add(new HttpField(Header.Key, Header.Value));
273 break;
274 }
275 }
276
277 if (!HasHost)
278 Headers2.Add(new HttpField("Host", Uri.Authority));
279
280 State State = null;
281 Timer Timer = null;
282
283 try
284 {
285 State = new State();
286 Timer = new Timer((P) =>
287 {
288 State.Done.TrySetResult(false);
289 }, null, TimeoutMs, Timeout.Infinite);
290
291 // TODO: Transport public part of Client certificate, if provided.
292
293 if (HttpxClient is null)
294 return new ContentStreamResponse(new Exception("No HTTPX client available."));
295
296 await HttpxClient.Request(FullJid, "GET", LocalUrl, async (Sender, e) =>
297 {
298 if (e.Ok)
299 {
300 State.HttpResponse = e.HttpResponse;
301 State.StatusCode = e.StatusCode;
302 State.StatusMessage = e.StatusMessage;
303
304 if (e.HasData)
305 {
306 State.File = new TemporaryStream();
307
308 if (!(e.Data is null))
309 {
310 await State.File.WriteAsync(e.Data, 0, e.Data.Length);
311 State.Done.TrySetResult(true);
312 }
313 }
314 else
315 State.Done.TrySetResult(true);
316 }
317 else
318 {
319 State.Done.TrySetException((Exception)e.StanzaError ??
320 new GenericException("Unable to get resource.", null, Uri.OriginalString));
321 }
322
323 }, async (Sender, e) =>
324 {
325 await (State.File?.WriteAsync(e.Data, 0, e.Data.Length) ?? Task.CompletedTask);
326 if (e.Last)
327 State.Done?.TrySetResult(true);
328
329 }, State, Headers2.ToArray());
330
331 if (!await State.Done.Task)
332 return new ContentStreamResponse(new GenericException(new TimeoutException("Request timed out."), null, Uri.OriginalString));
333
334 Timer.Dispose();
335 Timer = null;
336
337 if (State.StatusCode >= 200 && State.StatusCode < 300)
338 {
339 TemporaryStream Result = State.File;
340 State.File = null;
341
342 return new ContentStreamResponse(State.HttpResponse?.ContentType, Result);
343 }
344 else
345 {
346 string ContentType = string.Empty;
347 byte[] Data;
348
349 if (State.File is null)
350 Data = null;
351 else
352 {
353 ContentType = State.HttpResponse.ContentType;
354 State.File.Position = 0;
355 Data = await State.File.ReadAllAsync();
356 }
357
358 return new ContentStreamResponse(GetExceptionObject(State.StatusCode, State.StatusMessage,
359 State.HttpResponse, Data, ContentType));
360 }
361 }
362 finally
363 {
364 State.File?.Dispose();
365 State.File = null;
366
367 if (!(State.HttpResponse is null))
368 {
369 await State.HttpResponse.DisposeAsync();
370 State.HttpResponse = null;
371 }
372
373 Timer?.Dispose();
374 Timer = null;
375 }
376 }
377
378 internal static Exception GetExceptionObject(int StatusCode, string StatusMessage,
379 HttpResponse Response, byte[] Data, string ContentType)
380 {
381 return StatusCode switch
382 {
383 // Client Errors
384 BadRequestException.Code => new BadRequestException(Data, ContentType),
385 ConflictException.Code => new ConflictException(Data, ContentType),
386 FailedDependencyException.Code => new FailedDependencyException(Data, ContentType),
387 ForbiddenException.Code => new ForbiddenException(Response.Request, Data, ContentType),
388 GoneException.Code => new GoneException(Data, ContentType),
389 LockedException.Code => new LockedException(Data, ContentType),
390 MethodNotAllowedException.Code => new MethodNotAllowedException(GetMethods(Response.GetFirstHeader("Allow")), Data, ContentType),
391 MisdirectedRequestException.Code => new MisdirectedRequestException(Data, ContentType),
392 NotAcceptableException.Code => new NotAcceptableException(Data, ContentType),
393 NotFoundException.Code => new NotFoundException(Data, ContentType),
394 PreconditionFailedException.Code => new PreconditionFailedException(Data, ContentType),
395 PreconditionRequiredException.Code => new PreconditionRequiredException(Data, ContentType),
396 RangeNotSatisfiableException.Code => new RangeNotSatisfiableException(Data, ContentType),
397 RequestTimeoutException.Code => new RequestTimeoutException(Data, ContentType),
398 TooManyRequestsException.Code => new TooManyRequestsException(Data, ContentType),
399 UnauthorizedException.Code => new UnauthorizedException(Data, ContentType, Response.GetChallenges()),
400 UnavailableForLegalReasonsException.Code => new UnavailableForLegalReasonsException(Data, ContentType),
401 UnprocessableEntityException.Code => new UnprocessableEntityException(Data, ContentType),
402 UnsupportedMediaTypeException.Code => new UnsupportedMediaTypeException(Data, ContentType),
403 UpgradeRequiredException.Code => new UpgradeRequiredException(Response.GetFirstHeader("Upgrade"), Data, ContentType),
404 // Redirections
405 MovedPermanentlyException.Code => new MovedPermanentlyException(Response.GetFirstHeader("Location"), Data, ContentType),
406 FoundException.Code => new FoundException(Response.GetFirstHeader("Location"), Data, ContentType),
407 SeeOtherException.Code => new SeeOtherException(Response.GetFirstHeader("Location"), Data, ContentType),
408 NotModifiedException.Code => new NotModifiedException(),
409 UseProxyException.Code => new UseProxyException(Response.GetFirstHeader("Location"), Data, ContentType),
410 TemporaryRedirectException.Code => new TemporaryRedirectException(Response.GetFirstHeader("Location"), Data, ContentType),
411 PermanentRedirectException.Code => new PermanentRedirectException(Response.GetFirstHeader("Location"), Data, ContentType),
412 // Server Errors
413 BadGatewayException.Code => new BadGatewayException(Data, ContentType),
414 GatewayTimeoutException.Code => new GatewayTimeoutException(Data, ContentType),
415 InsufficientStorageException.Code => new InsufficientStorageException(Data, ContentType),
416 InternalServerErrorException.Code => new InternalServerErrorException(Data, ContentType),
417 LoopDetectedException.Code => new LoopDetectedException(Data, ContentType),
418 NetworkAuthenticationRequiredException.Code => new NetworkAuthenticationRequiredException(Data, ContentType),
419 NotExtendedException.Code => new NotExtendedException(Data, ContentType),
420 HTTP.NotImplementedException.Code => new HTTP.NotImplementedException(Data, ContentType),
421 ServiceUnavailableException.Code => new ServiceUnavailableException(Data, ContentType),
422 VariantAlsoNegotiatesException.Code => new VariantAlsoNegotiatesException(Data, ContentType),
423 _ => new HttpException(StatusCode, StatusMessage, Data, ContentType),
424 };
425 }
426
427 private static string[] GetMethods(string Allow)
428 {
429 if (string.IsNullOrEmpty(Allow))
430 return Array.Empty<string>();
431
432 string[] Result = Allow.Split(',');
433 int i, c = Result.Length;
434
435 for (i = 0; i < c; i++)
436 Result[i] = Result[i].Trim();
437
438 return Result;
439 }
440
441 private class State
442 {
443 public HttpResponse HttpResponse = null;
444 public TemporaryStream File = null;
445 public TaskCompletionSource<bool> Done = new TaskCompletionSource<bool>();
446 public string StatusMessage = string.Empty;
447 public int StatusCode = 0;
448 }
449
450 }
451}
Contains information about a response to a content request.
Contains information about a stream response to a content request.
Static class managing encoding and decoding of internet content.
static int DefaultTimeout
Default timeout of internet access methods, in milliseconds.
static Task< ContentResponse > DecodeAsync(string ContentType, byte[] Data, Encoding Encoding, KeyValuePair< string, string >[] Fields, Uri BaseUri)
Decodes an object.
Generic exception, with meta-data for logging.
The server, while acting as a gateway or proxy, received an invalid response from the upstream server...
The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repe...
The request could not be completed due to a conflict with the current state of the resource....
The request failed due to failure of a previous request (e.g., a PROPPATCH).
The server understood the request, but is refusing to fulfill it. Authorization will not help and the...
The requested resource resides temporarily under a different URI. Since the redirection might be alte...
The server, while acting as a gateway or proxy, did not receive a timely response from the upstream s...
The requested resource is no longer available at the server and no forwarding address is known....
Base class of all HTTP Exceptions.
Base class for all HTTP fields.
Definition: HttpField.cs:7
Represets a response of an HTTP client request.
Definition: HttpResponse.cs:23
HttpRequest Request
Corresponding HTTP Request
string GetFirstHeader(string FieldName)
Gets the first header value matching a given header field name.
string[] GetChallenges()
Gets available WWW-Authenticate challenges returned in the response.
The server is unable to store the representation needed to complete the request.
The server encountered an unexpected condition which prevented it from fulfilling the request.
The resource that is being accessed is locked.
The server detected an infinite loop while processing the request.
The server has not found anything matching the Request-URI. No indication is given of whether the con...
The request was directed at a server that is not able to produce a response (for example because a co...
The requested resource has been assigned a new permanent URI and any future references to this resour...
The client needs to authenticate to gain network access. Intended for use by intercepting proxies use...
The resource identified by the request is only capable of generating response entities which have con...
Further extensions to the request are required for the server to fulfil it.
The server has not found anything matching the Request-URI. No indication is given of whether the con...
If the client has performed a conditional GET request and access is allowed, but the document has not...
This means that the resource is now permanently located at another URI, specified by the Location: HT...
The precondition given in one or more of the request-header fields evaluated to false when it was tes...
The origin server requires the request to be conditional. Intended to prevent "the 'lost update' prob...
A server SHOULD return a response with this status code if a request included a Range request-header ...
The client did not produce a request within the time that the server was prepared to wait....
The response to the request can be found under a different URI and SHOULD be retrieved using a GET me...
The server is currently unable to handle the request due to a temporary overloading or maintenance of...
The requested resource resides temporarily under a different URI. Since the redirection MAY be altere...
The user has sent too many requests in a given amount of time. Intended for use with rate limiting sc...
Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or ...
The request was well-formed but was unable to be followed due to semantic errors.
The server is refusing to service the request because the entity of the request is in a format not su...
The client should switch to a different protocol such as TLS/1.0, given in the Upgrade header field.
The requested resource MUST be accessed through the proxy given by the Location field....
Transparent content negotiation for the request results in a circular reference.
Response to the HttpxProxy.GetClientAsync(Uri) method call.
string BareJid
Bare JID of entity hosting the resource.
HttpxClient HttpxClient
Corresponding HttpxClient object to use for the request..
string FullJid
Full JID of entity hosting the resource.
Task Request(string To, string Method, string LocalResource, EventHandlerAsync< HttpxResponseEventArgs > Callback, EventHandlerAsync< HttpxResponseDataEventArgs > DataCallback, object State, params HttpField[] Headers)
Performs an HTTP request.
Definition: HttpxClient.cs:255
Content Getter, retrieving content using the HTTPX URI Scheme.
Definition: HttpxGetter.cs:23
async Task< ContentStreamResponse > GetTempStreamAsync(Uri Uri, X509Certificate Certificate, EventHandler< RemoteCertificateEventArgs > RemoteCertificateValidator, int TimeoutMs, TemporaryStream Destination, params KeyValuePair< string, string >[] Headers)
Gets a (possibly big) resource, using a Uniform Resource Identifier (or Locator).
Definition: HttpxGetter.cs:186
async Task< ContentResponse > GetAsync(Uri Uri, X509Certificate Certificate, EventHandler< RemoteCertificateEventArgs > RemoteCertificateValidator, int TimeoutMs, params KeyValuePair< string, string >[] Headers)
Gets a resource, using a Uniform Resource Identifier (or Locator).
Definition: HttpxGetter.cs:96
string[] UriSchemes
Supported URI schemes.
Definition: HttpxGetter.cs:42
Task< ContentStreamResponse > GetTempStreamAsync(Uri Uri, X509Certificate Certificate, EventHandler< RemoteCertificateEventArgs > RemoteCertificateValidator, int TimeoutMs, params KeyValuePair< string, string >[] Headers)
Gets a (possibly big) resource, using a Uniform Resource Identifier (or Locator).
Definition: HttpxGetter.cs:164
Task< ContentStreamResponse > GetTempStreamAsync(Uri Uri, X509Certificate Certificate, EventHandler< RemoteCertificateEventArgs > RemoteCertificateValidator, TemporaryStream Destination, params KeyValuePair< string, string >[] Headers)
Gets a (possibly big) resource, using a Uniform Resource Identifier (or Locator).
Definition: HttpxGetter.cs:143
bool CanGet(Uri Uri, out Grade Grade)
If the getter is able to get a resource, given its URI.
Definition: HttpxGetter.cs:50
Task< ContentStreamResponse > GetTempStreamAsync(Uri Uri, X509Certificate Certificate, EventHandler< RemoteCertificateEventArgs > RemoteCertificateValidator, params KeyValuePair< string, string >[] Headers)
Gets a (possibly big) resource, using a Uniform Resource Identifier (or Locator).
Definition: HttpxGetter.cs:128
Task< ContentResponse > GetAsync(Uri Uri, X509Certificate Certificate, EventHandler< RemoteCertificateEventArgs > RemoteCertificateValidator, params KeyValuePair< string, string >[] Headers)
Gets a resource, using a Uniform Resource Identifier (or Locator).
Definition: HttpxGetter.cs:72
HttpxGetter()
Content Getter, retrieving content using the HTTPX URI Scheme.
Definition: HttpxGetter.cs:30
Implements a Proxy resource that allows Web clients to fetch HTTP-based resources over HTTPX.
Definition: HttpxProxy.cs:19
Maintains information about an item in the roster.
Definition: RosterItem.cs:75
bool HasLastPresence
If the roster item has received presence from an online resource having the given bare JID.
Definition: RosterItem.cs:425
string LastPresenceFullJid
Full JID of last resource sending online presence.
Definition: RosterItem.cs:343
PresenceEventArgs LastPresence
Last presence received from a resource having this bare JID.
Definition: RosterItem.cs:356
Manages an XMPP client connection. Implements XMPP, as defined in https://tools.ietf....
Definition: XmppClient.cs:58
bool Disposed
If the client has been disposed.
Definition: XmppClient.cs:1173
bool TryGetExtension(Type Type, out IXmppExtension Extension)
Tries to get a registered extension of a specific type from the client.
Definition: XmppClient.cs:7391
RosterItem GetRosterItem(string BareJID)
Gets a roster item.
Definition: XmppClient.cs:4571
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:15
static bool TryGetModuleParameter(string Name, out object Value)
Tries to get a module parameter value.
Definition: Types.cs:607
Manages a temporary stream. Contents is kept in-memory, if below a memory threshold,...
Basic interface for Internet Content getters. A class implementing this interface and having a defaul...
Definition: ImplTypes.g.cs:58
Grade
Grade enumeration
Definition: Grade.cs:7