static async Task <HttpContextInfo.ResponseInfo> HandlePendingRequest(HttpContextInfo.RequestInfo req)
        {
            var reqMsg = new HttpRequestMessage();

            reqMsg.Method = PortForwardingExtensions.HttpMethodFromString(req.Method);
            var reqUrl = $"{_addressTo}{req.Path}";

            if (req.QueryParameters.Count > 0)
            {
                reqUrl += "?";
                foreach (var queryItem in req.QueryParameters)
                {
                    reqUrl += $"{queryItem.Key}={queryItem.Value.First()}&";
                }

                reqUrl = reqUrl.Remove(reqUrl.Length - 1);
            }

            reqMsg.RequestUri = new Uri(reqUrl);
            if (!string.IsNullOrWhiteSpace(req.BodyText))
            {
                var content = new StringContent(req.BodyText);
                foreach (var header in req.Headers)
                {
                    content.Headers.TryAddWithoutValidation(header.Key, header.Value);
                }
                content.Headers.ContentType.MediaType = req.ContentType;
                reqMsg.Content = content;
            }

            var respMsg = await _client.SendAsync(reqMsg);

            var respInfo = new HttpContextInfo.ResponseInfo();

            respInfo.ContentType   = respMsg.Content.Headers.ContentType?.MediaType;
            respInfo.ContentLength = respMsg.Content.Headers.ContentLength ?? 0;
            respInfo.Headers       = new Dictionary <string, IEnumerable <string> >();
            foreach (var header in respMsg.Headers)
            {
                respInfo.Headers[header.Key] = header.Value;
            }
            try
            {
                respInfo.BodyText = await respMsg.Content.ReadAsStringAsync();
            }
            catch
            {
                // ignored
            }

            respInfo.StatusCode = (int)respMsg.StatusCode;

            return(respInfo);
        }
        static async Task SendResponseOfPending(Guid id, HttpContextInfo.ResponseInfo resp)
        {
            var reqMsg = new HttpRequestMessage();

            reqMsg.Method     = HttpMethod.Post;
            reqMsg.RequestUri = new Uri(_setPortsEndpoint);
            var dto = new SetResponseDto()
            {
                Id   = id,
                Resp = resp
            };
            var reqStr = JsonConvert.SerializeObject(dto);

            reqMsg.Content = new StringContent(reqStr);
            reqMsg.Content.Headers.ContentType.MediaType = "application/json";
            await _client.SendAsync(reqMsg);
        }
        public static void UseCatchRequestsMiddleware(this IApplicationBuilder app)
        {
            app.Use(async(ctx, next) =>
            {
                try
                {
                    var contextInfo = await ctx.ResolveInfo();
                    var copy        = JsonConvert.DeserializeObject <HttpContextInfo>(JsonConvert.SerializeObject(contextInfo));
                    if (contextInfo.Request.Path.StartsWith("/portforwarding/getPendingRequests"))
                    {
                        await GetPendingRequests(ctx);
                        return;
                    }
                    if (contextInfo.Request.Path.StartsWith("/portforwarding/setResponse"))
                    {
                        await SetResponse(ctx);
                        return;
                    }

                    var reqId             = Guid.NewGuid();
                    PendingReqDict[reqId] = contextInfo.Request;

                    var hub = ctx.RequestServices.GetRequiredService <IHubContext <PendingRequestsHub> >();
                    await hub.Clients.All.SendAsync("PendingRequest", new RequestDto()
                    {
                        Id  = reqId,
                        Req = contextInfo.Request
                    });

                    HttpContextInfo.ResponseInfo respInfo = null;
                    var startWaitAt = DateTime.UtcNow;
                    while (respInfo == null && DateTime.UtcNow - startWaitAt < AppSettings.RequestExpireTime)
                    {
                        PendingRespDict.TryGetValue(reqId, out respInfo);
                        await Task.Delay(10);
                    }
                    PendingReqDict.Remove(reqId);
                    PendingRespDict.Remove(reqId);
                    if (respInfo == null)
                    {
                        ctx.Response.StatusCode = 501;
                        await ctx.Response.WriteAsync("Response from client timeout.");
                    }
                    else
                    {
                        ctx.Response.StatusCode = respInfo.StatusCode;
                        if (respInfo.Headers != null)
                        {
                            foreach (var pair in respInfo.Headers)
                            {
                                if (pair.Key == "Transfer-Encoding")
                                {
                                    continue;
                                }
                                if (pair.Key == "Location")
                                {
                                    ctx.Response.Redirect(pair.Value.First());
                                    continue;
                                }
                                var sv = new StringValues(pair.Value.ToArray());
                                ctx.Response.Headers[pair.Key] = sv;
                            }
                        }
                        if (respInfo.ContentType != null)
                        {
                            ctx.Response.ContentType = respInfo.ContentType;
                        }
                        if (respInfo.StatusCode != 0)
                        {
                            ctx.Response.StatusCode = respInfo.StatusCode;
                        }
                        if (respInfo.BodyText != null)
                        {
                            await ctx.Response.WriteAsync(respInfo.BodyText);
                        }
                    }
                }
                catch (Exception ex)
                {
                    ctx.Response.StatusCode = 500;
                    await ctx.Response.WriteAsync($"Port forwarding server internal error.\n{ex}");
                    await next();
                }
            });
        }