I'm running a GraphQL API using GraphQL-SPQR and Spring Boot.
At the moment, I am throwing RuntimeExceptions to return GraphQL errors. I have a customExceptionHandler that implements DataFetcherExceptionHandler that returns errors in the correct format, as shown below:
class CustomExceptionHandler : DataFetcherExceptionHandler {
override fun onException(handlerParameters: DataFetcherExceptionHandlerParameters?): DataFetcherExceptionHandlerResult {
// get exception
var exception = handlerParameters?.exception
val locations = listOf(handlerParameters?.sourceLocation)
val path = listOf(handlerParameters?.path?.segmentName)
// create a GraphQLError from your exception
if (exception !is GraphQLError) {
exception = CustomGraphQLError(exception?.localizedMessage, locations, path)
}
// cast to GraphQLError
exception as CustomGraphQLError
exception.locations = locations
exception.path = path
val errors = listOf<GraphQLError>(exception)
return DataFetcherExceptionHandlerResult.Builder().errors(errors).build()
}
}
I use the CustomExceptionHandler as follows (in my main application class):
#Bean
fun graphQL(schema: GraphQLSchema): GraphQL {
return GraphQL.newGraphQL(schema)
.queryExecutionStrategy(AsyncExecutionStrategy(CustomExceptionHandler()))
.mutationExecutionStrategy(AsyncSerialExecutionStrategy(CustomExceptionHandler()))
.build()
}
I'd like to set a header variable for a UUID that corresponds to the exception, for logging purposes. How would I do that?
Even better, is it possible to create a Spring Bean that puts the UUID in the header for all queries and mutations?
Thanks!
when you're using spring boot, there's two options:
you're using the spring boot graphql spqr starter (which brings it's own controller to handle all graphQL requests)
you're using plain graphql-spqr and have your own controller to handle GraphQL requests
In any case, you've got a few options:
Making your CustomExceptionHandler a Spring Bean and Autowiring HttpServletResponse
That would probably be the easiest way to go - and it would probably work in any case: You could simply make your CustomExceptionHandler a Spring bean and have it autowire the HttpServletRequest - in the handler method, you could then set it to whatever you would like it to be. Here's some dummy code in Java (sorry, I am not proficient enough in Kotlin):
#Component
class CustomExceptionHandler implements DataFetcherExceptionHandler {
private final HttpServletResponse response;
public CustomExceptionHandler(HttpServletResponse response) {
this.response = response;
}
#Override
public DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters) {
response.setHeader("X-Request-ID", UUID.randomUUID().toString());
// ... your actual error handling code
}
}
This is going to work because spring will realise that HttpServletRequest differs for each request. It will therefore inject a dynamic proxy into your error handler that will point to the actual HttpServletResponse instance for every request.
I would argue, that it's not the most elegant way, but it will certainly solve your problem.
for the graphql-spqr spring boot starter
There's a default controller implementation that is used in projects using this starter. That controller will handle every graphql request that you receive. You can customise it, by implementing your own GraphQLExecutor and making it a spring bean. That executor is responsible to call the GraphQL engine, pass the parameters in and output the response. Here's the default implementation, that you might want to base your work on.
Similarly to the previous solution, you could autowire the HttpServletResponse in that class and set a HTTP Response header.
That solution would allow you to decide, if you want to set a request id in all cases, or just in specific error cases. (graphql.execute returns an object from which you can get the information if and what errors existed)
when using graphql-spqr without the spring boot starter
Locate your GraphQL controller, add an argument to that method of type HttpServletRequest - and then add headers to that as you prefer (see previous section on some more specific suggestions)
Related
I have a Spring GraphQL project. Each data fetcher (#SchemaMapping) will get data from a remote API protected by authentication.
I need to propagate the authorization header from the original request (that I can see inside the #QueryMapping method) to the data fetcher.
In the data fetcher I can use RequestContextHolder to get the request and the headers like this:
val request = (RequestContextHolder.getRequestAttributes() as ServletRequestAttributes?)?.getRequest()
val token = request?.getHeader("authorization")
This works but I am worried it could break.
Spring GraphQL documentation states that:
A DataFetcher and other components invoked by GraphQL Java may not always execute on the same thread as the Spring MVC handler, for example if an asynchronous WebInterceptor or DataFetcher switches to a different thread.
I tried adding a ThreadLocalAccessor component but it seems to me from debugging and reading source code that the restoreValue method gets called only in a WebFlux project.
How can I be sure to get the right RequestContextHolder in a WebMvc project?
UPDATE
I will add some code to better explain my use case.
CurrentActivity is the parent entity while Booking is the child entity.
I need to fetch the entities from a backend with APIs protected by authentication. I receive the auth token in the original request (the one with the graphql query).
CurrentActivityController.kt
#Controller
class CurrentActivityController #Autowired constructor(
val retrofitApiService: RetrofitApiService,
val request: HttpServletRequest
) {
#QueryMapping
fun currentActivity(graphQLContext: GraphQLContext): CurrentActivity {
// Get auth token from request.
// Can I use the injected request here?
// Or do I need to use Filter + ThreadLocalAccessor to get the token?
val token = request.getHeader("authorization")
// Can I save the token to GraphQL Context?
graphQLContext.put("AUTH_TOKEN", token)
return runBlocking {
// Authenticated API call to backend to get the CurrentActivity
return#runBlocking entityretrofitApiService.apiHandler.activitiesCurrent(mapOf("authorization" to token))
}
}
}
BookingController.kt
#Controller
class BookingController #Autowired constructor(val retrofitApiService: RetrofitApiService) {
#SchemaMapping
fun booking(
currentActivity: CurrentActivity,
graphQLContext: GraphQLContext,
): Booking? {
// Can I retrieve the token from GraphQL context?
val token: String = graphQLContext.get("AUTH_TOKEN")
return runBlocking {
// Authenticated API call to backend to get Booking entity
return#runBlocking currentActivity.currentCarBookingId?.let { currentCarBookingId ->
retrofitApiService.apiHandler.booking(
headerMap = mapOf("authorization" to token),
bookingId = currentCarBookingId
)
}
}
}
}
The ThreadLocalAccessor concept is really meant as a way to store/restore context values in an environment where execution can happen asynchronously, on a different thread if no other infrastructure already supports that.
In the case of Spring WebFlux, the Reactor context is already present and fills this role. A WebFlux application should use reactive DataFetchers and the Reactor Context natively.
ThreadLocalAccessor implementations are mostly useful for Spring MVC apps. Any ThreadLocalAccessor bean will be auto-configured by the starter.
In your case, you could follow one of the samples and have a similar arrangement:
Declare a Servlet filter that extracts the header value and set it as a request attribute with a well-known name
Create a ThreadLocalAccessor component and use it to store request attributes into the context
Fetch the relevant attribute from your DataFetcher
I tried adding a ThreadLocalAccessor component but it seems to me from
debugging and reading source code that the restoreValue method gets
called only in a WebFlux project.
Note that the restoreValue is only called if the current Thread is not the one values where extracted from originally (nothing needs to be done, values are already in the ThreadLocal).
I've successfully tested this approach, getting the "authorization" HTTP header value from the RequestContextHolder. It seems you tried this approach unsuccessfully - could you try with 1.0.0-M3 and let us know if it doesn't work? You can create an issue on the project with a link to a sample project that reproduces the issue.
Alternate solution
If you don't want to deal with ThreadLocal-bound values, you can always use a WebInterceptor to augment the GraphQLContext with custom values.
Here's an example:
#Component
public class AuthorizationWebInterceptor implements WebInterceptor {
#Override
public Mono<WebOutput> intercept(WebInput webInput, WebInterceptorChain chain) {
String authorization = webInput.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
webInput.configureExecutionInput((input, inputBuilder) ->
inputBuilder
.graphQLContext(contextBuilder -> contextBuilder.put("Authorization", authorization))
.build()
);
return chain.next(webInput);
}
}
With that, you can fetch that value from the GraphQL context:
#QueryMapping
public String greeting(GraphQLContext context) {
String authorization = context.getOrDefault("Authorization", "default");
return "Hello, " + authorization;
}
Problem
I have a simple Spring Boot app with a basic RestController (full code available here). It consumes JSON and uses Jackson to convert request from JSON and response to JSON.
#RestController("/")
#RequestMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
public class SomeController {
#Autowired
private SomeService someService;
#PostMapping
public ResponseEntity<SomeResponseDto> post(#RequestBody #Valid SomeRequestDto someRequestDto) {
final SomeResponseDto responseDto = new SomeResponseDto();
responseDto.setMessage(someRequestDto.getInputMessage());
responseDto.setUuid(someService.getUuid());
return ResponseEntity.ok(responseDto);
}
After start-up, the 1st request is about 10-times slower than any sub-sequent request. I debugged and profiled the app and it seems that on first request a Jackson JSON parser is getting initialized somewhere in AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters and AbstractJackson2HttpMessageConverter.
In sub-sequent requests, it seems to get re-used.
Question
How do I initialize Jackson JSON parsing during start-up so that also 1st request is fast?
I know how to trigger a method after Spring started. In PreloadComponent I added as an example how to do a REST request against the controller.
#Component
public class PreloadComponent implements ApplicationListener<ApplicationReadyEvent> {
private final Logger logger = LoggerFactory.getLogger(PreloadComponent.class);
#Autowired
private Environment environment;
#Autowired
private WebClient.Builder webClientBuilder;
#Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// uncomment following line to directly send a REST request on app start-up
// sendRestRequest();
}
private void sendRestRequest() {
final String serverPort = environment.getProperty("local.server.port");
final String baseUrl = "http://localhost:" + serverPort;
final String warmUpEndpoint = baseUrl + "/warmup";
logger.info("Sending REST request to force initialization of Jackson...");
final SomeResponseDto response = webClientBuilder.build().post()
.uri(warmUpEndpoint)
.header(CONTENT_TYPE, APPLICATION_JSON_VALUE)
.body(Mono.just(createSampleMessage()), SomeRequestDto.class)
.retrieve()
.bodyToMono(SomeResponseDto.class)
.timeout(Duration.ofSeconds(5))
.block();
logger.info("...done, response received: " + response.toString());
}
private SomeRequestDto createSampleMessage() {
final SomeRequestDto someRequestDto = new SomeRequestDto();
someRequestDto.setInputMessage("our input message");
return someRequestDto;
}
}
This only works in this toy example. In reality, I have many REST endpoints with complex DTOs and I would need to add a "warm-up" endpoint next to each "real" endpoint as I can't call my real endpoints.
What I already tried?
I added a second endpoint with a different DTO and called it in my PreloadComponent. This doesn't solve the problem. I assume that an Jackson / whatever instance is created for each type.
I autowired ObjectMapper into my PreloadComponent and parsed JSON to my DTO. Again, this doesn't solve the issue.
Full source available at: https://github.com/steinsag/warm-me-up
It turns out that Jackson validation is the problem. I added the JVM option
-verbose:class
to see when classes get loaded. I noticed that on 1st request, there are many Jackson validation classes getting loaded.
To confirm my assumption, I re-worked my example and added another independent warm-up controller with a distinct DTO.
This DTO uses all Java validation annotations also present like in the real DTO, e.g. #NotNull, #Min, etc. In addition, it also has a custom enum to also have validation of sub-types.
During start-up, I now do a REST request to this warm-up endpoint, which doesn't need to contain any business logic.
After start-up, my 1st request is now only 2-3 times slower than any sub-sequent requests. This is is acceptable. Before, the 1st request was 20-40 times slower.
I also evaluated if really a REST request is needed or if it is sufficient to just do JSON parsing or validation of a DTO (see PreloadComponent). This reduces runtime of 1st request a bit, but it is still 5-15 times slower than with proper warm-up. So I guess a REST request is needed to also load other classes in Spring Dispatcher, etc.
I updated my example at: https://github.com/steinsag/warm-me-up
I believe, that a lot of classes will be lazy-loaded. If first call performance is important, then I think warming up by calling each endpoint is the way to go.
Why do you say, that you cannot call the endpoints? If you have a database and you don't want to change the data, wrap everything in a transaction and roll it back after the warm up calls.
I haven't seen any other method to solve this, which doesn't necessarily mean, that it doesn't exist ;)
tl;dr: how to enable spring's ResourceUrlEncodingFilter for spring boot Error pages?
(Question written while using spring boot 1.3.7.RELEASE and Spring Framework/MVC 4.2.4.RELEASE)
Some background: We have a fairly standard spring boot/spring webmvc project using Thymeleaf as the view layer. We have the out-of-the-box spring boot Resource Chain enabled to serve static assets.
Our thymeleaf views have standard url-encoding syntax in them such as <script th:src="#{/js/some-page.js}"></script>. This relies on Spring's org.springframework.web.servlet.resource.ResourceUrlEncodingFilter to transform the url into an appropriately-versioned url such as /v1.6/js/some-page.js.
Our error handling is done by:
setting server.error.whitelabel.enabled=false
subclassing spring boot's default BasicErrorController to override public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response)
relying on our already-configured thymeleaf view resolvers to render our custom error page
The problem is: the ResourceUrlEncodingFilter isn't applying on our error pages. I assume it's a lack of the filter being registered for ERROR dispatched requests, but it's not obvious to me: a) how to customize this in spring boot; and b) why this wasn't done by default.
Update 1:
The issue seems to be with a combination of OncePerRequestFilter and the ERROR dispatcher. Namely:
ResouceUrlEncodingFilter does not bind to the ERROR dispatcher by default. While overriding this is messy it's not impossible, but doesn't help due to:
OncePerRequestFilter (parent of ResourceUrlEncodingFilter) sets an attribute on the Request indicating it's been applied so as to not re-apply. It then wraps the response object. However, when an ERROR is dispatched, the wrapped response is not used and the filter does not re-wrap due to the request attribute still being present.
Worse still, the logic for customizing boolean hasAlreadyFilteredAttribute is not overridable by request. OncePerRequestFilter's doFilter() method is final, and getAlreadyFilteredAttributeName() (the extension point) does not have access to the current request object to get the dispatcher.
I feel like I must be missing something; it seems impossible to use versioned resources on a 404 page in spring boot.
Update 2: A working but messy solution
This is the best I've been able to come up with, which still seems awfully messy:
public abstract class OncePerErrorRequestFilter extends OncePerRequestFilter {
#Override
protected String getAlreadyFilteredAttributeName() {
return super.getAlreadyFilteredAttributeName() + ".ERROR";
}
#Override
protected boolean shouldNotFilterErrorDispatch() {
return false;
}
}
public class ErrorPageCapableResourceUrlEncodingFilter extends OncePerErrorRequestFilter {
// everything in here is a perfect copy-paste of ResourceUrlEncodingFilter since the internal ResourceUrlEncodingResponseWrapper is private
}
// register the error-supporting version if the whitelabel error page has been disabled ... could/should use a dedicated property for this
#Configuration
#AutoConfigureAfter(WebMvcAutoConfiguration.class)
#ConditionalOnClass(OncePerErrorRequestFilter.class)
#ConditionalOnWebApplication
#ConditionalOnEnabledResourceChain
#ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", havingValue="false", matchIfMissing = false)
public static class ThymeleafResourceUrlEncodingFilterErrorConfiguration {
#Bean
public FilterRegistrationBean errorPageResourceUrlEncodingFilterRegistration() {
FilterRegistrationBean reg = new FilterRegistrationBean();
reg.setFilter(new ErrorPageCapableResourceUrlEncodingFilter());
reg.setDispatcherTypes(DispatcherType.ERROR);
return reg;
}
}
Better solutions?
This has been reported in spring-projects/spring-boot#7348 and a fix is on its way.
It seems you've made an extensive analysis of the issue; too bad you didn't report this issue earlier. Next time, please consider raising those on the Spring Boot tracker.
Thanks!
I have a scenario where I need to run a few db checks at the start of every web request, and in the case of success I need to store objects for use later in the request by the controller, or in the case of failure I need to render an error page.
A very similar real world example would be a SaaS app checking and loading the account based on a vanity url, then storing the account for use by controllers to avoid multiple db requests.
What are the best ways to achieve this in a Spring boot app? I have experimented with Filters but I think an Interceptor might be better at the task, that covers running the check but what about storing the objects for later use? Is there a request lifecycle context of some kind that I can store against?
Spring supports request scope for beans. You can use them for storing data used during request execution.
In my experience, best way I've done similar stuff is through HandlerMethodArgumentResolver.
Basically imagine you have a custom type, let's call it UserContext where you store the information that's needed for the request. And you have a UserContextService let's say that has a method getUserContext(HttServletRequest), that is used to retrieve the context based on the request, from which you can call your database based on whatever request parameter/header/path-variable, etc. You can refine that as you need. But based on this simple assumptions, you can have a controller that looks like this:
#RequestMapping("/some/url")
public SomeResponse someMethod(UserContext userContext, ...) {
//do something here with UserContext
}
The way that Spring will inject this UserContext into your controller would be with a custom HandlerMethodArgumentResolver like this:
#Component
public class UserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
#Autowired
UserContextService
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(UserContext.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest req = (HttpServletRequest)webRequest.getNativeRequest();
UserContext userContext = userContextService.getUserContext(req);
if (userContext != null) {
return userContext;
} else {
return WebArgumentResolver.UNRESOLVED;
//Or throw exception
}
}
}
That you'll register by overriding the WebMvcConfigurer.addArgumentResolvers method in your WebMvcConfigurer bean/config-class.
This mechanism is the same used by #PathVariable, #RequestParam, etc...
I have a basic Spring MVC controller that looks like this:
#Controller
public void MyController {
#RequestMapping("/secret")
public String show() {
return "secret.jsp";
}
}
I am going to have several similar URLs that can only be reached by signed-in users. Since this is a cross-cutting concern, I'd like to use AOP, and I'd like to make this work via annotations. In other words, I'd like to throw a #RequiresLogin annotation on every controller method that needs to be secret.
AnnotationMethodHandlerAdapter supports the concept of interceptors, which seems on the surface like the right way to go for this. However, I want to know which method is going to be invoked so that I can check it for my #RequiresLogin annotation. I see that there's an "Object handler" parameter that's passed in, but I'm not sure how to turn that into a Class and Method that will be invoked.
Ideas?
There are no good ways to get a method signature in the interceptor.
Try to apply a regular AOP advise to your controller, Spring MVC plays well with it as long as target class proxying is used.
As axtavt writes correctly, Spring-AOP works well with controllers if using proxy-target-class. But there is also the possibility of using JDK proxies if you follow some (tedious) conventions:
Working with interface-based #Controller classes
A common pitfall when working with
annotated controller classes happens
when applying functionality that
requires creating a proxy proxy for
the controller object (e.g.
#Transactional methods). Usually you
will introduce an interface for the
controller in order to use JDK dynamic
proxies. To make this work you must
move the #RequestMapping annotations
to the interface as the mapping
mechanism can only "see" the interface
exposed by the proxy. As an
alternative, you may choose to
activate proxy-target-class="true" in
the configuration for the
functionality applied to the
controller (in our transaction
scenario in <tx:annotation-driven />).
Doing so indicates that CGLIB-based
subclass proxies should be used
instead of interface-based JDK
proxies. For more information on
various proxying mechanisms see
Section 7.6, “Proxying mechanisms”.
Source: 15.3.2 Mapping requests with #RequestMapping
While using spring security would be the optimal approach here, you can implement similar functionality using Spring Aspects. Here is an example of using an Aspect to check for a method containing a particular Annotation.
#Aspect
public class MyAspect {
#Around("execution(* com.test.controllers..*.**(..)) && " +
"within(#org.springframework.sterotype.Controller *)")
public Object execute(ProceedingJoinPoint joinPoint) {
Object target = joinPoint.getTarget();
if (target != null) {
Signature tSig = joinPoint.getSignature();
if (tSig instanceof MethodSignature) {
MethodSignature mSig = (MethodSignature) tSig;
Method method = mSig.getMethod();
if (method != null && method.isAnnotationPresent(MyAnnotation.class)) {
// do something
// parameters are available from joinPoint.getArgs();
}
}
}
}
// allow method invocation to continue
return joinPoint.proceed();
}
The format of the #Around advice will be specific to your application. In this example, it checks for any class annotated with Controller in the package com.test.controllers and all subpackages. See http://static.springsource.org/spring/docs/3.0.x/reference/aop.html for additional options.
Good luck!
How about ResolveHandlerMethodInterceptor using reflection.
Below code is experimental and version-dependent(spring 3.0.2).
import java.lang.reflect.Method;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.FrameworkServlet;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;
public class ResolveHandlerMethodInterceptor implements HandlerInterceptor {
public final static String HANDLER_METHOD = "handlerMethod";
// Here is your servlet name
public final static String SERVLET_NAME = "XXXXX";
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object object, ModelAndView modelAndView)
throws Exception {
Method handlerMethod = (Method) request.getAttribute(HANDLER_METHOD);
System.out.println("postHandle>>>" + handlerMethod);
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object object, Exception exception)
throws Exception {
Method handlerMethod = (Method) request.getAttribute(HANDLER_METHOD);
System.out.println("afterCompletion>>>" + handlerMethod);
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
ServletContext servletContext = request.getSession().getServletContext();
String attrName = FrameworkServlet.SERVLET_CONTEXT_PREFIX + SERVLET_NAME;
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext, attrName);
AnnotationMethodHandlerAdapter adapter = context.getBean(AnnotationMethodHandlerAdapter.class);
Method getMethodResolverMethod = adapter.getClass().getDeclaredMethod("getMethodResolver", Object.class);
getMethodResolverMethod.setAccessible(true);
Object servletHandlerMethodResolver = getMethodResolverMethod.invoke(adapter, object);
Method resolveHandlerMethod = servletHandlerMethodResolver.getClass().getMethod("resolveHandlerMethod", HttpServletRequest.class);
resolveHandlerMethod.setAccessible(true);
Method handlerMethod = (Method) resolveHandlerMethod.invoke(servletHandlerMethodResolver, request);
request.setAttribute(HANDLER_METHOD, handlerMethod);
System.out.println("preHandle>>>" + handlerMethod);
return true;
}
}
==reference==
http://toby.epril.com/?p=934
http://www.jarvana.com/jarvana/view/org/springframework/spring-webmvc/3.0.2.RELEASE/spring-webmvc-3.0.2.RELEASE-sources.jar!/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java?format=ok
So, these approaches listed are good, but they all have limitations. The AOP stuff is a good idea, but its limitation is that I need a way to get ahold of the request and response objects if I want to redirect or modify the response. The controller methods don't necessarily need the requests and responses, and requiring that they appear seems inelegant. I could use spring magic to get the request object from the Aspect, but I couldn't find a way to get the response.
Eventually, I came up with a middle way. I used a filter bean to get the request and the response objects and store them in a ThreadLocal. Then I created an aspect that has a reference to that filter, so that it could easily see the request and response objects.
Then I made the aspect wrap around methods based on the annotation, so I didn't even need to check on whether the annotation was present using code.
This combination approach appears to be working perfectly!
The only downside is that I can't figure out a good way to write an integration test that verifies that the aspect is invoked when there's an incoming request to that URL. It's a little scary that removing a single annotation leaves all my tests passing but allows unauthorized users through.
Thanks everybody for the great suggestions!