/// <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));
        }
Example #2
0
        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);
        }
Example #3
0
        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);
        }
Example #4
0
        /// <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);
        }
Example #5
0
        /// <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);
        }