/// <summary> /// Initializes a new <see cref="HttpExceptionHandler"/> for the specified HTTP /// <paramref name="request"/> and <paramref name="response"/> /// </summary> /// <param name="request">The HTTP request being processed</param> /// <param name="response">The HTTP response being constructed</param> /// <param name="source">(Optional) The object in which the exception occurred</param> /// <param name="diagnosticService">(Optional) The service through which diagnostic events /// are reported and processed</param> /// <exception cref="ArgumentNullException">Thrown if <paramref name="request"/> or /// <paramref name="response"/> are <c>null</c></exception> public HttpExceptionHandler(IHttpResourceRequest request, IHttpResourceResponse response, IDiagnosticService diagnosticService, object source = null) { _request = request ?? throw new ArgumentNullException(nameof(request)); _response = response ?? throw new ArgumentNullException(nameof(response)); _diagnosticService = diagnosticService ?? DiagnosticService.DefaultInstance; _source = source ?? this; }
/// <summary> /// Processes the specified <paramref name="request"/> and updates the supplied /// <paramref name="response"/> /// </summary> /// <param name="request">The HTTP resource request to process</param> /// <param name="response">The HTTP response to update</param> /// <param name="subPath">The portion of the request path that remains after the /// request was routed to this controller</param> /// <returns>Returns a task that completes when the request has been processed and the /// response has been updated</returns> public async Task Process(IHttpResourceRequest request, IHttpResourceResponse response, IEnumerable <string> subPath) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (response == null) { throw new ArgumentNullException(nameof(response)); } var topicSegments = subPath.ToList(); if (!topicSegments.Any() && request.IsGet()) { await GetTopics(response); return; } if (topicSegments.Any()) { var topic = topicSegments.First(); var nestedResource = topicSegments.Skip(1).FirstOrDefault(); if ("subscriber".Equals(nestedResource, StringComparison.OrdinalIgnoreCase)) { await PostOrDeleteSubscriber(request, response, topic); return; } } response.StatusCode = 400; }
public async Task Process(IHttpResourceRequest request, IHttpResourceResponse response, IEnumerable<string> subPath) { if (request == null) throw new ArgumentNullException("request"); if (response == null) throw new ArgumentNullException("response"); var topicSegments = subPath.ToList(); if (!topicSegments.Any() && "get".Equals(request.HttpMethod, StringComparison.OrdinalIgnoreCase)) { await GetTopics(request, response); return; } if (topicSegments.Any()) { var topic = topicSegments.First(); var nestedResource = topicSegments.Skip(1).FirstOrDefault(); if ("subscriber".Equals(nestedResource, StringComparison.OrdinalIgnoreCase)) { var subscriberSubPath = topicSegments.Skip(2); await PostOrDeleteSubscriber(request, response, topic, subscriberSubPath); return; } } response.StatusCode = 400; }
/// <inheritdoc /> public async Task Process(IHttpResourceRequest request, IHttpResourceResponse response, IEnumerable <string> subPath) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (response == null) { throw new ArgumentNullException(nameof(response)); } if (!request.IsGet()) { response.StatusCode = (int)HttpStatusCode.MethodNotAllowed; response.AddHeader("Allow", "GET"); return; } var metricsSegments = subPath.ToList(); if (!metricsSegments.Any() && request.IsGet()) { await GetMetrics(response); return; } response.StatusCode = 400; }
private static MessageJournalFilter ConfigureFilter(IHttpResourceRequest request, ICollection <ErrorModel> errors) { var filter = new MessageJournalFilter(); var topic = request.QueryString["topic"]; if (!string.IsNullOrWhiteSpace(topic)) { filter.Topics = topic.Split(',') .Select(t => (TopicName)t) .ToList(); } var category = request.QueryString["category"]; if (!string.IsNullOrWhiteSpace(category)) { filter.Categories = category.Split(',') .Select(t => (MessageJournalCategory)t.Trim()) .ToList(); } filter.From = GetDateTime("from", request, errors); filter.To = GetDateTime("to", request, errors); filter.Origination = GetUri("origination", request, errors); filter.Destination = GetUri("destination", request, errors); filter.MessageName = request.QueryString["messageName"]; filter.RelatedTo = GetMessageId("relatedTo", request, errors); return(filter); }
/// <inheritdoc /> /// <summary> /// Routes a <paramref name="request" /> and <paramref name="response" /> to /// the appropriate controller /// </summary> /// <param name="request">The request to route</param> /// <param name="response">The response to route</param> /// <returns> /// Returns a task that completes once the request has been routed and handled /// </returns> public async Task Route(IHttpResourceRequest request, IHttpResourceResponse response) { var requestPath = request.Url.AbsolutePath; if (string.IsNullOrWhiteSpace(requestPath)) { return; } var pathSegments = requestPath.Split('/'); var supportedResourceTypes = ResourceTypes.ToList(); var resourceSegments = pathSegments .SkipWhile(segment => !supportedResourceTypes.Contains(segment)) .ToList(); var resourceType = resourceSegments.FirstOrDefault(); if (string.IsNullOrWhiteSpace(resourceType)) { response.StatusCode = 400; return; } var controller = GetController(resourceType); if (string.IsNullOrWhiteSpace(resourceType)) { response.StatusCode = 400; return; } var subPath = resourceSegments.Skip(1); // Skip resource type await controller.Process(request, response, subPath); }
private async Task PostOrDeleteSubscriber(IHttpResourceRequest request, IHttpResourceResponse response, TopicName topic) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (response == null) { throw new ArgumentNullException(nameof(response)); } var uri = request.QueryString["uri"]; if (uri == null) { response.StatusCode = 400; response.StatusDescription = "Subscriber URI is required"; return; } var subscriber = new Uri(uri); if (!_topics.Contains(topic)) { response.StatusCode = 404; response.StatusDescription = "Topic not found"; return; } var authorized = _authorizationService == null || await _authorizationService.IsAuthorizedToSubscribe(request.Principal, topic); if (!authorized) { response.StatusCode = 401; response.StatusDescription = "Unauthorized"; return; } if ("delete".Equals(request.HttpMethod, StringComparison.OrdinalIgnoreCase)) { // TODO: Verify that the subscriber is the one deleting the subscription // Ideas: Issue another HTTP request back to the subscriber to verify? // Require the subscriber to specify a subscription ID (GUID?) // when subscribing and then supply the same ID when unsubscribing? await _subscriptionTrackingService.RemoveSubscription(topic, subscriber); } else { var ttlStr = request.QueryString["ttl"]; var ttl = string.IsNullOrWhiteSpace(ttlStr) ? default(TimeSpan) : TimeSpan.FromSeconds(int.Parse(ttlStr)); await _subscriptionTrackingService.AddSubscription(topic, subscriber, ttl); } response.StatusCode = 200; }
protected void GivenRequest(string method, NameValueCollection parameters = null) { var mockRequest = new Mock <IHttpResourceRequest>(); mockRequest.Setup(x => x.HttpMethod).Returns(method); mockRequest.Setup(x => x.QueryString).Returns(parameters ?? new NameValueCollection()); Request = mockRequest.Object; }
public HttpExceptionHandler(IHttpResourceRequest request, IHttpResourceResponse response, ILog log = null) { if (request == null) throw new ArgumentNullException("request"); if (response == null) throw new ArgumentNullException("response"); _request = request; _response = response; _log = log ?? LogManager.GetLogger(LoggingCategories.Http); }
public async Task GetTopics(IHttpResourceRequest request, IHttpResourceResponse response) { var topicList = _topics.Select(t => t.ToString()).ToArray(); var responseContent = _serializer.Serialize(topicList); var encoding = response.ContentEncoding; var encodedContent = encoding.GetBytes(responseContent); await response.OutputStream.WriteAsync(encodedContent, 0, encodedContent.Length); response.StatusCode = 200; }
public async Task Post(IHttpResourceRequest request, IHttpResourceResponse response) { if (request == null) throw new ArgumentNullException("request"); if (response == null) throw new ArgumentNullException("response"); var flattenedHeaders = request.Headers.AllKeys .ToDictionary(k => k, k => request.Headers[k]); var messageHeaders = new MessageHeaders(flattenedHeaders); var content = await request.ReadContentAsString(); var message = new Message(messageHeaders, content); await _accept(message, request.Principal); response.StatusCode = 202; }
public async Task Process(IHttpResourceRequest request, IHttpResourceResponse response, IEnumerable<string> subPath) { if (request == null) throw new ArgumentNullException("request"); if (response == null) throw new ArgumentNullException("response"); if (!request.IsPost()) { response.StatusCode = 405; response.AddHeader("Allow", "POST"); return; } await Post(request, response); }
/// <summary> /// Reads the entire content of an HTTP resource request as a string /// </summary> /// <param name="request">The request</param> /// <returns>Returns a task whose result is the content of the <paramref name="request"/> /// as a string</returns> public static async Task <string> ReadContentAsString(this IHttpResourceRequest request) { if (request == null) { return(null); } var contentStream = request.InputStream; var contentEncoding = request.ContentEncoding; using (var contentReader = new StreamReader(contentStream, contentEncoding)) { return(await contentReader.ReadToEndAsync()); } }
private async Task <MessageJournalPosition> GetStartPosition(IHttpResourceRequest request, ICollection <ErrorModel> errors) { var startStr = request.QueryString["start"]; try { return(string.IsNullOrWhiteSpace(startStr) ? await _messageJournal.GetBeginningOfJournal() : _messageJournal.ParsePosition(startStr)); } catch (Exception) { errors.Add(new ErrorModel("Invalid start position", "start")); return(null); } }
private async Task Post(IHttpResourceRequest request, IHttpResourceResponse response) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (response == null) { throw new ArgumentNullException(nameof(response)); } var authorized = _authorizationService == null || await _authorizationService.IsAuthorizedToSendMessages(request.Principal); if (!authorized) { response.StatusCode = 401; response.StatusDescription = "Unauthorized"; return; } // The authorization header should have been processed by the HTTP host prior to this // point in order to initialize the principal on the request context. This is // sensitive information and should not be copied into and thus exposed by the message // headers. var authorizationHeader = HttpRequestHeader.Authorization.ToString("G"); var sensitiveHeaders = new[] { authorizationHeader }; var flattenedHeaders = request.Headers.AllKeys .Except(sensitiveHeaders, StringComparer.OrdinalIgnoreCase) .ToDictionary(k => k, k => request.Headers[k]); var messageHeaders = new MessageHeaders(flattenedHeaders) { Received = DateTime.UtcNow }; var content = await request.ReadContentAsString(); var message = new Message(messageHeaders, content); Thread.CurrentPrincipal = request.Principal; await _accept(message, request.Principal, default(CancellationToken)); response.StatusCode = 202; }
private static MessageId?GetMessageId(string parameter, IHttpResourceRequest request, ICollection <ErrorModel> errors) { var value = request.QueryString[parameter]; if (!string.IsNullOrWhiteSpace(value)) { if (!Guid.TryParse(value, out Guid guid)) { errors.Add(new ErrorModel("Invalid message ID: " + value, parameter)); } else { return(new MessageId(guid)); } } return(null); }
private static Uri GetUri(string parameter, IHttpResourceRequest request, ICollection <ErrorModel> errors) { var value = request.QueryString[parameter]; if (!string.IsNullOrWhiteSpace(value)) { if (!Uri.TryCreate(value, UriKind.Absolute, out Uri uri)) { errors.Add(new ErrorModel("Invalid URI: " + value, parameter)); } else { return(uri); } } return(null); }
private static DateTime?GetDateTime(string parameter, IHttpResourceRequest request, ICollection <ErrorModel> errors) { var value = request.QueryString[parameter]; if (!string.IsNullOrWhiteSpace(value)) { if (!TryParseDate(value, out DateTime dateValue)) { errors.Add(new ErrorModel("Invalid date/time: " + value, parameter)); } else { return(dateValue); } } return(null); }
private static int GetCount(IHttpResourceRequest request, ICollection <ErrorModel> errors) { var countStr = request.QueryString["count"]; if (string.IsNullOrWhiteSpace(countStr)) { errors.Add(new ErrorModel("Count is required", "count")); return(0); } if (!int.TryParse(countStr, out int count) || count <= 0) { errors.Add(new ErrorModel("Count must be a positive integer value", "count")); return(0); } return(count); }
/// <inheritdoc /> public async Task Process(IHttpResourceRequest request, IHttpResourceResponse response, IEnumerable <string> subPath) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (response == null) { throw new ArgumentNullException(nameof(response)); } if (!request.IsPost()) { response.StatusCode = 405; response.AddHeader("Allow", "POST"); return; } await Post(request, response); }
/// <inheritdoc /> public async Task Process(IHttpResourceRequest request, IHttpResourceResponse response, IEnumerable <string> subPath) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (response == null) { throw new ArgumentNullException(nameof(response)); } if (!request.IsGet()) { response.StatusCode = (int)HttpStatusCode.MethodNotAllowed; response.AddHeader("Allow", "GET"); return; } await Get(request, response); }
public async Task PostOrDeleteSubscriber(IHttpResourceRequest request, IHttpResourceResponse response, TopicName topic, IEnumerable<string> subPath) { if (request == null) throw new ArgumentNullException("request"); if (response == null) throw new ArgumentNullException("response"); var uri = request.QueryString["uri"]; if (uri == null) { response.StatusCode = 400; response.StatusDescription = "Subscriber URI is required"; return; } var subscriber = new Uri(uri); if (!_topics.Contains(topic)) { response.StatusCode = 404; response.StatusDescription = "Topic not found"; return; } if ("delete".Equals(request.HttpMethod, StringComparison.OrdinalIgnoreCase)) { await _subscriptionTrackingService.RemoveSubscription(topic, subscriber); } else { var ttlStr = request.QueryString["ttl"]; var ttl = string.IsNullOrWhiteSpace(ttlStr) ? default(TimeSpan) : TimeSpan.FromSeconds(int.Parse(ttlStr)); await _subscriptionTrackingService.AddSubscription(topic, subscriber, ttl); } response.StatusCode = 202; }
/// <summary> /// Indicates whether a <paramref name="request"/> is a GET method request /// </summary> /// <param name="request">The request</param> /// <returns>Returns <c>true</c> if the request is a GET method request</returns> public static bool IsGet(this IHttpResourceRequest request) { return(request != null && "GET".Equals(request.HttpMethod, StringComparison.OrdinalIgnoreCase)); }
private async Task Get(IHttpResourceRequest request, IHttpResourceResponse response) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (response == null) { throw new ArgumentNullException(nameof(response)); } if (_messageJournal == null) { // Message journaling is not enabled response.StatusCode = (int)HttpStatusCode.NotImplemented; return; } var authorized = _authorizationService == null || await _authorizationService.IsAuthorizedToQueryJournal(request.Principal); if (!authorized) { response.StatusCode = (int)HttpStatusCode.Unauthorized; response.StatusDescription = "Unauthorized"; return; } var responseModel = new JournalGetResponseModel(); var start = await GetStartPosition(request, responseModel.Errors); var count = GetCount(request, responseModel.Errors); var filter = ConfigureFilter(request, responseModel.Errors); if (responseModel.Errors.Any()) { response.StatusCode = (int)HttpStatusCode.BadRequest; } else { var result = await _messageJournal.Read(start, count, filter); responseModel.Start = start.ToString(); responseModel.Next = result.Next.ToString(); responseModel.EndOfJournal = result.EndOfJournal; responseModel.Entries = result.Entries.Select(entry => new MessageJournalEntryModel { Position = entry.Position.ToString(), Category = entry.Category, Timestamp = entry.Timestamp, Data = new MessageJournalEntryDataModel { Headers = entry.Data.Headers.ToDictionary(h => (string)h.Key, h => h.Value), Content = entry.Data.Content } }).ToList(); response.StatusCode = (int)HttpStatusCode.OK; } response.ContentType = "application/json"; var encoding = response.ContentEncoding; if (encoding == null) { encoding = Encoding.UTF8; response.ContentEncoding = encoding; } var serializedContent = _serializer.Serialize(responseModel); var encodedContent = encoding.GetBytes(serializedContent); response.StatusCode = 200; await response.OutputStream.WriteAsync(encodedContent, 0, encodedContent.Length); }