示例#1
0
 public static IMethodReference Resolve(this IMethodResolver resolver, IServiceDefinition serviceDefinition, MethodId methodId)
 {
     if (resolver.TryResolve(serviceDefinition, methodId, out var methodReference))
     {
         return(methodReference);
     }
     throw new MethodResolveException(serviceDefinition.Name, methodId);
 }
        private void EnumerateThroughPrimitiveType(
            IConfigurationSection parentSection, ConfigurationSectionKey serviceKey,
            PrimitiveType type, IServiceDefinition serviceDefinition,
            Dictionary <ConfigurationSectionKey, IConfigurationSection> map)
        {
            foreach (var section in parentSection.GetChildren())
            {
                if (section.Key.Equals("_all", StringComparison.OrdinalIgnoreCase))
                {
                    var allPrimitivesKey = serviceKey;
                    allPrimitivesKey.PrimitiveType = type;
                    map[allPrimitivesKey]          = section;
                }
                else if (type == PrimitiveType.Command || type == PrimitiveType.Query)
                {
                    var methodType = type;
                    var methodName = section.Key;

                    if (serviceDefinition != null && _methodResolver.TryResolve(serviceDefinition,
                                                                                new MethodId {
                        Name = methodName
                    }, out var methodReference))
                    {
                        methodType = methodReference.Definition.IsQuery ? PrimitiveType.Query : PrimitiveType.Command;
                        methodName = methodReference.Definition.Name;
                    }

                    var methodKey = serviceKey;
                    methodKey.PrimitiveType = methodType;
                    methodKey.PrimitiveName = methodName;

                    map[methodKey] = section;
                }
                else if (type == PrimitiveType.Event)
                {
                    var eventName = section.Key;

                    if (serviceDefinition != null && _eventResolver.TryResolve(serviceDefinition,
                                                                               new EventId {
                        Name = eventName
                    }, out var eventReference))
                    {
                        eventName = eventReference.Definition.Name;
                    }

                    var eventKey = serviceKey;
                    eventKey.PrimitiveType = type;
                    eventKey.PrimitiveName = eventName;

                    map[eventKey] = section;
                }
            }
        }
示例#3
0
        public Task <IEnumerable <IMessageListener> > StartListeningAsync(
            IConfiguration configuration,
            IServiceDefinition serviceDefinition,
            IDictionary <IEventDefinition, IConfiguration> eventConfigMap,
            CancellationToken ct)
        {
            // TODO: there are many scenarios that are not supported by the following code.
            // For example, exchange's connection is different from services' queues.

            var baseConnectionSettings = new ConnectionSettings();

            configuration.Bind(baseConnectionSettings);

            var baseConnection = _connectionManager.GetConnection(baseConnectionSettings);
            var baseChannel    = baseConnection.CreateModel();

            foreach (var eventDefinition in serviceDefinition.Events)
            {
                if (!eventConfigMap.TryGetValue(eventDefinition, out var eventConfiguration))
                {
                    eventConfiguration = configuration;
                }

                var listenerSettings = RabbitMQCommunicationMethod.CreateEventsDefaultSettings();
                eventConfiguration.Bind(listenerSettings);

                var exchangeName = listenerSettings.ExchangeName
                                   .Replace("{serviceName}", serviceDefinition.Name)
                                   .Replace("{eventName}", eventDefinition.Name);

                // TODO: declare once? what's the penalty?
                baseChannel.ExchangeDeclare(
                    exchangeName,
                    type: "fanout",
                    durable: true,
                    autoDelete: false,
                    arguments: null);

                var eventDesc = new EventDescriptor
                {
                    Service = new ServiceId {
                        Name = serviceDefinition.Name
                    },
                    Event = _eventIdProvider.GetId(eventDefinition.EventInfo)
                };

                foreach (var subscriber in _eventSubscriber.GetSubscribers(eventDesc))
                {
                    if (!_serviceResolver.TryResolve(subscriber.Service, out var subscriberServiceReference))
                    {
                        continue;
                    }

                    if (!_methodResolver.TryResolve(subscriberServiceReference.Definition, subscriber.Method, out var subscriberMethodReference))
                    {
                        continue;
                    }

                    var subscriberMethodConfiguration = _communicationModelConfiguration.GetMethodConfiguration(subscriberMethodReference.Definition, "communication");

                    var subscriberSettings = RabbitMQCommunicationMethod.CreateMethodsDefaultSettings();
                    subscriberMethodConfiguration.Bind(subscriberSettings);

                    var subscriberQueueName = subscriberSettings.QueueName
                                              .Replace("{serviceName}", subscriberServiceReference.Definition.Name)
                                              .Replace("{methodName}", subscriberMethodReference.Definition.Name);

                    // TODO: declare once? what's the penalty?
                    baseChannel.QueueBind(
                        queue: subscriberQueueName,
                        exchange: exchangeName,
                        routingKey: "",
                        arguments: null);
                }
            }

            // No direct listeners to RabbitMQ Exchanges, just configure exchange-queue bindings.

            // TODO: if events (or subscribers) use a different connection,
            // need to create a queue and a listener in that RabbitMQ instance.
            return(Task.FromResult <IEnumerable <IMessageListener> >(Array.Empty <IMessageListener>()));
        }
示例#4
0
        public async Task HandleAsync(PathString basePath, HttpContext context, CancellationToken ct)
        {
            var isQueryRequest   = context.Request.Method == "GET";
            var isCommandRequest = context.Request.Method == "POST";

            if (!isQueryRequest && !isCommandRequest)
            {
                await ReplyWithTextError(context.Response, 405, "Only GET and POST verbs are allowed");

                return;
            }

            var headers = context.Response.Headers;

            headers.Add(DasyncHttpHeaders.PoweredBy, "D-ASYNC");

            var basePathSegmentCount = basePath.Value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).Length;
            var pathSegments         = context.Request.Path.Value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).Skip(basePathSegmentCount).ToArray();

            if (pathSegments.Length == 0)
            {
                await ReplyWithTextError(context.Response, 404, "Empty request URL");

                return;
            }

            var serviceId = new ServiceId {
                Name = pathSegments[0]
            };

            if (!_serviceResolver.TryResolve(serviceId, out var serviceReference))
            {
                await ReplyWithTextError(context.Response, 404, $"Service '{serviceId.Name}' is not registered");

                return;
            }

            if (serviceReference.Definition.Type == ServiceType.External)
            {
                await ReplyWithTextError(context.Response, 404, $"Cannot invoke external service '{serviceReference.Definition.Name}' on your behalf");

                return;
            }

            if (pathSegments.Length == 1)
            {
                await ReplyWithTextError(context.Response, 404, "The request URL does not contain a service method");

                return;
            }

            var methodId = new MethodId {
                Name = pathSegments[1]
            };
            string methodIntentId = null;

            if (pathSegments.Length == 3)
            {
                methodIntentId = pathSegments[2];

                if (isQueryRequest)
                {
                    await HandleResultPoll(context, serviceReference.Id, methodId, methodIntentId);

                    return;
                }
            }

            if (pathSegments.Length > 3)
            {
                await ReplyWithTextError(context.Response, 404, "The request URL contains extra segments");

                return;
            }

            var         contentType = context.Request.GetContentType();
            ISerializer serializer;

            try
            {
                serializer = GetSerializer(contentType, isQueryRequest);
            }
            catch (ArgumentException)
            {
                await ReplyWithTextError(context.Response, 406, $"The Content-Type '{contentType.MediaType}' is not supported");

                return;
            }

            if (!_methodResolver.TryResolve(serviceReference.Definition, methodId, out var methodReference))
            {
                await ReplyWithTextError(context.Response, 404, $"The service '{serviceReference.Definition.Name}' does not have method '{methodId.Name}'");

                return;
            }

            if (isQueryRequest && !methodReference.Definition.IsQuery)
            {
                await ReplyWithTextError(context.Response, 404, $"The method '{methodId.Name}' of service '{serviceReference.Definition.Name}' is a command, but not a query, thus must be invoked with the POST verb");

                return;
            }

            MethodInvocationData   invokeData   = null;
            MethodContinuationData continueData = null;
            IValueContainer        parametersContainer;
            bool compressResponse    = false;
            bool respondWithEnvelope = false;

            if (isQueryRequest)
            {
                var inputObject = new JObject();

                foreach (var kvPair in context.Request.Query)
                {
                    if (kvPair.Value.Count == 0)
                    {
                        continue;
                    }

                    var parameterName = kvPair.Key;

                    if (kvPair.Value.Count == 1)
                    {
                        var parameterValue = kvPair.Value[0];
                        inputObject.Add(parameterName, parameterValue);
                    }
                    else
                    {
                        var values = new JArray();
                        foreach (var value in kvPair.Value)
                        {
                            values.Add(value);
                        }
                        inputObject.Add(parameterName, values);
                    }
                }

                var parametersJson = inputObject.ToString();

                parametersContainer = new SerializedValueContainer(parametersJson, _jsonSerializer);

                invokeData = new MethodInvocationData
                {
                    Service    = serviceId,
                    Method     = methodId,
                    Parameters = parametersContainer,
                    Caller     = GetCaller(context.Request.Headers)
                };
            }
            else
            {
                var payloadStream = context.Request.Body;

                var encoding = context.Request.GetContentEncoding();
                if (!string.IsNullOrEmpty(encoding))
                {
                    if ("gzip".Equals(encoding, StringComparison.OrdinalIgnoreCase))
                    {
                        compressResponse = true;
                        payloadStream    = new GZipStream(payloadStream, CompressionMode.Decompress, leaveOpen: true);
                    }
                    else if ("deflate".Equals(encoding, StringComparison.OrdinalIgnoreCase))
                    {
                        payloadStream = new DeflateStream(payloadStream, CompressionMode.Decompress, leaveOpen: true);
                    }
                    else
                    {
                        await ReplyWithTextError(context.Response, 406, $"The Content-Encoding '{encoding}' is not supported");

                        return;
                    }
                }

                var envelopeType = context.Request.Headers.GetValue(DasyncHttpHeaders.Envelope);

                if (string.IsNullOrEmpty(envelopeType))
                {
                    var payload = await payloadStream.ToBytesAsync();

                    parametersContainer = new SerializedValueContainer(payload, serializer);

                    if (string.IsNullOrEmpty(methodIntentId))
                    {
                        invokeData = new MethodInvocationData
                        {
                            Service    = serviceId,
                            Method     = methodId,
                            Parameters = parametersContainer,
                            Caller     = GetCaller(context.Request.Headers)
                        };
                    }
                    else
                    {
                        continueData = new MethodContinuationData
                        {
                            Service = serviceId,
                            Method  = methodId.CopyTo(new PersistedMethodId()),
                            Result  = parametersContainer,
                            Caller  = GetCaller(context.Request.Headers)
                        };
                        continueData.Method.IntentId = methodIntentId;
                        // TODO: get ETag from the query string
                    }
                }
                else if (envelopeType.Equals("invoke", StringComparison.OrdinalIgnoreCase))
                {
                    respondWithEnvelope = true;
                    invokeData          = await DeserializeAsync <MethodInvocationData>(serializer, payloadStream);
                }
                else if (envelopeType.Equals("continue", StringComparison.OrdinalIgnoreCase))
                {
                    respondWithEnvelope = true;
                    continueData        = await DeserializeAsync <MethodContinuationData>(serializer, payloadStream);
                }
                else
                {
                    await ReplyWithTextError(context.Response, 406, $"Unknown envelope type '{envelopeType}'");

                    return;
                }
            }

            var intentId              = context.Request.Headers.GetValue(DasyncHttpHeaders.IntentId) ?? _idGenerator.NewId();
            var externalRequestId     = context.Request.Headers.GetValue(DasyncHttpHeaders.RequestId);
            var externalCorrelationId = context.Request.Headers.GetValue(DasyncHttpHeaders.CorrelationId);
            var isRetry = context.Request.Headers.IsRetry();

            var rfc7240Preferences             = context.Request.Headers.GetRFC7240Preferences();
            var isHttpRequestBlockingExecution = !(rfc7240Preferences.RespondAsync == true);
            var waitTime = rfc7240Preferences.Wait;

            if (waitTime > MaxLongPollTime)
            {
                waitTime = MaxLongPollTime;
            }
            var waitForResult = isHttpRequestBlockingExecution || waitTime > TimeSpan.Zero;

            var communicatorMessage = new HttpCommunicatorMessage
            {
                IsRetry       = isRetry,
                RequestId     = externalRequestId,
                WaitForResult = waitForResult
            };

            if (invokeData != null)
            {
                if (invokeData.IntentId == null)
                {
                    invokeData.IntentId = intentId;
                }

                var invokeTask = _localTransitionRunner.RunAsync(invokeData, communicatorMessage);

                if (isHttpRequestBlockingExecution)
                {
                    var invocationResult = await invokeTask;

                    if (invocationResult.Outcome == InvocationOutcome.Complete)
                    {
                        await RespondWithMethodResult(context.Response, invocationResult.Result, serializer, respondWithEnvelope, compressResponse);

                        return;
                    }

                    if (waitForResult)
                    {
                        var taskResult = await TryWaitForResultAsync(
                            serviceReference.Id,
                            methodId, intentId, waitTime);

                        if (taskResult != null)
                        {
                            await RespondWithMethodResult(context.Response, taskResult, serializer, respondWithEnvelope, compressResponse);

                            return;
                        }
                    }
                }
                else
                {
                    // TODO: continue 'invokeTask' and handle exceptions in background
                }

                communicatorMessage.WaitForResult = false;

                var location = string.Concat(context.Request.Path, "/", intentId);
                context.Response.Headers.Add("Location", location);
                context.Response.Headers.Add(DasyncHttpHeaders.IntentId, intentId);
                context.Response.StatusCode = DasyncHttpCodes.Scheduled;
            }
            else if (continueData != null)
            {
                if (continueData.IntentId == null)
                {
                    continueData.IntentId = intentId;
                }

                var continueTask = _localTransitionRunner.ContinueAsync(continueData, communicatorMessage);
                // TODO: continue 'continueTask' in backgraound to handle exceptions

                context.Response.Headers.Add("Location", context.Request.Path.ToString());
                context.Response.Headers.Add(DasyncHttpHeaders.IntentId, continueData.Method.IntentId);
                context.Response.StatusCode = DasyncHttpCodes.Scheduled;
            }
        }