From 9c82f2076366843aff2dbbe5528877534ffb713e Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 31 May 2023 11:34:25 +0200 Subject: [PATCH] SEBSERV-445 using DerreferedResult for client connections --- .../gbl/async/AsyncServiceSpringConfig.java | 17 +- .../session/impl/SEBClientPingService.java | 12 +- .../weblayer/api/ControllerConfig.java | 44 +-- .../weblayer/api/ExamAPI_V1_Controller.java | 328 ++++++++++++------ 4 files changed, 259 insertions(+), 142 deletions(-) diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java b/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java index 50a20ce3..dcf25d9d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncServiceSpringConfig.java @@ -17,7 +17,6 @@ import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; @Configuration @EnableAsync @@ -75,14 +74,14 @@ public class AsyncServiceSpringConfig implements AsyncConfigurer { return executor; } - @Bean - public ThreadPoolTaskScheduler threadPoolTaskScheduler() { - final ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); - threadPoolTaskScheduler.setPoolSize(5); - threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(false); - threadPoolTaskScheduler.setThreadNamePrefix("SEB-Server-BgTask-"); - return threadPoolTaskScheduler; - } +// @Bean +// public ThreadPoolTaskScheduler threadPoolTaskScheduler() { +// final ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); +// threadPoolTaskScheduler.setPoolSize(5); +// threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(false); +// threadPoolTaskScheduler.setThreadNamePrefix("SEB-Server-BgTask-"); +// return threadPoolTaskScheduler; +// } @Override public Executor getAsyncExecutor() { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingService.java index db0b4959..8fb35fd0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingService.java @@ -46,7 +46,7 @@ public class SEBClientPingService { } @Scheduled( - fixedDelayString = "${sebserver.webservice.api.exam.session.ping.batch.interval:100}", + fixedDelayString = "${sebserver.webservice.api.exam.session.ping.batch.interval:500}", initialDelay = 1000) public void processPings() { if (this.pings.isEmpty()) { @@ -84,14 +84,10 @@ public class SEBClientPingService { } } - public String notifyPing( + public final String notifyPing( final String connectionToken, final String instructionConfirm) { - if (connectionToken == null) { - return null; - } - if (instructionConfirm != null) { this.pings.put(connectionToken, instructionConfirm); } else if (!this.pings.containsKey(connectionToken)) { @@ -106,6 +102,10 @@ public class SEBClientPingService { final String instructionConfirm, final long timestamp) { + if (connectionToken == null) { + return; + } + final ClientConnectionDataInternal activeClientConnection = this.examSessionCacheService .getClientConnection(connectionToken); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ControllerConfig.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ControllerConfig.java index 2557e075..436cb042 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ControllerConfig.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ControllerConfig.java @@ -8,34 +8,28 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.task.AsyncTaskExecutor; -import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; -import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; - -@EnableAsync -@Configuration -@WebServiceProfile +//@EnableAsync +//@Configuration +//@WebServiceProfile +@Deprecated public class ControllerConfig implements WebMvcConfigurer { - @Override - public void configureAsyncSupport(final AsyncSupportConfigurer configurer) { - configurer.setTaskExecutor(threadPoolTaskExecutor()); - configurer.setDefaultTimeout(30000); - } - - public AsyncTaskExecutor threadPoolTaskExecutor() { - final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(7); - executor.setMaxPoolSize(42); - executor.setQueueCapacity(11); - executor.setThreadNamePrefix("mvc-"); - executor.initialize(); - return executor; - } +// @Override +// public void configureAsyncSupport(final AsyncSupportConfigurer configurer) { +// configurer.setTaskExecutor(threadPoolTaskExecutor()); +// configurer.setDefaultTimeout(30000); +// } +// +// public AsyncTaskExecutor threadPoolTaskExecutor() { +// final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); +// executor.setCorePoolSize(7); +// executor.setMaxPoolSize(42); +// executor.setQueueCapacity(11); +// executor.setThreadNamePrefix("mvc-"); +// executor.initialize(); +// return executor; +// } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java index 04a16689..210ecddf 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java @@ -36,6 +36,7 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.async.DeferredResult; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.API; @@ -93,7 +94,7 @@ public class ExamAPI_V1_Controller { method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public CompletableFuture> handshakeCreate( + public DeferredResult> handshakeCreate( @RequestParam(name = API.PARAM_INSTITUTION_ID, required = false) final Long instIdRequestParam, @RequestParam(name = API.EXAM_API_PARAM_EXAM_ID, required = false) final Long examIdRequestParam, @RequestParam(name = API.EXAM_API_PARAM_CLIENT_ID, required = false) final String clientIdRequestParam, @@ -102,69 +103,133 @@ public class ExamAPI_V1_Controller { final HttpServletRequest request, final HttpServletResponse response) { - return CompletableFuture.supplyAsync( - () -> { + final DeferredResult> deferredResult = new DeferredResult<>(); + this.executor.execute(() -> { + final POSTMapper mapper = new POSTMapper(formParams, request.getQueryString()); - final POSTMapper mapper = new POSTMapper(formParams, request.getQueryString()); + final String remoteAddr = this.getClientAddress(request); + final Long institutionId = (instIdRequestParam != null) + ? instIdRequestParam + : mapper.getLong(API.PARAM_INSTITUTION_ID); + final Long examId = (examIdRequestParam != null) + ? examIdRequestParam + : mapper.getLong(API.EXAM_API_PARAM_EXAM_ID); + final String clientId = (clientIdRequestParam != null) + ? clientIdRequestParam + : mapper.getString(API.EXAM_API_PARAM_CLIENT_ID); - final String remoteAddr = this.getClientAddress(request); - final Long institutionId = (instIdRequestParam != null) - ? instIdRequestParam - : mapper.getLong(API.PARAM_INSTITUTION_ID); - final Long examId = (examIdRequestParam != null) - ? examIdRequestParam - : mapper.getLong(API.EXAM_API_PARAM_EXAM_ID); - final String clientId = (clientIdRequestParam != null) - ? clientIdRequestParam - : mapper.getString(API.EXAM_API_PARAM_CLIENT_ID); + // Create and get new ClientConnection if all integrity checks passes + final ClientConnection clientConnection = this.sebClientConnectionService + .createClientConnection( + principal, + institutionId, + remoteAddr, + mapper.getString(API.EXAM_API_PARAM_SEB_VERSION), + mapper.getString(API.EXAM_API_PARAM_SEB_OS_NAME), + mapper.getString(API.EXAM_API_PARAM_SEB_MACHINE_NAME), + examId, + clientId) + .getOrThrow(); - // Create and get new ClientConnection if all integrity checks passes - final ClientConnection clientConnection = this.sebClientConnectionService - .createClientConnection( - principal, - institutionId, - remoteAddr, - mapper.getString(API.EXAM_API_PARAM_SEB_VERSION), - mapper.getString(API.EXAM_API_PARAM_SEB_OS_NAME), - mapper.getString(API.EXAM_API_PARAM_SEB_MACHINE_NAME), - examId, - clientId) - .getOrThrow(); + response.setHeader( + API.EXAM_API_SEB_CONNECTION_TOKEN, + clientConnection.connectionToken); - response.setHeader( - API.EXAM_API_SEB_CONNECTION_TOKEN, - clientConnection.connectionToken); + // Crate list of running exams + List result; + if (examId == null) { + result = this.examSessionService.getRunningExamsForInstitution(institutionId) + .getOrThrow() + .stream() + .map(this::createRunningExamInfo) + .filter(this::checkConsistency) + .collect(Collectors.toList()); + } else { - // Crate list of running exams - List result; - if (examId == null) { - result = this.examSessionService.getRunningExamsForInstitution(institutionId) - .getOrThrow() - .stream() - .map(this::createRunningExamInfo) - .filter(this::checkConsistency) - .collect(Collectors.toList()); - } else { + final Exam exam = this.examSessionService + .getExamDAO() + .byPK(examId) + .getOrThrow(); - final Exam exam = this.examSessionService - .getExamDAO() - .byPK(examId) - .getOrThrow(); + result = Arrays.asList(createRunningExamInfo(exam)); + processASKSalt(response, clientConnection); + processAlternativeBEK(response, clientConnection.examId); + } - result = Arrays.asList(createRunningExamInfo(exam)); - processASKSalt(response, clientConnection); - processAlternativeBEK(response, clientConnection.examId); - } + if (result.isEmpty()) { + log.warn( + "There are no currently running exams for institution: {}. SEB connection creation denied", + institutionId); + } - if (result.isEmpty()) { - log.warn( - "There are no currently running exams for institution: {}. SEB connection creation denied", - institutionId); - } + deferredResult.setResult(result); + }); - return result; - }, - this.executor); + return deferredResult; + +// return CompletableFuture.supplyAsync( +// () -> { +// +// final POSTMapper mapper = new POSTMapper(formParams, request.getQueryString()); +// +// final String remoteAddr = this.getClientAddress(request); +// final Long institutionId = (instIdRequestParam != null) +// ? instIdRequestParam +// : mapper.getLong(API.PARAM_INSTITUTION_ID); +// final Long examId = (examIdRequestParam != null) +// ? examIdRequestParam +// : mapper.getLong(API.EXAM_API_PARAM_EXAM_ID); +// final String clientId = (clientIdRequestParam != null) +// ? clientIdRequestParam +// : mapper.getString(API.EXAM_API_PARAM_CLIENT_ID); +// +// // Create and get new ClientConnection if all integrity checks passes +// final ClientConnection clientConnection = this.sebClientConnectionService +// .createClientConnection( +// principal, +// institutionId, +// remoteAddr, +// mapper.getString(API.EXAM_API_PARAM_SEB_VERSION), +// mapper.getString(API.EXAM_API_PARAM_SEB_OS_NAME), +// mapper.getString(API.EXAM_API_PARAM_SEB_MACHINE_NAME), +// examId, +// clientId) +// .getOrThrow(); +// +// response.setHeader( +// API.EXAM_API_SEB_CONNECTION_TOKEN, +// clientConnection.connectionToken); +// +// // Crate list of running exams +// List result; +// if (examId == null) { +// result = this.examSessionService.getRunningExamsForInstitution(institutionId) +// .getOrThrow() +// .stream() +// .map(this::createRunningExamInfo) +// .filter(this::checkConsistency) +// .collect(Collectors.toList()); +// } else { +// +// final Exam exam = this.examSessionService +// .getExamDAO() +// .byPK(examId) +// .getOrThrow(); +// +// result = Arrays.asList(createRunningExamInfo(exam)); +// processASKSalt(response, clientConnection); +// processAlternativeBEK(response, clientConnection.examId); +// } +// +// if (result.isEmpty()) { +// log.warn( +// "There are no currently running exams for institution: {}. SEB connection creation denied", +// institutionId); +// } +// +// return result; +// }, +// this.executor); } private boolean checkConsistency(final RunningExamInfo info) { @@ -183,7 +248,7 @@ public class ExamAPI_V1_Controller { path = API.EXAM_API_HANDSHAKE_ENDPOINT, method = RequestMethod.PATCH, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) - public CompletableFuture handshakeUpdate( + public DeferredResult handshakeUpdate( @RequestHeader(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken, @RequestParam(name = API.EXAM_API_PARAM_EXAM_ID, required = false) final Long examId, @RequestParam(name = API.EXAM_API_USER_SESSION_ID, required = false) final String userSessionId, @@ -198,39 +263,71 @@ public class ExamAPI_V1_Controller { final HttpServletRequest request, final HttpServletResponse response) { - return CompletableFuture.runAsync( - () -> { + final DeferredResult deferredResult = new DeferredResult<>(); + this.executor.execute(() -> { + { - final String remoteAddr = this.getClientAddress(request); - final Long institutionId = getInstitutionId(principal); + final String remoteAddr = this.getClientAddress(request); + final Long institutionId = getInstitutionId(principal); - final ClientConnection clientConnection = this.sebClientConnectionService - .updateClientConnection( - connectionToken, - institutionId, - examId, - remoteAddr, - sebVersion, - sebOSName, - sebMachinName, - userSessionId, - clientId, - browserSignatureKey) - .getOrThrow(); + final ClientConnection clientConnection = this.sebClientConnectionService + .updateClientConnection( + connectionToken, + institutionId, + examId, + remoteAddr, + sebVersion, + sebOSName, + sebMachinName, + userSessionId, + clientId, + browserSignatureKey) + .getOrThrow(); - if (clientConnection.examId != null) { - processASKSalt(response, clientConnection); - processAlternativeBEK(response, clientConnection.examId); - } - }, - this.executor); + if (clientConnection.examId != null) { + processASKSalt(response, clientConnection); + processAlternativeBEK(response, clientConnection.examId); + } + + deferredResult.setResult(null); + } + }); + + return deferredResult; + +// return CompletableFuture.runAsync( +// () -> { +// +// final String remoteAddr = this.getClientAddress(request); +// final Long institutionId = getInstitutionId(principal); +// +// final ClientConnection clientConnection = this.sebClientConnectionService +// .updateClientConnection( +// connectionToken, +// institutionId, +// examId, +// remoteAddr, +// sebVersion, +// sebOSName, +// sebMachinName, +// userSessionId, +// clientId, +// browserSignatureKey) +// .getOrThrow(); +// +// if (clientConnection.examId != null) { +// processASKSalt(response, clientConnection); +// processAlternativeBEK(response, clientConnection.examId); +// } +// }, +// this.executor); } @RequestMapping( path = API.EXAM_API_HANDSHAKE_ENDPOINT, method = RequestMethod.PUT, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) - public CompletableFuture handshakeEstablish( + public DeferredResult handshakeEstablish( @RequestHeader(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken, @RequestParam(name = API.EXAM_API_PARAM_EXAM_ID, required = false) final Long examId, @RequestParam(name = API.EXAM_API_USER_SESSION_ID, required = false) final String userSessionId, @@ -245,31 +342,58 @@ public class ExamAPI_V1_Controller { final HttpServletRequest request, final HttpServletResponse response) { - return CompletableFuture.runAsync( - () -> { + final DeferredResult deferredResult = new DeferredResult<>(); + this.executor.execute(() -> { - final String remoteAddr = this.getClientAddress(request); - final Long institutionId = getInstitutionId(principal); + final String remoteAddr = this.getClientAddress(request); + final Long institutionId = getInstitutionId(principal); - final ClientConnection clientConnection = this.sebClientConnectionService - .establishClientConnection( - connectionToken, - institutionId, - examId, - remoteAddr, - sebVersion, - sebOSName, - sebMachinName, - userSessionId, - clientId, - browserSignatureKey) - .getOrThrow(); + final ClientConnection clientConnection = this.sebClientConnectionService + .establishClientConnection( + connectionToken, + institutionId, + examId, + remoteAddr, + sebVersion, + sebOSName, + sebMachinName, + userSessionId, + clientId, + browserSignatureKey) + .getOrThrow(); - if (clientConnection.examId != null) { - processAlternativeBEK(response, clientConnection.examId); - } - }, - this.executor); + if (clientConnection.examId != null) { + processAlternativeBEK(response, clientConnection.examId); + } + + deferredResult.setResult(null); + }); + return deferredResult; +// return CompletableFuture.runAsync( +// () -> { +// +// final String remoteAddr = this.getClientAddress(request); +// final Long institutionId = getInstitutionId(principal); +// +// final ClientConnection clientConnection = this.sebClientConnectionService +// .establishClientConnection( +// connectionToken, +// institutionId, +// examId, +// remoteAddr, +// sebVersion, +// sebOSName, +// sebMachinName, +// userSessionId, +// clientId, +// browserSignatureKey) +// .getOrThrow(); +// +// if (clientConnection.examId != null) { +// processAlternativeBEK(response, clientConnection.examId); +// } +// }, +// this.executor); } @RequestMapping(