Reactive File Controller with Custom response headers - spring

I am building a File Controller that utilizes zero-copy NIO functionality using Spring Boot 2.0 Spring Webflux. I also want to send custom response headers (for response caching, etc)
The code looks like below:
#RestController
#RequestMapping("/file")
public class FileController {
#GetMapping(value = "/{fileName:.+}")
public Mono<ServerResponse> getFile(#PathVariable("fileName") String fileName) {
Resource body = new ClassPathResource(fileName);
return ServerResponse.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, String.format("inline; filename=\"%s\"", fileName))
.contentType(MediaType.TEXT_PLAIN)
.body(BodyInserters.fromResource(body));
}
}
But when I execute the request from browser, I get the below error ins server logs:
2018-07-23 12:07:53.928 ERROR 9772 --- [ctor-http-nio-3] o.s.w.s.adapter.HttpWebHandlerAdapter : Unhandled failure: Type definition error: [simple type, class org.springframework.web.reactive.function.server.DefaultServerResponseBuilder$BodyInserterResponse]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.springframework.web.reactive.function.server.DefaultServerResponseBuilder$BodyInserterResponse and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS), response already set (status=null)
2018-07-23 12:07:53.928 WARN 9772 --- [ctor-http-nio-3] o.s.h.s.r.ReactorHttpHandlerAdapter : Handling completed with error: Type definition error: [simple type, class org.springframework.web.reactive.function.server.DefaultServerResponseBuilder$BodyInserterResponse]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.springframework.web.reactive.function.server.DefaultServerResponseBuilder$BodyInserterResponse and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
I don't know whats going wrong here. If I return Mono<Resource> from the controller method, then everything works fine but then I loose flexibility of providing customer response headers.
Any help on this is appreciated.

ServerResponse should be used with Spring WebFlux functional only - for this use case with the annotation model, ResponseEntity is the proper choice.
Because the WebFlux annotation model doesn't recognize the ServerResponse type, it is assuming it should serialize it (here, with Jackson).

Related

How to get user task List with assignee userId in camunda with java api?

I want to get user tasklist with java api in my service or controller layer with:
taskService.createTaskQuery().taskAssignee(userId) for Camunda Engine; and my code:
#GetMapping("/user-tasklist={userId}")
#ResponseBody
public List<Task> userTaskList(#PathVariable String userId){
return taskService.createTaskQuery().taskAssignee(userId).list();
}
And i get the error :Context.getCommandContext()" is null!!!
What is "CommandContext()" and how do I solve this problem? Am I using the wrong method? Such a method was used in the documentation and code samples, but I did not see anything about "CommandContext()" and it seems that this method works, but not for me.
Error:
2022-12-29 00:37:44.263 WARN 17664 --- [nio-8081-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Cannot invoke "org.camunda.bpm.engine.impl.interceptor.CommandContext.getExecutionManager()" because the return value of "org.camunda.bpm.engine.impl.context.Context.getCommandContext()" is null; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Cannot invoke "org.camunda.bpm.engine.impl.interceptor.CommandContext.getExecutionManager()" because the return value of "org.camunda.bpm.engine.impl.context.Context.getCommandContext()" is null (through reference chain: java.util.ArrayList[0]->org.camunda.bpm.engine.impl.persistence.entity.TaskEntity["execution"])]
You are returning the Task directly from the camunda java API. This Taskis just an interface, implemented by the inner logic of a TaskEntity in camunda. It is not serializable via json.
You need to map the task in the controller function to some kind of TaskDto that can be serialized.
You can write your own custom dto, use the default camunda REST API Task based on the OpenAPI spec (example here) or use (my) immutables project:
ImmutableTask dto = CamundaImmutables.task(task)

#PathVariable validation gives 500 instead of 400

In the Spring Framework validating the request respond with error 400 (Bad Request), specially when validating the request body and the request fields decorated with the
javax.validation.constraints.* annotations (which specified in JSR 303).
For make it more clear lets go through the example:
I have decorated the fields of the class ContactDetails with #Email and #NotEmpty constraints
class ContactDetails {
#Email
String email;
#NotEmpty
String message;
}
In the controller I used #Valid annotation to make Spring Validator validate the http request body.
#RestController
class NotificationController {
#PostMapping("/sendNotification")
public String sendNotification(#Valid #RequestBody ContactDetails contactDetails) {
...
}
}
If the validation fails, it will trigger a MethodArgumentNotValidException. By default, Spring will translate this exception to a HTTP status 400 (Bad Request).
But for validating the request params or path variables based on Spring documentations I will decorate the controller class with #Validated and just using javax.validation.constraints.* annotations on the parameters and I expect the same result same as validating the request body.
#Validated
#RestController
class NotificationController {
#GetMapping("/getContactDetailsByEmail/{email}")
public ContactDetails findContactDetails(#Email String email) {
...
}
}
In contrast to request body validation a failed validation will trigger a ConstraintViolationException instead of a MethodArgumentNotValidException. Spring does not register a default exception handler for this exception, so it will by default cause a response with HTTP status 500 (Internal Server Error).
I expected to get error 400 for this scenario and I do not know if I missed any thing in my code?
That would be great if any body can help me with this scenario why Spring has different approaches for validating the parameters.
You can create the answer you want by using the fields in the ConstraintViolationException with the following method;
#ExceptionHandler(ConstraintViolationException.class)
protected ResponseEntity<Object> handlePathVariableError(final ConstraintViolationException exception) {
log.error(exception.getMessage(), exception);
final List<SisSubError> subErrors = new ArrayList<>();
exception.getConstraintViolations().forEach(constraintViolation -> subErrors.add(generateSubError(constraintViolation)));
final SisError error = generateErrorWithSubErrors(VALIDATION_ERROR, HttpStatus.BAD_REQUEST, subErrors);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
In the first case - with argument annotated with #RequestBody - Spring uses RequestResponseBodyMethodProcessor to validate it and throws MethodArgumentNotValidException if validation fails, which is handled later by ResponseEntityExceptionHandler or DefaultHandlerExceptionResolver by translating it into 400 BAD REQUEST http response code.
In the second case Spring is using AOP for method validation with MethodValidationInterceptor class, which throws ConstraintViolationException if validation fails.
But, unlike the first case, Spring doesn't provide a default exception handler for ConstraintViolationException, so it's translated into 500 http response code.
That's why developers should consider creating their own exception handlers for this kind of method-level validation.

How to set header variables in GraphQL-SPQR

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)

RequestMapping conversion exception

I have a Spring RequestMapping that is taking a JSON structure and converting it to a POJO. If I send the route a malformed JSON structure, I get back a "400 Bad Request" but what I'd like to receive is an error message stating why it was a bad request. I have a Validator also checking the JSON structure, but it seems that it's not called if the structure is unable to be converted. Is there a way in which I can access the built in HttpMessageConverter errors or exceptions?
I think you can use a ResponseEntityExceptionHandler for this, by overriding the handleHttpMessageNotReadable method.
From: 17.11.3 Handling Standard Spring MVC Exceptions:
If you prefer to write error content via #ExceptionHandler methods you can extend ResponseEntityExceptionHandler instead. This is a convenient base for #ControllerAdvice classes providing an #ExceptionHandler method to handle standard Spring MVC exceptions and return ResponseEntity. That allows you to customize the response and write error content with message converters. See the ResponseEntityExceptionHandler javadocs for more details.
An example usage of the ResponseEntityExceptionHandler can be found here: http://www.jayway.com/2013/02/03/improve-your-spring-rest-api-part-iii/
Otherwise you can find more spring mvc exception handling approaches here:
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
If your needs aren't too complex you might just want to use a SimpleMappingExceptionResolver
ResponseEntityExceptionHandler is used to identify the Spring MVC specific exceptions & to implement a common error response handling strategy for them.
It consists of many methods for example:
handleMissingServletRequestParameter > handle if invalid request params
handleHttpMessageNotReadable > handles if invalid request body
handleExceptionInternal > A single place to customize the response body of all exception types.
The below mentioned code overrides handleExceptionInternal method
#ControllerAdvice
public class JavaWebExeptionHandler extends ResponseEntityExceptionHandler {
public class ExpnDetails {
public int expnCode;
public String expnMessage;
}
// A single place to customize the response body of all exception types.
#Override
protected ResponseEntity<ExpnDetails> handleExceptionInternal(Exception ex, Object body,
HttpHeaders headers,HttpStatus status, WebRequest request) {
return new ResponseEntity<Object>(new ExpnDetails(status.value(), ex.getMessage()), status);
}
}
So all the Spring MVC specific exception are handled at one place and ExpnDetails object is sent as response body which consists of a expnCode & expnMessage.
Output:
{
"expnCode": 415,
"expnMessage": "Content type 'application/json1' not supported"
}
{
"expnCode": 400,
"expnMessage": "JSON parse error: Unexpected character ('"
' (code 34)): was expecting comma to separate Object entries; nested exception is com.fasterxml.jackson.core.JsonParseException: Unexpected character ('
"' (code 34)): was expecting comma to separate Object entries at [Source: (PushbackInputStream); line: 3, column: 4]"
}
Note this solution also helps in hiding container(ex. tomcat server) details to appear in response error when request fails.

Spring tries to instantiate the interface when injecting a parameter

I hope you can help me with this.
I'm using Spring MVC (3.1.1) in my web application, and am facing a strange situation.
I have this simple #Controller, that makes use of the ServicioUsuario Service, and works fine:
#Controller
#RequestMapping("/ajax")
public class ControladorAjax extends ControladorGenerico {
#Autowired
ServicioUsuario servicioUsuario;
#RequestMapping("/check")
public ResponseEntity<String> check(#RequestParam String email) {
// Declarations and other operations omitted...
// Use servicioUsuario
servicioUsuario.doStuff();
return response;
}
}
However, if I remove the #Autowiring, and try to make Spring inject servicioUsuario as a parameter (i.e. by changing the method signature to: public ResponseEntity<String> check(#RequestParam String email, ServicioUsuario servicioUsuario)) the whole thing breaks, and I get this sort of exceptions in Tomcat's log:
javax.servlet.ServletException: NestedServletException in java.lang.Thread.getStackTrace:: Request processing failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [com.package.ServicioUsuario]: Specified class is an interface
I have these interfaces:
com.package
|-> Servicio.java (interface)
|-> ServicioUsuario.java (interface that extends Servicio)
and these clases:
com.package.impl
|-> ServicioImpl.java (implements Servicio)
|-> ServicioUsuarioImpl.java (#Service("servicioUsuario") that extends ServicioImpl implements ServicioUsuario)
and configured Spring to scan both packages with:
<context:component-scan base-package="com.package
com.package.impl" />
Why is Spring trying to instantiate the interface and not the implementing class? Is it something I'm doing wrong?
According to 16.3.3.1 Supported method argument types of the official documentation, this is a complete list of what controller methods can take:
Request or response objects (Servlet API). [...] for example ServletRequest or HttpServletRequest.
Session object (Servlet API): of type HttpSession.
org.springframework.web.context.request.WebRequest or org.springframework.web.context.request.NativeWebRequest.
java.util.Locale for the current request locale [...]
java.io.InputStream / java.io.Reader for access to the request's content. [...]
java.io.OutputStream / java.io.Writer for generating the response's content. [...]
java.security.Principal containing the currently authenticated user.
#PathVariable annotated parameters for access to URI template variables. [...]
#RequestParam annotated parameters for access to specific Servlet request parameters. [...]
#RequestHeader annotated parameters for access to specific Servlet request HTTP headers. [...]
#RequestBody annotated parameters for access to the HTTP request body. [...]
#RequestPart annotated parameters for access to the content of a "multipart/form-data" request part. [...]
HttpEntity<?> parameters for access to the Servlet request HTTP headers and contents. [...]
java.util.Map / org.springframework.ui.Model / org.springframework.ui.ModelMap [...]
org.springframework.web.servlet.mvc.support.RedirectAttributes [...]
Command or form objects to bind request parameters to bean properties (via setters) or directly to fields, [...]
org.springframework.validation.Errors / org.springframework.validation.BindingResult [...]
org.springframework.web.bind.support.SessionStatus [...]
org.springframework.web.util.UriComponentsBuilder [...]
As you can see, Spring beans are not on this (quite impressive) list. Why would you inject services via controller method? They never change. It's enough to inject them once and assign them to a field.

Resources