/// <summary> /// Creates, configures, sends, and returns a <see cref="HttpContext"/>. This completes as soon as the response is started. /// </summary> /// <returns></returns> public async Task <HttpContext> SendAsync(Action <HttpContext> configureContext, CancellationToken cancellationToken = default) { if (configureContext == null) { throw new ArgumentNullException(nameof(configureContext)); } var builder = new HttpContextBuilder(_application); builder.Configure(context => { var request = context.Request; request.Scheme = BaseAddress.Scheme; request.Host = HostString.FromUriComponent(BaseAddress); if (BaseAddress.IsDefaultPort) { request.Host = new HostString(request.Host.Host); } var pathBase = PathString.FromUriComponent(BaseAddress); if (pathBase.HasValue && pathBase.Value.EndsWith("/")) { pathBase = new PathString(pathBase.Value.Substring(0, pathBase.Value.Length - 1)); } request.PathBase = pathBase; }); builder.Configure(configureContext); return(await builder.SendAsync(cancellationToken).ConfigureAwait(false)); }
public async Task <WebSocket> ConnectAsync(Uri uri, CancellationToken cancellationToken) { WebSocketFeature webSocketFeature = null; var contextBuilder = new HttpContextBuilder(_application, AllowSynchronousIO, PreserveExecutionContext); contextBuilder.Configure(context => { var request = context.Request; var scheme = uri.Scheme; scheme = (scheme == "ws") ? "http" : scheme; scheme = (scheme == "wss") ? "https" : scheme; request.Scheme = scheme; if (!request.Host.HasValue) { request.Host = uri.IsDefaultPort ? new HostString(HostString.FromUriComponent(uri).Host) : HostString.FromUriComponent(uri); } request.Path = PathString.FromUriComponent(uri); request.PathBase = PathString.Empty; if (request.Path.StartsWithSegments(_pathBase, out var remainder)) { request.Path = remainder; request.PathBase = _pathBase; } request.QueryString = QueryString.FromUriComponent(uri); request.Headers.Add(HeaderNames.Connection, new string[] { "Upgrade" }); request.Headers.Add(HeaderNames.Upgrade, new string[] { "websocket" }); request.Headers.Add(HeaderNames.SecWebSocketVersion, new string[] { "13" }); request.Headers.Add(HeaderNames.SecWebSocketKey, new string[] { CreateRequestKey() }); if (SubProtocols.Any()) { request.Headers.Add(HeaderNames.SecWebSocketProtocol, SubProtocols.ToArray()); } request.Body = Stream.Null; // WebSocket webSocketFeature = new WebSocketFeature(context); context.Features.Set <IHttpWebSocketFeature>(webSocketFeature); ConfigureRequest?.Invoke(context.Request); }); var httpContext = await contextBuilder.SendAsync(cancellationToken); if (httpContext.Response.StatusCode != StatusCodes.Status101SwitchingProtocols) { throw new InvalidOperationException("Incomplete handshake, status code: " + httpContext.Response.StatusCode); } if (webSocketFeature.ClientWebSocket == null) { throw new InvalidOperationException("Incomplete handshake"); } return(webSocketFeature.ClientWebSocket); }
public async Task <WebSocket> ConnectAsync(Uri uri, CancellationToken cancellationToken) { WebSocketFeature webSocketFeature = null; var contextBuilder = new HttpContextBuilder(_application); contextBuilder.Configure(context => { var request = context.Request; var scheme = uri.Scheme; scheme = (scheme == "ws") ? "http" : scheme; scheme = (scheme == "wss") ? "https" : scheme; request.Scheme = scheme; request.Path = PathString.FromUriComponent(uri); request.PathBase = PathString.Empty; if (request.Path.StartsWithSegments(_pathBase, out var remainder)) { request.Path = remainder; request.PathBase = _pathBase; } request.QueryString = QueryString.FromUriComponent(uri); request.Headers.Add("Connection", new string[] { "Upgrade" }); request.Headers.Add("Upgrade", new string[] { "websocket" }); request.Headers.Add("Sec-WebSocket-Version", new string[] { "13" }); request.Headers.Add("Sec-WebSocket-Key", new string[] { CreateRequestKey() }); request.Body = Stream.Null; // WebSocket webSocketFeature = new WebSocketFeature(context); context.Features.Set <IHttpWebSocketFeature>(webSocketFeature); ConfigureRequest?.Invoke(context.Request); }); var httpContext = await contextBuilder.SendAsync(cancellationToken); if (httpContext.Response.StatusCode != StatusCodes.Status101SwitchingProtocols) { throw new InvalidOperationException("Incomplete handshake, status code: " + httpContext.Response.StatusCode); } if (webSocketFeature.ClientWebSocket == null) { throw new InvalidOperationException("Incomplete handshake"); } return(webSocketFeature.ClientWebSocket); }
/// <summary> /// This adapts HttpRequestMessages to ASP.NET Core requests, dispatches them through the pipeline, and returns the /// associated HttpResponseMessage. /// </summary> /// <param name="request"></param> /// <param name="cancellationToken"></param> /// <returns></returns> protected override async Task <HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { if (request == null) { throw new ArgumentNullException(nameof(request)); } var contextBuilder = new HttpContextBuilder(_application, AllowSynchronousIO, PreserveExecutionContext); Stream responseBody = null; var requestContent = request.Content ?? new StreamContent(Stream.Null); var body = await requestContent.ReadAsStreamAsync(); contextBuilder.Configure(context => { var req = context.Request; req.Protocol = "HTTP/" + request.Version.ToString(fieldCount: 2); req.Method = request.Method.ToString(); req.Scheme = request.RequestUri.Scheme; foreach (var header in request.Headers) { req.Headers.Append(header.Key, header.Value.ToArray()); } if (!req.Host.HasValue) { // If Host wasn't explicitly set as a header, let's infer it from the Uri req.Host = HostString.FromUriComponent(request.RequestUri); if (request.RequestUri.IsDefaultPort) { req.Host = new HostString(req.Host.Host); } } req.Path = PathString.FromUriComponent(request.RequestUri); req.PathBase = PathString.Empty; if (req.Path.StartsWithSegments(_pathBase, out var remainder)) { req.Path = remainder; req.PathBase = _pathBase; } req.QueryString = QueryString.FromUriComponent(request.RequestUri); if (requestContent != null) { foreach (var header in requestContent.Headers) { req.Headers.Append(header.Key, header.Value.ToArray()); } } if (body.CanSeek) { // This body may have been consumed before, rewind it. body.Seek(0, SeekOrigin.Begin); } req.Body = new AsyncStreamWrapper(body, () => contextBuilder.AllowSynchronousIO); responseBody = context.Response.Body; }); var httpContext = await contextBuilder.SendAsync(cancellationToken); var response = new HttpResponseMessage(); response.StatusCode = (HttpStatusCode)httpContext.Response.StatusCode; response.ReasonPhrase = httpContext.Features.Get <IHttpResponseFeature>().ReasonPhrase; response.RequestMessage = request; response.Content = new StreamContent(responseBody); foreach (var header in httpContext.Response.Headers) { if (!response.Headers.TryAddWithoutValidation(header.Key, (IEnumerable <string>)header.Value)) { bool success = response.Content.Headers.TryAddWithoutValidation(header.Key, (IEnumerable <string>)header.Value); Contract.Assert(success, "Bad header"); } } return(response); }
/// <summary> /// This adapts HttpRequestMessages to ASP.NET Core requests, dispatches them through the pipeline, and returns the /// associated HttpResponseMessage. /// </summary> /// <param name="request"></param> /// <param name="cancellationToken"></param> /// <returns></returns> protected override async Task <HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { if (request == null) { throw new ArgumentNullException(nameof(request)); } var contextBuilder = new HttpContextBuilder(_application, AllowSynchronousIO, PreserveExecutionContext); var requestContent = request.Content ?? new StreamContent(Stream.Null); // Read content from the request HttpContent into a pipe in a background task. This will allow the request // delegate to start before the request HttpContent is complete. A background task allows duplex streaming scenarios. contextBuilder.SendRequestStream(async writer => { if (requestContent is StreamContent) { // This is odd but required for backwards compat. If StreamContent is passed in then seek to beginning. // This is safe because StreamContent.ReadAsStreamAsync doesn't block. It will return the inner stream. var body = await requestContent.ReadAsStreamAsync(); if (body.CanSeek) { // This body may have been consumed before, rewind it. body.Seek(0, SeekOrigin.Begin); } await body.CopyToAsync(writer); } else { await requestContent.CopyToAsync(writer.AsStream()); } await writer.CompleteAsync(); }); contextBuilder.Configure((context, reader) => { var req = context.Request; if (request.Version == HttpVersion.Version20) { // https://tools.ietf.org/html/rfc7540 req.Protocol = "HTTP/2"; } else { req.Protocol = "HTTP/" + request.Version.ToString(fieldCount: 2); } req.Method = request.Method.ToString(); req.Scheme = request.RequestUri.Scheme; foreach (var header in request.Headers) { // User-Agent is a space delineated single line header but HttpRequestHeaders parses it as multiple elements. if (string.Equals(header.Key, HeaderNames.UserAgent, StringComparison.OrdinalIgnoreCase)) { req.Headers.Append(header.Key, string.Join(" ", header.Value)); } else { req.Headers.Append(header.Key, header.Value.ToArray()); } } if (!req.Host.HasValue) { // If Host wasn't explicitly set as a header, let's infer it from the Uri req.Host = HostString.FromUriComponent(request.RequestUri); if (request.RequestUri.IsDefaultPort) { req.Host = new HostString(req.Host.Host); } } req.Path = PathString.FromUriComponent(request.RequestUri); req.PathBase = PathString.Empty; if (req.Path.StartsWithSegments(_pathBase, out var remainder)) { req.Path = remainder; req.PathBase = _pathBase; } req.QueryString = QueryString.FromUriComponent(request.RequestUri); if (requestContent != null) { foreach (var header in requestContent.Headers) { req.Headers.Append(header.Key, header.Value.ToArray()); } } req.Body = new AsyncStreamWrapper(reader.AsStream(), () => contextBuilder.AllowSynchronousIO); }); var response = new HttpResponseMessage(); // Copy trailers to the response message when the response stream is complete contextBuilder.RegisterResponseReadCompleteCallback(context => { var responseTrailersFeature = context.Features.Get <IHttpResponseTrailersFeature>(); foreach (var trailer in responseTrailersFeature.Trailers) { bool success = response.TrailingHeaders.TryAddWithoutValidation(trailer.Key, (IEnumerable <string>)trailer.Value); Contract.Assert(success, "Bad trailer"); } }); var httpContext = await contextBuilder.SendAsync(cancellationToken); response.StatusCode = (HttpStatusCode)httpContext.Response.StatusCode; response.ReasonPhrase = httpContext.Features.Get <IHttpResponseFeature>().ReasonPhrase; response.RequestMessage = request; response.Version = request.Version; response.Content = new StreamContent(httpContext.Response.Body); foreach (var header in httpContext.Response.Headers) { if (!response.Headers.TryAddWithoutValidation(header.Key, (IEnumerable <string>)header.Value)) { bool success = response.Content.Headers.TryAddWithoutValidation(header.Key, (IEnumerable <string>)header.Value); Contract.Assert(success, "Bad header"); } } return(response); }