question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Hang when using a controller to proxy form

See original GitHub issue

When proxying through a controller, there is a hang when handling a POST or PUT request with either a Content-Type: application/x-www-form-urlencoded or Content-Type: multipart/form-data header.

I’ve reproduced it starting with a simple Web API project, with the following changs:

  • Adding services.AddProxies() to ConfigureServices().
  • Removing app.UseHttpsRedirection() from Configure() so we can avoid certificate issues.
  • Adding app.RunProxy(proxy => proxy.UseHttp("http://localhost:5000/proxied")) to the end of Configure() (after UseEndpoints()), so that we can try this proxy method, but only if the request doesn’t match a valid route. Deliberately adds to the path, so that the initial request doesn’t have to match a valid route when the proxied request does.
  • Creating the following controller, which handles that variant of proxying, and also has the destination we’re proxying to:
    [ApiController]
    public class ProxyController : ControllerBase {
        [Route("proxy/{**rest}")]
        public Task ProxyAsync(string rest) {
            return this.HttpProxyAsync($"http://localhost:5000/{rest}");
        }

        [Route("echo")]
        [Route("proxied/echo2")]
        public async Task<ActionResult<string>> EchoAsync() {
            using var sr = new StreamReader(Request.Body);
            return await sr.ReadToEndAsync();
        }
    }

I’ve used an API testing client (in my case the Firefox extension RESTer) to send various requests to this at https://localhost:5001/proxy/echo (proxies to localhost:5000/echo via the controller). As long as the request does not have one of the mentioned Content-Type headers, or has not content, this works fine. But with either of these content types, it returns an exception:

Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Reading the request body timed out due to data arriving too slowly. See MinRequestBodyDataRate.
   at Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException.Throw(RequestRejectionReason reason)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1ContentLengthMessageBody.ReadAsyncInternal(CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestStream.ReadAsyncInternal(Memory`1 buffer, CancellationToken cancellationToken)
   at System.IO.StreamReader.ReadBufferAsync(CancellationToken cancellationToken)
   at System.IO.StreamReader.ReadToEndAsyncInternal()
   at ProxyTest.Controllers.ProxyController.EchoAsync() in /home/<removed>/Projects/ProxyTest/Controllers/ProxyController.cs:line 22
   at lambda_method(Closure , Object )
   at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

HEADERS
=======
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/x-www-form-urlencoded
Host: localhost:5000
TE: trailers
Request-Id: |b5df5be7-411c1e27a666778b.1.
Content-Length: 14
X-Forwarded-For: 127.0.0.1
X-Forwarded-Proto: https
X-Forwarded-Host: localhost:5001
Forwarded: proto=https;host=localhost:5001;by=127.0.0.1;for=127.0.0.1;

But everything works correctly when requesting https://localhost:5001/echo2 (doesn’t match a route, so proxies to localhost:5000/proxied/echo2).

This must be the controller mucking around with something when handling form data, but I don’t know what…

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:16 (10 by maintainers)

github_iconTop GitHub Comments

1reaction
PreferLinuxcommented, Jun 18, 2020

Just did some more experimenting. Started by using the EchoAsync() method above, checking out the request headers and in the process noticed the HttpRequest.Form – reading Request.Form first meant the body was consumed, and vise versa (expected). Then I tried adding a string route parameter, with Route("echo/{**rest}") and EchoAsync(string rest), to match the ProxyAsync() method. Now Request.Form always had the data, and not the body. Adding [FromRoute] and such didn’t help. I had a look to see if I could find something to prevent reading the form, but I didn’t come up with anything much. So I recalled that Request does have request path stuff too – and also route values. So checked those out, and they work as expected. Keep the catchall on the route, don’t have any parameters / model binding at all, access the remaining path with Request.RouteValues["rest"], and everything seems to work fine.

So long story short, replace Proxy() above with this, and it works.

[Route("proxy/{**rest}")]
public Task ProxyAsync() {
    var rest = Request.RouteValues["rest"];
    return this.HttpProxyAsync($"http://localhost:5000/{rest}");
}

I’d prefer an option, and this seems a little fragile, but it’ll do for now.

0reactions
twitchaxcommented, Aug 6, 2020

Cool. I will release tomorrow.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Proxied requests other than GET are hanging with NestJS ...
The problem seems to be that the controller in the downstream service is never invoked. import * as proxy from 'http-proxy-middleware'; export ...
Read more >
Hello World Cannot proxy application requests...Error ...
I finally have been able to setup SAP WEB IDE for SAP HANA to run with SAP Hana Express Edition (HXE) on CAL...
Read more >
Linkerd `policy` container of `destination` Pod and `proxy- ...
Bug Report What is the issue? Linkerd core components destination and proxy-injector crashing and never coming up. How can it be reproduced?
Read more >
Avoiding the Top 10 NGINX Configuration Mistakes
We help you avoid the 10 most common NGINX configuration errors, explaining the problems caused by each and how to fix them.
Read more >
Storage Migration Service known issues
This issue is caused by a limitation in the Storage Migration Service Proxy service when an entire NTFS volume has been configured with...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found