Пример #1
0
        /// <summary>
        /// Orchestrates an Http/s call to a remote service pointed to by a set of EndpointAssigment objects.
        /// This algorithm provides fail-over and circuit breaker functionality built-in.
        /// An actual call is performed in a passed-in call body functor.
        /// </summary>
        /// <typeparam name="TResult">The resulting type of the call, as obtained from call body</typeparam>
        /// <param name="assignments">An enumerable of hosts to be tried in sequence. The subsequent host are tried in case of failure</param>
        /// <param name="body">Call body functor. May not be null</param>
        /// <param name="cancellation">Optional CancellationToken</param>
        /// <returns>TResult call result or throws `ClientException` if call eventually failed after all fail-overs tried</returns>
        public static async Task <TResult> Call <TResult>(this IEnumerable <EndpointAssignment> assignments,
                                                          Func <IHttpTransport, CancellationToken?, Task <TResult> > body,
                                                          CancellationToken?cancellation = null)
        {
            body.NonNull(nameof(body));

            var first = assignments.NonNull(nameof(assignments)).FirstOrDefault();

            if (!first.IsAssigned)
            {
                throw new ClientException(StringConsts.HTTP_CLIENT_CALL_ASSIGMENT_ERROR.Args("No assignments provided"));
            }

            var service = first.Endpoint.Service as IHttpService;

            if (service == null)
            {
                throw new ClientException(StringConsts.HTTP_CLIENT_CALL_ASSIGMENT_ERROR.Args("Wrong service type assignments"));
            }

            if (!assignments.All(a => a.Endpoint.Service == service &&
                                 a.MappedContract == first.MappedContract &&
                                 a.MappedRemoteAddress == first.MappedRemoteAddress &&
                                 a.Endpoint.Network == first.Endpoint.Network &&
                                 a.Endpoint.Binding == first.Endpoint.Binding))
            {
                throw new ClientException(StringConsts.HTTP_CLIENT_CALL_ASSIGMENT_ERROR.Args("Inconsistent endpoint assignments"));
            }

            var tries = 0;
            List <Exception> errors = null;

            foreach (var assigned in assignments)
            {
                if (!assigned.Endpoint.IsAvailable)
                {
                    continue;                        //offline or Circuit tripped  todo:  instrument
                }
                tries++;

                var ep = assigned.Endpoint as IEndpointImplementation;

                var transport = service.AcquireTransport(assigned);
                try
                {
                    var http   = (transport as IHttpTransport).NonNull("Implementation error: cast to IHttpTransport");
                    var result = await body(http, cancellation).ConfigureAwait(false);

                    CallGuardException.Protect(ep, _ => _.NotifyCallSuccess(transport));
                    return(result);
                }
                catch (Exception error)
                {
                    //Implementation error
                    if (error is CallGuardException)
                    {
                        throw;
                    }

                    //TaskCanceledException gets thrown on simple timeout even when cancellation was NOT requested
                    if (error is TaskCanceledException && cancellation.HasValue && cancellation.Value.IsCancellationRequested)
                    {
                        throw;
                    }

                    var errorClass = ep.NotifyCallError(transport, error);
                    //todo instrument

                    if (errorClass == CallErrorClass.ServiceLogic)
                    {
                        throw;                                 //throw logical errors
                    }
                    if (errors == null)
                    {
                        errors = new List <Exception>();
                    }
                    errors.Add(error);
                }
                finally
                {
                    service.ReleaseTransport(transport);
                }
            }//foreach

            var toThrow = new ClientException(StringConsts.HTTP_CLIENT_CALL_FAILED.Args(service.GetType().Name, first.MappedRemoteAddress.TakeLastChars(32, "..."), tries),
                                              errors != null ? new AggregateException(errors) :
                                              new AggregateException("No inner errors"));

            if (service.ComponentEffectiveLogLevel <= Log.MessageType.Error)
            {
                service.App.Log.Write(
                    new Log.Message
                {
                    Topic     = service.ComponentLogTopic,
                    Type      = Log.MessageType.Error,
                    From      = "{0}{1}.{2}".Args(service.ComponentLogFromPrefix, nameof(HttpCallExtensions), nameof(Call)),
                    Text      = "Service `{0}` call error: {1}".Args(service.Name, toThrow.ToMessageWithType()),
                    Exception = toThrow,
                    RelatedTo = (Ambient.CurrentCallFlow?.ID) ?? Guid.Empty
                });
            }

            throw toThrow;
        }
Пример #2
0
        /// <summary>
        /// Performs a Http/s call to a remote service pointed to by a EndpointAssigment object.
        /// An actual call is performed in a passed-in call body functor.
        /// </summary>
        /// <typeparam name="TResult">The resulting type of the call, as obtained from call body</typeparam>
        /// <param name="assignment">An endpoint assignment to call</param>
        /// <param name="body">Call body functor. May not be null</param>
        /// <param name="cancellation">Optional CancellationToken</param>
        /// <returns>TResult call result or throws `ClientException` if call failed </returns>
        public static async Task <TResult> CallOne <TResult>(this EndpointAssignment assignment,
                                                             Func <IHttpTransport, CancellationToken?, Task <TResult> > body,
                                                             CancellationToken?cancellation = null)
        {
            body.NonNull(nameof(body));
            if (!assignment.IsAssigned)
            {
                throw new ClientException(StringConsts.HTTP_CLIENT_CALL_ASSIGMENT_ERROR.Args("No assignments provided"));
            }

            var service = assignment.Endpoint.Service as IHttpService;

            if (service == null)
            {
                throw new ClientException(StringConsts.HTTP_CLIENT_CALL_ASSIGMENT_ERROR.Args("Wrong service type assignments"));
            }

            Exception cause = null;

            if (assignment.Endpoint.IsAvailable)
            {
                var ep        = assignment.Endpoint as IEndpointImplementation;
                var transport = service.AcquireTransport(assignment);
                try
                {
                    var http   = (transport as IHttpTransport).NonNull("Implementation error: cast to IHttpTransport");
                    var result = await body(http, cancellation).ConfigureAwait(false);

                    CallGuardException.Protect(ep, _ => _.NotifyCallSuccess(transport));
                    return(result);
                }
                catch (Exception error)
                {
                    //Implementation error
                    if (error is CallGuardException)
                    {
                        throw;
                    }

                    //TaskCanceledException gets thrown on simple timeout even when cancellation was NOT requested
                    if (error is TaskCanceledException && cancellation.HasValue && cancellation.Value.IsCancellationRequested)
                    {
                        throw;
                    }

                    var errorClass = ep.NotifyCallError(transport, error);
                    //todo instrument

                    if (errorClass == CallErrorClass.ServiceLogic)
                    {
                        throw;                                 //throw logical errors
                    }
                    cause = error;
                }
                finally
                {
                    service.ReleaseTransport(transport);
                }
            }
            else
            {
                cause = new ClientException("Endpoint !Available");
            }

            var toThrow = new ClientException(StringConsts.HTTP_CLIENT_CALL_FAILED
                                              .Args(service.GetType().Name,
                                                    assignment.MappedRemoteAddress.TakeLastChars(32, "..."),
                                                    1),
                                              cause);

            if (service.ComponentEffectiveLogLevel <= Log.MessageType.Error)
            {
                service.App.Log.Write(
                    new Log.Message
                {
                    Topic     = service.ComponentLogTopic,
                    Type      = Log.MessageType.Error,
                    From      = "{0}{1}.{2}".Args(service.ComponentLogFromPrefix, nameof(HttpCallExtensions), nameof(Call)),
                    Text      = "Service `{0}` call error: {1}".Args(service.Name, toThrow.ToMessageWithType()),
                    Exception = toThrow,
                    RelatedTo = (Ambient.CurrentCallFlow?.ID) ?? Guid.Empty
                });
            }

            throw toThrow;
        }//CallOne