Git Product home page Git Product logo

graphql-java-spring's People

Contributors

andimarek avatar andue avatar marceloverdijk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphql-java-spring's Issues

Can not test GraphQL with MockMvc, as the response is always empty.

I'm using MockMvc to test GraphQL API, but tests always failure because the response is empty. The full code can be found at Spring Boot in Practice.

package net.jaggerwang.sbip.api;

...

@SpringBootTest()
@AutoConfigureMockMvc
@ActiveProfiles("test")
@Sql({"/db/init-db-test.sql"})
@Sql(scripts = {"/db/clean-db-test.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
@EnabledIfSystemProperty(named = "test.api.enabled", matches = "true")
public class UserGraphQLApiTests {
    @Autowired
    private MockMvc mvc;

    @Value("${graphql.url}")
    private String graphqlUrl;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    void login() throws Exception {
        var userEntity = UserEntity.builder().username("jaggerwang").password("123456").build();
        var content = new ObjectMapper().createObjectNode();
        content.put("query", "mutation($user: UserInput!) { authLogin(user: $user) { id username } }");
        content.putObject("variables").putObject("user").put("username", userEntity.getUsername())
                .put("password", userEntity.getPassword());
        mvc.perform(post(graphqlUrl).contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(content))).andExpect(status().isOk())
                .andExpect(jsonPath("$.errors").doesNotExist())
                .andExpect(jsonPath("$.data.authLogin.username").value(userEntity.getUsername()));
    }

    ...
}

Test output:

...

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /graphql
       Parameters = {}
          Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"149"]
             Body = {"query":"mutation($user: UserInput!) { authLogin(user: $user) { id username } }","variables":{"user":{"username":"jaggerwang","password":"123456"}}}
    Session Attrs = {}

Handler:
             Type = graphql.spring.web.servlet.components.GraphQLController
           Method = graphql.spring.web.servlet.components.GraphQLController#graphqlPOST(String, String, String, String, String, WebRequest)

Async:
    Async started = true
     Async result = {data={authLogin={id=1, username=jaggerwang}}}

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY", Set-Cookie:"SESSION=OTFiNzRmNDgtYTIzNy00N2ZlLTk4MWQtNmMxNzMwYzU1ZDVk; Path=/; HttpOnly; SameSite=Lax"]
     Content type = null
             Body =
    Forwarded URL = null
   Redirected URL = null
          Cookies = [[MockCookie@7d9c448f name = 'SESSION', value = 'OTFiNzRmNDgtYTIzNy00N2ZlLTk4MWQtNmMxNzMwYzU1ZDVk', comment = [null], domain = [null], maxAge = -1, path = '/', secure = false, version = 0, httpOnly = true]]

...

[ERROR] login  Time elapsed: 0.187 s  <<< FAILURE!
java.lang.AssertionError: No value at JSON path "$.data.authLogin.username"
	at net.jaggerwang.sbip.api.UserGraphQLApiTests.login(UserGraphQLApiTests.java:45)
Caused by: java.lang.IllegalArgumentException: json can not be null or empty
	at net.jaggerwang.sbip.api.UserGraphQLApiTests.login(UserGraphQLApiTests.java:45)

...

Can we expect integration with Spring Security?

Can we implement some sort of integration with spring security? I think it might look like special GraphQL mutation, witch will be handled by library. The problem is that I cannot configure app using existing '..ConfigurerAdapter's since they rely on filters and with GraphQL we always have one endpoint.
I'd like to avoid manual authentication on home grown mutation.

Can't get current ServerWebExchange object using Mono.subscriberContext in webflux.

I'm using graphql in spring cloud gateway, so it is a webflux environment. I've already successfully run a graphql endpoint, the data fetchers need to get data from backend services using webclient, so I need to pass through the Authorization header. I wrote a webclient filter to do this automatically. The full source code can be found at Spring Cloud in Practice.

package net.jaggerwang.scip.common.api.filter;

import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Set;

public class HeadersRelayFilter implements ExchangeFilterFunction {
    private Set<String> headers;

    public HeadersRelayFilter(Set<String> headers) {
        this.headers = headers;
    }

    @Override
    public Mono<ClientResponse> filter(ClientRequest clientRequest,
                                       ExchangeFunction exchangeFunction) {
        return Mono.subscriberContext()
                .flatMap(ctx -> {
                    var upstreamExchange = ctx.getOrEmpty(ServerWebExchange.class);
                    if (upstreamExchange.isPresent()) {
                        var upstreamHeaders = ((ServerWebExchange) upstreamExchange.get())
                                .getRequest().getHeaders();
                        for (var header: headers) {
                            clientRequest.headers().addAll(header, upstreamHeaders.get(header));
                        }
                    }

                    return exchangeFunction.exchange(clientRequest);
                });
    }
}

But the ctx is always empty. I can get current exchange object in normal webflux handler, so it seems the problem of GraphQLController. I'm using spring boot 2.2.2.RELEASE and spring cloud Hoxton.SR1.

<?xml version="1.0" ?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>net.jaggerwang</groupId>
        <artifactId>spring-cloud-in-practice</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <groupId>net.jaggerwang</groupId>
    <artifactId>spring-cloud-in-practice-gateway</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <name>spring-cloud-in-practice-gateway</name>
    <description>Spring cloud in practice gateway</description>

    <dependencies>
        <dependency>
            <groupId>net.jaggerwang</groupId>
            <artifactId>spring-cloud-in-practice-common</artifactId>
            <version>${scip-common.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
        </dependency>

        <dependency>
            <groupId>com.graphql-java</groupId>
            <artifactId>graphql-java-spring-boot-starter-webflux</artifactId>
            <version>${graphql-starter.version}</version>
        </dependency>
        <dependency>
            <groupId>com.graphql-java</groupId>
            <artifactId>graphql-java-extended-scalars</artifactId>
            <version>${graphql-extended-scalars.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Add support for application/graphql content type and post query params

As stated in https://graphql.org/learn/serving-over-http/ besides the 'normal' GET and POST requests the following is recommended:

  • If the "query" query string parameter is present (as in the GET example above), it should be parsed and handled in the same way as the HTTP GET case.
  • If the "application/graphql" Content-Type header is present, treat the HTTP POST body contents as the GraphQL query string.

If interested I can create a PR for this. Let me know.

Note: in the Micronaut integration lib we are doing the same: https://github.com/micronaut-projects/micronaut-graphql/blob/master/graphql/src/main/java/io/micronaut/graphql/GraphQLController.java#L116-L178

Please, Release with DataLoader

Hello,

You've merged the pull request from marceloverdijk.

This allows to use DataLoader with Spring Boot. Very interesting !

Please publish a release, so we can use it for our builds.

Etienne

graphql query methond mapping

I am trying to introduce graphql from my project ,
now , who can tell can me ,how to implement mapping my function with graphql
just like resultful

Code :
public CustomerEntity getCustomer(String account) {
CustomerEntity customerEntity = this.customerMapper.selectByAccount(account);
if (null == customerEntity.getAccount()) {
return null;
}
if (customerEntity.getCustomerStatus() > 9) {
return null;
}
return customerEntity;
}

Properties: graphql.url does not work

Same to the title;

in my application.properties
graphql.url = /other

but still only request to the graphql, and when request to /other ,response with status 404

Request-scoped DataLoaderRegistry blows up

I have given a try the code introduced by #12
I used version 1.0 and created a CustomGraphQLInvocation class based on https://github.com/graphql-java/graphql-java-spring/pull/12/files#diff-cb93f026b7b911f1cdfa731e56caa39f

The comment in this PR says:
I think it's overkill to create a ExecutionInputCustomizer for this, simply defining a (request-scoped) @bean should be sufficient.

I've create that bean like this:

    @Bean
    @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
    public DataLoaderRegistry dataLoaderRegistry() {...}

And it fails with:

{
  "timestamp": "2019-09-29T11:07:42.456+0000",
  "status": 500,
  "error": "Internal Server Error",
  "message": "Error creating bean with name 'scopedTarget.dataLoaderRegistry': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.",
  "path": "/graphql"
}

If I change the scope to WebApplicationContext.SCOPE_APPLICATION then it doesn't throw an error. How should this be approached to have a request scoped DataLoaderRegistry?

A working workaround is to change the DefaultGraphQLInvocation and remove:

    @Autowired(required = false)
    DataLoaderRegistry dataLoaderRegistry;

and replace:

        if (dataLoaderRegistry != null) {
            executionInputBuilder.dataLoaderRegistry(dataLoaderRegistry);
        }

with:

        DataLoaderRegistry dataLoaderRegistry = new DataLoaderRegistry();

        // Loader specific configuration
        DataLoader<String, CountryTO> countryLoader = DataLoader.newDataLoader(dataFetchers.countryBatchLoader());
        dataLoaderRegistry.register("countries", countryLoader);
        // Loader specific configuration end
        
        executionInputBuilder.dataLoaderRegistry(dataLoaderRegistry);
        executionInputBuilder.context(dataLoaderRegistry); // add it to the context to use it later in fetchers

Is it possible to support Mono return type in DataFetcher?

Right now, graphql java webflux only support CompletableFuture return type, the mono returned need to be converted to CompletableFuture by calling toFuture() method.

package net.jaggerwang.scip.gateway.adapter.graphql.datafetcher.query;

import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import net.jaggerwang.scip.gateway.adapter.graphql.datafetcher.AbstractDataFetcher;
import org.springframework.stereotype.Component;

@Component
public class QueryUserInfoDataFetcher extends AbstractDataFetcher implements DataFetcher {
    @Override
    public Object get(DataFetchingEnvironment env) {
        var id = Long.valueOf((Integer) env.getArgument("id"));
        return userAsyncService.info(id)
                .subscriberContext(context -> env.getContext())
                .toFuture();
    }
}

But if I want to use spring security to protect method by @EnableReactiveMethodSecurity, spring security will require the method to return Mono or Flux. The two are conflicted.

@Component
public class QueryUserInfoDataFetcher extends AbstractDataFetcher implements DataFetcher {
    @Override
    @PreAuthorize("isAuthenticated()")
    public Object get(DataFetchingEnvironment env) {
        var id = Long.valueOf((Integer) env.getArgument("id"));
        return monoWithContext(userAsyncService.info(id), env);
    }
}

How can I solve this problem?

Incorrect handling of POST query param

In graphql-java-spring-webmvc, GraphQLController.java, when request has content type of application/json and contains query parameter, it is not handled, as following if statement is trying to parse request body (which is null in this case):

Is this correct behavior? According to this article it is not said that application/json cannot be used with query parameter.

Do Not Support Subscriptions Autoconfigure

The project can autoconfigure graphqlController, provides an rest mapping of /graphql be default. It is great for my project, save me lot of code.

Recently, I want to write a subscription to monitor changes from server. I checked out the source code and related artifacts about spring-boot-starter of graphql.

I founded com.graphql-java:graphql-spring-boot-starter:5.0.2 can do that, but that uses an old graphql-java core dependency version released around 2018. Which have not more newer version, and have unofficial package name in source code, so I think it is likely be DEPRECATED? or retired?

And graphql-java-spring likely the official "starter" project, but have no subscriptions support, any other friends meet the similar problems?

CORS configuration

My Graphql-Java server is working fine and queries work fine through Insomnia. However I am trying to use React/Relay as the client and I am getting the following error message from my React App
Access to fetch at 'http://localhost:8080/graphql' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Do you know how I can set-up my GraphQL server to respond correctly?
Thanks, Martin

When used with webflux and spring security the current implementation will cause the SecurityContext to be lost

Spring webflux uses Reactor for its async logic. Its made of a chain of Mono<T>'s. That chain can carry a context that can be used anywhere in your code within the scope of that single request. The SecurityContext resides within that chain context.

That chain is broken by the underlying implementation of DataFetcher which uses CompletableFuture for its own async code. As a result the User is no longer authenticated at any point after the logic is delegated to GraphQL-Java.

One solution to this is to add the SecurityContext to the ExecutionInput's context, so that we are at least able to access it (even if its not the normal way)

Some example code written in kotlin

@Component
@Internal
class DefaultGraphQLInvocation(private val graphQL: GraphQL) : GraphQLInvocation {
    
    override fun invoke(invocationData: GraphQLInvocationData, webRequest: ServerWebExchange): Mono<ExecutionResult> {
        return ReactiveSecurityContextHolder.getContext().flatMap { securityContext ->
            val executionInput = ExecutionInput.newExecutionInput()
                .query(invocationData.query)
                .operationName(invocationData.operationName)
                .variables(invocationData.variables)
                .context(securityContext)
                .build()

            Mono.fromCompletionStage(graphQL.executeAsync(executionInput))
        }
    }
}

Customize ObjectMapper / inject custom ObjectMapper

Currently the default Spring ObjectMapper is injected.

But in many applications it is already widely used and cannot be customized for graphql.

In my case a legacy app uses ObjectMapper with setSerializationInclusion(Include.NON_NULL). This will break lot's of graphql clients. But I can't change it, because a lot of stuff within the legacy app relies on that behavior.

Multipart upload

I used this depenendy in my app and everything worked just fine, until I decided to add uploading files feature. I have read that this feature is already implemented in
graphql-java-kickstart/graphql-java-servlet#102
so I declared the new Scalar (in schema and in RuntimeWiring configuration .scalar(ApolloScalars.Upload)). Sadly when I try to POST with the file
curl http://localhost:8080/graphql \ -F operations='{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { id } }", "variables": { "file": null } }' \ -F map='{ "0": ["variables.file"] }' \ -F [email protected]
I get
{"timestamp":"2019-05-19T16:31:05.151+0000","status":415,"error":"Unsupported Media Type","message":"Content type 'multipart/form-data;boundary=------------------------07182a3f39beb5b0;charset=UTF-8' not supported","path":"/graphql"}
I figured out that it was because @GraphQLController from this repository only consumes JSON and not multipart/form-data. Do you have any suggestions how to solve it?

graphql-java v15 changed graphql.Assert in ways that breaks graphql-java-spring-webmvc

I'm trying to run the graphql-java-spring-webmvc web app locally, which works fine with graphql-java 14.1, but breaks with 15.0:

2020-06-04 21:55:15.014 ERROR 45400 --- [nio-8080-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.NoSuchMethodError: graphql.Assert.assertNotNull(Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;] with root cause

java.lang.NoSuchMethodError: graphql.Assert.assertNotNull(Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;
	at graphql.spring.web.servlet.GraphQLInvocationData.<init>(GraphQLInvocationData.java:17) ~[graphql-java-spring-webmvc-1.0.jar!/:na]
	at graphql.spring.web.servlet.components.GraphQLController.graphqlPOST(GraphQLController.java:47) ~[graphql-java-spring-webmvc-1.0.jar!/:na]
	...

The problem is that graphql-java version 15 made changes to the graphql.Assert class that aren't backwards-compatible. As a result, the call from the GraphQLInvocationData constructor to Assert.assertNotNull() fails to find a method matching the expected signature.

I don't know if there are other changes in v15 that need to be addressed here as well, but I wanted to bring this one to your attention. I'll stay on v14.1 for now, as a result of this problem.

Provide configuration to disable the GET HTTP method, and consider disabling it by default

Since a GraphQL query can contain mutations, allowing the GET HTTP method opens the door for CSRF attacks in certain configurations.

When GET is allowed for APIs that use HTTP Cookies, a malicious actor can trick another user into clicking a link containing a GraphQL query that mutates state. Of course it’s important to note that disallowing GET is not sufficient to prevent all CSRF attacks.

See the Get Scenario on OWASP's CSRF article for more details.

For an API to be susceptible to this type of attack:

  1. CORS must be configured to allow credentials.
  2. CORS must be configured to allow all origins.
  3. Authentication must be accomplished via HTTP Cookies.

1 and 2 are very common for APIs. 3 is less common (and a bad idea) but still used.

Since GraphQL clients tend to prefer the POST method, disabling the GET method is possible with little (no?) fallout.

Are the project maintainers open to this change? If so I am willing to do the work.

Spring boot version upgrade

Hello,
Are there any plans to upgrade to the latest spring boot version ?
Existing version: 2.1.5.RELEASE (May 2019)

I know spring is moving very fast, but would be good to keep up to date with them - looks like we are +/- 50 releases behind.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.