getResource(String id, String path) {
+ MavenMirrorRepository repository = map.get(id);
+ if (repository == null) {
+ //думаю лучше кидать ошибку о несуществующем id
+ return Optional.empty();
+ }
+
+ return repository.getResource(path);
+ }
+}
diff --git a/src/main/java/ru/di9/mirror/utils/Cache404.java b/src/main/java/ru/di9/mirror/utils/Cache404.java
new file mode 100644
index 0000000..678faf6
--- /dev/null
+++ b/src/main/java/ru/di9/mirror/utils/Cache404.java
@@ -0,0 +1,76 @@
+package ru.di9.mirror.utils;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Класс кэширования не найденных (404) удалённых ресурсов.
+ *
+ * Необходим для того, чтобы не "задалбывать" удалённые репозитории запросами ресурсов, которых у них нет.
+ * Т.е. мы один раз опрашиваем удалённый репозиторий на получение ресурса. И если ресурса нет, то сохраняем
+ * эту информацию в этот класс. Если в течении указанного времени актуальности кэша будет повторный запрос ресурса
+ * которого нет, то мы не станем повторно спрашивать об этом удалённый репозиторий, а сразу ответим 404.
+ */
+public class Cache404 {
+ private static final long ONE_MIN = TimeUnit.MINUTES.toMillis(1);
+ private final Map map = new HashMap<>();
+ private long lastCheck = System.currentTimeMillis();
+
+ private long actualTime;
+
+ /**
+ * Создание кэша.
+ *
+ * @param actualTime актуальность кэша в минутах. Минимум 1 минута.
+ */
+ public Cache404(int actualTime) {
+ if (actualTime < 1) {
+ actualTime = 1;
+ }
+ this.actualTime = TimeUnit.MINUTES.toMillis(actualTime);
+ }
+
+ /**
+ * Проверяет, есть ли ресурс в кэше.
+ *
+ * @param path путь к ресурсу
+ * @return true, если ресурс есть в кэше
+ */
+ public boolean contains(String path) {
+ checkOutTimes();
+ Long exp = map.get(path);
+ if (exp == null) {
+ return false;
+ } else if ((System.currentTimeMillis() - exp) > actualTime) {
+ map.remove(path);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Сохранить информацию об отсутствии ресурса.
+ *
+ * @param path путь к ресурсу
+ */
+ public void put(String path) {
+ checkOutTimes();
+ long exp = System.currentTimeMillis() + actualTime;
+ map.put(path, exp);
+ }
+
+ /**
+ * Чистим кэш от устаревших данных.
+ * Не чаще одного раза в минуту.
+ */
+ private void checkOutTimes() {
+ if ((System.currentTimeMillis() - lastCheck) <= ONE_MIN) {
+ return;
+ }
+
+ long currTime = System.currentTimeMillis();
+ map.entrySet().removeIf(e -> (currTime - e.getValue()) > actualTime);
+ lastCheck = currTime;
+ }
+}
diff --git a/src/main/java/ru/di9/mirror/utils/MavenClient.java b/src/main/java/ru/di9/mirror/utils/MavenClient.java
new file mode 100644
index 0000000..17e4c16
--- /dev/null
+++ b/src/main/java/ru/di9/mirror/utils/MavenClient.java
@@ -0,0 +1,59 @@
+package ru.di9.mirror.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.IOUtils;
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URI;
+
+/**
+ * Клиент для работы с Maven-like репозиториями.
+ */
+@Slf4j
+public class MavenClient {
+ private final String baseUrl;
+ private final CloseableHttpClient httpClient;
+
+ /**
+ * Создание клиента.
+ *
+ * @param url базовый URL репозитория
+ */
+ public MavenClient(String url) {
+ this.baseUrl = url.endsWith("/") ? url.substring(0, url.length() - 1) : url;
+ this.httpClient = HttpClientBuilder.create().build();
+ }
+
+ /**
+ * Получить ресурс из репозитория.
+ *
+ * @param path путь к ресурсу
+ * @param outputStream стрим, в который загрузится ресурс
+ */
+ public boolean getResource(String path, OutputStream outputStream) {
+ URI uri = URI.create(baseUrl + (path.startsWith("/") ? "" : "/") + path);
+ log.debug("GET '{}'", uri);
+ var httpGet = new HttpGet(uri);
+
+ try {
+ return httpClient.execute(httpGet, resp -> {
+ if (resp.getCode() == 404) {
+ return false;
+ } else if (resp.getCode() != 200) {
+ log.error("HTTP GET '{}' RETURN CODE {}", uri, resp.getCode());
+ return false;
+ }
+
+ IOUtils.copy(resp.getEntity().getContent(), outputStream);
+ return true;
+ });
+ } catch (IOException e) {
+ log.error("ERROR HTTP GET '{}'", uri, e);
+ return false;
+ }
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 0000000..93038dc
--- /dev/null
+++ b/src/main/resources/application.properties
@@ -0,0 +1,8 @@
+server.address=0.0.0.0
+server.port=8080
+
+app.maven.storage=storage
+
+app.maven.repository[0].id=central
+app.maven.repository[0].url=https://repo1.maven.org/maven2
+app.maven.repository[0].cache-time=60
diff --git a/web-spring/build.gradle b/web-spring/build.gradle
deleted file mode 100644
index 7562d08..0000000
--- a/web-spring/build.gradle
+++ /dev/null
@@ -1,29 +0,0 @@
-apply plugin: 'org.springframework.boot'
-apply plugin: 'application'
-
-dependencies {
- implementation(project(':core'))
-
- annotationProcessor(deps.springboot.config_processor)
- implementation(deps.springboot.web)
-}
-
-application {
- mainClass = 'ru.di9.mirror.web.Application'
-}
-
-tasks.named('compileJava') {
- it.dependsOn('moveSpringConfigurationMetadata')
-}
-
-tasks.register('moveSpringConfigurationMetadata').configure {
- it.dependsOn('processResources')
- doLast {
- def metafile = file("${sourceSets.main.output.resourcesDir}/META-INF/additional-spring-configuration-metadata.json")
- if (metafile.exists()) {
- def metafileTo = file("${sourceSets.main.output.classesDirs.asPath}/META-INF/spring-configuration-metadata.json")
- metafileTo.parentFile.mkdirs()
- metafile.renameTo(metafileTo)
- }
- }
-}
diff --git a/web-spring/src/main/java/ru/di9/mirror/web/config/MavenMirrorsProperties.java b/web-spring/src/main/java/ru/di9/mirror/web/config/MavenMirrorsProperties.java
deleted file mode 100644
index 8597396..0000000
--- a/web-spring/src/main/java/ru/di9/mirror/web/config/MavenMirrorsProperties.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package ru.di9.mirror.web.config;
-
-import lombok.Getter;
-import lombok.Setter;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.stereotype.Component;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@Component
-@ConfigurationProperties(prefix = "maven-mirrors")
-public class MavenMirrorsProperties {
-
- @Getter
- @Setter
- private String storage;
-
- @Getter
- private final List list = new ArrayList<>();
-
- public record MirrorData(String id, String url) {
- }
-}
diff --git a/web-spring/src/main/java/ru/di9/mirror/web/config/WebConfig.java b/web-spring/src/main/java/ru/di9/mirror/web/config/WebConfig.java
deleted file mode 100644
index 98cb33a..0000000
--- a/web-spring/src/main/java/ru/di9/mirror/web/config/WebConfig.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package ru.di9.mirror.web.config;
-
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import ru.di9.mirror.core.FileStorage;
-import ru.di9.mirror.core.MavenClient;
-import ru.di9.mirror.core.MirrorService;
-import ru.di9.mirror.core.MirrorService.MirrorPair;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-@Slf4j
-@Configuration
-public class WebConfig {
-
- @Bean
- @SuppressWarnings("java:S3864")
- public MirrorService mirrorService(MavenMirrorsProperties mirrorsProperties) {
- Path storagePath = Paths.get(mirrorsProperties.getStorage());
-
- return new MirrorService(mirrorsProperties.getList()
- .stream()
- .peek(v -> log.info("read config mirror: {}|{}", v.id(), v.url()))
- .map(mirrorData -> new MirrorPair(
- new FileStorage(mirrorData.id(), storagePath),
- new MavenClient(mirrorData.url())
- ))
- .toList());
- }
-}
diff --git a/web-spring/src/main/java/ru/di9/mirror/web/controller/MavenController.java b/web-spring/src/main/java/ru/di9/mirror/web/controller/MavenController.java
deleted file mode 100644
index ec998d3..0000000
--- a/web-spring/src/main/java/ru/di9/mirror/web/controller/MavenController.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package ru.di9.mirror.web.controller;
-
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.core.io.InputStreamResource;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import ru.di9.mirror.core.MirrorService;
-
-@Slf4j
-@Controller
-@RequestMapping(path = "/maven")
-public class MavenController {
-
- private final MirrorService mirrorService;
-
- public MavenController(MirrorService mirrorService) {
- this.mirrorService = mirrorService;
- }
-
- @GetMapping(path = "/{*path}")
- public ResponseEntity artifactFileGet(@PathVariable("path") String httpPath) {
- return mirrorService.getFile(httpPath)
- .map(fileRecord -> ResponseEntity.ok()
- .header(HttpHeaders.CONTENT_DISPOSITION,
- "attachment; filename=\"" + fileRecord.name() + "\"")
- .contentType(MediaType.APPLICATION_OCTET_STREAM)
- .contentLength(fileRecord.size())
- .body(new InputStreamResource(fileRecord.inputStream())))
- .orElse(ResponseEntity.notFound().build());
- }
-}
diff --git a/web-spring/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/web-spring/src/main/resources/META-INF/additional-spring-configuration-metadata.json
deleted file mode 100644
index 65b3609..0000000
--- a/web-spring/src/main/resources/META-INF/additional-spring-configuration-metadata.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "properties": [
- {
- "name": "maven-mirrors.storage",
- "type": "java.lang.String"
- },
- {
- "name": "maven-mirrors.list",
- "type": "java.util.List",
- "sourceType": "java.util.List"
- }
- ]
-}
diff --git a/web-spring/src/main/resources/application.yml b/web-spring/src/main/resources/application.yml
deleted file mode 100644
index 600b85c..0000000
--- a/web-spring/src/main/resources/application.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-server:
- address: 127.0.0.1
- port: 8080
-
-debug: false
-
-maven-mirrors:
- storage: './storage'
- list:
- - id: 'maven-central'
- url: 'https://repo1.maven.org/maven2'