Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AspNetCoreServer incorrectly sets IHttpRequestBodyDetectionFeature.CanHaveBody that leads to breaking change after net 7 upgrade #1854

Closed
1 task
MadSciencist opened this issue Oct 31, 2024 · 7 comments
Assignees
Labels
bug This issue is a bug. module/aspnetcore-support p1 This is a high priority issue queued

Comments

@MadSciencist
Copy link

Describe the bug

Hi,

It appears that the behavior of the APIGatewayProxyFunction is inconsistent with the behavior of Kestrel when handling requests with an empty body directed to endpoints with a parameter marked [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)].

In the InvokeFeatures class, the CanHaveBody property is set based on requestFeature.Body != null. This is problematic because the IHttpRequestFeature.Body property is initialized with a new MemoryStream() value, therefor CanHaveBody is never false.

As a result, this change introduces a breaking behavior when upgrading from .NET 6.0 to .NET 7.0 in scenarios where requests with an empty body are sent from AWS Lambda. ASP.NET Core attempts to deserialize the request (due to the CanHaveBody property), and because the request body is an empty stream, it causes the API to throw an error.

Regression Issue

  • Select this option if this issue appears to be a regression.

Expected Behavior

Endpoints with parameters marked with [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] attribute can handle request both with and without body.

Current Behavior

AspNetCore tries to deserialize the body and throws The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. Path: $ | LineNumber: 0 | BytePositionInLine: 0.

Reproduction Steps

Create PUT endpoint with optional body:

    public IActionResult Test([FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] Body request = default)
    {
        return Accepted();
    }

    public class Body
    {
        public string Prop { get; set; }
    }

and send requests with and without body.

Possible Solution

No response

Additional Information/Context

No response

AWS .NET SDK and/or Package version used

Amazon.Lambda.AspNetCoreServer 9.0.1

Targeted .NET Platform

NET 8.0

Operating System and version

Windows 10

@MadSciencist MadSciencist added bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. labels Oct 31, 2024
@bhoradc bhoradc added needs-reproduction This issue needs reproduction. module/aspnetcore-support p2 This is a standard priority issue and removed needs-triage This issue or PR still needs to be triaged. labels Oct 31, 2024
@bhoradc bhoradc self-assigned this Oct 31, 2024
@ashishdhingra
Copy link
Contributor

ashishdhingra commented Nov 18, 2024

@MadSciencist Good morning. Could you please share if you are using Amazon.Lambda.Annotations.APIGateway.FromBodyAttribute attribute or Microsoft.AspNetCore.Mvc.FromBodyAttribute class? The one provided by Amazon.Lambda.Annotations package does not support EmptyBodyBehavior property.

EDIT: Ignore my comment. This has nothing to do with annotations. Will try to reproduce it at my end.

Thanks,
Ashish

@ashishdhingra ashishdhingra added the response-requested Waiting on additional info and feedback. Will move to close soon in 7 days. label Nov 18, 2024
@ashishdhingra ashishdhingra removed the response-requested Waiting on additional info and feedback. Will move to close soon in 7 days. label Nov 18, 2024
@ashishdhingra
Copy link
Contributor

ashishdhingra commented Nov 18, 2024

@MadSciencist Good afternoon. Somehow, I'm unable to reproduce the issue using the below code (used AWS Serverless Web API template):
.csproj

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
    <AWSProjectType>Lambda</AWSProjectType>
    <!-- This property makes the build directory similar to a publish directory and helps the AWS .NET Lambda Mock Test Tool find project dependencies. -->
    <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
    <!-- Generate ready to run images during publishing to improve cold start time. -->
    <PublishReadyToRun>true</PublishReadyToRun>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Amazon.Lambda.AspNetCoreServer" Version="9.0.1" />
  </ItemGroup>
</Project>

Controllers\ValuesController.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace AWSServerlessApiNET8.Controllers
{
    [Route("api/[controller]")]
    public class ValuesController : ControllerBase
    {
        [HttpPut("test")]
        public IActionResult Test([FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] Body request = default)
        {
            return Accepted();
        }

        public class Body
        {
            public string Prop { get; set; }
        }
    }
}

Below are the results:

  • Executing locally works fine.
  • Deploying to Lambda and testing using API Gateway endpoint (e.g. https://<<api-id>>.execute-api.us-east-2.amazonaws.com/Prod/api/values/test) works fine with or without request Body (used Postman with Content-Type header as application/json set).
    curl request with Body
    curl --location --request PUT 'https://<<api-id>>.execute-api.us-east-2.amazonaws.com/Prod/api/values/test' \
    --header 'Content-Type: application/json' \
    --data '{"Prop": "test"}'
    
    curl request without Body
    curl --location --request PUT 'https://<<api-id>>.execute-api.us-east-2.amazonaws.com/Prod/api/values/test' \
    --header 'Content-Type: application/json' \
    --data ''
    
    OR
    curl --location --request PUT 'https://<<api-id>>.execute-api.us-east-2.amazonaws.com/Prod/api/values/test' \
    --header 'Content-Type: application/json'
    
  • Below are CloudWatch logs:
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|   timestamp   |                                                                                                                                                                                                message                                                                                                                                                                                                 |
|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1731965976403 | INIT_START Runtime Version: dotnet:8.v29 Runtime Version ARN: arn:aws:lambda:us-east-2::runtime:a80f5bd0010789587efde8cc8718de53e230fba733dc31ed8ba53bf0d0b8d6f0                                                                                                                                                                                                                                       |
| 1731965976745 | 2024-11-18T21:39:36.739Z  trce [Information] Microsoft.Hosting.Lifetime: Application started. Press Ctrl+C to shut down.                                                                                                                                                                                                                                                                               |
| 1731965976746 | 2024-11-18T21:39:36.746Z  trce [Information] Microsoft.Hosting.Lifetime: Hosting environment: Production                                                                                                                                                                                                                                                                                               |
| 1731965976746 | 2024-11-18T21:39:36.746Z  trce [Information] Microsoft.Hosting.Lifetime: Content root path: /var/task                                                                                                                                                                                                                                                                                                  |
| 1731965976805 | START RequestId: 26489e3b-9702-430b-b1e7-7008601b6195 Version: $LATEST                                                                                                                                                                                                                                                                                                                                 |
| 1731965976974 | 2024-11-18T21:39:36.974Z 26489e3b-9702-430b-b1e7-7008601b6195 trce [Information] Microsoft.AspNetCore.Hosting.Diagnostics: Request starting  PUT https://<<api-id>>.execute-api.us-east-2.amazonaws.com/Prod/api/values/test - application/json 0                                                                                                                                                      |
| 1731965977018 | 2024-11-18T21:39:37.018Z 26489e3b-9702-430b-b1e7-7008601b6195 trce [Information] Microsoft.AspNetCore.Routing.EndpointMiddleware: Executing endpoint 'AWSServerlessApiNET8.Controllers.ValuesController.Test (AWSServerlessApiNET8)'                                                                                                                                                                   |
| 1731965977058 | 2024-11-18T21:39:37.058Z 26489e3b-9702-430b-b1e7-7008601b6195 trce [Information] Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker: Route matched with {action = "Test", controller = "Values"}. Executing controller action with signature Microsoft.AspNetCore.Mvc.IActionResult Test(Body) on controller AWSServerlessApiNET8.Controllers.ValuesController (AWSServerlessApiNET8).    |
| 1731965977120 | 2024-11-18T21:39:37.120Z 26489e3b-9702-430b-b1e7-7008601b6195 trce [Information] Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor: Executing AcceptedResult, writing value of type 'null'.                                                                                                                                                                                                 |
| 1731965977138 | 2024-11-18T21:39:37.138Z 26489e3b-9702-430b-b1e7-7008601b6195 trce [Information] Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker: Executed action AWSServerlessApiNET8.Controllers.ValuesController.Test (AWSServerlessApiNET8) in 61.6712ms                                                                                                                                           |
| 1731965977138 | 2024-11-18T21:39:37.138Z 26489e3b-9702-430b-b1e7-7008601b6195 trce [Information] Microsoft.AspNetCore.Routing.EndpointMiddleware: Executed endpoint 'AWSServerlessApiNET8.Controllers.ValuesController.Test (AWSServerlessApiNET8)'                                                                                                                                                                    |
| 1731965977139 | 2024-11-18T21:39:37.139Z 26489e3b-9702-430b-b1e7-7008601b6195 trce [Information] Microsoft.AspNetCore.Hosting.Diagnostics: Request finished  PUT https://<<api-id>>.execute-api.us-east-2.amazonaws.com/Prod/api/values/test - 202 - - 165.6165ms                                                                                                                                                      |
| 1731965977196 | END RequestId: 26489e3b-9702-430b-b1e7-7008601b6195                                                                                                                                                                                                                                                                                                                                                    |
| 1731965977196 | REPORT RequestId: 26489e3b-9702-430b-b1e7-7008601b6195 Duration: 390.33 ms Billed Duration: 391 ms Memory Size: 512 MB Max Memory Used: 89 MB Init Duration: 399.51 ms                                                                                                                                                                                                                                 |
| 1731966059781 | START RequestId: c1b94fd5-639b-487c-88a5-e24827178caf Version: $LATEST                                                                                                                                                                                                                                                                                                                                 |
| 1731966059782 | 2024-11-18T21:40:59.782Z c1b94fd5-639b-487c-88a5-e24827178caf trce [Information] Microsoft.AspNetCore.Hosting.Diagnostics: Request starting  PUT https://<<api-id>>.execute-api.us-east-2.amazonaws.com/Prod/api/values/test - application/json 16                                                                                                                                                     |
| 1731966059782 | 2024-11-18T21:40:59.782Z c1b94fd5-639b-487c-88a5-e24827178caf trce [Information] Microsoft.AspNetCore.Routing.EndpointMiddleware: Executing endpoint 'AWSServerlessApiNET8.Controllers.ValuesController.Test (AWSServerlessApiNET8)'                                                                                                                                                                   |
| 1731966059783 | 2024-11-18T21:40:59.783Z c1b94fd5-639b-487c-88a5-e24827178caf trce [Information] Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker: Route matched with {action = "Test", controller = "Values"}. Executing controller action with signature Microsoft.AspNetCore.Mvc.IActionResult Test(Body) on controller AWSServerlessApiNET8.Controllers.ValuesController (AWSServerlessApiNET8).    |
| 1731966059789 | 2024-11-18T21:40:59.789Z c1b94fd5-639b-487c-88a5-e24827178caf trce [Information] Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor: Executing AcceptedResult, writing value of type 'null'.                                                                                                                                                                                                 |
| 1731966059789 | 2024-11-18T21:40:59.789Z c1b94fd5-639b-487c-88a5-e24827178caf trce [Information] Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker: Executed action AWSServerlessApiNET8.Controllers.ValuesController.Test (AWSServerlessApiNET8) in 6.3805ms                                                                                                                                            |
| 1731966059789 | 2024-11-18T21:40:59.789Z c1b94fd5-639b-487c-88a5-e24827178caf trce [Information] Microsoft.AspNetCore.Routing.EndpointMiddleware: Executed endpoint 'AWSServerlessApiNET8.Controllers.ValuesController.Test (AWSServerlessApiNET8)'                                                                                                                                                                    |
| 1731966059789 | 2024-11-18T21:40:59.789Z c1b94fd5-639b-487c-88a5-e24827178caf trce [Information] Microsoft.AspNetCore.Hosting.Diagnostics: Request finished  PUT https://<<api-id>>.execute-api.us-east-2.amazonaws.com/Prod/api/values/test - 202 - - 6.9651ms                                                                                                                                                        |
| 1731966059791 | END RequestId: c1b94fd5-639b-487c-88a5-e24827178caf                                                                                                                                                                                                                                                                                                                                                    |
| 1731966059791 | REPORT RequestId: c1b94fd5-639b-487c-88a5-e24827178caf Duration: 9.73 ms Billed Duration: 10 ms Memory Size: 512 MB Max Memory Used: 89 MB                                                                                                                                                                                                                                                             |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Please advise if I'm missing anything in reproduction.

Thanks,
Ashish

@ashishdhingra ashishdhingra added the response-requested Waiting on additional info and feedback. Will move to close soon in 7 days. label Nov 18, 2024
@MadSciencist
Copy link
Author

MadSciencist commented Nov 19, 2024

Good morning,
Thank you for your time. I took another look at it, and you're right - it works with the basic AspNetCoreWebAPI template.
So, I started moving code from my project until it broke, and it seems that the [ApiController] attribute (try decorating ValuesController) is what is required to trigger the issue.

@ashishdhingra
Copy link
Contributor

Good morning, Thank you for your time. I took another look at it, and you're right - it works with the basic AspNetCoreWebAPI template. So, I started moving code from my project until it broke, and it seems that the [ApiController] attribute (try decorating ValuesController) is what is required to trigger the issue.

@MadSciencist Thanks for the input. After decorating ValuesController with [ApiController] attribute:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace AWSServerlessApiNET8.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        [HttpPut("test")]
        public IActionResult Test([FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] Body request = default)
        {
            return Accepted();
        }

        public class Body
        {
            public string Prop { get; set; }
        }
    }
}
  • Issue is not reproducible locally using Kestrel irrespective if we send body, empty body or no body.

  • Issue is reproducible when sending empty body or no body when the project is deployed to API Gateway.

    {
        "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
        "title": "One or more validation errors occurred.",
        "status": 400,
        "errors": {
            "$": [
                "The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. Path: $ | LineNumber: 0 | BytePositionInLine: 0."
            ]
        },
        "traceId": "00-1ef5b305213ec74a8a63e242898c4a5c-40bd9145a0afe04c-00"
    }
    

    Logs from CloudWatch:

    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    | timestamp | message |
    | ---------------| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
    | 1732044466909 | 2024-11-19T19:27:46.909Z cafed777-d8e8-4631-b0bd-7035e5b5b95a trce [Information] Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker: Route matched with {action = "Test", controller = "Values"}. Executing controller action with signature Microsoft.AspNetCore.Mvc.IActionResult Test(Body) on controller AWSServerlessApiNET8.Controllers.ValuesController (AWSServerlessApiNET8).    |
    | 1732044467007 | 2024-11-19T19:27:47.007Z cafed777-d8e8-4631-b0bd-7035e5b5b95a trce [Information] Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor: Executing BadRequestObjectResult, writing value of type 'Microsoft.AspNetCore.Mvc.ValidationProblemDetails'.                                                                                                                                            |
    | 1732044467094 | 2024-11-19T19:27:47.093Z cafed777-d8e8-4631-b0bd-7035e5b5b95a trce [Information] Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker: Executed action AWSServerlessApiNET8.Controllers.ValuesController.Test (AWSServerlessApiNET8) in 160.7038ms                                                                                                                                          |
    | 1732044467094 | 2024-11-19T19:27:47.094Z cafed777-d8e8-4631-b0bd-7035e5b5b95a trce [Information] Microsoft.AspNetCore.Routing.EndpointMiddleware: Executed endpoint 'AWSServerlessApiNET8.Controllers.ValuesController.Test (AWSServerlessApiNET8)'                                                                                                                                                                    |
    | 1732044467109 | 2024-11-19T19:27:47.109Z cafed777-d8e8-4631-b0bd-7035e5b5b95a trce [Information] Microsoft.AspNetCore.Hosting.Diagnostics: Request finished  PUT https://<<api-id>>.execute-api.us-east-2.amazonaws.com/Prod/api/values/test - 400 - application/problem+json;+charset=utf-8 318.0821ms                                                                                                                |
    | 1732044467188 | END RequestId: cafed777 - d8e8 - 4631 - b0bd - 7035e5b5b95a |
    | 1732044701209 | REPORT RequestId: dcbeaf95 - 4a52 - 457e-86a4 - 57002305b6e3 Duration: 3.33 ms Billed Duration: 4 ms Memory Size: 512 MB Max Memory Used: 90 MB |
    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    

Just on the side note, [ApiController] attribute:

  • Makes attribute routing a requirement, meaning the API methods are inaccessible via conventional routes.
  • Also makes model validation errors automatically trigger an HTTP 400 response. Basically, the framework perform following code behind the scene:
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

I'm unsure why model validation is not kicked in locally when executing under Kestrel, when using [ApiController] attribute.

@ashishdhingra ashishdhingra added needs-review queued and removed needs-reproduction This issue needs reproduction. response-requested Waiting on additional info and feedback. Will move to close soon in 7 days. needs-review labels Nov 19, 2024
@96malhar 96malhar added p1 This is a high priority issue and removed p2 This is a standard priority issue labels Nov 25, 2024
@normj
Copy link
Member

normj commented Nov 26, 2024

Version 9.0.3 of Amazon.Lambda.AspNetCoreServer and 1.7.3 of Amazon.Lambda.AspNetCoreServer.Hosting have been released with this fix. Thanks for letting us know about the issue.

@ashishdhingra
Copy link
Contributor

@MadSciencist Closing this issue. Please let us know if you still encounter the issue in the latest versions.

Copy link
Contributor

Comments on closed issues are hard for our team to see.
If you need more assistance, please either tag a team member or open a new issue that references this one.
If you wish to keep having a conversation with other community members under this issue feel free to do so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug This issue is a bug. module/aspnetcore-support p1 This is a high priority issue queued
Projects
None yet
Development

No branches or pull requests

5 participants