internal static ServiceResult TryAddHeaders(HttpHeaders httpHeaders, IEnumerable <KeyValuePair <string, string> > headers)
        {
            if (headers != null)
            {
                foreach (var header in headers)
                {
                    try
                    {
                        if (header.Value != null)
                        {
                            httpHeaders.Add(header.Key, header.Value);
                        }
                    }
                    catch (FormatException)
                    {
                        return(ServiceResult.Failure(HttpServiceErrors.CreateHeaderInvalidFormat(header.Key)));
                    }
                    catch (InvalidOperationException)
                    {
                        return(ServiceResult.Failure(HttpServiceErrors.CreateHeaderNotSupported(header.Key)));
                    }
                }
            }

            return(ServiceResult.Success());
        }
示例#2
0
        /// <summary>
        /// Called to create an error object from an unhandled HTTP response.
        /// </summary>
        protected virtual async Task <ServiceErrorDto> CreateErrorFromHttpResponseAsync(HttpResponseMessage response, CancellationToken cancellationToken)
        {
            var result = await ContentSerializer.ReadHttpContentAsync <ServiceErrorDto>(response.Content, cancellationToken).ConfigureAwait(false);

            if (result.IsFailure || string.IsNullOrWhiteSpace(result.Value.Code))
            {
                return(HttpServiceErrors.CreateErrorForStatusCode(response.StatusCode, response.ReasonPhrase));
            }

            return(result.Value);
        }
示例#3
0
 /// <summary>
 /// Reads a DTO from the specified HTTP content.
 /// </summary>
 protected override async Task <ServiceResult <object> > ReadHttpContentAsyncCore(Type dtoType, HttpContent content, CancellationToken cancellationToken)
 {
     try
     {
         using (var stream = await content.ReadAsStreamAsync().ConfigureAwait(false))
             using (var textReader = new StreamReader(stream))
                 return(ServiceResult.Success(ServiceJsonUtility.FromJsonTextReader(textReader, dtoType)));
     }
     catch (JsonException exception)
     {
         return(ServiceResult.Failure(HttpServiceErrors.CreateInvalidContent(exception.Message)));
     }
 }
示例#4
0
        /// <summary>
        /// Reads a DTO from the specified HTTP content.
        /// </summary>
        public async Task <ServiceResult <object> > ReadHttpContentAsync(Type dtoType, HttpContent content, CancellationToken cancellationToken = default(CancellationToken))
        {
            var contentType = content?.Headers.ContentType;

            if (contentType == null)
            {
                return(ServiceResult.Failure(HttpServiceErrors.CreateMissingContentType()));
            }

            string mediaType = contentType.MediaType;

            if (!IsSupportedMediaType(mediaType))
            {
                return(ServiceResult.Failure(HttpServiceErrors.CreateUnsupportedContentType(mediaType)));
            }

            return(await ReadHttpContentAsyncCore(dtoType, content, cancellationToken).ConfigureAwait(false));
        }
        /// <summary>
        /// Attempts to handle a service method.
        /// </summary>
        protected async Task <HttpResponseMessage> TryHandleServiceMethodAsync <TRequest, TResponse>(HttpMethodMapping <TRequest, TResponse> mapping, HttpRequestMessage httpRequest, Func <TRequest, CancellationToken, Task <ServiceResult <TResponse> > > invokeMethodAsync, CancellationToken cancellationToken)
            where TRequest : ServiceDto, new()
            where TResponse : ServiceDto, new()
        {
            if (httpRequest.Method != mapping.HttpMethod)
            {
                return(null);
            }

            var pathParameters = TryMatchHttpRoute(httpRequest.RequestUri, m_rootPath + mapping.Path);

            if (pathParameters == null)
            {
                return(null);
            }

            var context = new ServiceHttpContext();

            ServiceHttpContext.SetContext(httpRequest, context);

            var aspectHttpResponse = await AdaptTask(RequestReceivedAsync(httpRequest, cancellationToken)).ConfigureAwait(true);

            if (aspectHttpResponse != null)
            {
                return(aspectHttpResponse);
            }

            string mediaType = GetResponseMediaType(httpRequest);

            ServiceErrorDto error = null;

            object requestBody = null;

            if (mapping.RequestBodyType != null)
            {
                var requestResult = await AdaptTask(m_contentSerializer.ReadHttpContentAsync(mapping.RequestBodyType, httpRequest.Content, cancellationToken)).ConfigureAwait(true);

                if (requestResult.IsFailure)
                {
                    error = requestResult.Error;
                }
                else
                {
                    requestBody = requestResult.Value;
                }
            }

            TResponse response = null;

            if (error == null)
            {
                var request = mapping.CreateRequest(requestBody);

                var uriParameters = new Dictionary <string, string>();
                foreach (var queryParameter in ParseQueryString(httpRequest.RequestUri.Query))
                {
                    uriParameters[queryParameter.Key] = queryParameter.Value[0];
                }
                foreach (var pathParameter in pathParameters)
                {
                    uriParameters[pathParameter.Key] = pathParameter.Value;
                }
                request = mapping.SetUriParameters(request, uriParameters);
                request = mapping.SetRequestHeaders(request, HttpServiceUtility.CreateDictionaryFromHeaders(httpRequest.Headers));

                context.Request = request;

                var methodResult = await invokeMethodAsync(request, cancellationToken).ConfigureAwait(true);

                if (methodResult.IsFailure)
                {
                    error = methodResult.Error;
                }
                else
                {
                    response = methodResult.Value;
                }

                context.Result = error != null?ServiceResult.Failure(error) : ServiceResult.Success <ServiceDto>(response);
            }

            HttpResponseMessage httpResponse;

            if (error == null)
            {
                var responseMappingGroups = mapping.ResponseMappings
                                            .GroupBy(x => x.MatchesResponse(response))
                                            .Where(x => x.Key != false)
                                            .OrderByDescending(x => x.Key)
                                            .ToList();
                if (responseMappingGroups.Count >= 1 && responseMappingGroups[0].Count() == 1)
                {
                    var responseMapping = responseMappingGroups[0].Single();
                    httpResponse = new HttpResponseMessage(responseMapping.StatusCode);

                    var headersResult = HttpServiceUtility.TryAddHeaders(httpResponse.Headers, mapping.GetResponseHeaders(response));
                    if (headersResult.IsFailure)
                    {
                        throw new InvalidOperationException(headersResult.Error.Message);
                    }

                    if (responseMapping.ResponseBodyType != null)
                    {
                        httpResponse.Content = m_contentSerializer.CreateHttpContent(responseMapping.GetResponseBody(response), mediaType);
                    }
                }
                else
                {
                    throw new InvalidOperationException($"Found {responseMappingGroups.Sum(x => x.Count())} valid HTTP responses for {typeof(TResponse).Name}: {response}");
                }
            }
            else
            {
                var statusCode = error.Code == null ? HttpStatusCode.InternalServerError :
                                 (TryGetCustomHttpStatusCode(error.Code) ?? HttpServiceErrors.TryGetHttpStatusCode(error.Code) ?? HttpStatusCode.InternalServerError);
                httpResponse = new HttpResponseMessage(statusCode);
                if (statusCode != HttpStatusCode.NoContent && statusCode != HttpStatusCode.NotModified)
                {
                    httpResponse.Content = m_contentSerializer.CreateHttpContent(error, mediaType);
                }
            }

            httpResponse.RequestMessage = httpRequest;
            await AdaptTask(ResponseReadyAsync(httpResponse, cancellationToken)).ConfigureAwait(true);

            return(httpResponse);
        }
示例#6
0
        /// <summary>
        /// Attempts to handle a service method.
        /// </summary>
        protected async Task <HttpResponseMessage?> TryHandleServiceMethodAsync <TRequest, TResponse>(HttpMethodMapping <TRequest, TResponse> mapping, HttpRequestMessage httpRequest, Func <TRequest, CancellationToken, Task <ServiceResult <TResponse> > > invokeMethodAsync, CancellationToken cancellationToken)
            where TRequest : ServiceDto, new()
            where TResponse : ServiceDto, new()
        {
            if (mapping == null)
            {
                throw new ArgumentNullException(nameof(mapping));
            }
            if (httpRequest == null)
            {
                throw new ArgumentNullException(nameof(httpRequest));
            }
            if (invokeMethodAsync == null)
            {
                throw new ArgumentNullException(nameof(invokeMethodAsync));
            }
            if (httpRequest.RequestUri == null)
            {
                throw new ArgumentException("RequestUri must be specified.", nameof(httpRequest));
            }

            if (httpRequest.Method != mapping.HttpMethod)
            {
                return(null);
            }

            var pathParameters = TryMatchHttpRoute(httpRequest.RequestUri, m_rootPath + mapping.Path);

            if (pathParameters == null)
            {
                return(null);
            }

            var context = new ServiceHttpContext();

            ServiceHttpContext.SetContext(httpRequest, context);

            var aspectHttpResponse = await AdaptTask(RequestReceivedAsync(httpRequest, cancellationToken)).ConfigureAwait(true);

            if (aspectHttpResponse != null)
            {
                return(aspectHttpResponse);
            }

            ServiceErrorDto?error = null;

            object?requestBody = null;

            if (mapping.RequestBodyType != null)
            {
                try
                {
                    var serializer    = GetHttpContentSerializer(mapping.RequestBodyType);
                    var requestResult = await AdaptTask(serializer.ReadHttpContentAsync(mapping.RequestBodyType, httpRequest.Content, cancellationToken)).ConfigureAwait(true);

                    if (requestResult.IsFailure)
                    {
                        error = requestResult.Error;
                    }
                    else
                    {
                        requestBody = requestResult.Value;
                    }
                }
                catch (Exception exception) when(ShouldCreateErrorFromException(exception))
                {
                    // cancellation can cause the wrong exception
                    cancellationToken.ThrowIfCancellationRequested();

                    // error reading request body
                    error = CreateErrorFromException(exception);
                }
            }

            TResponse?response = null;

            if (error == null)
            {
                var request = mapping.CreateRequest(requestBody);

                var uriParameters = new Dictionary <string, string?>(StringComparer.OrdinalIgnoreCase);
                foreach (var queryParameter in ParseQueryString(httpRequest.RequestUri.Query))
                {
                    uriParameters[queryParameter.Key] = queryParameter.Value[0];
                }
                foreach (var pathParameter in pathParameters)
                {
                    uriParameters[pathParameter.Key] = pathParameter.Value;
                }
                request = mapping.SetUriParameters(request, uriParameters);
                request = mapping.SetRequestHeaders(request, HttpServiceUtility.CreateDictionaryFromHeaders(httpRequest.Headers, httpRequest.Content?.Headers) !);

                context.Request = request;

                if (!m_skipRequestValidation && !request.Validate(out var requestErrorMessage))
                {
                    error = ServiceErrors.CreateInvalidRequest(requestErrorMessage);
                }
                else
                {
                    var methodResult = await invokeMethodAsync(request, cancellationToken).ConfigureAwait(true);

                    if (methodResult.IsFailure)
                    {
                        error = methodResult.Error;
                    }
                    else
                    {
                        response = methodResult.Value;

                        if (!m_skipResponseValidation && !response.Validate(out var responseErrorMessage))
                        {
                            error    = ServiceErrors.CreateInvalidResponse(responseErrorMessage);
                            response = null;
                        }
                    }
                }

                context.Result = error != null?ServiceResult.Failure(error) : ServiceResult.Success <ServiceDto>(response !);
            }

            HttpResponseMessage httpResponse;

            if (error == null)
            {
                var responseMappingGroups = mapping.ResponseMappings
                                            .GroupBy(x => x.MatchesResponse(response !))
                                            .Where(x => x.Key != false)
                                            .OrderByDescending(x => x.Key)
                                            .ToList();
                if (responseMappingGroups.Count >= 1 && responseMappingGroups[0].Count() == 1)
                {
                    var responseMapping = responseMappingGroups[0].Single();
                    httpResponse = new HttpResponseMessage(responseMapping.StatusCode);

                    var responseHeaders = mapping.GetResponseHeaders(response !);
                    var headersResult   = HttpServiceUtility.TryAddNonContentHeaders(httpResponse.Headers, responseHeaders);
                    if (headersResult.IsFailure)
                    {
                        throw new InvalidOperationException(headersResult.Error !.Message);
                    }

                    if (responseMapping.ResponseBodyType != null)
                    {
                        var serializer = GetHttpContentSerializer(responseMapping.ResponseBodyType);
                        var mediaType  = responseMapping.ResponseBodyContentType ?? responseHeaders?.GetContentType() ?? GetAcceptedMediaType(httpRequest, serializer);
                        httpResponse.Content = serializer.CreateHttpContent(responseMapping.GetResponseBody(response !) !, mediaType);
                    }
                }
                else
                {
                    throw new InvalidOperationException($"Found {responseMappingGroups.Sum(x => x.Count())} valid HTTP responses for {typeof(TResponse).Name}: {response}");
                }
            }
            else
            {
                var statusCode = error.Code == null ? HttpStatusCode.InternalServerError :
                                 (TryGetCustomHttpStatusCode(error.Code) ?? HttpServiceErrors.TryGetHttpStatusCode(error.Code) ?? HttpStatusCode.InternalServerError);
                httpResponse = new HttpResponseMessage(statusCode);
                if (statusCode != HttpStatusCode.NoContent && statusCode != HttpStatusCode.NotModified)
                {
                    var mediaType = GetAcceptedMediaType(httpRequest, m_contentSerializer);
                    httpResponse.Content = m_contentSerializer.CreateHttpContent(error, mediaType);
                }
            }

            httpResponse.RequestMessage = httpRequest;
            await AdaptTask(ResponseReadyAsync(httpResponse, cancellationToken)).ConfigureAwait(true);

            return(httpResponse);
        }