private async Task SendSubscriptionRequest(HttpSubscriptionMetadata subscriptionMetadata, CancellationToken cancellationToken = default(CancellationToken)) { if (subscriptionMetadata == null) { throw new ArgumentNullException(nameof(subscriptionMetadata)); } var endpoint = subscriptionMetadata.Endpoint; var topic = subscriptionMetadata.Topic; var ttl = subscriptionMetadata.TTL; HttpClient httpClient = null; HttpResponseMessage httpResponseMessage = null; try { var endpointBaseUri = endpoint.Address.WithTrailingSlash(); httpClient = await _httpClientFactory.GetClient(endpointBaseUri, endpoint.Credentials, cancellationToken); var urlSafeTopicName = UrlEncoder.Encode(topic); var relativeUri = $"topic/{urlSafeTopicName}/subscriber?uri={_baseUri}"; if (ttl > TimeSpan.Zero) { relativeUri += "&ttl=" + ttl.TotalSeconds; } var postUri = new Uri(endpointBaseUri, relativeUri); httpResponseMessage = await httpClient.PostAsync(relativeUri, new StringContent(""), cancellationToken); var status = (int?)httpResponseMessage.StatusCode; await _diagnosticService.EmitAsync( new HttpEventBuilder(this, HttpEventType.HttpSubscriptionRequestSent) { Uri = postUri, Topic = subscriptionMetadata.Topic, Status = status }.Build(), cancellationToken); HandleHttpErrorResponse(httpResponseMessage); } catch (TransportException) { throw; } catch (TaskCanceledException) { throw; } catch (Exception ex) { var errorMessage = $"Error sending subscription request for topic {topic} of publisher {endpoint.Address}"; TryHandleCommunicationException(ex, endpoint.Address); throw new TransportException(errorMessage, ex); } finally { httpResponseMessage?.Dispose(); httpClient?.Dispose(); } }
/// <inheritdoc /> public async Task Subscribe(IEndpoint endpoint, TopicName topicName, TimeSpan ttl, CancellationToken cancellationToken = default(CancellationToken)) { try { var subscription = new HttpSubscriptionMetadata(endpoint, topicName, ttl); while (!cancellationToken.IsCancellationRequested) { TimeSpan retryOrRenewAfter; try { await SendSubscriptionRequest(subscription, cancellationToken); if (!subscription.Expires) { // Subscription is not set to expire on the remote server. Since // publications are pushed to the subscribers, we don't need to keep // this task running. return; } retryOrRenewAfter = subscription.RenewalInterval; _diagnosticService.Emit( new HttpEventBuilder(this, DiagnosticEventType.SubscriptionRenewed) { Detail = "Subscription renewed. Next renewal in " + retryOrRenewAfter, Topic = topicName, }.Build()); } catch (EndpointNotFoundException enfe) { // Endpoint is not defined in the supplied configuration, // so we cannot determine the URI. This is an unrecoverable // error, so simply return. _diagnosticService.Emit( new HttpEventBuilder(this, DiagnosticEventType.EndpointNotFound) { Detail = "Fatal error sending subscription request: endpoint not found", Topic = topicName, Exception = enfe }.Build()); _diagnosticService.Emit( new HttpEventBuilder(this, DiagnosticEventType.SubscriptionFailed) { Topic = topicName }.Build()); return; } catch (NameResolutionFailedException nrfe) { // The transport was unable to resolve the hostname in the // endpoint URI. This may or may not be a temporary error. // In either case, retry after 30 seconds. retryOrRenewAfter = subscription.RetryInterval; _diagnosticService.Emit( new HttpEventBuilder(this, HttpEventType.HttpCommunicationError) { Detail = "Non-fatal error sending subscription request: unable to resolve address for hostname " + nrfe.Hostname + ". Retry in " + retryOrRenewAfter, Topic = topicName, Exception = nrfe }.Build()); } catch (ConnectionRefusedException cre) { // The transport was unable to resolve the hostname in the // endpoint URI. This may or may not be a temporary error. // In either case, retry after 30 seconds. retryOrRenewAfter = subscription.RetryInterval; _diagnosticService.Emit( new HttpEventBuilder(this, HttpEventType.HttpCommunicationError) { Detail = "Non-fatal error sending subscription request: connection refused for " + cre.Host + ":" + cre.Port + ". Retry in " + retryOrRenewAfter, Topic = topicName, Exception = cre }.Build()); } catch (ResourceNotFoundException ire) { // Topic is not found. This may be temporary. retryOrRenewAfter = subscription.RetryInterval; _diagnosticService.Emit( new HttpEventBuilder(this, HttpEventType.HttpCommunicationError) { Detail = "Non-fatal error sending subscription request: resource not found. Topic may be misconfigured. Retry in " + retryOrRenewAfter, Topic = topicName, Exception = ire }.Build()); } catch (InvalidRequestException ire) { // Request is not valid. Either the URL is malformed or the // topic does not exist. In any case, retrying would be // fruitless, so just return. _diagnosticService.Emit( new HttpEventBuilder(this, DiagnosticEventType.EndpointNotFound) { Detail = "Fatal error sending subscription request: invalid request. Subscription abandoned.", Topic = topicName, Exception = ire }.Build()); _diagnosticService.Emit( new HttpEventBuilder(this, DiagnosticEventType.SubscriptionFailed) { Topic = topicName }.Build()); return; } catch (TransportException te) { // Unspecified transport error. This may or may not be // due to temporary conditions that will resolve // themselves. Retry in 30 seconds. retryOrRenewAfter = subscription.RetryInterval; _diagnosticService.Emit( new HttpEventBuilder(this, HttpEventType.HttpCommunicationError) { Detail = "Non-fatal error sending subscription request: communication error. Retry in " + retryOrRenewAfter, Topic = topicName, Exception = te }.Build()); } await Task.Delay(retryOrRenewAfter, cancellationToken); } } catch (OperationCanceledException) { } }