internal async Task ReturnResponseMessageAsync() { // Check if the response is already returning because the TrySetResult below could happen a bit late // (as it happens on a different thread) by which point the CompleteResponseAsync could run and calls this // method again. if (!_returningResponse) { _returningResponse = true; try { await _responseFeature.FireOnSendingHeadersAsync(); } catch (Exception ex) { Abort(ex); return; } // Copy the feature collection so we're not multi-threading on the same collection. var newFeatures = new FeatureCollection(); foreach (var pair in _httpContext.Features) { newFeatures[pair.Key] = pair.Value; } var serverResponseFeature = _httpContext.Features.Get <IHttpResponseFeature>() !; // The client gets a deep copy of this so they can interact with the body stream independently of the server. var clientResponseFeature = new HttpResponseFeature() { StatusCode = serverResponseFeature.StatusCode, ReasonPhrase = serverResponseFeature.ReasonPhrase, Headers = serverResponseFeature.Headers, Body = _responseReaderStream }; newFeatures.Set <IHttpResponseFeature>(clientResponseFeature); newFeatures.Set <IHttpResponseBodyFeature>(new StreamResponseBodyFeature(_responseReaderStream)); _responseTcs.TrySetResult(new DefaultHttpContext(newFeatures)); } }
public async Task CompareRequestResponse() { var requestFeature = new HttpRequestFeature { Path = "/api/batch" }; requestFeature.Headers.Add(HeaderNames.ContentType, "multipart/mixed; boundary=\"batch_357647d1-a6b5-4e6a-aa73-edfc88d8866e\""); requestFeature.Body = TestUtilities.GetNormalizedContentStream("MultipartRequest.txt"); var responseFeature = new HttpResponseFeature(); var mockedEvents = new MockedBatchEventHandler(); using (responseFeature.Body = new MemoryStream()) { await AssertExecution(requestFeature, responseFeature, mockedEvents, CreateFirstResponse(), CreateSecondResponse(), CreateThirdResponse(), CreateFourthResponse()).ConfigureAwait(false); Assert.Equal(StatusCodes.Status200OK, responseFeature.StatusCode); var refText = await TestUtilities.GetNormalizedContentStream("MultipartResponse.txt") .ReadAsStringAsync().ConfigureAwait(false); responseFeature.Body.Position = 0; var outputText = await responseFeature.Body.ReadAsStringAsync().ConfigureAwait(false); var boundary = Regex.Match(outputText, "--(.+?)--").Groups[1].Value; refText = refText.Replace("61cfbe41-7ea6-4771-b1c5-b43564208ee5", boundary); // replace with current boundary; Assert.Equal(refText, outputText); Assert.Equal(1, mockedEvents.BatchEndCount); Assert.Equal(1, mockedEvents.BatchStartCount); Assert.Equal(4, mockedEvents.BatchRequestPreparationCount); Assert.Equal(4, mockedEvents.BatchRequestExecutingCount); Assert.Equal(4, mockedEvents.BatchRequestExecutedCount); } }
public async Task NoBoundary() { var requestFeature = new HttpRequestFeature { Path = "/api/batch" }; requestFeature.Headers.Add(HeaderNames.ContentType, "multipart/mixed"); requestFeature.Body = Stream.Null; var responseFeature = new HttpResponseFeature(); var mockedEvents = new MockedBatchEventHandler(); using (responseFeature.Body = new MemoryStream()) { await AssertExecution(requestFeature, responseFeature, mockedEvents, CreateFirstResponse(), CreateSecondResponse(), CreateThirdResponse(), CreateFourthResponse()).ConfigureAwait(false); Assert.Equal(StatusCodes.Status400BadRequest, responseFeature.StatusCode); Assert.Equal(0, mockedEvents.BatchStartCount); } }
private static void CloneHttpContext(HttpContext context, HttpConnectionContext connection) { // The reason we're copying the base features instead of the HttpContext properties is // so that we can get all of the logic built into DefaultHttpContext to extract higher level // structure from the low level properties var existingRequestFeature = context.Features.Get <IHttpRequestFeature>() !; var requestFeature = new HttpRequestFeature(); requestFeature.Protocol = existingRequestFeature.Protocol; requestFeature.Method = existingRequestFeature.Method; requestFeature.Scheme = existingRequestFeature.Scheme; requestFeature.Path = existingRequestFeature.Path; requestFeature.PathBase = existingRequestFeature.PathBase; requestFeature.QueryString = existingRequestFeature.QueryString; requestFeature.RawTarget = existingRequestFeature.RawTarget; var requestHeaders = new Dictionary <string, StringValues>(existingRequestFeature.Headers.Count, StringComparer.OrdinalIgnoreCase); foreach (var header in existingRequestFeature.Headers) { requestHeaders[header.Key] = header.Value; } requestFeature.Headers = new HeaderDictionary(requestHeaders); var existingConnectionFeature = context.Features.Get <IHttpConnectionFeature>(); var connectionFeature = new HttpConnectionFeature(); if (existingConnectionFeature != null) { connectionFeature.ConnectionId = existingConnectionFeature.ConnectionId; connectionFeature.LocalIpAddress = existingConnectionFeature.LocalIpAddress; connectionFeature.LocalPort = existingConnectionFeature.LocalPort; connectionFeature.RemoteIpAddress = existingConnectionFeature.RemoteIpAddress; connectionFeature.RemotePort = existingConnectionFeature.RemotePort; } // The response is a dud, you can't do anything with it anyways var responseFeature = new HttpResponseFeature(); var features = new FeatureCollection(); features.Set <IHttpRequestFeature>(requestFeature); features.Set <IHttpResponseFeature>(responseFeature); features.Set <IHttpResponseBodyFeature>(new StreamResponseBodyFeature(Stream.Null)); features.Set <IHttpConnectionFeature>(connectionFeature); // REVIEW: We could strategically look at adding other features but it might be better // if we expose a callback that would allow the user to preserve HttpContext properties. var newHttpContext = new DefaultHttpContext(features); newHttpContext.TraceIdentifier = context.TraceIdentifier; var endpointFeature = context.Features.Get <IEndpointFeature>(); newHttpContext.SetEndpoint(endpointFeature?.Endpoint); CloneUser(newHttpContext, context); connection.ServiceScope = context.RequestServices.CreateAsyncScope(); newHttpContext.RequestServices = connection.ServiceScope.Value.ServiceProvider; // REVIEW: This extends the lifetime of anything that got put into HttpContext.Items newHttpContext.Items = new Dictionary <object, object?>(context.Items); connection.HttpContext = newHttpContext; }
public override void OnStarting(Func <object, Task> callback, object state) { HttpResponseFeature.OnStarting(callback, state); }
public override void OnCompleted(Func <object, Task> callback, object state) { HttpResponseFeature.OnCompleted(callback, state); }
private static HttpContext CloneHttpContext(HttpContext context) { // The reason we're copying the base features instead of the HttpContext properties is // so that we can get all of the logic built into DefaultHttpContext to extract higher level // structure from the low level properties var existingRequestFeature = context.Features.Get <IHttpRequestFeature>(); var requestFeature = new HttpRequestFeature(); requestFeature.Protocol = existingRequestFeature.Protocol; requestFeature.Method = existingRequestFeature.Method; requestFeature.Scheme = existingRequestFeature.Scheme; requestFeature.Path = existingRequestFeature.Path; requestFeature.PathBase = existingRequestFeature.PathBase; requestFeature.QueryString = existingRequestFeature.QueryString; requestFeature.RawTarget = existingRequestFeature.RawTarget; var requestHeaders = new Dictionary <string, StringValues>(existingRequestFeature.Headers.Count, StringComparer.Ordinal); foreach (var header in existingRequestFeature.Headers) { requestHeaders[header.Key] = header.Value; } requestFeature.Headers = new HeaderDictionary(requestHeaders); var existingConnectionFeature = context.Features.Get <IHttpConnectionFeature>(); var connectionFeature = new HttpConnectionFeature(); if (existingConnectionFeature != null) { connectionFeature.ConnectionId = existingConnectionFeature.ConnectionId; connectionFeature.LocalIpAddress = existingConnectionFeature.LocalIpAddress; connectionFeature.LocalPort = existingConnectionFeature.LocalPort; connectionFeature.RemoteIpAddress = existingConnectionFeature.RemoteIpAddress; connectionFeature.RemotePort = existingConnectionFeature.RemotePort; } // The response is a dud, you can't do anything with it anyways var responseFeature = new HttpResponseFeature(); var features = new FeatureCollection(); features.Set <IHttpRequestFeature>(requestFeature); features.Set <IHttpResponseFeature>(responseFeature); features.Set <IHttpConnectionFeature>(connectionFeature); // REVIEW: We could strategically look at adding other features but it might be better // if we expose a callback that would allow the user to preserve HttpContext properties. var newHttpContext = new DefaultHttpContext(features); newHttpContext.TraceIdentifier = context.TraceIdentifier; CloneUser(newHttpContext, context); // Making request services function property could be tricky and expensive as it would require // DI scope per connection. It would also mean that services resolved in middleware leading up to here // wouldn't be the same instance (but maybe that's fine). For now, we just return an empty service provider newHttpContext.RequestServices = EmptyServiceProvider.Instance; // REVIEW: This extends the lifetime of anything that got put into HttpContext.Items newHttpContext.Items = new Dictionary <object, object>(context.Items); return(newHttpContext); }
private static FeatureCollection BuildFeatureCollection(HttpRequestFeature requestFeature, HttpResponseFeature responseFeature) { var features = new FeatureCollection(); features.Set <IHttpRequestFeature>(requestFeature); features.Set <IHttpResponseFeature>(responseFeature); return(features); }
public void TestNegotiateHandlerWithMultipleEndpointsAndCustomRouter() { var config = new ConfigurationBuilder().Build(); var router = new TestCustomRouter(); var serviceProvider = new ServiceCollection().AddSignalR() .AddAzureSignalR( o => o.Endpoints = new ServiceEndpoint[] { new ServiceEndpoint(ConnectionString2), new ServiceEndpoint(ConnectionString3, name: "chosen"), new ServiceEndpoint(ConnectionString4), }) .Services .AddLogging() .AddSingleton <IEndpointRouter>(router) .AddSingleton <IConfiguration>(config) .BuildServiceProvider(); var requestFeature = new HttpRequestFeature { Path = "/user/path/negotiate/", QueryString = "?endpoint=chosen" }; var features = new FeatureCollection(); features.Set <IHttpRequestFeature>(requestFeature); var httpContext = new DefaultHttpContext(features); var handler = serviceProvider.GetRequiredService <NegotiateHandler>(); var negotiateResponse = handler.Process(httpContext, "chat"); Assert.NotNull(negotiateResponse); Assert.Equal($"http://localhost3/client/?hub=chat&asrs.op=%2Fuser%2Fpath&endpoint=chosen", negotiateResponse.Url); // With no query string should return 400 requestFeature = new HttpRequestFeature { Path = "/user/path/negotiate/", }; var responseFeature = new HttpResponseFeature(); features.Set <IHttpRequestFeature>(requestFeature); features.Set <IHttpResponseFeature>(responseFeature); httpContext = new DefaultHttpContext(features); handler = serviceProvider.GetRequiredService <NegotiateHandler>(); negotiateResponse = handler.Process(httpContext, "chat"); Assert.Null(negotiateResponse); Assert.Equal(400, responseFeature.StatusCode); // With no query string should return 400 requestFeature = new HttpRequestFeature { Path = "/user/path/negotiate/", QueryString = "?endpoint=notexists" }; responseFeature = new HttpResponseFeature(); features.Set <IHttpRequestFeature>(requestFeature); features.Set <IHttpResponseFeature>(responseFeature); httpContext = new DefaultHttpContext(features); handler = serviceProvider.GetRequiredService <NegotiateHandler>(); Assert.Throws <InvalidOperationException>(() => handler.Process(httpContext, "chat")); }
private static HttpContext CreateHttpContext(HttpContext originalContext) { // Clone the features so that a new set is used for each context. // The features themselves will be reused but not the collection. We // store the request container as a feature of the request and we don't want // the features added to one context/request to be visible on another. // // Note that just about everything inm the HttpContext and HttpRequest is // backed by one of these features. So reusing the features means the HttContext // and HttpRequests are the same without needing to copy properties. To make them // different, we need to avoid copying certain features to that the objects don't // share the same storage/ IFeatureCollection features = new FeatureCollection(); string pathBase = ""; foreach (KeyValuePair <Type, object> kvp in originalContext.Features) { // Don't include the OData features. They may already // be present. This will get re-created later. // // Also, clear out the items feature, which is used // to store a few object, the one that is an issue here is the Url // helper, which has an affinity to the context. If we leave it, // the context of the helper no longer matches the new context and // the resulting url helper doesn't have access to the OData feature // because it's looking in the wrong context. // // Because we need a different request and response, leave those features // out as well. if (kvp.Key == typeof(IHttpRequestFeature)) { pathBase = ((IHttpRequestFeature)kvp.Value).PathBase; } if (kvp.Key == typeof(IODataBatchFeature) || kvp.Key == typeof(IODataFeature) || kvp.Key == typeof(IItemsFeature) || kvp.Key == typeof(IHttpRequestFeature) || kvp.Key == typeof(IHttpResponseFeature)) { continue; } features[kvp.Key] = kvp.Value; } // Add in an items, request and response feature. features[typeof(IItemsFeature)] = new ItemsFeature(); features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { PathBase = pathBase }; features[typeof(IHttpResponseFeature)] = new HttpResponseFeature(); // Create a context from the factory or use the default context. HttpContext context = null; IHttpContextFactory httpContextFactory = originalContext.RequestServices.GetRequiredService <IHttpContextFactory>(); if (httpContextFactory != null) { context = httpContextFactory.Create(features); } else { context = new DefaultHttpContext(features); } // Clone parts of the request. All other parts of the request will be // populated during batch processing. context.Request.Cookies = originalContext.Request.Cookies; foreach (KeyValuePair <string, StringValues> header in originalContext.Request.Headers) { context.Request.Headers.Add(header); } // Create a response body as the default response feature does not // have a valid stream. context.Response.Body = new MemoryStream(); return(context); }
public override void OnSendingHeaders(Action <object> callback, object state) { HttpResponseFeature.OnSendingHeaders(callback, state); }
private static async Task WritePartToResponse(Kooboo.IndexedDB.FilePart part, HttpResponseFeature Res) { long offset = part.BlockPosition + part.RelativePosition; long totalToSend = part.Length; Res.Headers["Content-Length"] = totalToSend.ToString(); var stream = Kooboo.IndexedDB.StreamManager.OpenReadStream(part.FullFileName); stream.Position = offset; try { await stream.ChunkCopyAsync(Res.Body, totalToSend); } catch (IndexOutOfRangeException) { } finally { await Res.Body.FlushAsync(); } }
public async Task <AckResult> HandleMessageTask(IHandledMessage message) { if (_cancellationToken.IsCancellationRequested) { return(AckResult.NackRequeue); } try { var bytes = message.Body; //note: This may not be the correct way to implement the FeatureCollection. In most of the available samples, //features are static and the HttpContext is customized so it can be manipulated directly within this method. //For the time being, this approach is very easy and provides better decoupling. When things get more complicated //we may revert to the other approach. //Typical webserver is a lot more complicated because it needs to handle the http protocol.HttpProtocol.ProcessRequests //and can handle multiple requests via the same connection. MsgNThen is entirely reliant on RabbitMQ client to handle all //such issues. //The Protocol can't directly access the HttpContext itself (which is really strange at first glance). Instead it implements //"IFeature"s that the HttpContext uses to implement its properties. var requestFeatures = new FeatureCollection(); var messageRequestFeature = new HttpRequestFeature { Path = $"/{message.RoutingKey}", //fix: use MemoryStream.WriteAsync(ReadOnlyMemory<Byte>) Body = new MemoryStream(bytes.ToArray()), Method = "GET", Headers = new HeaderDictionary(), }; if (message.Properties.Headers != null) { foreach (var property in message.Properties.Headers) { messageRequestFeature.Headers[property.Key] = property.Value?.ToString(); } } var groupInfo = GetMessageGroupInfo(message); messageRequestFeature.Headers[HeaderConstants.MessageId] = message.Properties.MessageId; messageRequestFeature.Headers[HeaderConstants.AppId] = message.Properties.AppId; messageRequestFeature.Headers[HeaderConstants.ReplyTo] = message.Properties.ReplyTo; messageRequestFeature.Headers[HeaderConstants.CorrelationId] = message.Properties.CorrelationId; //note: https://www.rabbitmq.com/validated-user-id.html messageRequestFeature.Headers[HeaderConstants.UserId] = message.Properties.UserId; messageRequestFeature.Headers.ContentLength = message.Body.Length; messageRequestFeature.Headers[HeaderNames.ContentType] = message.Properties.ContentType; var responseStream = new MemoryStream(); var responseBody = new StreamResponseBodyFeature(responseStream); var httpResponseFeature = new HttpResponseFeature() { Body = responseStream }; requestFeatures.Set <IHttpResponseBodyFeature>(responseBody); requestFeatures.Set <IHttpRequestFeature>(messageRequestFeature); requestFeatures.Set <IHttpResponseFeature>(httpResponseFeature); var context = _application.CreateContext(requestFeatures); await _application.ProcessRequestAsync(context); if (groupInfo != null) { _messageGroupHandler.MessageHandled(groupInfo.Value.messageGroupId, groupInfo.Value.messageId); } if (responseStream.Length > 0) { if (groupInfo != null) { //when the application populated the response we could either // - store this information so that the andThen processor can pick it up later or // - stream this request as data to the ReplyTo address for the same reason. //use Redis and S3 as result stores so that the final andThen can use the data collected from //those requests. } else { //since this isn't an andThen request, we should send this to the ReplyTo address //todo: XXXX implement result forwarding (using something like IAndThenMessageDeliverer) } //any/all of the above scenarios are handled by the following interface by using the //replyHandler to interpret the response and the message.Properties.ReplyTo address. } return(AckResult.Ack); } catch (Exception e) { _logger.LogError(e, "HandleMessageTask Failed"); return(AckResult.NackQuit); } }
private static async Task WritePartToResponse(Kooboo.IndexedDB.FilePart part, HttpResponseFeature Res) { long offset = part.BlockPosition + part.RelativePosition; byte[] buffer = new byte[8096]; long totalToSend = part.Length; int count = 0; Res.Headers["Content-Length"] = totalToSend.ToString(); long bytesRemaining = totalToSend; var stream = Kooboo.IndexedDB.StreamManager.OpenReadStream(part.FullFileName); stream.Position = offset; while (bytesRemaining > 0) { try { if (bytesRemaining <= buffer.Length) { count = await stream.ReadAsync(buffer, 0, (int)bytesRemaining); } else { count = await stream.ReadAsync(buffer, 0, buffer.Length); } if (count == 0) { return; } await Res.Body.WriteAsync(buffer, 0, count); bytesRemaining -= count; } catch (IndexOutOfRangeException) { await Res.Body.FlushAsync(); return; } finally { await Res.Body.FlushAsync(); } } }
/// <summary> /// Invokes batch request. /// </summary> /// <param name="httpContext">Source batch request context.</param> /// <param name="requests">Array of requests.</param> private async Task InvokeAsyncBatchAsync(HttpContext httpContext, JArray requests) { JArray responses = new JArray(); int i = 1; foreach (JObject requestObj in requests) { var contextFeatures = new FeatureCollection(httpContext.Features); StringBuilder requestStringBuilder = new StringBuilder(); await requestObj.WriteToAsync(new JsonTextWriter(new StringWriter(requestStringBuilder))); var requestFeature = new HttpRequestFeature() { Body = new MemoryStream(Encoding.UTF8.GetBytes(requestStringBuilder.ToString())), Headers = httpContext.Request.Headers, Method = httpContext.Request.Method, Protocol = httpContext.Request.Protocol, Scheme = httpContext.Request.Scheme, QueryString = httpContext.Request.QueryString.Value }; contextFeatures.Set <IHttpRequestFeature>(requestFeature); var responseMemoryStream = new MemoryStream(); var responseFeature = new HttpResponseFeature() { Body = responseMemoryStream }; contextFeatures.Set <IHttpResponseFeature>(responseFeature); contextFeatures.Set <IHttpRequestLifetimeFeature>(new HttpRequestLifetimeFeature()); var context = this.httpContextFactory.Create(contextFeatures); try { await this.next.Invoke(context).ConfigureAwait(false); } catch (Exception ex) { await this.HandleRpcInvokeExceptionAsync(context, ex); } responseMemoryStream.Position = 0; var response = (responseMemoryStream.Length == 0) ? CreateError(RPCErrorCode.RPC_METHOD_NOT_FOUND, "Method not found") : await JObject.LoadAsync(new JsonTextReader(new StreamReader(responseMemoryStream))); if (requestObj.ContainsKey("id")) { response["id"] = requestObj["id"]; } else { response.Remove("id"); } responses.Add(response); i++; } httpContext.Response.ContentType = "application/json; charset=utf-8"; // Update the response with the array of responses. using (StreamWriter streamWriter = new StreamWriter(httpContext.Response.Body)) using (JsonTextWriter textWriter = new JsonTextWriter(streamWriter)) { await responses.WriteToAsync(textWriter); } }
private static HttpContext CreateHttpContext(HttpContext originalContext) { // Clone the features so that a new set is used for each context. // The features themselves will be reused but not the collection. We // store the request container as a feature of the request and we don't want // the features added to one context/request to be visible on another. // // Note that just about everything in the HttpContext and HttpRequest is // backed by one of these features. So reusing the features means the HttContext // and HttpRequests are the same without needing to copy properties. To make them // different, we need to avoid copying certain features to that the objects don't // share the same storage/ IFeatureCollection features = new FeatureCollection(); string pathBase = ""; foreach (KeyValuePair <Type, object> kvp in originalContext.Features) { // Don't include the OData features. They may already // be present. This will get re-created later. // // Also, clear out the items feature, which is used // to store a few object, the one that is an issue here is the Url // helper, which has an affinity to the context. If we leave it, // the context of the helper no longer matches the new context and // the resulting url helper doesn't have access to the OData feature // because it's looking in the wrong context. // // Because we need a different request and response, leave those features // out as well. if (kvp.Key == typeof(IHttpRequestFeature)) { pathBase = ((IHttpRequestFeature)kvp.Value).PathBase; } if (kvp.Key == typeof(IODataBatchFeature) || kvp.Key == typeof(IODataFeature) || kvp.Key == typeof(IItemsFeature) || kvp.Key == typeof(IHttpRequestFeature) || kvp.Key == typeof(IHttpResponseFeature) || kvp.Key == typeof(IQueryFeature)) // Noted: we should not pass the QueryFeature from Main request to the sub request { continue; } if (kvp.Key == typeof(IEndpointFeature)) { continue; } features[kvp.Key] = kvp.Value; } // Add in an items, request and response feature. features[typeof(IItemsFeature)] = new ItemsFeature(); features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { PathBase = pathBase }; features[typeof(IHttpResponseFeature)] = new HttpResponseFeature(); // Create a context from the factory or use the default context. HttpContext context = null; IHttpContextFactory httpContextFactory = originalContext.RequestServices.GetService <IHttpContextFactory>(); if (httpContextFactory != null) { context = httpContextFactory.Create(features); } else { context = new DefaultHttpContext(features); } // Clone parts of the request. All other parts of the request will be // populated during batch processing. context.Request.Cookies = originalContext.Request.Cookies; foreach (KeyValuePair <string, StringValues> header in originalContext.Request.Headers) { string headerKey = header.Key.ToLowerInvariant(); // do not copy over headers that should not be inherited from batch to individual requests if (!nonInheritableHeaders.Contains(headerKey)) { // some preferences may be inherited, others discarded if (headerKey == "prefer") { string preferencesToInherit = GetPreferencesToInheritFromBatch(header.Value); if (!string.IsNullOrEmpty(preferencesToInherit)) { context.Request.Headers.Add(header.Key, preferencesToInherit); } } // do not copy already existing headers, such as Cookie else if (!context.Request.Headers.ContainsKey(header.Key)) { context.Request.Headers.Add(header); } } } // Create a response body as the default response feature does not // have a valid stream. // Use a special batch stream that remains open after the writer is disposed. context.Response.Body = new ODataBatchStream(); return(context); }
/// <summary> /// Helper method to get the odata path for an arbitrary odata uri. /// </summary> /// <param name="request">The request instance in current context</param> /// <param name="uri">OData uri</param> /// <returns>The parsed odata path</returns> #if NETCORE public static ODataPath CreateODataPath(this HttpRequest request, Uri uri) { if (uri == null) { throw new ArgumentNullException("uri"); } // Clone the features so that a new set is used for each context. // The features themselves will be reused but not the collection. We // store the request container as a feature of the request and we don't want // the features added to one context/request to be visible on another. // // Note that just about everything in the HttpContext and HttpRequest is // backed by one of these features. So reusing the features means the HttContext // and HttpRequests are the same without needing to copy properties. To make them // different, we need to avoid copying certain features to that the objects don't // share the same storage/ IFeatureCollection features = new FeatureCollection(); foreach (KeyValuePair <Type, object> kvp in request.HttpContext.Features) { // Don't include the OData features. They may already // be present. This will get re-created later. // // Also, clear out the items feature, which is used // to store a few object, the one that is an issue here is the Url // helper, which has an affinity to the context. If we leave it, // the context of the helper no longer matches the new context and // the resulting url helper doesn't have access to the OData feature // because it's looking in the wrong context. // // Because we need a different request and response, leave those features // out as well. if (kvp.Key == typeof(IODataBatchFeature) || kvp.Key == typeof(IODataFeature) || kvp.Key == typeof(IItemsFeature) || kvp.Key == typeof(IHttpRequestFeature) || kvp.Key == typeof(IHttpResponseFeature)) { continue; } features[kvp.Key] = kvp.Value; } // Add in an items, request and response feature. features[typeof(IItemsFeature)] = new ItemsFeature(); features[typeof(IHttpRequestFeature)] = new HttpRequestFeature(); features[typeof(IHttpResponseFeature)] = new HttpResponseFeature(); // Create a context from the factory or use the default context. HttpContext context = new DefaultHttpContext(features); // Clone parts of the request. All other parts of the request will be // populated during batch processing. context.Request.Cookies = request.HttpContext.Request.Cookies; foreach (KeyValuePair <string, StringValues> header in request.HttpContext.Request.Headers) { context.Request.Headers.Add(header); } // Copy the Uri. context.Request.Scheme = uri.Scheme; context.Request.Host = uri.IsDefaultPort ? new HostString(uri.Host) : new HostString(uri.Host, uri.Port); context.Request.QueryString = new QueryString(uri.Query); context.Request.Path = new PathString(uri.AbsolutePath); // Get the existing OData route IRoutingFeature routingFeature = context.Features[typeof(IRoutingFeature)] as IRoutingFeature; ODataRoute route = routingFeature.RouteData.Routers.OfType <ODataRoute>().FirstOrDefault(); // Attempt to route the new request and extract the path. RouteContext routeContext = new RouteContext(context); route.RouteAsync(routeContext).Wait(); return(context.Request.ODataFeature().Path); }
/// <summary> /// Invokes single request. /// </summary> /// <param name="httpContext">Source batch request context.</param> /// <param name="requestObj">Single request object.</param> /// <returns>Single response objects.</returns> private async Task <JObject> InvokeSingleAsync(HttpContext httpContext, JObject requestObj) { var contextFeatures = new FeatureCollection(httpContext.Features); var requestFeature = new HttpRequestFeature() { Body = new MemoryStream(Encoding.UTF8.GetBytes(requestObj.ToString())), Headers = httpContext.Request.Headers, Method = httpContext.Request.Method, Protocol = httpContext.Request.Protocol, Scheme = httpContext.Request.Scheme, QueryString = httpContext.Request.QueryString.Value }; contextFeatures.Set <IHttpRequestFeature>(requestFeature); var responseMemoryStream = new MemoryStream(); var responseFeature = new HttpResponseFeature() { Body = responseMemoryStream }; contextFeatures.Set <IHttpResponseFeature>(responseFeature); contextFeatures.Set <IHttpRequestLifetimeFeature>(new HttpRequestLifetimeFeature()); var context = this.httpContextFactory.Create(contextFeatures); JObject response; try { await this.next.Invoke(context).ConfigureAwait(false); if (responseMemoryStream.Length == 0) { throw new Exception("Method not found"); } responseMemoryStream.Position = 0; using (var streamReader = new StreamReader(responseMemoryStream)) using (var textReader = new JsonTextReader(streamReader)) { // Ensure floats are parsed as decimals and not as doubles. textReader.FloatParseHandling = FloatParseHandling.Decimal; response = await JObject.LoadAsync(textReader); } } catch (Exception ex) { await this.HandleRpcInvokeExceptionAsync(context, ex); context.Response.Body.Position = 0; using (var streamReader = new StreamReader(context.Response.Body, Encoding.Default, true, 1024, true)) using (var textReader = new JsonTextReader(streamReader)) { // Ensure floats are parsed as decimals and not as doubles. textReader.FloatParseHandling = FloatParseHandling.Decimal; string val = streamReader.ReadToEnd(); context.Response.Body.Position = 0; response = await JObject.LoadAsync(textReader); } } if (requestObj.ContainsKey("id")) { response["id"] = requestObj["id"]; } else { response.Remove("id"); } return(response); }
private static ModuleHttpResponse BuildResponseMessage(MemoryStream responseStream, HttpResponseFeature responseFeature) { var response = new ModuleHttpResponse { StatusCode = responseFeature.StatusCode, ReasonPhrase = responseFeature.ReasonPhrase, Body = responseStream.ToArray(), Headers = new Dictionary <string, string[]>() }; foreach (var entry in responseFeature.Headers) { response.Headers.Add(entry.Key, entry.Value.ToArray()); } return(response); }