I'm trying to make the GraphQL Java server work with GraphiQL server.
Using the GraphiQL running locally I submit a query with the following parameters:
My Spring controller (copied from here) looks like this:
@Controller
@EnableAutoConfiguration
public class MavenController {
private final MavenSchema schema = new MavenSchema();
private final GraphQL graphql = new GraphQL(schema.getSchema());
private static final Logger log = LoggerFactory.getLogger(MavenController.class);
@RequestMapping(value = "/graphql", method = RequestMethod.OPTIONS, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Object executeOperation(@RequestBody Map body) {
log.error("body: " + body);
final String query = (String) body.get("query");
final Map<String, Object> variables = (Map<String, Object>) body.get("variables");
final ExecutionResult executionResult = graphql.execute(query, (Object) null, variables);
final Map<String, Object> result = new LinkedHashMap<>();
if (executionResult.getErrors().size() > 0) {
result.put("errors", executionResult.getErrors());
log.error("Errors: {}", executionResult.getErrors());
}
log.error("data: " + executionResult.getData());
result.put("data", executionResult.getData());
return result;
}
}
In theory, executeOperation
should be invoked, when I submit the query in GraphiQL. It's not, I don't see the log statement in the console output.
What am I doing wron? How can I make sure that the MavenController.executeOperation
is called, when a query is submitted in GraphiQL?
Update 1 (13.01.2017 13:35 MSK): Here is a tutorial on how to reproduce the error. My goal is to create a Java-based GraphQL server, with which I can interact using GraphiQL. If possible, this should work locally.
I’ve read that in order to do that, following steps are necessary:
- Set up a GraphQL server. An example of this can be found here https://github.com/graphql-java/todomvc-relay-java. That example uses Spring Boot, but you can use whatever HTTP server you like to get that going.
- Set up the GraphiQL server. That is a bit beyond the scope of this project, but basically you need to get GraphiQL talking to the server in step 1 above. It will use introspection to load the schema.
I checked out the project todomvc-relay-java, modified it according to my needs and put it into directory E:\graphiql-java\graphql-server
. You can download an archive with that directory here.
Step 1: Install Node.JS
Step 2
Go to E:\graphiql-java\graphql-server\app
and run npm install
there.
Step 3
Run npm start
from the same directory.
Step 4
Go to E:\graphiql-java\graphql-server
and run gradlew start
there.
Step 5
Run docker run -p 8888:8080 -d -e GRAPHQL_SERVER=http://localhost:8080/graphql merapar/graphql-browser-docker
.
Docker sources: graphql-browser-docker
Step 6
Launch Chrome with disabled XSS check, e. g. "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --args --disable-xss-auditor
. Some sources claim you have to kill all other Chrome instances in order for these arguments to have any effect.
Step 7
Open http://localhost:8888/ in that browser.
Step 8
Try to run query
{
allArtifacts(group: "com.graphql-java", name: "graphql-java") {
group
name
version
}
}
Actual results:
1) Error in the Console tab of Chrome: Fetch API cannot load http://localhost:8080/graphql. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8888' is therefore not allowed access. The response had HTTP status code 403. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
.
2) Errors in the Network tab of Chrome:
Update 2 (14.01.2017 13:51): Currently, CORS is configured in the Java appliction in the following way.
Main class:
@SpringBootApplication
public class Main {
public static void main(String[] args) throws Exception {
SpringApplication.run(Main.class, args);
}
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry
.addMapping("/**")
.allowedMethods("OPTIONS")
.allowedOrigins("*")
.allowedHeaders(
"Access-Control-Request-Headers",
"Access-Control-Request-Method",
"Host",
"Connection",
"Origin",
"User-Agent",
"Accept",
"Referer",
"Accept-Encoding",
"Accept-Language",
"Access-Control-Allow-Origin"
)
.allowCredentials(true)
;
}
};
}
}
WebSecurityConfigurerAdapter
subclass:
@Configuration
@EnableWebSecurity
public class SpringWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(final HttpSecurity http) throws Exception {
System.out.println("SpringWebSecurityConfiguration");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("OPTIONS");
source.registerCorsConfiguration("/**", config);
http
.addFilterBefore(new CorsFilter(source), ChannelProcessingFilter.class)
.httpBasic()
.disable()
.authorizeRequests()
.anyRequest()
.permitAll()
.and()
.csrf()
.disable();
}
@Override
public void configure(WebSecurity web) throws Exception {
}
}
The controller:
@Controller
@EnableAutoConfiguration
public class MavenController {
private final MavenSchema schema = new MavenSchema();
private final GraphQL graphql = new GraphQL(schema.getSchema());
private static final Logger log = LoggerFactory.getLogger(MavenController.class);
@CrossOrigin(
origins = {"http://localhost:8888", "*"},
methods = {RequestMethod.OPTIONS},
allowedHeaders = {"Access-Control-Request-Headers",
"Access-Control-Request-Method",
"Host",
"Connection",
"Origin",
"User-Agent",
"Accept",
"Referer",
"Accept-Encoding",
"Accept-Language",
"Access-Control-Allow-Origin"},
exposedHeaders = "Access-Control-Allow-Origin"
)
@RequestMapping(value = "/graphql", method = RequestMethod.OPTIONS, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Object executeOperation(@RequestBody Map body) {
[...]
}
}
MavenController.executeOperation
is the method that's supposed to be called, when I issue a query request in GraphiQL.
Update 3 (15.01.2017 22:05): Tried to use CORSFilter
, new source code is here. No result, I'm still getting "Invalid CORS response" error.
The problem is the with the usage of .allowedMethods("OPTIONS")
configuration in your application.
Pre-flight requests by design sent with OPTIONS
request method.
Access-Control-Request-Method is added by the browser automatically and allowedMethods
property actually controls what request method are allowed for the actual request.
From the docs,
The Access-Control-Request-Method header notifies the server as part of a preflight request that when the actual request is sent, it will be sent with a POST request method.
So it is POST
method and and the request fails as you're only allowing OPTIONS
when the validation is done.
So changing allowedMethods
to *
will match browser set POST
request method for actual request.
After you do above change you'll get 405 as your controller only allows OPTIONS
for your POST
request.
So, you'll need to update the controller request mapping to allow POST for the actual request to succeed after pre-flight request.
Sample Response:
{
"timestamp": 1484606833696,
"status": 500,
"error": "Internal Server Error",
"exception": "graphql.AssertException",
"message": "arguments can't be null",
"path": "/graphql"
}
I'm not sure looks like you've CORS configuration set at too many places. I just have to change the .allowedMethods
in the spring security configuration to make it work the way I described. So you may want to look into that.