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
<ProjectReference Include="..\..\src\Core\PersistedQueries.FileSystem\PersistedQueries.FileSystem.csproj" />
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");
{
hero {
name
appearsIn
}
}
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"
]
}
}
}
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.
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.
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.