Hotchocolate: 400 Bad Request - using namedQuery with GET and

Created on 19 Aug 2020  路  8Comments  路  Source: ChilliCream/hotchocolate

I have been attempting to use HotChocolate.PersistedQueries.FileSystem for GET requests, but I always get a 400 response when I make a GET request with the parameter ?namedQuery=foo

If I make a POST request with the following request body, I get the expected results.

{
    "namedQuery": "foo"
}

To Reproduce

  1. Using latest stable HotChocolate 10.5.2 (main branch)
  2. Open the Examples -> AspNetCore.StarWars project
  3. Add a project to the solution and project reference to AspNetCore.StarWars:
<ProjectReference Include="..\..\src\Core\PersistedQueries.FileSystem\PersistedQueries.FileSystem.csproj" />
  1. In the Startup.cs, change the GraphQL setup to use peristed queries:
            // Add GraphQL Services
            services.AddGraphQL(sp => SchemaBuilder.New()
                .AddServices(sp)

                // Adds the authorize directive and
                // enable the authorization middleware.
                .AddAuthorizeDirectiveType()

                .AddQueryType<QueryType>()
                .AddMutationType<MutationType>()
                .AddSubscriptionType<SubscriptionType>()
                .AddType<HumanType>()
                .AddType<DroidType>()
                .AddType<EpisodeType>()
                .Create(),
                b =>
                {
                    b.UsePersistedQueryPipeline();
                });

            services.AddReadOnlyFileSystemQueryStorage("./queries");
  1. Add a folder+file '\queries\foo' at the root of the AspNetCore.StarWars project.
  2. Put the following query in the foo file:
{
  hero {
    name
    appearsIn
  }
}
  1. Run the AspNetCore.StarWars project.
  2. Issue a POST request for the named query:
curl -X POST \
  https://localhost:44356/graphql \
  -H 'Content-Type: application/json' \
  -H 'cache-control: no-cache' \
  -d '{
    "namedQuery": "foo"
}'

You get the expected result of:

{
    "data": {
        "hero": {
            "name": "R2-D2",
            "appearsIn": [
                "NEWHOPE",
                "EMPIRE",
                "JEDI"
            ]
        }
    }
}
  1. Now make a GET request:
curl -X GET \
  'https://localhost:44356/graphql?namedQuery=foo' \
  -H 'cache-control: no-cache'

You now get a 400 Bad Request response.

Expected behavior
The response from both a POST request and a GET request for the same named query SHOULD return the same response, no?

Additional context
I was also seeing this in previous 10.4.x versions, but unsure how far back it goes.

bug 馃尪 hot chocolate

All 8 comments

This might be a documentation error. If you use id it should work just fine.

curl -X POST \
  https://localhost:44356/graphql \
  -H 'Content-Type: application/json' \
  -H 'cache-control: no-cache' \
  -d '{
    "id": "foo"
}'

Hi @michaelstaib,
No sorry, that doesn't work. That responds with a 404.
With namedQuery, it at least gets handled by the HttpGetMiddleware, but there's no provision for matching a parameter id that I can see:

        private const string _namedQueryIdentifier = "namedQuery";
        private const string _queryIdentifier = "query";

        protected override bool CanHandleRequest(HttpContext context)
        {
            return string.Equals(
                context.Request.Method,
                HttpMethods.Get,
                StringComparison.Ordinal) &&
                    HasQueryParameter(context);
        }

        private static bool HasQueryParameter(HttpContext context)
        {
#if ASPNETCLASSIC
            return context.Request.Query[_queryIdentifier] != null
                || context.Request.Query[_namedQueryIdentifier] != null;
#else
            return context.Request.Query[_queryIdentifier].Count != 0
                || context.Request.Query[_namedQueryIdentifier].Count != 0;
#endif
        }

I think the problem is further down the pipeline...

@michaelstaib although, the id parameter DOES work with a POST

So, the issue is with HTTP GET requests. I will issue a fix for this week.

@michaelstaib thanks.
I debugged a bit further through and it is this code in QueryRequestBuilder.cs that is throwing an exception because the querySource parameter is null:

        public IQueryRequestBuilder SetQuery(string querySource)
        {
            if (string.IsNullOrEmpty(querySource))
            {
                throw new ArgumentException(
                    AbstractionResources.QueryRequestBuilder_QueryIsNullOrEmpty,
                    nameof(querySource));
            }

            _query = new QuerySourceText(querySource);
            return this;
        }

Which is called by this in HttpGetMiddleware.ExecuteRequestAsync:

            builder
                .SetQuery(requestQuery[_queryIdentifier])  /<----
                .SetQueryName(requestQuery[_namedQueryIdentifier])
                .SetOperation(requestQuery[_operationNameIdentifier]);

This one is fixed.

  • [ ] we should create an explicit test that refers to this issue.

Added notes to the Slack channel for v10 -> v11 migration on why I think this still isn't working.

It looks to me like the original issue I reported has made it into v11 (main branch as of 25 Nov 2020)
DefaultHttpRequestParser :

        private const string _queryIdIdentifier = "id";
        private const string _operationNameIdentifier = "operationName";
        private const string _queryIdentifier = "query";

        public GraphQLRequest ReadParamsRequest(IQueryCollection parameters)
        {
            // next we deserialize the GET request with the query request builder ...
            string query = parameters[_queryIdentifier];
            string queryId = parameters[_queryIdIdentifier];
            string operationName = parameters[_operationNameIdentifier];

            if (string.IsNullOrEmpty(query) && string.IsNullOrEmpty(queryId))
            {
                throw DefaultHttpRequestParser_QueryAndIdMissing();
            }

            try
            {
                DocumentNode document = Utf8GraphQLParser.Parse(query);

This code allows EITHER a 'query' parameter OR an 'id' parameter on the GET request, but then immediately tries to parse the 'query' parameter without taking into account that a persisted query request might only contain the query id when using the readonly file storage pipeline.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

IKolosynskyi picture IKolosynskyi  路  3Comments

lTimeless picture lTimeless  路  5Comments

jbray1982 picture jbray1982  路  5Comments

sfmskywalker picture sfmskywalker  路  3Comments

sgt picture sgt  路  4Comments