I have a controller with WebAsyncTask. Further on I'm using a timeout callback.
As writen here I shall have an option to notifies the Callable to cancel processing. However I don't see any option to do so.
#Controller
public class UserDataProviderController {
private static final Logger log = LoggerFactory.getLogger(UserDataProviderController.class.getName());
#Autowired
private Collection<UserDataService> dataServices;
#RequestMapping(value = "/client/{socialSecurityNumber}", method = RequestMethod.GET)
public #ResponseBody
WebAsyncTask<ResponseEntity<CustomDataResponse>> process(#PathVariable final String socialSecurityNumber) {
final Callable<ResponseEntity<CustomDataResponse>> callable = new Callable<ResponseEntity<CustomDataResponse>>() {
#Override
public ResponseEntity<CustomDataResponse> call() throws Exception {
CustomDataResponse CustomDataResponse = CustomDataResponse.newInstance();
// Find user data
for(UserDataService dataService:dataServices)
{
List<? extends DataClient> clients = dataService.findBySsn(socialSecurityNumber);
CustomDataResponse.put(dataService.getDataSource(), UserDataConverter.convert(clients));
}
// test long execution
Thread.sleep(4000);
log.info("Execution thread continued and shall be terminated:"+Thread.currentThread().getName());
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(new MediaType("application", "json", Charset.forName("UTF-8")));
return new ResponseEntity(CustomDataResponse,responseHeaders,HttpStatus.OK);
}
};
final Callable<ResponseEntity<CustomDataResponse>> callableTimeout = new Callable<ResponseEntity<CustomDataResponse>>() {
#Override
public ResponseEntity<CustomDataResponse> call() throws Exception {
// Error response
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(new MediaType("application", "json", Charset.forName("UTF-8")));
return new ResponseEntity("Request has timed out!",responseHeaders,HttpStatus.INTERNAL_SERVER_ERROR);
}
};
WebAsyncTask<ResponseEntity<CustomDataResponse>> task = new WebAsyncTask<>(3000,callable);
task.onTimeout(callableTimeout);
return task;
}
}
My #WebConfig
#Configuration
#EnableWebMvc
class WebAppConfig extends WebMvcConfigurerAdapter {
#Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setKeepAliveSeconds(60 * 60);
executor.afterPropertiesSet();
configurer.registerCallableInterceptors(new TimeoutCallableProcessingInterceptor());
configurer.setTaskExecutor(executor);
}
}
And quite standard Interceptor:
public class TimeoutCallableProcessingInterceptor extends CallableProcessingInterceptorAdapter {
#Override
public <T> Object handleTimeout(NativeWebRequest request, Callable<T> task) {
throw new IllegalStateException("[" + task.getClass().getName() + "] timed out");
}
}
Everything work as it should, but Callable from controller always completes, which is obvious, but how to stop processing there ?
You can use WebAsyncTask to implement the timeout control and Thread management to stop the new async thread gracefully.
Implement a Callable to run the process
In this method (that runs in a diferent thread) store the current Thread in a Controller's local variable
Implement another Callable to handle timeout event
In this method retrieve the previously stored Thread and interrupt it calling the interrupt() method.
Also throw a TimeoutException to stop the controller process
In the running process, check if the thread interrupted with Thread.currentThread().isInterrupted(), if so, then rollback the transaction throwing an Exception.
Controller:
public WebAsyncTask<ResponseEntity<BookingFileDTO>> confirm(#RequestBody final BookingConfirmationRQDTO bookingConfirmationRQDTO)
throws AppException,
ProductException,
ConfirmationException,
BeanValidationException {
final Long startTimestamp = System.currentTimeMillis();
// The compiler obligates to define the local variable shared with the callable as final array
final Thread[] asyncTaskThread = new Thread[1];
/**
* Asynchronous execution of the service's task
* Implemented without ThreadPool, we're using Tomcat's ThreadPool
* To implement an specific ThreadPool take a look at http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-ann-async-configuration-spring-mvc
*/
Callable<ResponseEntity<BookingFileDTO>> callableTask = () -> {
//Stores the thread of the newly started asynchronous task
asyncTaskThread[0] = Thread.currentThread();
log.debug("Running saveBookingFile task at `{}`thread", asyncTaskThread[0].getName());
BookingFileDTO bookingFileDTO = bookingFileService.saveBookingFile(
bookingConfirmationRQDTO,
MDC.get(HttpHeader.XB3_TRACE_ID))
.getValue();
if (log.isDebugEnabled()) {
log.debug("The saveBookingFile task took {} ms",
System.currentTimeMillis() - startTimestamp);
}
return new ResponseEntity<>(bookingFileDTO, HttpStatus.OK);
};
/**
* This method is executed if a timeout occurs
*/
Callable<ResponseEntity<BookingFileDTO>> callableTimeout = () -> {
String msg = String.format("Timeout detected at %d ms during confirm operation",
System.currentTimeMillis() - startTimestamp);
log.error("Timeout detected at {} ms during confirm operation: informing BookingFileService.", msg);
// Informs the service that the time has ran out
asyncTaskThread[0].interrupt();
// Interrupts the controller call
throw new TimeoutException(msg);
};
WebAsyncTask<ResponseEntity<BookingFileDTO>> webAsyncTask = new WebAsyncTask<>(timeoutMillis, callableTask);
webAsyncTask.onTimeout(callableTimeout);
log.debug("Timeout set to {} ms", timeoutMillis);
return webAsyncTask;
}
Service implementation:
/**
* If the service has been informed that the time has ran out
* throws an AsyncRequestTimeoutException to roll-back transactions
*/
private void rollbackOnTimeout() throws TimeoutException {
if(Thread.currentThread().isInterrupted()) {
log.error(TIMEOUT_DETECTED_MSG);
throw new TimeoutException(TIMEOUT_DETECTED_MSG);
}
}
#Transactional(rollbackFor = TimeoutException.class, propagation = Propagation.REQUIRES_NEW)
DTOSimpleWrapper<BookingFileDTO> saveBookingFile(BookingConfirmationRQDTO bookingConfirmationRQDTO, String traceId) {
// Database operations
// ...
return retValue;
}
Related
I am new to vertx and async programming.
I have 2 verticles communicating via an event bus as follows:
//API Verticle
public class SearchAPIVerticle extends AbstractVerticle {
public static final String GET_USEARCH_DOCS = "get.usearch.docs";
#Autowired
private Integer defaultPort;
private void sendSearchRequest(RoutingContext routingContext) {
final JsonObject requestMessage = routingContext.getBodyAsJson();
final EventBus eventBus = vertx.eventBus();
eventBus.request(GET_USEARCH_DOCS, requestMessage, reply -> {
if (reply.succeeded()) {
Logger.info("Search Result = " + reply.result().body());
routingContext.response()
.putHeader("content-type", "application/json")
.setStatusCode(200)
.end((String) reply.result().body());
} else {
Logger.info("Document Search Request cannot be processed");
routingContext.response()
.setStatusCode(500)
.end();
}
});
}
#Override
public void start() throws Exception {
Logger.info("Starting the Gateway service (Event Sender) verticle");
// Create a Router
Router router = Router.router(vertx);
//Added bodyhandler so we can process json messages via the event bus
router.route().handler(BodyHandler.create());
// Mount the handler for incoming requests
// Find documents
router.post("/api/search/docs/*").handler(this::sendSearchRequest);
// Create an HTTP Server using default options
HttpServer server = vertx.createHttpServer();
// Handle every request using the router
server.requestHandler(router)
//start listening on port 8083
.listen(config().getInteger("http.port", 8083)).onSuccess(msg -> {
Logger.info("*************** Search Gateway Server started on "
+ server.actualPort() + " *************");
});
}
#Override
public void stop(){
//house keeping
}
}
//Below is the target verticle should be making the multiple web client call and merging the responses
.
#Component
public class SolrCloudVerticle extends AbstractVerticle {
public static final String GET_USEARCH_DOCS = "get.usearch.docs";
#Autowired
private SearchRepository searchRepositoryService;
#Override
public void start() throws Exception {
Logger.info("Starting the Solr Cloud Search Service (Event Consumer) verticle");
super.start();
ConfigStoreOptions fileStore = new ConfigStoreOptions().setType("file")
.setConfig(new JsonObject().put("path", "conf/config.json"));
ConfigRetrieverOptions configRetrieverOptions = new ConfigRetrieverOptions()
.addStore(fileStore);
ConfigRetriever configRetriever = ConfigRetriever.create(vertx, configRetrieverOptions);
configRetriever.getConfig(ar -> {
if (ar.succeeded()) {
JsonObject configJson = ar.result();
EventBus eventBus = vertx.eventBus();
eventBus.<JsonObject>consumer(GET_USEARCH_DOCS).handler(getDocumentService(searchRepositoryService, configJson));
Logger.info("Completed search service event processing");
} else {
Logger.error("Failed to retrieve the config");
}
});
}
private Handler<Message<JsonObject>> getDocumentService(SearchRepository searchRepositoryService, JsonObject configJson) {
return requestMessage -> vertx.<String>executeBlocking(future -> {
try {
//I need to incorporate the logic here that adds futures to list and composes the compositefuture
/*
//Below is my logic to populate the future list
WebClient client = WebClient.create(vertx);
List<Future> futureList = new ArrayList<>();
for (Object collection : searchRepositoryService.findAllCollections(configJson).getJsonArray(SOLR_CLOUD_COLLECTION).getList()) {
Future<String> future1 = client.post(8983, "127.0.0.1", "/solr/" + collection + "/query")
.expect(ResponsePredicate.SC_OK)
.sendJsonObject(requestMessage.body())
.map(HttpResponse::bodyAsString).recover(error -> {
System.out.println(error.getMessage());
return Future.succeededFuture();
});
futureList.add(future1);
}
//Below is the CompositeFuture logic, but the logic and construct does not make sense to me. What goes as first and second argument of executeBlocking method
/*CompositeFuture.join(futureList)
.onSuccess(result -> {
result.list().forEach( x -> {
if(x != null){
requestMessage.reply(result.result());
}
}
);
})
.onFailure(error -> {
System.out.println("We should not fail");
})
*/
future.complete("DAO returns a Json String");
} catch (Exception e) {
future.fail(e);
}
}, result -> {
if (result.succeeded()) {
requestMessage.reply(result.result());
} else {
requestMessage.reply(result.cause()
.toString());
}
});
}
}
I was able to use the org.springframework.web.reactive.function.client.WebClient calls to compose my search result from multiple web client calls, as against using Future<io.vertx.ext.web.client.WebClient> with CompositeFuture.
I was trying to avoid mixing Springboot and Vertx, but unfortunately Vertx CompositeFuture did not work here:
//This method supplies the parameter for the future.complete(..) line in getDocumentService(SearchRepository,JsonObject)
private List<JsonObject> findByQueryParamsAndDataSources(SearchRepository searchRepositoryService,
JsonObject configJson,
JsonObject requestMessage)
throws SolrServerException, IOException {
List<JsonObject> searchResultList = new ArrayList<>();
for (Object collection : searchRepositoryService.findAllCollections(configJson).getJsonArray(SOLR_CLOUD_COLLECTION).getList()) {
searchResultList.add(new JsonObject(doSearchPerCollection(collection.toString(), requestMessage.toString())));
}
return aggregateMultiCollectionSearchResults(searchResultList);
}
public String doSearchPerCollection(String collection, String message) {
org.springframework.web.reactive.function.client.WebClient client =
org.springframework.web.reactive.function.client.WebClient.create();
return client.post()
.uri("http://127.0.0.1:8983/solr/" + collection + "/query")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(message.toString()))
.retrieve()
.bodyToMono(String.class)
.block();
}
private List<JsonObject> aggregateMultiCollectionSearchResults(List<JsonObject> searchList){
//TODO: Search result aggregation
return searchList;
}
My use case is the second verticle should make multiple vertx web client calls and should combine the responses.
If an API call falls, I want to log the error and still continue processing and merging responses from other calls.
Please, any help on how my code above could be adaptable to handle the use case?
I am looking at vertx CompositeFuture, but no headway or useful example seen yet!
What you are looking for can done with Future coordination with a little bit of additional handling:
CompositeFuture.join(future1, future2, future3).onComplete(ar -> {
if (ar.succeeded()) {
// All succeeded
} else {
// All completed and at least one failed
}
});
The join composition waits until all futures are completed, either with a success or a failure.
CompositeFuture.join
takes several futures arguments (up to 6) and returns a future that is succeeded when all the futures are succeeded, and failed when all the futures are completed and at least one of them is failed
Using join you will wait for all Futures to complete, the issue is that if one of them fails you will not be able to obtain response from others as CompositeFuture will be failed. To avoid this you should add Future<T> recover(Function<Throwable, Future<T>> mapper) on each of your Futures in which you should log the error and pass an empty response so that the future does not fail.
Here is short example:
Future<String> response1 = client.post(8887, "localhost", "work").expect(ResponsePredicate.SC_OK).send()
.map(HttpResponse::bodyAsString).recover(error -> {
System.out.println(error.getMessage());
return Future.succeededFuture();
});
Future<String> response2 = client.post(8887, "localhost", "error").expect(ResponsePredicate.SC_OK).send()
map(HttpResponse::bodyAsString).recover(error -> {
System.out.println(error.getMessage());
return Future.succeededFuture();
});
CompositeFuture.join(response2, response1)
.onSuccess(result -> {
result.list().forEach(x -> {
if(x != null) {
System.out.println(x);
}
});
})
.onFailure(error -> {
System.out.println("We should not fail");
});
Edit 1:
Limit for CompositeFuture.join(Future...) is 6 Futures, in the case you need more you can use: CompositeFuture.join(Arrays.asList(future1, future2, future3)); where you can pass unlimited number of futures.
RpcDispatcher#callRemoteMethods does not work while calling MembershipListener#viewAccepted when using ForkChannel.
I am trying to upgrade the version of JGroups used in my application from 2 to 4.1.0.Final.
The application uses MuxRpcDispatcher because it uses multiple RpcDispatchers.
The mux package has been deprecated in version 4 and we are trying to use an alternative fork-stack.
However, when you execute RpcDispatcher in viewAccepted, processing freeze.
RpcDispatcher#callRemoteMethods does not work while calling MembershipListener#viewAccepted when using ForkChannel.
channel = new JChannel();
channel.setReceiver(this);
if (channel.getProtocolStack().findProtocol(FORK.class) == null) {
channel.getProtocolStack().addProtocol(new FORK());
}
forkChannel = new ForkChannel(channel, "fork", "fork");
dispatcher1 = new RpcDispatcher(forkChannel, new Boe1());
channel.connect("test");
forkChannel.connect("test");
Calling RpcDispatcher in viewAccepted. Processing stops on this call.
#Override
public void viewAccepted(final View new_view) {
LOGGER.info("viewAccepted:start");
try {
final MethodCall call = new MethodCall(Boe1.class.getMethod("boeee"));
final RequestOptions options = new RequestOptions(ResponseMode.GET_ALL, 0, true, null);
dispatcher1.callRemoteMethods(null, call, options);
} catch (final Exception e) {
e.printStackTrace();
}
LOGGER.info("viewAccepted:end");
}
The following is a thread dump in the stopped state.
"jgroups-10,test,IM9072-10017" #22 prio=5 os_prio=0 tid=0x000000002b4b5800 nid=0x2cf8 waiting on condition [0x000000002c84d000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000071b24ccd0> (a java.util.concurrent.CompletableFuture$Signaller)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.CompletableFuture$Signaller.block(CompletableFuture.java:1693)
at java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3323)
at java.util.concurrent.CompletableFuture.waitingGet(CompletableFuture.java:1729)
at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895)
at org.jgroups.blocks.GroupRequest.access$7(GroupRequest.java:1)
at org.jgroups.blocks.GroupRequest$$Lambda$135/1688803174.call(Unknown Source)
at org.jgroups.blocks.GroupRequest.doAndComplete(GroupRequest.java:274)
at org.jgroups.blocks.GroupRequest.waitForCompletion(GroupRequest.java:254)
at org.jgroups.blocks.GroupRequest.waitForCompletion(GroupRequest.java:1)
at org.jgroups.blocks.Request.execute(Request.java:52)
at org.jgroups.blocks.MessageDispatcher.cast(MessageDispatcher.java:319)
at org.jgroups.blocks.MessageDispatcher.castMessage(MessageDispatcher.java:251)
at org.jgroups.blocks.RpcDispatcher.callRemoteMethods(RpcDispatcher.java:96)
at ppp.network.JChannelRunner.viewAccepted(JChannelRunner.java:79)
at org.jgroups.JChannel.invokeCallback(JChannel.java:917)
at org.jgroups.JChannel.up(JChannel.java:759)
at org.jgroups.stack.ProtocolStack.up(ProtocolStack.java:908)
at org.jgroups.protocols.FORK.up(FORK.java:131)
Is there a way to avoid freezes?
You should never block in a callback, such as viewAccepted()! If you absolutely have to invoke an RPC, either invoke it asynchronously (mode=GET_NONE), or out-of-band (OOB).
You could also do this in a separate thread.
See [1] for details.
Cheers,
[1] http://www.jgroups.org/manual4/index.html#ReceiverAdapter
The mistake you made is that you registered a view listener on the JChannel, but not on the ForkChannel.
This means the viewAccepted() callback got invoked when the ForkChannel didn't yet have a view, so invoking the method would return immediately as no membership had been recorded at that point.
I commented that line and added dispatcher1.setMembershipListener(), and now, the sample code works.
Cheers,
public class JChannelRunner extends ReceiverAdapter implements Closeable {
public static class Boe1 {
public void boeee() {System.out.println("boe-1");}
}
private final static Logger LOGGER = Logger.getLogger(JChannelRunner.class.getName());
JChannel channel;
ForkChannel forkChannel;
RpcDispatcher dispatcher1;
protected void start() throws Exception {
channel = new JChannel();
// channel.setReceiver(this);
if (channel.getProtocolStack().findProtocol(FORK.class) == null) {
channel.getProtocolStack().addProtocol(new FORK());
}
forkChannel = new ForkChannel(channel, "fork", "fork");
dispatcher1 = new RpcDispatcher(forkChannel, new Boe1());
dispatcher1.setMembershipListener(this);
channel.connect("test");
forkChannel.connect("test");
}
public JChannelRunner() throws Exception {}
public static void main(final String[] args) throws IOException, Exception {
try (JChannelRunner runner = new JChannelRunner()) {
runner.start();
Thread.sleep(60 * 1000);
}
}
#Override
public void close() throws IOException {
Util.close(dispatcher1, forkChannel, channel);
}
#Override
public void viewAccepted(final View new_view) {
LOGGER.info("viewAccepted:start");
try {
final MethodCall call = new MethodCall(Boe1.class.getMethod("boeee"));
final RequestOptions options = new RequestOptions(ResponseMode.GET_ALL, 0, true, null);
dispatcher1.callRemoteMethods(null, call, options);
} catch (final Exception e) {
e.printStackTrace();
}
LOGGER.info("viewAccepted:end");
}
}
My application is using spring boot with batch and testing it in aws lambda I want run the job in main method and NOT through scheduler. Is it possible to do that?
#SpringBootApplication
#EnableAutoConfiguration
#EnableJpaRepositories("com.myrepo.repository")
#ComponentScan("com.myrepo")
#EnableScheduling
public class Main {
private static final Logger LOG = LoggerFactory.getLogger(hMain.class);
#Autowired
JobLauncher launcher;
#Autowired
Job job;
public static void main(String[] args) {
try {
LOG.info("Start of application - debt card notofication JOB");
SpringApplication.run(BatchMain.class, args);
} catch (Exception e) {
LOG.error("Exception caught bathch Main, );
}
}
}
EDIT -- I wrote below code but it is not working inside aws lambda function
#Scheduled(cron = "0/1 * * * * *")
public void performBatchOpertaion() {
try {
LOG.info("Scheduling Job and Launcher {}, {}", job, launcher);
JobParameters params = new JobParametersBuilder()
.addString(Constants.MYBATCH, String.valueOf(System.currentTimeMillis()))
.toJobParameters();
launcher.run(job, params);
} catch (Exception e) {
LOG.error("Unable to schedules ", e.getCause());
}
}
public static void startApp() {
LOG.info("start batch job ");
SpringApplication.run(Main.class);
LOG.info("end batch job ");
}
here is my Request handler class which call statApp() of Main class
--------------------------------------------------------
public class MyHandler implements RequestHandler<Map<String, Object>, String> {
private static final Logger LOG = LoggerFactory.getLogger(MyHandler.class);
#Autowired
BatchMain main;
#Override
public String handleRequest(Map<String, Object> input, Context context) {
LOG.info("Inside the handler request");
BatchMain.startApp();
LOG.info("End of handler request");
return "End Of Application";
}
}
handleRequestmethod wait until the BatchMain to complete. You can use Thread join . Lambda execution engine suspend the main thread once the handle request return the result . In the next event it may complete the previous suspended tasks
I want to use RxJava in my project and I wrote simple asynchronous method and now I try to test it. I cannot test it with Executor because I get result: java.lang.AssertionError: Not completed! (0 completions)
Service method:
#Override
public Observable<User> addOrderAsynchronously(String username, Order order) {
if (username != null && order != null) {
return Observable
.fromCallable(() -> userRepository.findByUsername(username))
.filter(Objects::nonNull)
.map(user -> {
synchronized (this) {
user.getOrders().add(order);
order.setUser(user);
}
return user;
})
.map(userRepository::save);
}
return Observable.empty();
}
Working test without Executor:
#Test
public void shouldSaveUserSynchronouslyTest() throws Exception {
// given
TestSubscriber<User> subscriber = new TestSubscriber<>();
final Order order = new Order();
final User user = new User();
when(userRepository.findByUsername(user.getUsername()))
.thenReturn(user);
when(userRepository.save(any(User.class)))
.thenReturn(user);
// when
userService.addOrderAsynchronously(user.getUsername(), order)
.subscribe(subscriber);
// then
subscriber.assertCompleted();
subscriber.assertNoErrors();
assertThat(subscriber.getOnNextEvents()).isEqualTo(Collections.singletonList(user));
}
And now I want to test with executor:
#Test
public void shouldSaveUserSynchronouslyTest() throws Exception {
// given
TestSubscriber<User> subscriber = new TestSubscriber<>();
final Order order = new Order();
final User user = new User();
when(userRepository.findByUsername(user.getUsername()))
.thenReturn(user);
when(userRepository.save(any(User.class)))
.thenReturn(user);
// when
userService.addOrderAsynchronously(user.getUsername(), order)
.subscribeOn(Schedulers.from(executor))
.subscribe(subscriber);
// then
subscriber.assertCompleted();
subscriber.assertNoErrors();
assertThat(subscriber.getOnNextEvents()).isEqualTo(Collections.singletonList(user));
}
And this test doesn't work because I get result java.lang.AssertionError: Not completed! (0 completions).
executor is comes from #Bean method:
#Bean
public Executor executor() {
final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(8);
executor.setMaxPoolSize(8);
executor.setQueueCapacity(500);
executor.initialize();
return executor;
}
How can I write unit test with executor?
I am trying to make server-sent events with Spring 4 (tomcat 7, servlet-api 3.0.1).
The problem is my Events aren't sent right after method send was called. They all come simultaneously (with same timestamp) to client only after timeout of SseEmitter, with EventSource's error event. And then client is trying to reconnect. Any idea what's happening?
I have created a simple service:
#RequestMapping(value = "subscribe", method = RequestMethod.GET)
public SseEmitter subscribe () throws IOException {
final SseEmitter emitter = new SseEmitter();
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
try {
emitter.send(SseEmitter.event().data("Thread writing: " + Thread.currentThread()).name("ping"));
} catch (Exception e) {
}
}
} , 1000, 1000, TimeUnit.MILLISECONDS);
return emitter;
}
with client code:
sse = new EventSource(urlBuilder(base, url));
sse.addEventListener('ping', function (event) {
dfd.notify(event);
});
sse.addEventListener('message', function(event){
dfd.notify(event);
});
sse.addEventListener('close', function(event){
dfd.notify(event);
});
sse.onerror = function (error) {
console.log(error);
};
sse.onmessage = function (event){
dfd.notify(event);
};
App initalizer code
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(AppConfig.class);
ctx.setServletContext(servletContext);
ctx.refresh();
ServletRegistration.Dynamic dynamic = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
dynamic.setAsyncSupported(true);
dynamic.addMapping("/api/*");
dynamic.setLoadOnStartup(1);
dynamic.setMultipartConfig(ctx.getBean(MultipartConfigElement.class));
javax.servlet.FilterRegistration.Dynamic filter = servletContext
.addFilter("StatelessAuthenticationFilter",
ctx.getBean("statelessAuthenticationFilter", StatelessAuthenticationFilter.class));
filter.setAsyncSupported(true);
filter.addMappingForUrlPatterns(null, false, "/api/*");
filter = servletContext.addFilter("HibernateSessionRequestFilter",
ctx.getBean("hibernateSessionRequestFilter", HibernateSessionRequestFilter.class));
filter.setAsyncSupported(true);
filter.addMappingForUrlPatterns(null, false, "/api/user/*");
}
}
AppConfig.java
#Configuration
#ComponentScan("ru.esoft.workflow")
#EnableWebMvc
#PropertySource({"classpath:mail.properties", "classpath:fatclient.properties"})
#EnableAsync
#EnableScheduling
public class AppConfig extends WebMvcConfigurerAdapter {
...
}
Image of my client log:
I ran into this myself when testing SSEEmitters. From everything I've read online, SSEEmitters are meant to be used in conjunction with some implementation of Reactive Streams, such as RxJava. It's a bit complex, but it definitely works. The idea is that you create the emitter, and an Observable, and subscribe the latter to a Publisher. The Publisher executes its behavior in a separate thread, notifying the Observable when output is ready, and the observable triggers the emitter.send. Here is an example snippet that should do what you want:
#RequestMapping("/whatever")
public SseEmitter index(
SseEmitter emitter = new SseEmitter();
Publisher<String> responsePublisher = someResponseGenerator.getPublisher();
Observable<String> responseObservable = RxReactiveStreams.toObservable(responsePublisher);
responseObservable.subscribe(
str -> {
try {
emitter.send(str);
} catch (IOException ex) {
emitter.completeWithError(ex);
}
},
error -> {
emitter.completeWithError(error);
},
emitter::complete
);
return emitter;
};
Here is a the corresponding Publisher:
public class SomeResponseGenerator {
public Publisher<String> getPublisher() {
Publisher<String> pub = new Publisher<String>() {
#Override
public void subscribe(Subscriber subscriber) {
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
subscriber.onNext("Thread writing: " + Thread.currentThread().getName());
}
}, 1000, 1000, TimeUnit.MILLISECONDS);
}
};
return pub;
}
}
There are a few examples of this model online here and here, and you can find more by Googling 'RxJava SseEmitter'. It takes some time to grok the Reactive Streams/RxJava/SseEmitter interactions, but once you do it is pretty elegant. Hope this sets you on the right path!
While the other answer is correct, if you want to manage it yourself you can call:
emitter.complete()