SEBSERV-445 using DerreferedResult for client connections

This commit is contained in:
anhefti 2023-05-31 11:34:25 +02:00
parent 6ccf74f9c1
commit 9c82f20763
4 changed files with 259 additions and 142 deletions

View file

@ -17,7 +17,6 @@ import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
@Configuration @Configuration
@EnableAsync @EnableAsync
@ -75,14 +74,14 @@ public class AsyncServiceSpringConfig implements AsyncConfigurer {
return executor; return executor;
} }
@Bean // @Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() { // public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
final ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); // final ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(5); // threadPoolTaskScheduler.setPoolSize(5);
threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(false); // threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(false);
threadPoolTaskScheduler.setThreadNamePrefix("SEB-Server-BgTask-"); // threadPoolTaskScheduler.setThreadNamePrefix("SEB-Server-BgTask-");
return threadPoolTaskScheduler; // return threadPoolTaskScheduler;
} // }
@Override @Override
public Executor getAsyncExecutor() { public Executor getAsyncExecutor() {

View file

@ -46,7 +46,7 @@ public class SEBClientPingService {
} }
@Scheduled( @Scheduled(
fixedDelayString = "${sebserver.webservice.api.exam.session.ping.batch.interval:100}", fixedDelayString = "${sebserver.webservice.api.exam.session.ping.batch.interval:500}",
initialDelay = 1000) initialDelay = 1000)
public void processPings() { public void processPings() {
if (this.pings.isEmpty()) { if (this.pings.isEmpty()) {
@ -84,14 +84,10 @@ public class SEBClientPingService {
} }
} }
public String notifyPing( public final String notifyPing(
final String connectionToken, final String connectionToken,
final String instructionConfirm) { final String instructionConfirm) {
if (connectionToken == null) {
return null;
}
if (instructionConfirm != null) { if (instructionConfirm != null) {
this.pings.put(connectionToken, instructionConfirm); this.pings.put(connectionToken, instructionConfirm);
} else if (!this.pings.containsKey(connectionToken)) { } else if (!this.pings.containsKey(connectionToken)) {
@ -106,6 +102,10 @@ public class SEBClientPingService {
final String instructionConfirm, final String instructionConfirm,
final long timestamp) { final long timestamp) {
if (connectionToken == null) {
return;
}
final ClientConnectionDataInternal activeClientConnection = this.examSessionCacheService final ClientConnectionDataInternal activeClientConnection = this.examSessionCacheService
.getClientConnection(connectionToken); .getClientConnection(connectionToken);

View file

@ -8,34 +8,28 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api; 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 org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; //@EnableAsync
//@Configuration
@EnableAsync //@WebServiceProfile
@Configuration @Deprecated
@WebServiceProfile
public class ControllerConfig implements WebMvcConfigurer { public class ControllerConfig implements WebMvcConfigurer {
@Override // @Override
public void configureAsyncSupport(final AsyncSupportConfigurer configurer) { // public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
configurer.setTaskExecutor(threadPoolTaskExecutor()); // configurer.setTaskExecutor(threadPoolTaskExecutor());
configurer.setDefaultTimeout(30000); // configurer.setDefaultTimeout(30000);
} // }
//
public AsyncTaskExecutor threadPoolTaskExecutor() { // public AsyncTaskExecutor threadPoolTaskExecutor() {
final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(7); // executor.setCorePoolSize(7);
executor.setMaxPoolSize(42); // executor.setMaxPoolSize(42);
executor.setQueueCapacity(11); // executor.setQueueCapacity(11);
executor.setThreadNamePrefix("mvc-"); // executor.setThreadNamePrefix("mvc-");
executor.initialize(); // executor.initialize();
return executor; // return executor;
} // }
} }

View file

@ -36,6 +36,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController; 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.Constants;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
@ -93,7 +94,7 @@ public class ExamAPI_V1_Controller {
method = RequestMethod.POST, method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
public CompletableFuture<Collection<RunningExamInfo>> handshakeCreate( public DeferredResult<Collection<RunningExamInfo>> handshakeCreate(
@RequestParam(name = API.PARAM_INSTITUTION_ID, required = false) final Long instIdRequestParam, @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_EXAM_ID, required = false) final Long examIdRequestParam,
@RequestParam(name = API.EXAM_API_PARAM_CLIENT_ID, required = false) final String clientIdRequestParam, @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 HttpServletRequest request,
final HttpServletResponse response) { final HttpServletResponse response) {
return CompletableFuture.supplyAsync( final DeferredResult<Collection<RunningExamInfo>> 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); // Create and get new ClientConnection if all integrity checks passes
final Long institutionId = (instIdRequestParam != null) final ClientConnection clientConnection = this.sebClientConnectionService
? instIdRequestParam .createClientConnection(
: mapper.getLong(API.PARAM_INSTITUTION_ID); principal,
final Long examId = (examIdRequestParam != null) institutionId,
? examIdRequestParam remoteAddr,
: mapper.getLong(API.EXAM_API_PARAM_EXAM_ID); mapper.getString(API.EXAM_API_PARAM_SEB_VERSION),
final String clientId = (clientIdRequestParam != null) mapper.getString(API.EXAM_API_PARAM_SEB_OS_NAME),
? clientIdRequestParam mapper.getString(API.EXAM_API_PARAM_SEB_MACHINE_NAME),
: mapper.getString(API.EXAM_API_PARAM_CLIENT_ID); examId,
clientId)
.getOrThrow();
// Create and get new ClientConnection if all integrity checks passes response.setHeader(
final ClientConnection clientConnection = this.sebClientConnectionService API.EXAM_API_SEB_CONNECTION_TOKEN,
.createClientConnection( clientConnection.connectionToken);
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( // Crate list of running exams
API.EXAM_API_SEB_CONNECTION_TOKEN, List<RunningExamInfo> result;
clientConnection.connectionToken); 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 final Exam exam = this.examSessionService
List<RunningExamInfo> result; .getExamDAO()
if (examId == null) { .byPK(examId)
result = this.examSessionService.getRunningExamsForInstitution(institutionId) .getOrThrow();
.getOrThrow()
.stream()
.map(this::createRunningExamInfo)
.filter(this::checkConsistency)
.collect(Collectors.toList());
} else {
final Exam exam = this.examSessionService result = Arrays.asList(createRunningExamInfo(exam));
.getExamDAO() processASKSalt(response, clientConnection);
.byPK(examId) processAlternativeBEK(response, clientConnection.examId);
.getOrThrow(); }
result = Arrays.asList(createRunningExamInfo(exam)); if (result.isEmpty()) {
processASKSalt(response, clientConnection); log.warn(
processAlternativeBEK(response, clientConnection.examId); "There are no currently running exams for institution: {}. SEB connection creation denied",
} institutionId);
}
if (result.isEmpty()) { deferredResult.setResult(result);
log.warn( });
"There are no currently running exams for institution: {}. SEB connection creation denied",
institutionId);
}
return result; return deferredResult;
},
this.executor); // 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<RunningExamInfo> 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) { private boolean checkConsistency(final RunningExamInfo info) {
@ -183,7 +248,7 @@ public class ExamAPI_V1_Controller {
path = API.EXAM_API_HANDSHAKE_ENDPOINT, path = API.EXAM_API_HANDSHAKE_ENDPOINT,
method = RequestMethod.PATCH, method = RequestMethod.PATCH,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public CompletableFuture<Void> handshakeUpdate( public DeferredResult<Void> handshakeUpdate(
@RequestHeader(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken, @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_PARAM_EXAM_ID, required = false) final Long examId,
@RequestParam(name = API.EXAM_API_USER_SESSION_ID, required = false) final String userSessionId, @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 HttpServletRequest request,
final HttpServletResponse response) { final HttpServletResponse response) {
return CompletableFuture.runAsync( final DeferredResult<Void> deferredResult = new DeferredResult<>();
() -> { this.executor.execute(() -> {
{
final String remoteAddr = this.getClientAddress(request); final String remoteAddr = this.getClientAddress(request);
final Long institutionId = getInstitutionId(principal); final Long institutionId = getInstitutionId(principal);
final ClientConnection clientConnection = this.sebClientConnectionService final ClientConnection clientConnection = this.sebClientConnectionService
.updateClientConnection( .updateClientConnection(
connectionToken, connectionToken,
institutionId, institutionId,
examId, examId,
remoteAddr, remoteAddr,
sebVersion, sebVersion,
sebOSName, sebOSName,
sebMachinName, sebMachinName,
userSessionId, userSessionId,
clientId, clientId,
browserSignatureKey) browserSignatureKey)
.getOrThrow(); .getOrThrow();
if (clientConnection.examId != null) { if (clientConnection.examId != null) {
processASKSalt(response, clientConnection); processASKSalt(response, clientConnection);
processAlternativeBEK(response, clientConnection.examId); processAlternativeBEK(response, clientConnection.examId);
} }
},
this.executor); 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( @RequestMapping(
path = API.EXAM_API_HANDSHAKE_ENDPOINT, path = API.EXAM_API_HANDSHAKE_ENDPOINT,
method = RequestMethod.PUT, method = RequestMethod.PUT,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public CompletableFuture<Void> handshakeEstablish( public DeferredResult<Void> handshakeEstablish(
@RequestHeader(name = API.EXAM_API_SEB_CONNECTION_TOKEN, required = true) final String connectionToken, @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_PARAM_EXAM_ID, required = false) final Long examId,
@RequestParam(name = API.EXAM_API_USER_SESSION_ID, required = false) final String userSessionId, @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 HttpServletRequest request,
final HttpServletResponse response) { final HttpServletResponse response) {
return CompletableFuture.runAsync( final DeferredResult<Void> deferredResult = new DeferredResult<>();
() -> { this.executor.execute(() -> {
final String remoteAddr = this.getClientAddress(request); final String remoteAddr = this.getClientAddress(request);
final Long institutionId = getInstitutionId(principal); final Long institutionId = getInstitutionId(principal);
final ClientConnection clientConnection = this.sebClientConnectionService final ClientConnection clientConnection = this.sebClientConnectionService
.establishClientConnection( .establishClientConnection(
connectionToken, connectionToken,
institutionId, institutionId,
examId, examId,
remoteAddr, remoteAddr,
sebVersion, sebVersion,
sebOSName, sebOSName,
sebMachinName, sebMachinName,
userSessionId, userSessionId,
clientId, clientId,
browserSignatureKey) browserSignatureKey)
.getOrThrow(); .getOrThrow();
if (clientConnection.examId != null) { if (clientConnection.examId != null) {
processAlternativeBEK(response, clientConnection.examId); processAlternativeBEK(response, clientConnection.examId);
} }
},
this.executor); 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( @RequestMapping(