I have some REST APIs that may take a while to execute, and I want to limit their execution duration. Preferably, if 30 seconds passed and the request didn't return, I would like to return a specific HTTP code / data and terminate that request completly.
The current code:
#RestController
#CrossOrigin(origins = {"*"}, maxAge = 4800, allowCredentials = "false")
public class APIController {
#RequestMapping(value = "/api/myapifunc", method = RequestMethod.POST, produces = "application/json")
public ResponseEntity<?> optimize(#RequestParam(value="param1", defaultValue="")) {
// Code here
}
It looks like you are describing the Circuit Breaker pattern. If you have control over both the client and server code and want to explore Spring Cloud and Netflix Hysterix libraries you can take a look at Getting Started: Circuit Breaker guide.
If you are using Apache Tomcat as your servlet container you can configure Stuck Thread Detection Valve:
This valve allows to detect requests that take a long time to process, which might indicate that the thread that is processing it is stuck. Additionally it can optionally interrupt such threads to try and unblock them.
When such a request is detected, the current stack trace of its thread is written to Tomcat log with a WARN level.
The IDs and names of the stuck threads are available through JMX in the stuckThreadIds and stuckThreadNames attributes. The IDs can be used with the standard Threading JVM MBean (java.lang:type=Threading) to retrieve other information about each stuck thread.
With Spring Boot 2.3 / Tomcat 9, you can set a timeout for ALL incoming HTTP requests to complete by installing a Tomcat StuckThreadDetectionValve. Here's the Spring configuration code you'll need (it's Kotlin):
import org.apache.catalina.valves.StuckThreadDetectionValve
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
#Configuration
class RequestTimeoutConfiguration(
#Value("\${app.tomcat.stuck-thread-detection.request-timeout.seconds}")
private val stuckThreadTimeoutSeconds: Int
) {
#Bean
fun stuckThreadDetectionValve() =
StuckThreadDetectionValve().apply {
threshold = stuckThreadTimeoutSeconds
interruptThreadThreshold = stuckThreadTimeoutSeconds
}
#Bean
fun stuckThreadDetectionWebServerFactoryCustomizer(valve: StuckThreadDetectionValve) =
WebServerFactoryCustomizer { factory: TomcatServletWebServerFactory ->
factory.addContextValves(valve)
}
}
Then you just need the property in application.properties to control it:
app.tomcat.stuck-thread-detection.request-timeout.seconds=130
#RequestMapping(value = "/api/myapifunc", method = RequestMethod.POST, produces =
"application/json")
public ResponseEntity<?> optimize(#RequestParam(value="param1", defaultValue="")) {
return new Callable<String>() {
#Override
public String call() throws Exception {
Thread.sleep(3000); //this will cause a timeout
return "foobar";
}
};
}
Future you can use or annotation
#Timed
#Transactional(timeout = 3000)
You can use Future timeout:
final Future<Object> submit = service.submit(new Callable<Object>() {
#Override
public Object call() throws Exception {
......YOUR CODE
return "";
}
});
try {
submit.get(3, TimeUnit.SECONDS);
} catch (Exception e) {
log.error("fail",e);
}
You can set this property configuration
server.connection-timeout=30000
in your application.properties.
Based on official documentation says:
server.connection-timeout= # Time that connectors wait for another HTTP request before closing the connection. When not set, the connector's container-specific default is used. Use a value of -1 to indicate no (that is, an infinite) timeout.
Related
I have a simple Spring Boot REST service for the IFTTT platform. Each authorized request will contain a header IFTTT-Service-Key with my account's service key and I will use that to either process the request or return a 401 (Unauthorized). However, I only want to do this for select endpoints -- and specifically not for ANY of the Spring actuator endpoints.
I have looked into Spring Security, using filters, using HandlerInterceptors, but none seem to fit what I am trying to do exactly. Spring security seems to come with a lot of extra stuff (especially the default user login), filters don't really seem to match the use case, and the handler interceptor works fine but I would have to code logic in to watch specific URLs and ignore others.
What is the best way to achieve what I am trying to do?
For reference, this is the code I have now:
public class ServiceKeyValidator implements HandlerInterceptor {
private final String myIftttServiceKey;
public ServiceKeyValidator(#Value("${ifttt.service-key}") String myIftttServiceKey) {
this.myIftttServiceKey = myIftttServiceKey;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// TODO will have to put logic in to skip this when actuator endpoints are added
String serviceKeyHeader = request.getHeader("IFTTT-Service-Key");
if (!myIftttServiceKey.equals(serviceKeyHeader)) {
var error = new Error("Incorrect value for IFTTT-Service-Key");
var errorResponse = new ErrorResponse(Collections.singletonList(error));
throw new UnauthorizedException(errorResponse);
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
You need to add filtering for the required endpoints in the place where you register your HandlerInterceptor.
For example:
#EnableWebMvc
#Configuration
public class AppConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new ServiceKeyValidator())
.addPathPatterns("/ifttt/**")
.excludePathPatterns("/actuator/**");
}
}
You can use different URLs path matchers to filter which URL endpoints must be handled by your interceptor and which are not. As the method addPathPatterns returns InterceptorRegistration object that configures this.
I have a simple Micronaut- based "hello world" service that has a simple security built in (for the sake of testing and illustrating the Micronaut security). The controller code in the service that implements the hello service is provided below:
#Controller("/hello")
public class HelloController
{
public HelloController()
{
// Might put some stuff in in the future
}
#Get("/")
#Produces(MediaType.TEXT_PLAIN)
public String index()
{
return("Hello to the World of Micronaut!!!");
}
}
In order to test the security mechanism, I have followed the Micronaut tutorial instructions and created a security service class:
#Singleton
public class SecurityService
{
public SecurityService()
{
// Might put in some stuff in the future
}
Flowable<Boolean> checkAuthorization(HttpRequest<?> theReq)
{
Flowable<Boolean> flow = Flowable.fromCallable(()->{
System.out.println("Security Engaged!");
return(false); <== The tutorial says return true
}).subscribeOn(Schedulers.io());
return(flow);
}
}
It should be noted that, in a departure from the tutorial, the flowable.fromCallable() lambda returns false. In the tutorial, it returns true. I had assumed that a security check would fail if a false is returned, and that a failure would cause the hello service to fail to respond.
According to the tutorials, in ordeer to begin using the Security object, it is necessary to have a filter. The filter I created is shown below:
#Filter("/**")
public class HelloFilter implements HttpServerFilter
{
private final SecurityService secService;
public HelloFilter(SecurityService aSec)
{
System.out.println("Filter Created!");
secService = aSec;
}
#Override
public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> theReq, ServerFilterChain theChain)
{
System.out.println("Filtering!");
Publisher<MutableHttpResponse<?>> resp = secService.checkAuthorization(theReq)
.doOnNext(res->{
System.out.println("Responding!");
});
return(resp);
}
}
The problem occurs when I run the microservice and access the Helo world URL. (http://localhost:8080/hello) I cannot cause the access to the service to fail. The filter catches all requests, and the security object is engaged, but it does not seem to prevent access to the hello service. I do not know what it takes to make the access fail.
Can someone help on this matter? Thank you.
You need to change request in your filter when you no have access to resource or process request as usual. Your HelloFilter looks like this:
#Override
public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> theReq, ServerFilterChain theChain) {
System.out.println("Filtering!");
Publisher<MutableHttpResponse<?>> resp = secService.checkAuthorization(theReq)
.switchMap((authResult) -> { // authResult - is you result from SecurityService
if (!authResult) {
return Publishers.just(HttpResponse.status(HttpStatus.FORBIDDEN)); // reject request
} else {
return theChain.proceed(theReq); // process request as usual
}
})
.doOnNext(res -> {
System.out.println("Responding!");
});
return (resp);
}
And in the last - micronaut has security module with SecurityFilter, you can use #Secured annotations or write access rules in configuration files more examples in the doc
Question
Is there a way to globally handle Spring Messaging MessageDeliveryException caused by error (usualy insufficient authorities) in Spring WebSocket module?
Use case
I have implemented Spring WebSockets over STOMP to support ws connection in my webapp. To secure websocket endpoint I have created interceptor that authorizes user to start STOMP session at STOMP CONNECT time (as suggested in Spring documentation here in 22.4.11 section):
#Component
public class StompMessagingInterceptor extends ChannelInterceptorAdapter {
// Some code not important to the problem
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor headerAccessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
switch (headerAccessor.getCommand()) {
// Authenticate STOMP session on CONNECT using jwt token passed as a STOMP login header - it's working great
case CONNECT:
authorizeStompSession(headerAccessor);
break;
}
// Returns processed message
return message;
}
// Another part of code not important for the problem
}
and included spring-security-messaging configuration to add some fine-grained control over authorities when messaging:
#Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.simpTypeMatchers(
SimpMessageType.CONNECT,
SimpMessageType.DISCONNECT,
SimpMessageType.HEARTBEAT
).authenticated()
.simpSubscribeDestMatchers("/queue/general").authenticated()
.simpSubscribeDestMatchers("/user/queue/priv").authenticated()
.simpDestMatchers("/app/general").authenticated()
.simpDestMatchers("/user/*/queue/priv").hasAuthority("ADMIN")
.anyMessage().denyAll();
}
#Override
protected boolean sameOriginDisabled() {
return true;
}
}
First of all - this configuration works as expected, the problem is when some security exception happens during websocket communication (say user without admin authority tries to send message on "/user/{something}/queue/priv" endpoint) it will end in org.springframework.messaging.MessageDeliveryException being rised and:
Full exception stack trace being written down to my server log
Returning STOMP ERROR frame containing part of stack trace as it's message field.
What I would like to do is catching (if possible globally) DeliveryException, checking what caused it and accoridingly to that create my own message for returning in STOMP ERROR frame (lets say with some error code like just 403 to mimic HTTP) and instead of throwing original exception further just logging some warning with my logger. Is it possible?
What I tried
When looking for solution I found some people using #MessageExceptionHandler to catch messaging exceptions, Spring 4.2.3 (which is version I use) documentation mentions it only once here in 25.4.11 section. I tried to use it like this:
#Controller
#ControllerAdvice
public class WebSocketGeneralController {
...
#MessageExceptionHandler
public WebSocketMessage handleException(org.springframework.messaging.MessageDeliveryException e) {
WebSocketMessage errorMessage = new WebSocketMessage();
errorMessage.setMessage(e.getClass().getName());
return errorMessage;
}
}
but it seems like method isn't called at any point (tried catching different exceptions, just Exception including - no results). What else should I look into?
#ControllerAdvice and #MessageExceptionHandler are working on business-logic level (like #MessageMapping or SimpMessagingTemplate).
To handle STOMP exceptions, you need to set STOMP error handler in STOMP registry:
#Configuration
#EnableWebSocketMessageBroker
class WebSocketConfiguration : WebSocketMessageBrokerConfigurer {
override fun configureMessageBroker(registry: MessageBrokerRegistry) {
// ...
}
override fun registerStompEndpoints(registry: StompEndpointRegistry) {
registry.addEndpoint("/ws")
// Handle exceptions in interceptors and Spring library itself.
// Will terminate a connection and send ERROR frame to the client.
registry.setErrorHandler(object : StompSubProtocolErrorHandler() {
override fun handleInternal(
errorHeaderAccessor: StompHeaderAccessor,
errorPayload: ByteArray,
cause: Throwable?,
clientHeaderAccessor: StompHeaderAccessor?
): Message<ByteArray> {
errorHeaderAccessor.message = null
val message = "..."
return MessageBuilder.createMessage(message.toByteArray(), errorHeaderAccessor.messageHeaders)
}
})
}
}
It does not work because of #ControllerAdvice catch exception from the request that passed dispatcher servlet. When you secure your endpoint and someone makes an unauthorized request it does not pass through dispatcher servlet. The request is caught by spring interceptors.
I have a Java rest API which will be used by IOT devices to send data. Each device has a time period (say 15 seconds) to communicate with the API. Within that time period there can be more than one message with the same set of data.
What I want to do is, when the API receive a new message from a device, it wait till the end of the time period and collect the messages received. And process the messages only when the time period is over.
What should I use to collect and process messages for a given time period?
Thanks.
EDIT
Using spring boot.
You should try using an asyncronous endpoint to call to a syncronous REST. You can define what to do after a timeout is reached.
For example, in Spring Boot you could return a Callable and use a TaskExecutor:
#Controller
public class MyController {
#RequestMapping("/endpoint")
public #ResponseBody WebAsyncTask<String> handleRequest (HttpServletRequest request) {
Callable<String> callable = () -> {
return "Callable";
};
ConcurrentTaskExecutor taskExecutor = new ConcurrentTaskExecutor(Executors.newFixedThreadPool(1));
return new WebAsyncTask<>(15000L, taskExecutor, callable);
}
}
You will probably need to add some configuration in Spring for the Task Executor Thread Pool:
#SpringBootApplication
public class AsyncConfigExample {
#Bean
WebMvcConfigurer configurer(){
return new WebMvcConfigurerAdapter(){
#Override
public void configureAsyncSupport (AsyncSupportConfigurer configurer) {
ThreadPoolTaskExecutor t = new ThreadPoolTaskExecutor();
t.setCorePoolSize(10);
t.setMaxPoolSize(100);
t.setQueueCapacity(50);
t.setAllowCoreThreadTimeOut(true);
t.setKeepAliveSeconds(120);
t.initialize();
configurer.setTaskExecutor(t);
}
};
}
}
Here is more to read:
Spring MVC: Configuring Asynchronous Request Processing
Spring Boot: Executing asynchronous method backed with a queue
I am writing spring rest service using spring boot framework and jetty as container.
In controller I have used the callable like this.
#RequestMapping(value = "/{key}/events", method = RequestMethod.GET)
public Callable<String> getEvents(#PathVariable("key") final String key,
#RequestParam(required = false) final String startAt,
#RequestParam(required = false) final String maxResults) {
return new Callable<String>() {
#Override
public String call() throws Exception {
// here logic that return json string
}
}
}
and the servlet conatiner factory I wrote like this
#Bean
public EmbeddedServletContainerFactory servletContainer(){
JettyEmbeddedServletContainerFactory jetty=new JettyEmbeddedServletContainerFactory();
jetty.addServerCustomizers(new JettyServerCustomizer() {
#Override
public void customize(final Server server) {
// Tweak the connection pool used by Jetty to handle incoming HTTP connections
final QueuedThreadPool threadPool = server.getBean(QueuedThreadPool.class);
threadPool.setMaxThreads(Integer.valueOf(200));
threadPool.setMinThreads(Integer.valueOf(100));
threadPool.setIdleTimeout(Integer.valueOf(100000));
threadPool.setStopTimeout(10000);
}
});
jetty.setPort(4040);
jetty.setContextPath("/mycontextpath");
return jetty;
}
Now My problem is when I run application,
I hit the url from browser first time it not gives the output (service not available message from jetty server).
but again I hit the URL second, third, fourth time it gives me the output.
So for implementing Callable as Controller do I missing something?
I debug code and found that first time internal processing is still going on and the browser complete response...
so What I do? please suggest,
My main aim is that controller should accept more client request at particular time and respond.