Please excuse any horrible mistakes, I literally picked up Kotlin fully and some Spring about a week ago, due to a project requirement. I am trying to create a simple RESTful API with an endpoint that can accept a file via Multipart. Later on, there will be a few pages outside the API that will display HTML, I am using Koreander for that. From what I can see, and based on Java tutorials, exception handling should work like this.
For the API, my defined exception handler for MaxUploadSizeExceededException, however, does not fire at all. My application.kt:
#SpringBootApplication
#EnableConfigurationProperties(StorageProperties::class)
class JaApplication {
#Bean fun koreanderViewResolver(): ViewResolver = KoreanderViewResolver()
}
fun main(args: Array<String>) {
runApplication<JaApplication>(*args)
}
My controller:
#RestController
#RequestMapping("/api")
#EnableAutoConfiguration
class APIController {
#PostMapping(
value = "/convert",
produces = arrayOf(MediaType.APPLICATION_JSON_VALUE)
)
fun convert(#RequestParam("file") multipartFile: MultipartFile): Result {
return Result(status = 0, message = "You have uploaded: ${multipartFile.getOriginalFilename()}.")
}
}
#ControllerAdvice
class ExceptionHandlers {
#ExceptionHandler(MultipartException::class)
fun handleException(e: MultipartException): String = Result(status = 1, message = "File is too large.")
}
}
When I am attempting to post a large file via curl, I get a JSON reply:
curl -F 'file=#path-to-large-file' http://localhost:8080/api/convert
{"timestamp":"2018-11-27T15:47:31.907+0000","status":500,"error":"Internal Serve
r Error","message":"Maximum upload size exceeded; nested exception is java.lang.
IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$Siz
eLimitExceededException: the request was rejected because its size (4294967496)
exceeds the configured maximum (529530880)","path":"/api/convert"}
Is it possible that Tomcat does not pass this exception to Spring? If yes, how may I go about catching this? It also works if I can set the size to unlimited and check for file size at upload time, though I would need to do that before the server starts receiving the file, and I assume by the time I get to the /api/convert endpoint, it is too late.
Thanks for any help.
Found the issue. I'm posting it for anyone else who might have the same issue in the future.
#ControllerAdvice
class ExceptionHandlers():ResponseEntityExceptionHandler() {
#ExceptionHandler(MaxUploadSizeExceededException::class)
fun handleException(e: MultipartException, request:WebRequest): ResponseEntity<Any> {
return handleExceptionInternal(e, Result(message = "File is too large."), HttpHeaders(), HttpStatus.PAYLOAD_TOO_LARGE, request)
}
}
Related
I have a feign client like this with endpoints to two APIs from PROJECT-SERVICE
#FeignClient(name = "PROJECT-SERVICE", fallbackFactory = ProjectServiceFallbackFactory.class)
public interface ProjectServiceClient {
#GetMapping("/api/projects/{projectKey}")
public ResponseEntity<Project> getProjectDetails(#PathVariable("projectKey") String projectKey);
#PostMapping("/api/projects")
public ResponseEntity<Project> createProject(#RequestBody Project project);
}
I'm using those clients like this:
#Service
public class MyService {
#Autowired
private ProjectServiceClient projectServiceClient;
public void doSomething() {
// Some code
ResponseEntity<Project> projectResponse = projectServiceClient.getProjectDetails(projectKey);
// Some more code
}
public void doSomethingElse() {
// Some code
ResponseEntity<Project> projectResponse = projectServiceClient.createProject(Project projectToBeCreated);
// Some more code
}
}
My problem is, most of the times (around 60% of the time), either one of these Feign calls result in a HystrixTimeoutException.
I initially thought there could be a problem in the downstream micro service (PROJECT-SERVICE in this case), but that is not the case. In fact, when getProjectDetails() or createProject() is called, the PROJECT-SERVICE actually does the job and returns a ResponseEntity<Project> with status 200 and 201 respectively, but my fallback is activated with the HystrixTimeoutException.
I'm trying in vain to find what might be causing this issue.
I, however, have this in my main application configuration:
feign.hystrix.enabled=true
feign.client.config.default.connect-timeout=5000
feign.client.config.default.read-timeout=60000
Can anyone point me towards a solution?
Thanks,
Sriram Sridharan
Hystrix's timeout is not tied to that of Feign. There is a default 1 second execution timeout enabled for Hystrix. You need to configure this timeout to be slightly longer than Feign's, to avoid HystrixTimeoutException getting thrown earlier than desired timeout. Like so:
feign.client.config.default.connect-timeout=5000
feign.client.config.default.read-timeout=5000
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=6000
Doing so would allow FeignException, caused by timeout after 5 seconds, to be thrown first, and then wrapped in a HystrixTimeoutException
Using Springboot 1.5.x, Spring Cloud, and JAX-RS:
I could use a second pair of eyes since it is not clear to me whether the Spring configured, Javanica HystrixCommand works for all use cases or whether I may have an error in my code. Below is an approximation of what I'm doing, the code below will not actually compile.
From below WebService lives in a library with separate package path to the main application(s). Meanwhile MyWebService lives in the application that is in the same context path as the Springboot application. Also MyWebService is functional, no issues there. This just has to do with the visibility of HystrixCommand annotation in regards to Springboot based configuration.
At runtime, what I notice is that when a code like the one below runs, I do see "commandKey=A" in my response. This one I did not quite expect since it's still running while the data is obtained. And since we log the HystrixRequestLog, I also see this command key in my logs.
But all the other Command keys are not visible at all, regardless of where I place them in the file. If I remove CommandKey-A then no commands are visible whatsoever.
Thoughts?
// Example WebService that we use as a shared component for performing a backend call that is the same across different resources
#RequiredArgsConstructor
#Accessors(fluent = true)
#Setter
public abstract class WebService {
private final #Nonnull Supplier<X> backendFactory;
#Setter(AccessLevel.PACKAGE)
private #Nonnull Supplier<BackendComponent> backendComponentSupplier = () -> new BackendComponent();
#GET
#Produces("application/json")
#HystrixCommand(commandKey="A")
public Response mainCall() {
Object obj = new Object();
try {
otherCommandMethod();
} catch (Exception commandException) {
// do nothing (for this example)
}
// get the hystrix request information so that we can determine what was executed
Optional<Collection<HystrixInvokableInfo<?>>> executedCommands = hystrixExecutedCommands();
// set the hystrix data, viewable in the response
obj.setData("hystrix", executedCommands.orElse(Collections.emptyList()));
if(hasError(obj)) {
return Response.serverError()
.entity(obj)
.build();
}
return Response.ok()
.entity(healthObject)
.build();
}
#HystrixCommand(commandKey="B")
private void otherCommandMethod() {
backendComponentSupplier
.get()
.observe()
.toBlocking()
.subscribe();
}
Optional<Collection<HystrixInvokableInfo<?>>> hystrixExecutedCommands() {
Optional<HystrixRequestLog> hystrixRequest = Optional
.ofNullable(HystrixRequestLog.getCurrentRequest());
// get the hystrix executed commands
Optional<Collection<HystrixInvokableInfo<?>>> executedCommands = Optional.empty();
if (hystrixRequest.isPresent()) {
executedCommands = Optional.of(hystrixRequest.get()
.getAllExecutedCommands());
}
return executedCommands;
}
#Setter
#RequiredArgsConstructor
public class BackendComponent implements ObservableCommand<Void> {
#Override
#HystrixCommand(commandKey="Y")
public Observable<Void> observe() {
// make some backend call
return backendFactory.get()
.observe();
}
}
}
// then later this component gets configured in the specific applications with sample configuraiton that looks like this:
#SuppressWarnings({ "unchecked", "rawtypes" })
#Path("resource/somepath")
#Component
public class MyWebService extends WebService {
#Inject
public MyWebService(Supplier<X> backendSupplier) {
super((Supplier)backendSupplier);
}
}
There is an issue with mainCall() calling otherCommandMethod(). Methods with #HystrixCommand can not be called from within the same class.
As discussed in the answers to this question this is a limitation of Spring's AOP.
I'm currently writing a REST service with spring boot which should provide a file download, i. e. a client application can download files from the service. A file can be several gigabytes big (sometimes bigger than the main memory), i. e. loading the file into the main memory is not an option. So I need some kind of streaming meachanism when sending a file to the client.
One promising solution on the net (taken from here):
#RestController
// ...
#RequestMapping(value = "/files/{file_name}", method = RequestMethod.GET)
#ResponseBody
public FileSystemResource getFile(#PathVariable("file_name") String fileName) {
return new FileSystemResource(myService.getFileFor(fileName));
}
But it does not work. I get:
java.lang.IllegalStateException: Unsupported resource class: class org.springframework.core.io.FileSystemResource
at org.springframework.http.converter.ResourceHttpMessageConverter.readInternal(ResourceHttpMessageConverter.java:100)
I researched but don't know what is causing the issue. I tried other approaches (e. g. Downloading a file from spring controllers) but I get the same error. Does anyone know why the solutions seem to work for others but not for me?
Edit: Here is where I call the REST service (jUnit test case):
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class DownloadTest {
#LocalServerPort
private int port;
private RestTemplate template;
private String requestPath;
#Before
public void setUp() throws Exception {
template = new RestTemplate();
requestPath = "http://localhost:" + port + "/files/download";
}
#Test
public void test() {
Resource FileSystemResource =
template.getForObject(requestPath, FileSystemResource.class);
}
}
The code of the RestController is working as expected with a small memory footprint (the file is never loaded to the main memory completely).
The client code that works for me (also with a small memory footprint) can be found here.
i fixed the java.lang.IllegalStateException: Unsupported resource error changing this
Resource FileSystemResource =
template.getForObject(requestPath, FileSystemResource.class);
for this
Resource FileSystemResource =
template.getForObject(requestPath, Resource .class);
I want to intercept a bad JSON input and return custom error messages using Dropwizard application. I followed the approach of defining a custom exception mapper as mentioned here : http://gary-rowe.com/agilestack/2012/10/23/how-to-implement-a-runtimeexceptionmapper-for-dropwizard/ . But it did not work for me. This same question has been asked here https://groups.google.com/forum/#!topic/dropwizard-user/r76Ny-pCveA but unanswered.
Any help would be highly appreciated.
My code below and I am registering it in dropwizard as environment.jersey().register(RuntimeExceptionMapper.class);
#Provider
public class RuntimeExceptionMapper implements ExceptionMapper<RuntimeException> {
private static Logger logger = LoggerFactory.getLogger(RuntimeExceptionMapper.class);
#Override
public Response toResponse(RuntimeException runtime) {
logger.error("API invocation failed. Runtime : {}, Message : {}", runtime, runtime.getMessage());
return Response.serverError().type(MediaType.APPLICATION_JSON).entity(new Error()).build();
}
}
Problem 1:
The exception being thrown by Jackson doesn't extends RuntimeException, but it does extend Exception. This doesn't matter though. (See Problem 2)
Problem 2:
DropwizardResourceConfig, registers it's own JsonProcessingExceptionMapper. So you should already see results similar to
{
"message":"Unrecognized field \"field\" (class d.s.h.c.MyClass),..."
}
Now if you want to override this, then you should create a more specific exception mapper. When working with exception mappers the most specific one will be chosen. JsonProcessingException is subclassed by JsonMappingException and JsonProcessingException, so you will want to create an exception mapper for each of these. Then register them. I am not sure how to unregister the Dropwizard JsonProcessingExceptionMapper, otherwise we could just create a mapper for JsonProcessingException, which will save us the hassle of create both.
Update
So you can remove the Dropwizard mapper, if you want, with the following
Set<Object> providers = environment.jersey().getResourceConfig().getSingletons();
Iterator it = providers.iterator();
while (it.hasNext()) {
Object val = it.next();
if (val instanceof JsonProcessingExceptionMapper) {
it.remove();
break;
}
}
Then you are free to use your own mapper, JsonProcessingException
I am using quartz within a Spring MVC application.
I have a task class, all functionality and logic is written there. I have a separate quartz configuration file. I am just hitting a URL and in controller function initializing the quartz conf file. The job is running fine. The issue I am facing is:
In my task class, some code is running and from a point of time. I am not getting it to run and I am not able to see any error or exception. Here is the code for my task class. I am able to run the getValues() function on a timely basis with quartz. The problem is it's printing the value hi and nothing else. It's not going in if nor else and neither is showing any error or exception.
public class TeamUpdateImpl implements TeamUpdate {
// #Autowired
ReadXmlDao readXmlDao;
public void setReadXmlDao(ReadXmlDao readXmlDao) {
this.readXmlDao = readXmlDao;
}
public void getValues() {
System.out.print("Hi");
if (readXmlDao.getName().equals("Hema")) {
System.out.print("if cond");
} else {
System.out.print("else cond");
}
}
}
Please suggest a solution, some logging thing or something so that I could get at least errors on my console to fix them.
Thanks.
I guess readXmlDao or readXmlDao.getName() is null.
Try to print it.
System.out.print("readXmlDao = "+readXmlDao);
System.out.print("readXmlDao.getName() = "+readXmlDao.getName());
You will get npe on printing readXmlDao.getName() if readXmlDao is null.
Also
Try to set #Autowired on setReadXmlDao method.
#Autowired
public void setReadXmlDao(ReadXmlDao readXmlDao) {
this.readXmlDao = readXmlDao;
}