Ejemplo n.º 1
0
        // 处理protocol;
        private void handleProtocol(object obj)
        {
            string pro = obj as string;
            ProtocolHelper helper = new ProtocolHelper(pro);
            FileProtocol protocol = helper.GetProtocol();

            if (protocol.Mode == FileRequestMode.Send)
            {
                // 客户端发送文件,对服务端来说则是接收文件;
                receiveFile(protocol);
            }
            else if (protocol.Mode == FileRequestMode.Receive)
            {
                // 客户端接收文件,对服务端来说则是发送文件;
                // sendFile(protocol);
            }
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Implementation of the test itself.
        /// </summary>
        /// <returns>true if the test passes, false otherwise.</returns>
        public override async Task <bool> RunAsync()
        {
            IPAddress NodeIp      = (IPAddress)ArgumentValues["Node IP"];
            int       PrimaryPort = (int)ArgumentValues["primary Port"];

            log.Trace("(NodeIp:'{0}',PrimaryPort:{1})", NodeIp, PrimaryPort);

            bool res = false;

            Passed = false;

            ProtocolClient client = new ProtocolClient();

            try
            {
                MessageBuilder mb = client.MessageBuilder;

                // Step 1
                await client.ConnectAsync(NodeIp, PrimaryPort, false);

                byte[]  payload        = Encoding.UTF8.GetBytes("test");
                Message requestMessage = mb.CreatePingRequest(payload);

                byte[] messageData = ProtocolHelper.GetMessageBytes(requestMessage);
                byte[] part1       = new byte[6];
                byte[] part2       = new byte[messageData.Length - part1.Length];
                Array.Copy(messageData, 0, part1, 0, part1.Length);
                Array.Copy(messageData, part1.Length, part2, 0, part2.Length);
                await client.SendRawAsync(part1);


                log.Trace("Entering 500 seconds wait...");
                await Task.Delay(500 * 1000);

                log.Trace("Wait completed.");

                // We should be disconnected by now, so sending or receiving should throw.
                bool disconnectedOk = false;
                try
                {
                    await client.SendRawAsync(part2);

                    await client.ReceiveMessageAsync();
                }
                catch
                {
                    log.Trace("Expected exception occurred.");
                    disconnectedOk = true;
                }

                // Step 1 Acceptance
                Passed = disconnectedOk;

                res = true;
            }
            catch (Exception e)
            {
                log.Error("Exception occurred: {0}", e.ToString());
            }
            client.Dispose();

            log.Trace("(-):{0}", res);
            return(res);
        }
 protected override void OnInitializingHandler()
 {
     base.OnInitializingHandler();
     if (!base.ClientRequest.IsAuthenticated)
     {
         base.IsWsSecurityRequest = base.ClientRequest.IsAnyWsSecurityRequest();
         if (base.IsWsSecurityRequest && !AutodiscoverEwsWebConfiguration.WsSecurityEndpointEnabled)
         {
             throw new HttpException(404, "WS-Security endpoint is not supported");
         }
     }
     if (base.ClientRequest.Url.ToString().EndsWith("autodiscover.xml", StringComparison.OrdinalIgnoreCase) || ProtocolHelper.IsAutodiscoverV2Request(base.ClientRequest.Url.AbsolutePath))
     {
         base.PreferAnchorMailboxHeader = true;
     }
 }
Ejemplo n.º 4
0
        /// <summary>
        /// Proxies a normal (i.e. non-upgradable) request to the upstream server, and the response back to our client.
        /// </summary>
        /// <remarks>
        /// Normal proxying comprises the following steps:
        ///    (0) Disable ASP .NET Core limits for streaming requests
        ///    (1) Create outgoing HttpRequestMessage
        ///    (2) Setup copy of request body (background)             Downstream --► Proxy --► Upstream
        ///    (3) Copy request headers                                Downstream --► Proxy --► Upstream
        ///    (4) Send the outgoing request using HttpMessageInvoker  Downstream --► Proxy --► Upstream
        ///    (5) Copy response status line                           Downstream ◄-- Proxy ◄-- Upstream
        ///    (6) Copy response headers                               Downstream ◄-- Proxy ◄-- Upstream
        ///    (7) Copy response body                                  Downstream ◄-- Proxy ◄-- Upstream
        ///    (8) Copy response trailer headers and finish response   Downstream ◄-- Proxy ◄-- Upstream
        ///    (9) Wait for completion of step 2: copying request body Downstream --► Proxy --► Upstream
        ///
        /// ASP .NET Core (Kestrel) will finally send response trailers (if any)
        /// after we complete the steps above and relinquish control.
        /// </remarks>
        private async Task NormalProxyAsync(
            HttpContext context,
            HttpRequestMessage upstreamRequest,
            Transforms transforms,
            HttpMessageInvoker httpClient,
            ProxyTelemetryContext proxyTelemetryContext,
            CancellationToken shortCancellation,
            CancellationToken longCancellation)
        {
            _ = context ?? throw new ArgumentNullException(nameof(context));
            _ = upstreamRequest ?? throw new ArgumentNullException(nameof(upstreamRequest));
            _ = httpClient ?? throw new ArgumentNullException(nameof(httpClient));

            // :::::::::::::::::::::::::::::::::::::::::::::
            // :: Step 0: Disable ASP .NET Core limits for streaming requests
            var isIncomingHttp2 = ProtocolHelper.IsHttp2(context.Request.Protocol);

            // NOTE: We heuristically assume gRPC-looking requests may require streaming semantics.
            // See https://github.com/microsoft/reverse-proxy/issues/118 for design discussion.
            var isStreamingRequest = isIncomingHttp2 && ProtocolHelper.IsGrpcContentType(context.Request.ContentType);

            if (isStreamingRequest)
            {
                DisableMinRequestBodyDataRateAndMaxRequestBodySize(context);
            }

            // :::::::::::::::::::::::::::::::::::::::::::::
            // :: Step 2: Setup copy of request body (background) Downstream --► Proxy --► Upstream
            // Note that we must do this before step (3) because step (3) may also add headers to the HttpContent that we set up here.
            var bodyToUpstreamContent = SetupCopyBodyUpstream(context.Request.Body, upstreamRequest, in proxyTelemetryContext, isStreamingRequest, longCancellation);

            // :::::::::::::::::::::::::::::::::::::::::::::
            // :: Step 3: Copy request headers Downstream --► Proxy --► Upstream
            CopyHeadersToUpstream(context, upstreamRequest, transforms.CopyRequestHeaders, transforms.RequestHeaderTransforms);

            // :::::::::::::::::::::::::::::::::::::::::::::
            // :: Step 4: Send the outgoing request using HttpClient
            ////this.logger.LogInformation($"   Starting Proxy --> upstream request");
            var upstreamResponse = await httpClient.SendAsync(upstreamRequest, shortCancellation);

            // Detect connection downgrade, which may be problematic for e.g. gRPC.
            if (isIncomingHttp2 && upstreamResponse.Version.Major != 2)
            {
                // TODO: Do something on connection downgrade...
                Log.HttpDowngradeDeteced(_logger);
            }

            // Assert that, if we are proxying content upstream, it must have started by now
            // (since HttpClient.SendAsync has already completed asynchronously).
            // If this check fails, there is a coding defect which would otherwise
            // cause us to wait forever in step 9, so fail fast here.
            if (bodyToUpstreamContent != null && !bodyToUpstreamContent.Started)
            {
                // TODO: bodyToUpstreamContent is never null. HttpClient might would not need to read the body in some scenarios, such as an early auth failure with Expect: 100-continue.
                throw new InvalidOperationException("Proxying the downstream request body to the upstream server hasn't started. This is a coding defect.");
            }

            // :::::::::::::::::::::::::::::::::::::::::::::
            // :: Step 5: Copy response status line Downstream ◄-- Proxy ◄-- Upstream
            ////this.logger.LogInformation($"   Setting downstream <-- Proxy status: {(int)upstreamResponse.StatusCode} {upstreamResponse.ReasonPhrase}");
            context.Response.StatusCode = (int)upstreamResponse.StatusCode;
            context.Features.Get <IHttpResponseFeature>().ReasonPhrase = upstreamResponse.ReasonPhrase;

            // :::::::::::::::::::::::::::::::::::::::::::::
            // :: Step 6: Copy response headers Downstream ◄-- Proxy ◄-- Upstream
            CopyHeadersToDownstream(upstreamResponse, context, transforms.ResponseHeaderTransforms);

            // NOTE: it may *seem* wise to call `context.Response.StartAsync()` at this point
            // since it looks like we are ready to send back response headers
            // (and this might help reduce extra delays while we wait to receive the body from upstream).
            // HOWEVER, this would produce the wrong result if it turns out that there is no content
            // from the upstream -- instead of sending headers and terminating the stream at once,
            // we would send headers thinking a body may be coming, and there is none.
            // This is problematic on gRPC connections when the upstream server encounters an error,
            // in which case it immediately returns the response headers and trailing headers, but no content,
            // and clients misbehave if the initial headers response does not indicate stream end.

            // TODO: Some of the tasks in steps (7) - (9) may go unobserved depending on what fails first. Needs more consideration.

            // :::::::::::::::::::::::::::::::::::::::::::::
            // :: Step 7: Copy response body Downstream ◄-- Proxy ◄-- Upstream
            await CopyBodyDownstreamAsync(upstreamResponse.Content, context.Response.Body, proxyTelemetryContext, longCancellation);

            // :::::::::::::::::::::::::::::::::::::::::::::
            // :: Step 8: Copy response trailer headers and finish response Downstream ◄-- Proxy ◄-- Upstream
            CopyTrailingHeadersToDownstream(upstreamResponse, context, transforms.ResponseTrailerTransforms);

            if (isStreamingRequest)
            {
                // NOTE: We must call `CompleteAsync` so that Kestrel will flush all bytes to the client.
                // In the case where there was no response body,
                // this is also when headers and trailing headers are sent to the client.
                // Without this, the client might wait forever waiting for response bytes,
                // while we might wait forever waiting for request bytes,
                // leading to a stuck connection and no way to make progress.
                await context.Response.CompleteAsync();
            }

            // :::::::::::::::::::::::::::::::::::::::::::::
            // :: Step 9: Wait for completion of step 2: copying request body Downstream --► Proxy --► Upstream
            if (bodyToUpstreamContent != null)
            {
                ////this.logger.LogInformation($"   Waiting for downstream --> Proxy --> upstream body proxying to complete");
                await bodyToUpstreamContent.ConsumptionTask;
            }
        }
Ejemplo n.º 5
0
        /// <summary>
        /// Implementation of the test itself.
        /// </summary>
        /// <returns>true if the test passes, false otherwise.</returns>
        public override async Task <bool> RunAsync()
        {
            IPAddress ServerIp    = (IPAddress)ArgumentValues["Server IP"];
            int       PrimaryPort = (int)ArgumentValues["primary Port"];
            int       BasePort    = (int)ArgumentValues["Base Port"];
            int       LocPort     = (int)ArgumentValues["LOC Port"];

            log.Trace("(ServerIp:'{0}',PrimaryPort:{1},BasePort:{2},LocPort:{3})", ServerIp, PrimaryPort, BasePort, LocPort);

            bool res = false;

            Passed = false;

            ProtocolClient client        = new ProtocolClient();
            ProfileServer  profileServer = null;
            LocServer      locServer     = null;

            try
            {
                MessageBuilder mb = client.MessageBuilder;

                // Step 1
                log.Trace("Step 1");

                // Get port list.
                await client.ConnectAsync(ServerIp, PrimaryPort, false);

                Dictionary <ServerRoleType, uint> rolePorts = new Dictionary <ServerRoleType, uint>();
                bool listPortsOk = await client.ListServerPorts(rolePorts);

                client.CloseConnection();

                // Create identities.
                ProfilePublicKeys = new List <byte[]>();
                for (int i = 0; i < ProfileNames.Count; i++)
                {
                    ProtocolClient protocolClient = new ProtocolClient();
                    ProfilePublicKeys.Add(protocolClient.GetIdentityKeys().PublicKey);
                    protocolClient.Dispose();
                }


                // Start simulated profile server.
                profileServer = new ProfileServer("TestProfileServer", ServerIp, BasePort, client.GetIdentityKeys(), new GpsLocation(1, 2));
                bool profileServerStartOk = profileServer.Start();

                // Start simulated LOC server.
                locServer = new LocServer("TestLocServer", ServerIp, LocPort);
                bool locServerStartOk = locServer.Start();

                await locServer.WaitForProfileServerConnectionAsync();

                bool step1Ok = profileServerStartOk && locServerStartOk;
                log.Trace("Step 1: {0}", step1Ok ? "PASSED" : "FAILED");


                // Step 2
                log.Trace("Step 2");

                // Initialize the original set of update messages update.
                List <SharedProfileUpdateItem> originalUpdateItems = new List <SharedProfileUpdateItem>();
                for (int i = 0; i < ProfileNames.Count; i++)
                {
                    SharedProfileUpdateItem updateItem = new SharedProfileUpdateItem()
                    {
                        Add = new SharedProfileAddItem()
                        {
                            Version           = SemVer.V100.ToByteString(),
                            Name              = ProfileNames[i],
                            Type              = ProfileTypes[i],
                            ExtraData         = ProfileExtraData[i] != null ? ProfileExtraData[i] : "",
                            Latitude          = ProfileLocations[i].GetLocationTypeLatitude(),
                            Longitude         = ProfileLocations[i].GetLocationTypeLongitude(),
                            IdentityPublicKey = ProtocolHelper.ByteArrayToByteString(ProfilePublicKeys[i]),
                            SetThumbnailImage = ProfileImages[i] != null,
                            ThumbnailImage    = ProtocolHelper.ByteArrayToByteString(ProfileImages[i] != null ? File.ReadAllBytes(ProfileImages[i]) : new byte[0])
                        }
                    };

                    originalUpdateItems.Add(updateItem);
                }


                List <SharedProfileUpdateItem> updateItems = new List <SharedProfileUpdateItem>(originalUpdateItems);
                updateItems[2]             = new SharedProfileUpdateItem(updateItems[2]);
                updateItems[2].Add.Version = ProtocolHelper.ByteArrayToByteString(new byte[] { 1, 0 });

                bool initOk = await PerformInitializationProcessWithUpdateItemsAsync(updateItems, profileServer, locServer, "2.add.version");

                bool step2Ok = initOk;
                log.Trace("Step 2: {0}", step2Ok ? "PASSED" : "FAILED");



                // Step 3
                log.Trace("Step 3");

                updateItems                = new List <SharedProfileUpdateItem>(originalUpdateItems);
                updateItems[2]             = new SharedProfileUpdateItem(updateItems[2]);
                updateItems[2].Add.Version = ProtocolHelper.ByteArrayToByteString(new byte[] { 0, 0, 0 });

                initOk = await PerformInitializationProcessWithUpdateItemsAsync(updateItems, profileServer, locServer, "2.add.version");

                bool step3Ok = initOk;
                log.Trace("Step 3: {0}", step3Ok ? "PASSED" : "FAILED");


                // Step 4
                log.Trace("Step 4");

                updateItems    = new List <SharedProfileUpdateItem>(originalUpdateItems);
                updateItems[2] = new SharedProfileUpdateItem(updateItems[2]);
                updateItems[2].Add.IdentityPublicKey = ProtocolHelper.ByteArrayToByteString(Encoding.UTF8.GetBytes(new string ('a', 300)));

                initOk = await PerformInitializationProcessWithUpdateItemsAsync(updateItems, profileServer, locServer, "2.add.identityPublicKey");

                bool step4Ok = initOk;
                log.Trace("Step 4: {0}", step4Ok ? "PASSED" : "FAILED");


                // Step 5
                log.Trace("Step 5");

                updateItems    = new List <SharedProfileUpdateItem>(originalUpdateItems);
                updateItems[2] = new SharedProfileUpdateItem(updateItems[2]);
                updateItems[2].Add.IdentityPublicKey = updateItems[0].Add.IdentityPublicKey;

                initOk = await PerformInitializationProcessWithUpdateItemsAsync(updateItems, profileServer, locServer, "2.add.identityPublicKey");

                bool step5Ok = initOk;
                log.Trace("Step 5: {0}", step5Ok ? "PASSED" : "FAILED");


                // Step 6
                log.Trace("Step 6");

                updateItems             = new List <SharedProfileUpdateItem>(originalUpdateItems);
                updateItems[2]          = new SharedProfileUpdateItem(updateItems[2]);
                updateItems[2].Add.Name = new string('a', 70);

                initOk = await PerformInitializationProcessWithUpdateItemsAsync(updateItems, profileServer, locServer, "2.add.name");

                bool step6Ok = initOk;
                log.Trace("Step 6: {0}", step6Ok ? "PASSED" : "FAILED");


                // Step 7
                log.Trace("Step 7");

                updateItems             = new List <SharedProfileUpdateItem>(originalUpdateItems);
                updateItems[2]          = new SharedProfileUpdateItem(updateItems[2]);
                updateItems[2].Add.Name = new string('ɐ', 50);

                initOk = await PerformInitializationProcessWithUpdateItemsAsync(updateItems, profileServer, locServer, "2.add.name");

                bool step7Ok = initOk;
                log.Trace("Step 7: {0}", step7Ok ? "PASSED" : "FAILED");


                // Step 8
                log.Trace("Step 8");

                updateItems             = new List <SharedProfileUpdateItem>(originalUpdateItems);
                updateItems[2]          = new SharedProfileUpdateItem(updateItems[2]);
                updateItems[2].Add.Type = new string('a', 70);

                initOk = await PerformInitializationProcessWithUpdateItemsAsync(updateItems, profileServer, locServer, "2.add.type");

                bool step8Ok = initOk;
                log.Trace("Step 8: {0}", step8Ok ? "PASSED" : "FAILED");


                // Step 9
                log.Trace("Step 9");

                updateItems             = new List <SharedProfileUpdateItem>(originalUpdateItems);
                updateItems[2]          = new SharedProfileUpdateItem(updateItems[2]);
                updateItems[2].Add.Type = new string('ɐ', 50);

                initOk = await PerformInitializationProcessWithUpdateItemsAsync(updateItems, profileServer, locServer, "2.add.type");

                bool step9Ok = initOk;
                log.Trace("Step 9: {0}", step9Ok ? "PASSED" : "FAILED");


                // Step 10
                log.Trace("Step 10");

                updateItems             = new List <SharedProfileUpdateItem>(originalUpdateItems);
                updateItems[2]          = new SharedProfileUpdateItem(updateItems[2]);
                updateItems[2].Add.Type = "";

                initOk = await PerformInitializationProcessWithUpdateItemsAsync(updateItems, profileServer, locServer, "2.add.type");

                bool step10Ok = initOk;
                log.Trace("Step 10: {0}", step10Ok ? "PASSED" : "FAILED");


                // Step 11
                log.Trace("Step 11");

                updateItems    = new List <SharedProfileUpdateItem>(originalUpdateItems);
                updateItems[2] = new SharedProfileUpdateItem(updateItems[2]);
                updateItems[2].Add.SetThumbnailImage = true;
                updateItems[2].Add.ThumbnailImage    = ProtocolHelper.ByteArrayToByteString(new byte[0]);

                initOk = await PerformInitializationProcessWithUpdateItemsAsync(updateItems, profileServer, locServer, "2.add.thumbnailImage");

                bool step11Ok = initOk;
                log.Trace("Step 11: {0}", step11Ok ? "PASSED" : "FAILED");


                // Step 12
                log.Trace("Step 12");

                updateItems    = new List <SharedProfileUpdateItem>(originalUpdateItems);
                updateItems[2] = new SharedProfileUpdateItem(updateItems[2]);
                updateItems[2].Add.SetThumbnailImage = true;
                updateItems[2].Add.ThumbnailImage    = ProtocolHelper.ByteArrayToByteString(new byte[] { 0, 1, 2 });

                initOk = await PerformInitializationProcessWithUpdateItemsAsync(updateItems, profileServer, locServer, "2.add.thumbnailImage");

                bool step12Ok = initOk;
                log.Trace("Step 12: {0}", step12Ok ? "PASSED" : "FAILED");


                // Step 13
                log.Trace("Step 13");

                updateItems    = new List <SharedProfileUpdateItem>(originalUpdateItems);
                updateItems[2] = new SharedProfileUpdateItem(updateItems[2]);
                updateItems[2].Add.Latitude = 987654321;

                initOk = await PerformInitializationProcessWithUpdateItemsAsync(updateItems, profileServer, locServer, "2.add.latitude");

                bool step13Ok = initOk;
                log.Trace("Step 13: {0}", step13Ok ? "PASSED" : "FAILED");


                // Step 14
                log.Trace("Step 14");

                updateItems    = new List <SharedProfileUpdateItem>(originalUpdateItems);
                updateItems[2] = new SharedProfileUpdateItem(updateItems[2]);
                updateItems[2].Add.Longitude = 987654321;

                initOk = await PerformInitializationProcessWithUpdateItemsAsync(updateItems, profileServer, locServer, "2.add.longitude");

                bool step14Ok = initOk;
                log.Trace("Step 14: {0}", step14Ok ? "PASSED" : "FAILED");


                // Step 15
                log.Trace("Step 15");

                updateItems    = new List <SharedProfileUpdateItem>(originalUpdateItems);
                updateItems[2] = new SharedProfileUpdateItem(updateItems[2]);
                updateItems[2].Add.ExtraData = new string('a', 270);

                initOk = await PerformInitializationProcessWithUpdateItemsAsync(updateItems, profileServer, locServer, "2.add.extraData");

                bool step15Ok = initOk;
                log.Trace("Step 15: {0}", step15Ok ? "PASSED" : "FAILED");


                // Step 16
                log.Trace("Step 16");

                updateItems    = new List <SharedProfileUpdateItem>(originalUpdateItems);
                updateItems[2] = new SharedProfileUpdateItem(updateItems[2]);
                updateItems[2].Add.ExtraData = new string('ɐ', 150);

                initOk = await PerformInitializationProcessWithUpdateItemsAsync(updateItems, profileServer, locServer, "2.add.extraData");

                bool step16Ok = initOk;
                log.Trace("Step 16: {0}", step16Ok ? "PASSED" : "FAILED");


                // Step 17
                log.Trace("Step 17");

                updateItems    = new List <SharedProfileUpdateItem>(originalUpdateItems);
                updateItems[2] = new SharedProfileUpdateItem()
                {
                    Change = new SharedProfileChangeItem()
                    {
                        IdentityNetworkId = ProtocolHelper.ByteArrayToByteString(Crypto.Sha256(updateItems[0].Add.IdentityPublicKey.ToByteArray()))
                    }
                };

                initOk = await PerformInitializationProcessWithUpdateItemsAsync(updateItems, profileServer, locServer, "2.actionType");

                bool step17Ok = initOk;
                log.Trace("Step 17: {0}", step17Ok ? "PASSED" : "FAILED");


                // Step 18
                log.Trace("Step 18");

                updateItems    = new List <SharedProfileUpdateItem>(originalUpdateItems);
                updateItems[2] = new SharedProfileUpdateItem()
                {
                    Delete = new SharedProfileDeleteItem()
                    {
                        IdentityNetworkId = ProtocolHelper.ByteArrayToByteString(Crypto.Sha256(updateItems[0].Add.IdentityPublicKey.ToByteArray()))
                    }
                };

                initOk = await PerformInitializationProcessWithUpdateItemsAsync(updateItems, profileServer, locServer, "2.actionType");

                bool step18Ok = initOk;
                log.Trace("Step 18: {0}", step18Ok ? "PASSED" : "FAILED");


                // Step 19
                log.Trace("Step 19");

                updateItems             = new List <SharedProfileUpdateItem>(originalUpdateItems);
                updateItems[2]          = new SharedProfileUpdateItem(updateItems[2]);
                updateItems[2].Add.Name = "";

                initOk = await PerformInitializationProcessWithUpdateItemsAsync(updateItems, profileServer, locServer, "2.add.name");

                bool step19Ok = initOk;
                log.Trace("Step 19: {0}", step19Ok ? "PASSED" : "FAILED");



                Passed = step1Ok && step2Ok && step3Ok && step4Ok && step5Ok && step6Ok && step7Ok && step8Ok && step9Ok && step10Ok &&
                         step11Ok && step12Ok && step13Ok && step14Ok && step15Ok && step16Ok && step17Ok && step18Ok && step19Ok;

                res = true;
            }
            catch (Exception e)
            {
                log.Error("Exception occurred: {0}", e.ToString());
            }
            client.Dispose();

            if (profileServer != null)
            {
                profileServer.Shutdown();
            }
            if (locServer != null)
            {
                locServer.Shutdown();
            }

            log.Trace("(-):{0}", res);
            return(res);
        }
Ejemplo n.º 6
0
        /// <summary>
        /// Processes PingRequest message from client.
        /// <para>Simply copies the payload to a new ping response message.</para>
        /// </summary>
        /// <param name="Client">Client that sent the request.</param>
        /// <param name="RequestMessage">Full request message.</param>
        /// <returns>Response message to be sent to the client.</returns>
        public Message ProcessMessagePingRequest(IncomingClient Client, Message RequestMessage)
        {
            log.Trace("()");

            MessageBuilder messageBuilder = Client.MessageBuilder;
            PingRequest    pingRequest    = RequestMessage.Request.SingleRequest.Ping;

            Message res = messageBuilder.CreatePingResponse(RequestMessage, pingRequest.Payload.ToByteArray(), ProtocolHelper.GetUnixTimestampMs());

            log.Trace("(-):*.Response.Status={0}", res.Response.Status);
            return(res);
        }
Ejemplo n.º 7
0
    /// <summary>
    /// Proxies the incoming request to the destination server, and the response back to the client.
    /// </summary>
    /// <remarks>
    /// In what follows, as well as throughout in Reverse Proxy, we consider
    /// the following picture as illustrative of the Proxy.
    /// <code>
    ///      +-------------------+
    ///      |  Destination      +
    ///      +-------------------+
    ///            ▲       |
    ///        (b) |       | (c)
    ///            |       ▼
    ///      +-------------------+
    ///      |      Proxy        +
    ///      +-------------------+
    ///            ▲       |
    ///        (a) |       | (d)
    ///            |       ▼
    ///      +-------------------+
    ///      | Client            +
    ///      +-------------------+
    /// </code>
    ///
    /// (a) and (b) show the *request* path, going from the client to the target.
    /// (c) and (d) show the *response* path, going from the destination back to the client.
    ///
    /// Normal proxying comprises the following steps:
    ///    (0) Disable ASP .NET Core limits for streaming requests
    ///    (1) Create outgoing HttpRequestMessage
    ///    (2) Setup copy of request body (background)             Client --► Proxy --► Destination
    ///    (3) Copy request headers                                Client --► Proxy --► Destination
    ///    (4) Send the outgoing request using HttpMessageInvoker  Client --► Proxy --► Destination
    ///    (5) Copy response status line                           Client ◄-- Proxy ◄-- Destination
    ///    (6) Copy response headers                               Client ◄-- Proxy ◄-- Destination
    ///    (7-A) Check for a 101 upgrade response, this takes care of WebSockets as well as any other upgradeable protocol.
    ///        (7-A-1)  Upgrade client channel                     Client ◄--- Proxy ◄--- Destination
    ///        (7-A-2)  Copy duplex streams and return             Client ◄--► Proxy ◄--► Destination
    ///    (7-B) Copy (normal) response body                       Client ◄-- Proxy ◄-- Destination
    ///    (8) Copy response trailer headers and finish response   Client ◄-- Proxy ◄-- Destination
    ///    (9) Wait for completion of step 2: copying request body Client --► Proxy --► Destination
    ///
    /// ASP .NET Core (Kestrel) will finally send response trailers (if any)
    /// after we complete the steps above and relinquish control.
    /// </remarks>
    public async ValueTask <ForwarderError> SendAsync(
        HttpContext context,
        string destinationPrefix,
        HttpMessageInvoker httpClient,
        ForwarderRequestConfig requestConfig,
        HttpTransformer transformer)
    {
        _ = context ?? throw new ArgumentNullException(nameof(context));
        _ = destinationPrefix ?? throw new ArgumentNullException(nameof(destinationPrefix));
        _ = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
        _ = requestConfig ?? throw new ArgumentNullException(nameof(requestConfig));
        _ = transformer ?? throw new ArgumentNullException(nameof(transformer));

        // HttpClient overload for SendAsync changes response behavior to fully buffered which impacts performance
        // See discussion in https://github.com/microsoft/reverse-proxy/issues/458
        if (httpClient is HttpClient)
        {
            throw new ArgumentException($"The http client must be of type HttpMessageInvoker, not HttpClient", nameof(httpClient));
        }

        ForwarderTelemetry.Log.ForwarderStart(destinationPrefix);

        var activityCancellationSource = ActivityCancellationTokenSource.Rent(requestConfig?.ActivityTimeout ?? DefaultTimeout, context.RequestAborted);

        try
        {
            var isClientHttp2OrGreater = ProtocolHelper.IsHttp2OrGreater(context.Request.Protocol);

            // NOTE: We heuristically assume gRPC-looking requests may require streaming semantics.
            // See https://github.com/microsoft/reverse-proxy/issues/118 for design discussion.
            var isStreamingRequest = isClientHttp2OrGreater && ProtocolHelper.IsGrpcContentType(context.Request.ContentType);

            // :: Step 1-3: Create outgoing HttpRequestMessage
            var(destinationRequest, requestContent) = await CreateRequestMessageAsync(
                context, destinationPrefix, transformer, requestConfig, isStreamingRequest, activityCancellationSource);

            // :: Step 4: Send the outgoing request using HttpClient
            HttpResponseMessage destinationResponse;
            try
            {
                ForwarderTelemetry.Log.ForwarderStage(ForwarderStage.SendAsyncStart);
                destinationResponse = await httpClient.SendAsync(destinationRequest, activityCancellationSource.Token);

                ForwarderTelemetry.Log.ForwarderStage(ForwarderStage.SendAsyncStop);

                // Reset the timeout since we received the response headers.
                activityCancellationSource.ResetTimeout();
            }
            catch (Exception requestException)
            {
                return(await HandleRequestFailureAsync(context, requestContent, requestException, transformer, activityCancellationSource));
            }

            Log.ResponseReceived(_logger, destinationResponse);

            try
            {
                // :: Step 5: Copy response status line Client ◄-- Proxy ◄-- Destination
                // :: Step 6: Copy response headers Client ◄-- Proxy ◄-- Destination
                var copyBody = await CopyResponseStatusAndHeadersAsync(destinationResponse, context, transformer);

                if (!copyBody)
                {
                    // The transforms callback decided that the response body should be discarded.
                    destinationResponse.Dispose();

                    if (requestContent is not null && requestContent.InProgress)
                    {
                        activityCancellationSource.Cancel();
                        await requestContent.ConsumptionTask;
                    }

                    return(ForwarderError.None);
                }
            }
            catch (Exception ex)
            {
                destinationResponse.Dispose();

                if (requestContent is not null && requestContent.InProgress)
                {
                    activityCancellationSource.Cancel();
                    await requestContent.ConsumptionTask;
                }

                ReportProxyError(context, ForwarderError.ResponseHeaders, ex);
                // Clear the response since status code, reason and some headers might have already been copied and we want clean 502 response.
                context.Response.Clear();
                context.Response.StatusCode = StatusCodes.Status502BadGateway;
                return(ForwarderError.ResponseHeaders);
            }

            // :: Step 7-A: Check for a 101 upgrade response, this takes care of WebSockets as well as any other upgradeable protocol.
            if (destinationResponse.StatusCode == HttpStatusCode.SwitchingProtocols)
            {
                Debug.Assert(requestContent?.Started != true);
                return(await HandleUpgradedResponse(context, destinationResponse, activityCancellationSource));
            }

            // NOTE: it may *seem* wise to call `context.Response.StartAsync()` at this point
            // since it looks like we are ready to send back response headers
            // (and this might help reduce extra delays while we wait to receive the body from the destination).
            // HOWEVER, this would produce the wrong result if it turns out that there is no content
            // from the destination -- instead of sending headers and terminating the stream at once,
            // we would send headers thinking a body may be coming, and there is none.
            // This is problematic on gRPC connections when the destination server encounters an error,
            // in which case it immediately returns the response headers and trailing headers, but no content,
            // and clients misbehave if the initial headers response does not indicate stream end.

            // :: Step 7-B: Copy response body Client ◄-- Proxy ◄-- Destination
            var(responseBodyCopyResult, responseBodyException) = await CopyResponseBodyAsync(destinationResponse.Content, context.Response.Body, activityCancellationSource);

            if (responseBodyCopyResult != StreamCopyResult.Success)
            {
                return(await HandleResponseBodyErrorAsync(context, requestContent, responseBodyCopyResult, responseBodyException !, activityCancellationSource));
            }

            // :: Step 8: Copy response trailer headers and finish response Client ◄-- Proxy ◄-- Destination
            await CopyResponseTrailingHeadersAsync(destinationResponse, context, transformer);

            if (isStreamingRequest)
            {
                // NOTE: We must call `CompleteAsync` so that Kestrel will flush all bytes to the client.
                // In the case where there was no response body,
                // this is also when headers and trailing headers are sent to the client.
                // Without this, the client might wait forever waiting for response bytes,
                // while we might wait forever waiting for request bytes,
                // leading to a stuck connection and no way to make progress.
                await context.Response.CompleteAsync();
            }

            // :: Step 9: Wait for completion of step 2: copying request body Client --► Proxy --► Destination
            // NOTE: It is possible for the request body to NOT be copied even when there was an incoming requet body,
            // e.g. when the request includes header `Expect: 100-continue` and the destination produced a non-1xx response.
            // We must only wait for the request body to complete if it actually started,
            // otherwise we run the risk of waiting indefinitely for a task that will never complete.
            if (requestContent is not null && requestContent.Started)
            {
                var(requestBodyCopyResult, requestBodyException) = await requestContent.ConsumptionTask;

                if (requestBodyCopyResult != StreamCopyResult.Success)
                {
                    // The response succeeded. If there was a request body error then it was probably because the client or destination decided
                    // to cancel it. Report as low severity.

                    var error = requestBodyCopyResult switch
                    {
                        StreamCopyResult.InputError => ForwarderError.RequestBodyClient,
                        StreamCopyResult.OutputError => ForwarderError.RequestBodyDestination,
                        StreamCopyResult.Canceled => ForwarderError.RequestBodyCanceled,
                        _ => throw new NotImplementedException(requestBodyCopyResult.ToString())
                    };
                    ReportProxyError(context, error, requestBodyException !);
                    return(error);
                }
            }
        }
        finally
        {
            activityCancellationSource.Return();
            ForwarderTelemetry.Log.ForwarderStop(context.Response.StatusCode);
        }

        return(ForwarderError.None);
    }
Ejemplo n.º 8
0
    private StreamCopyHttpContent?SetupRequestBodyCopy(HttpRequest request, bool isStreamingRequest, ActivityCancellationTokenSource activityToken)
    {
        // If we generate an HttpContent without a Content-Length then for HTTP/1.1 HttpClient will add a Transfer-Encoding: chunked header
        // even if it's a GET request. Some servers reject requests containing a Transfer-Encoding header if they're not expecting a body.
        // Try to be as specific as possible about the client's intent to send a body. The one thing we don't want to do is to start
        // reading the body early because that has side-effects like 100-continue.
        var hasBody       = true;
        var contentLength = request.Headers.ContentLength;
        var method        = request.Method;

#if NET
        var canHaveBodyFeature = request.HttpContext.Features.Get <IHttpRequestBodyDetectionFeature>();
        if (canHaveBodyFeature is not null)
        {
            // 5.0 servers provide a definitive answer for us.
            hasBody = canHaveBodyFeature.CanHaveBody;
        }
        else
#endif
        // https://tools.ietf.org/html/rfc7230#section-3.3.3
        // All HTTP/1.1 requests should have Transfer-Encoding or Content-Length.
        // Http.Sys/IIS will even add a Transfer-Encoding header to HTTP/2 requests with bodies for back-compat.
        // HTTP/1.0 Connection: close bodies are only allowed on responses, not requests.
        // https://tools.ietf.org/html/rfc1945#section-7.2.2
        //
        // Transfer-Encoding overrides Content-Length per spec
        if (request.Headers.TryGetValue(HeaderNames.TransferEncoding, out var transferEncoding) &&
            transferEncoding.Count == 1 &&
            string.Equals("chunked", transferEncoding.ToString(), StringComparison.OrdinalIgnoreCase))
        {
            hasBody = true;
        }
        else if (contentLength.HasValue)
        {
            hasBody = contentLength > 0;
        }
        // Kestrel HTTP/2: There are no required headers that indicate if there is a request body so we need to sniff other fields.
        else if (!ProtocolHelper.IsHttp2OrGreater(request.Protocol))
        {
            hasBody = false;
        }
        // https://tools.ietf.org/html/rfc7231#section-4.3.1
        // A payload within a GET/HEAD/DELETE/CONNECT request message has no defined semantics; sending a payload body on a
        // GET/HEAD/DELETE/CONNECT request might cause some existing implementations to reject the request.
        // https://tools.ietf.org/html/rfc7231#section-4.3.8
        // A client MUST NOT send a message body in a TRACE request.
        else if (HttpMethods.IsGet(method) ||
                 HttpMethods.IsHead(method) ||
                 HttpMethods.IsDelete(method) ||
                 HttpMethods.IsConnect(method) ||
                 HttpMethods.IsTrace(method))
        {
            hasBody = false;
        }
        // else hasBody defaults to true

        if (hasBody)
        {
            if (isStreamingRequest)
            {
                DisableMinRequestBodyDataRateAndMaxRequestBodySize(request.HttpContext);
            }

            // Note on `autoFlushHttpClientOutgoingStream: isStreamingRequest`:
            // The.NET Core HttpClient stack keeps its own buffers on top of the underlying outgoing connection socket.
            // We flush those buffers down to the socket on every write when this is set,
            // but it does NOT result in calls to flush on the underlying socket.
            // This is necessary because we proxy http2 transparently,
            // and we are deliberately unaware of packet structure used e.g. in gRPC duplex channels.
            // Because the sockets aren't flushed, the perf impact of this choice is expected to be small.
            // Future: It may be wise to set this to true for *all* http2 incoming requests,
            // but for now, out of an abundance of caution, we only do it for requests that look like gRPC.
            return(new StreamCopyHttpContent(
                       request: request,
                       autoFlushHttpClientOutgoingStream: isStreamingRequest,
                       clock: _clock,
                       activityToken));
        }

        return(null);
    }