diff --git a/config.md b/config.md new file mode 100644 index 0000000..00daa29 --- /dev/null +++ b/config.md @@ -0,0 +1,41 @@ +# Описание конфигурации параметров проекта + +В micord.env заданы следующие переменные окружения: + +#### Конфигурация и топики Kafka ЛК РП +- `AV_KAFKA_BOOTSTRAP_SERVERS` - список пар хост:порт, использующихся для установки первоначального соединения с кластером Kafka +- `AV_KAFKA_SECURITY_PROTOCOL` - протокол, используемый для взаимодействия с брокерами +- `AV_KAFKA_SASL_MECHANISM` - механизм SASL, используемый для клиентских подключений +- `AV_KAFKA_USERNAME` - пользователь для подключения к Kafka +- `AV_KAFKA_PASSWORD` - пароль для подключения к Kafka +- `AV_KAFKA_GROUP_ID` - идентификатор группы потребителей, который отвечает за создание группы для объединения нескольких потребителей +- `AV_KAFKA_TOPIC_NAME` - топик для чтения данных по загруженному в личном кабинете файлу +- `AV_KAFKA_STATUS_TOPIC_NAME` - топик для отправки статусов проверки файла + +#### Конфигурация и топики Kafka ЕРВУ +- `ERVU_KAFKA_BOOTSTRAP_SERVERS` - список пар хост:порт, использующихся для установки первоначального соединения с кластером Kafka +- `ERVU_KAFKA_SECURITY_PROTOCOL` - протокол, используемый для взаимодействия с брокерами +- `ERVU_KAFKA_SASL_MECHANISM` - механизм SASL, используемый для клиентских подключений +- `ERVU_KAFKA_USERNAME` - пользователь для подключения к Kafka +- `ERVU_KAFKA_PASSWORD` - пароль для подключения к Kafka +- `ERVU_KAFKA_GROUP_ID` - идентификатор группы потребителей, который отвечает за создание группы для объединения нескольких потребителей +- `ERVU_KAFKA_ERROR_TOPIC_NAME` - топик для отправки данных об ошибках проверки файла или наличии вирусов +- `ERVU_KAFKA_SUCCESS_TOPIC_NAME` - топик для отправки данных об успешной проверке файла +- `ERVU_KAFKA_RESPONSE_TOPIC_NAME` - топик для чтения статусов файла, полученных от ЕРВУ + +#### Настройки взаимодействия с файлами и антивирусом +- `FILE_SAVING_PATH` - путь для сохранения файла на диске +- `AV_CHECK_ENABLED` - параметр включения/отключения проверки на вирусы +- `AV_REST_ADDRESS` - url для отправки файлов на проверку антивирусом +- `AV_FIRST_TIMEOUT_MILLISECONDS` - таймаут ожидания окончания проверки анивирусом +- `AV_RETRY_MAX_ATTEMPTS_COUNT` - количество попыток проверки файла +- `AV_RETRY_DELAY_MILLISECONDS` - задержка между попытками проверки файла + +#### Конфигурация S3 +- `S3_ENDPOINT` - url для подключения к S3 +- `S3_ACCESS_KEY` - публичная часть пары ключей AWS +- `S3_SECRET_KEY` - закрытая часть пары ключей AWS +- `S3_BUCKET_NAME` - наименование бакета для сохранения проверенного файла +- `S3_PATH_STYLE_ACCESS_ENABLED` - параметр включения/отключения стиля, при котором название бакета указывается в части пути до объекта в URI + + diff --git a/pom.xml b/pom.xml index ef0efbb..c58ab24 100644 --- a/pom.xml +++ b/pom.xml @@ -7,13 +7,13 @@ org.springframework.boot spring-boot-starter-parent - 3.3.0 + 3.3.5 ru.micord.ervu.lkrp file-upload - 1.0.0-SNAPSHOT + 1.10.0-SNAPSHOT @@ -42,12 +42,6 @@ 4.5.14 - - org.postgresql - postgresql - 42.7.3 - - org.projectlombok lombok @@ -77,11 +71,6 @@ httpmime - - org.postgresql - postgresql - - org.projectlombok lombok @@ -104,10 +93,6 @@ org.springframework.boot spring-boot-starter-web - - org.springframework.boot - spring-boot-starter-jooq - org.springframework.boot spring-boot-starter-actuator diff --git a/src/main/java/ru/micord/ervu/av/FileUploadApplication.java b/src/main/java/ru/micord/ervu/av/FileUploadApplication.java index 0fbd59a..ae6ac96 100644 --- a/src/main/java/ru/micord/ervu/av/FileUploadApplication.java +++ b/src/main/java/ru/micord/ervu/av/FileUploadApplication.java @@ -4,13 +4,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration; import org.springframework.retry.annotation.EnableRetry; -import org.springframework.transaction.annotation.EnableTransactionManagement; /** * @author r.latypov */ @EnableRetry -@EnableTransactionManagement @SpringBootApplication(exclude = KafkaAutoConfiguration.class) public class FileUploadApplication { diff --git a/src/main/java/ru/micord/ervu/av/exception/RetryableException.java b/src/main/java/ru/micord/ervu/av/exception/RetryableException.java index 3cc7f82..fb162da 100644 --- a/src/main/java/ru/micord/ervu/av/exception/RetryableException.java +++ b/src/main/java/ru/micord/ervu/av/exception/RetryableException.java @@ -4,4 +4,8 @@ package ru.micord.ervu.av.exception; * @author r.latypov */ public class RetryableException extends RuntimeException { + + public RetryableException(String message) { + super(message); + } } diff --git a/src/main/java/ru/micord/ervu/av/kafka/dto/FileStatus.java b/src/main/java/ru/micord/ervu/av/kafka/dto/FileStatus.java index 95729f0..020c879 100644 --- a/src/main/java/ru/micord/ervu/av/kafka/dto/FileStatus.java +++ b/src/main/java/ru/micord/ervu/av/kafka/dto/FileStatus.java @@ -16,4 +16,7 @@ public record FileStatus(String code, String status, String description) { public static final FileStatus FILE_STATUS_04 = new FileStatus("04", "Получен ЕРВУ", "Файл был принят в обработку" ); + public static final FileStatus FILE_STATUS_11 = new FileStatus("11", "Невозможно проверить файл ЛК РП", + "Невозможно проверить файл по причине недоступности или ошибки в работе антивируса" + ); } diff --git a/src/main/java/ru/micord/ervu/av/service/FileManager.java b/src/main/java/ru/micord/ervu/av/service/FileManager.java new file mode 100644 index 0000000..4b52afe --- /dev/null +++ b/src/main/java/ru/micord/ervu/av/service/FileManager.java @@ -0,0 +1,86 @@ +package ru.micord.ervu.av.service; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Path; + +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.conn.HttpHostConnectException; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.springframework.http.HttpStatus; +import org.springframework.retry.annotation.Backoff; +import org.springframework.retry.annotation.Retryable; +import org.springframework.stereotype.Component; +import ru.micord.ervu.av.exception.FileUploadException; +import ru.micord.ervu.av.exception.InvalidHttpFileUrlException; +import ru.micord.ervu.av.exception.RetryableException; + +/** + * @author gulnaz + */ +@Component +public class FileManager { + + @Retryable(retryFor = {InvalidHttpFileUrlException.class}, + maxAttemptsExpression = "${av.retry.max.attempts.count}", + backoff = @Backoff(delayExpression = "${av.retry.delay.milliseconds}")) + public void downloadFile(String fileUrl, Path filePath) + throws InvalidHttpFileUrlException, FileUploadException { + File file = filePath.toFile(); + HttpGet request = new HttpGet(fileUrl); + + try (CloseableHttpClient client = HttpClients.createDefault(); + CloseableHttpResponse response = client.execute(request)) { + + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode == HttpStatus.OK.value()) { + HttpEntity entity = response.getEntity(); + if (entity != null) { + try (FileOutputStream outputStream = new FileOutputStream(file)) { + entity.writeTo(outputStream); + } + } + } + else { + // в хранилище не обнаружено файла; сообщение некорректно + String message = "http status code " + statusCode + " : " + fileUrl; + throw new InvalidHttpFileUrlException(message); + } + } + catch (HttpHostConnectException e) { + throw new FileUploadException(e); + } + catch (IOException e) { + // хранилище недоступно; сообщение некорректно + String message = + (e.getMessage() == null ? e.getCause().getMessage() : e.getMessage()) + " : " + fileUrl; + throw new InvalidHttpFileUrlException(message, e); + } + } + + @Retryable(retryFor = {RetryableException.class}, + maxAttemptsExpression = "${av.retry.max.attempts.count}", + backoff = @Backoff(delayExpression = "${av.retry.delay.milliseconds}")) + public void deleteFile(String fileUrl) throws FileUploadException { + try (CloseableHttpClient client = HttpClients.createDefault()) { + HttpDelete delete = new HttpDelete(fileUrl); + + try (CloseableHttpResponse response = client.execute(delete)) { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode != HttpStatus.NO_CONTENT.value()) { + String message = "http status code " + statusCode + " : " + fileUrl; + throw new RetryableException(message); + } + } + } + catch (IOException e) { + // непредусмотренная ошибка доступа через http-клиент + throw new FileUploadException(e); + } + } +} diff --git a/src/main/java/ru/micord/ervu/av/service/FileUploadService.java b/src/main/java/ru/micord/ervu/av/service/FileUploadService.java index 87457b3..b4f44a7 100644 --- a/src/main/java/ru/micord/ervu/av/service/FileUploadService.java +++ b/src/main/java/ru/micord/ervu/av/service/FileUploadService.java @@ -1,7 +1,5 @@ package ru.micord.ervu.av.service; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -9,22 +7,9 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import com.google.gson.JsonSyntaxException; -import org.apache.http.HttpEntity; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.conn.HttpHostConnectException; -import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.entity.mime.content.FileBody; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.util.EntityUtils; import org.apache.kafka.clients.admin.NewTopic; import org.apache.kafka.clients.producer.ProducerRecord; import org.slf4j.Logger; @@ -32,7 +17,6 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpStatus; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.support.Acknowledgment; @@ -42,10 +26,10 @@ import org.springframework.messaging.handler.annotation.Header; import org.springframework.stereotype.Service; import ru.micord.ervu.av.exception.FileUploadException; import ru.micord.ervu.av.exception.InvalidHttpFileUrlException; +import ru.micord.ervu.av.exception.RetryableException; import ru.micord.ervu.av.kafka.dto.DownloadRequest; import ru.micord.ervu.av.kafka.dto.DownloadResponse; import ru.micord.ervu.av.kafka.dto.FileStatus; -import ru.micord.ervu.av.response.AvFileSendResponse; import ru.micord.ervu.av.response.AvResponse; import ru.micord.ervu.av.s3.S3Service; @@ -57,10 +41,6 @@ public class FileUploadService { private static final Logger logger = LoggerFactory.getLogger(FileUploadService.class); @Value("${av.check.enabled}") private boolean avCheckEnabled; - @Value("${av.rest.address}") - private String avRestAddress; - @Value("${av.first.timeout.milliseconds}") - private Long avFirstTimeoutMilliseconds; @Value("${file.saving.path}") private String fileSavingPath; private final KafkaTemplate kafkaTemplate; @@ -68,14 +48,18 @@ public class FileUploadService { private final NewTopic outErrorTopic; private final NewTopic outSuccessTopic; private final NewTopic inStatusTopic; + private final FileManager fIleManager; private final ReceiveScanReportRetryable receiveScanReportRetryable; private final S3Service s3Service; @Autowired - public FileUploadService(@Qualifier("outputKafkaTemplate") KafkaTemplate kafkaTemplate, + public FileUploadService( + @Qualifier("outputKafkaTemplate") KafkaTemplate kafkaTemplate, @Qualifier("inputKafkaTemplate") KafkaTemplate inKafkaTemplate, - NewTopic outErrorTopic, NewTopic outSuccessTopic, ReceiveScanReportRetryable receiveScanReportRetryable, - S3Service s3Service, NewTopic inStatusTopic) { + NewTopic outErrorTopic, NewTopic outSuccessTopic, + ReceiveScanReportRetryable receiveScanReportRetryable, + S3Service s3Service, NewTopic inStatusTopic, + FileManager fIleManager) { this.kafkaTemplate = kafkaTemplate; this.outErrorTopic = outErrorTopic; this.outSuccessTopic = outSuccessTopic; @@ -83,6 +67,7 @@ public class FileUploadService { this.s3Service = s3Service; this.inKafkaTemplate = inKafkaTemplate; this.inStatusTopic = inStatusTopic; + this.fIleManager = fIleManager; } @KafkaListener(id = "${spring.kafka.consumer.group.id}", topics = "${kafka.in.topic.name}", @@ -103,23 +88,33 @@ public class FileUploadService { logger.info("working in {}", System.getProperty("user.home")); Path filePath = Paths.get(fileSavingPath, fileUrl.fileName()); String downloadUrl = fileUrl.fileUrl(); - downloadFile(downloadUrl, filePath); + fIleManager.downloadFile(downloadUrl, filePath); + boolean isAvError = false; boolean clean = true; boolean infected = false; if (avCheckEnabled) { - AvResponse avResponse = checkFile(filePath); - - clean = Arrays.stream(avResponse.verdicts()) - .anyMatch(verdict -> verdict.equalsIgnoreCase(AvResponse.Scan.VERDICT_CLEAN)); - infected = Arrays.stream(avResponse.verdicts()) - .anyMatch(verdict -> verdict.equalsIgnoreCase(AvResponse.Scan.VERDICT_INFECTED)); + try { + AvResponse avResponse = receiveScanReportRetryable.checkFile(filePath); + clean = Arrays.stream(avResponse.verdicts()) + .anyMatch(verdict -> verdict.equalsIgnoreCase(AvResponse.Scan.VERDICT_CLEAN)); + infected = Arrays.stream(avResponse.verdicts()) + .anyMatch(verdict -> verdict.equalsIgnoreCase(AvResponse.Scan.VERDICT_INFECTED)); + } + catch (FileUploadException | RetryableException e) { + logger.error(e.getMessage(), e); + isAvError = true; + } } - if (infected || !clean) { + if (isAvError || infected || !clean) { downloadRequest.fileInfo().setFileUrl(null); - downloadRequest.fileInfo().setFileStatus(FileStatus.FILE_STATUS_02); - sendMessage(outErrorTopic.name(), downloadRequest, messageId, kafkaTemplate); + FileStatus fileStatus = isAvError ? FileStatus.FILE_STATUS_11 : FileStatus.FILE_STATUS_02; + downloadRequest.fileInfo().setFileStatus(fileStatus); + + if (!isAvError) { + sendMessage(outErrorTopic.name(), downloadRequest, messageId, kafkaTemplate); + } } else { String fileRef = s3Service.putFile(filePath, fileUrl.fileName()); @@ -130,7 +125,7 @@ public class FileUploadService { } sendMessage(inStatusTopic.name(), downloadRequest, messageId, inKafkaTemplate); - deleteFile(downloadUrl); + fIleManager.deleteFile(downloadUrl); try { Files.delete(filePath); @@ -144,10 +139,12 @@ public class FileUploadService { } catch (InvalidHttpFileUrlException e) { // считаем, что повторная обработка сообщения не нужна - // ошибку логируем, сообщаем об ошибке, помечаем прочтение сообщения + // ошибку логируем, отправляем сообщение с новым статусом, помечаем прочтение сообщения logger.error(e.getMessage() + ": " + kafkaInMessage); + downloadRequest.fileInfo().setFileUrl(null); + downloadRequest.fileInfo().setFileStatus(FileStatus.FILE_STATUS_11); + sendMessage(inStatusTopic.name(), downloadRequest, messageId, inKafkaTemplate); acknowledgment.acknowledge(); - throw new RuntimeException(kafkaInMessage, e); } catch (FileUploadException e) { // считаем, что нужно повторное считывание сообщения @@ -187,120 +184,6 @@ public class FileUploadService { } } - private void downloadFile(String fileUrl, Path filePath) - throws InvalidHttpFileUrlException, FileUploadException { - File file = filePath.toFile(); - HttpGet request = new HttpGet(fileUrl); - - try (CloseableHttpClient client = HttpClients.createDefault(); - CloseableHttpResponse response = client.execute(request)) { - - int statusCode = response.getStatusLine().getStatusCode(); - if (statusCode == HttpStatus.OK.value()) { - HttpEntity entity = response.getEntity(); - if (entity != null) { - try (FileOutputStream outputStream = new FileOutputStream(file)) { - entity.writeTo(outputStream); - } - } - } - else { - // в хранилище не обнаружено файла; сообщение некорректно - String message = "http status code " + statusCode + " : " + fileUrl; - throw new InvalidHttpFileUrlException(message); - } - } - catch (HttpHostConnectException e) { - throw new FileUploadException(e); - } - catch (IOException e) { - // хранилище недоступно; сообщение некорректно - String message = - (e.getMessage() == null ? e.getCause().getMessage() : e.getMessage()) + " : " + fileUrl; - throw new InvalidHttpFileUrlException(message, e); - } - } - - private AvResponse checkFile(Path filePath) throws FileUploadException { - File file = filePath.toFile(); - - try (CloseableHttpClient client = HttpClients.createDefault()) { - HttpPost post = new HttpPost(avRestAddress); - HttpEntity entity = MultipartEntityBuilder.create() - .addPart("file", new FileBody(file)) - .build(); - post.setEntity(entity); - - try (CloseableHttpResponse postResponse = client.execute(post)) { - int postStatusCode = postResponse.getStatusLine().getStatusCode(); - String postResponseJson = EntityUtils.toString(postResponse.getEntity()); - - AvFileSendResponse avFileSendResponse; - try { - avFileSendResponse = new Gson().fromJson(postResponseJson, AvFileSendResponse.class); - } - catch (JsonSyntaxException e) { - throw new FileUploadException("error json: " + postResponseJson, e); - } - - if (postStatusCode != HttpStatus.CREATED.value()) { - StringBuilder stringBuilder = new StringBuilder( - "http status code " + postStatusCode + " for " + avRestAddress + " post request."); - - String status = avFileSendResponse.status(); - if (status != null) { - stringBuilder.append(" Status: ").append(status).append("."); - } - if (avFileSendResponse.error() != null) { - stringBuilder.append(" Error code: ") - .append(avFileSendResponse.error().code()) - .append(". Error message: ") - .append(avFileSendResponse.error().message()) - .append(". "); - } - throw new FileUploadException(stringBuilder.toString()); - } - - String id = avFileSendResponse.id(); - String reportRequestUri = avRestAddress + "/" + id; - HttpGet get = new HttpGet(reportRequestUri); - - // waiting for timeout time before first request - try { - TimeUnit.MILLISECONDS.sleep( - avFirstTimeoutMilliseconds == null ? 1000L : avFirstTimeoutMilliseconds); - } - catch (InterruptedException e) { - throw new FileUploadException(e); - } - - return receiveScanReportRetryable.receiveScanReport(client, get); - } - } - catch (IOException e) { - // непредусмотренная ошибка доступа через http-клиент - throw new FileUploadException(e); - } - } - - private void deleteFile(String fileUrl) throws FileUploadException { - try (CloseableHttpClient client = HttpClients.createDefault()) { - HttpDelete delete = new HttpDelete(fileUrl); - - try (CloseableHttpResponse response = client.execute(delete)) { - int statusCode = response.getStatusLine().getStatusCode(); - if (statusCode != HttpStatus.NO_CONTENT.value()) { - String message = "http status code " + statusCode + " : " + fileUrl; - throw new RuntimeException(message); - } - } - } - catch (IOException e) { - // непредусмотренная ошибка доступа через http-клиент - throw new FileUploadException(e); - } - } - private void sendMessage(@NonNull String topicName, Object object, String messageId, KafkaTemplate template) { ProducerRecord record = new ProducerRecord<>(topicName, diff --git a/src/main/java/ru/micord/ervu/av/service/ReceiveScanReportRetryable.java b/src/main/java/ru/micord/ervu/av/service/ReceiveScanReportRetryable.java index 77d3838..1b4fbc4 100644 --- a/src/main/java/ru/micord/ervu/av/service/ReceiveScanReportRetryable.java +++ b/src/main/java/ru/micord/ervu/av/service/ReceiveScanReportRetryable.java @@ -1,19 +1,30 @@ package ru.micord.ervu.av.service; +import java.io.File; import java.io.IOException; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import org.apache.http.HttpEntity; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.entity.mime.content.FileBody; import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; import org.springframework.stereotype.Service; import ru.micord.ervu.av.exception.FileUploadException; import ru.micord.ervu.av.exception.RetryableException; +import ru.micord.ervu.av.response.AvFileSendResponse; import ru.micord.ervu.av.response.AvResponse; /** @@ -21,19 +32,92 @@ import ru.micord.ervu.av.response.AvResponse; */ @Service public class ReceiveScanReportRetryable { + + @Value("${av.rest.address}") + private String avRestAddress; + @Value("${av.first.timeout.milliseconds:1000}") + private Long avFirstTimeoutMilliseconds; + @Retryable(retryFor = {RetryableException.class}, maxAttemptsExpression = "${av.retry.max.attempts.count}", backoff = @Backoff(delayExpression = "${av.retry.delay.milliseconds}")) - public AvResponse receiveScanReport(CloseableHttpClient client, HttpGet get) - throws FileUploadException { + public AvResponse checkFile(Path filePath) throws RetryableException, FileUploadException { + File file = filePath.toFile(); + + try (CloseableHttpClient client = HttpClients.createDefault()) { + HttpPost post = new HttpPost(avRestAddress); + HttpEntity entity = MultipartEntityBuilder.create() + .addPart("file", new FileBody(file)) + .build(); + post.setEntity(entity); + + try (CloseableHttpResponse postResponse = client.execute(post)) { + int postStatusCode = postResponse.getStatusLine().getStatusCode(); + String postResponseJson = EntityUtils.toString(postResponse.getEntity()); + AvFileSendResponse avFileSendResponse; + + try { + avFileSendResponse = new Gson().fromJson(postResponseJson, AvFileSendResponse.class); + } + catch (JsonSyntaxException e) { + throw new FileUploadException("error json: " + postResponseJson, e); + } + + if (postStatusCode != HttpStatus.CREATED.value()) { + StringBuilder stringBuilder = new StringBuilder( + "http status code " + postStatusCode + " for " + avRestAddress + " post request."); + String status = avFileSendResponse.status(); + + if (status != null) { + stringBuilder.append(" Status: ").append(status).append("."); + } + + if (avFileSendResponse.error() != null) { + stringBuilder.append(" Error code: ") + .append(avFileSendResponse.error().code()) + .append(". Error message: ") + .append(avFileSendResponse.error().message()) + .append(". "); + } + throw new FileUploadException(stringBuilder.toString()); + } + + String id = avFileSendResponse.id(); + String reportRequestUri = avRestAddress + "/" + id; + HttpGet get = new HttpGet(reportRequestUri); + + // waiting for timeout time before first request + try { + TimeUnit.MILLISECONDS.sleep(avFirstTimeoutMilliseconds); + } + catch (InterruptedException e) { + throw new FileUploadException(e); + } + + return receiveScanReport(client, get); + } + } + catch (ClientProtocolException e) { + throw new RetryableException("Failed to check file"); + } + catch (IOException e) { + // непредусмотренная ошибка доступа через http-клиент + throw new FileUploadException(e); + } + } + + private AvResponse receiveScanReport(CloseableHttpClient client, HttpGet get) + throws RetryableException, FileUploadException { try (CloseableHttpResponse getResponse = client.execute(get)) { int getStatusCode = getResponse.getStatusLine().getStatusCode(); + if (getStatusCode == HttpStatus.OK.value()) { String getResponseJson = EntityUtils.toString(getResponse.getEntity()); AvResponse avResponse = new Gson().fromJson(getResponseJson, AvResponse.class); + if (avResponse.completed() == null) { - throw new RetryableException(); + throw new RetryableException("Failed to complete file scan"); } return avResponse; } @@ -44,7 +128,7 @@ public class ReceiveScanReportRetryable { } catch (ClientProtocolException e) { // непредусмотренная ошибка доступа через http-клиент - throw new RuntimeException(e); + throw new RetryableException("Failed to receive scan report"); } catch (IOException e) { // непредусмотренная ошибка доступа через http-клиент diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d2d72f8..fa9859a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -63,10 +63,6 @@ s3.out.secret_key=${S3_SECRET_KEY} s3.out.bucket_name=${S3_OUT_BUCKET_NAME} s3.out.path.style.access.enabled=${S3_OUT_PATH_STYLE_ACCESS_ENABLED} # -# spring jooq dsl bean properties begin -> -spring.jooq.sql-dialect=Postgres -spring.datasource.driver-class-name=org.postgresql.Driver -# spring jooq dsl bean properties <- end # endpoints management management.endpoints.web.exposure.include = health, info, metrics