From 740cae0ffe27ac3ecac015c5951fed53b31133c4 Mon Sep 17 00:00:00 2001 From: dzikoysk Date: Thu, 21 May 2020 23:31:06 +0200 Subject: [PATCH] GH-55 Support for proxy repositories (Resolve #55) --- README.md | 4 +- .../panda_lang/reposilite/Configuration.java | 9 ++ .../reposilite/ReposiliteConstants.java | 2 +- .../repository/DownloadController.java | 82 +++++++++++-------- .../repository/RepositoryUtils.java | 28 +++++++ src/main/resources/reposilite.yml | 6 ++ 6 files changed, 95 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 1e6d1b320..e008dc7ce 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ $ docker pull dzikoysk/reposilite List of available management commands ```bash -Reposilite 2.3.2 Commands: +Reposilite 2.3.4 Commands: help - List available commands status - Display metrics tokens - List all generated tokens @@ -79,3 +79,5 @@ To use generated token add a new server in your `~/m2/settings.xml` {token} ``` + +#### FAQ diff --git a/src/main/java/org/panda_lang/reposilite/Configuration.java b/src/main/java/org/panda_lang/reposilite/Configuration.java index 0d729bcf8..6bd7c06ea 100644 --- a/src/main/java/org/panda_lang/reposilite/Configuration.java +++ b/src/main/java/org/panda_lang/reposilite/Configuration.java @@ -24,6 +24,7 @@ public final class Configuration implements Serializable { private String hostname; private int port; private List repositories; + private List proxied; private boolean deployEnabled; private boolean rewritePathsEnabled; private boolean fullAuthEnabled; @@ -40,6 +41,10 @@ public void setRepositories(List repositories) { this.repositories = repositories; } + public void setProxied(List proxied) { + this.proxied = proxied; + } + public void setDeployEnabled(boolean deployEnabled) { this.deployEnabled = deployEnabled; } @@ -60,6 +65,10 @@ public int getPort() { return port; } + public List getProxied() { + return proxied; + } + public List getRepositories() { return repositories; } diff --git a/src/main/java/org/panda_lang/reposilite/ReposiliteConstants.java b/src/main/java/org/panda_lang/reposilite/ReposiliteConstants.java index 0b8013cc9..b17fd80d3 100644 --- a/src/main/java/org/panda_lang/reposilite/ReposiliteConstants.java +++ b/src/main/java/org/panda_lang/reposilite/ReposiliteConstants.java @@ -22,7 +22,7 @@ public final class ReposiliteConstants { - public static final String VERSION = "2.3.2"; + public static final String VERSION = "2.3.4"; static final String GREETING_MESSAGE = ansi().bold().fg(Color.GREEN).a("Reposilite ").reset().a(ReposiliteConstants.VERSION).reset().toString(); diff --git a/src/main/java/org/panda_lang/reposilite/repository/DownloadController.java b/src/main/java/org/panda_lang/reposilite/repository/DownloadController.java index 328b49795..b6ce95924 100644 --- a/src/main/java/org/panda_lang/reposilite/repository/DownloadController.java +++ b/src/main/java/org/panda_lang/reposilite/repository/DownloadController.java @@ -16,19 +16,24 @@ package org.panda_lang.reposilite.repository; +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestFactory; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.javanet.NetHttpTransport; import fi.iki.elonen.NanoHTTPD; +import fi.iki.elonen.NanoHTTPD.IHTTPSession; import fi.iki.elonen.NanoHTTPD.Response; import fi.iki.elonen.NanoHTTPD.Response.Status; import org.panda_lang.reposilite.Configuration; +import org.panda_lang.reposilite.Reposilite; import org.panda_lang.reposilite.ReposiliteController; import org.panda_lang.reposilite.ReposiliteHttpServer; -import org.panda_lang.reposilite.Reposilite; import org.panda_lang.reposilite.auth.Authenticator; import org.panda_lang.reposilite.auth.Session; import org.panda_lang.reposilite.metadata.MetadataService; import org.panda_lang.reposilite.metadata.MetadataUtils; import org.panda_lang.reposilite.utils.Result; -import org.panda_lang.utilities.commons.StringUtils; import org.panda_lang.utilities.commons.text.ContentJoiner; import java.io.File; @@ -45,6 +50,7 @@ final class DownloadController implements ReposiliteController { private final Authenticator authenticator; private final MetadataService metadataService; private final RepositoryService repositoryService; + private final HttpRequestFactory requestFactory; public DownloadController(Reposilite reposilite) { this.reposilite = reposilite; @@ -52,10 +58,44 @@ public DownloadController(Reposilite reposilite) { this.authenticator = reposilite.getAuthenticator(); this.metadataService = reposilite.getMetadataService(); this.repositoryService = reposilite.getRepositoryService(); + this.requestFactory = configuration.getProxied().isEmpty() ? null : new NetHttpTransport().createRequestFactory(); } @Override - public NanoHTTPD.Response serve(ReposiliteHttpServer server, NanoHTTPD.IHTTPSession httpSession) throws IOException { + public Response serve(ReposiliteHttpServer server, IHTTPSession httpSession) throws Exception { + Response response = serveLocal(httpSession); + + if (response.getStatus().getRequestStatus() == Status.NOT_FOUND.getRequestStatus()) { + return serveProxied(httpSession); + } + + return response; + } + + public Response serveProxied(IHTTPSession httpSession) throws IOException { + String uri = httpSession.getUri(); + + for (String proxied : configuration.getProxied()) { + HttpRequest request = requestFactory.buildGetRequest(new GenericUrl(proxied + uri)); + request.setThrowExceptionOnExecuteError(false); + HttpResponse response = request.execute(); + + if (!response.isSuccessStatusCode()) { + continue; + } + + return NanoHTTPD.newFixedLengthResponse( + Status.lookup(response.getStatusCode()), + response.getMediaType().toString(), + response.getContent(), + response.getContent().available() + ); + } + + return notFound(reposilite, "Artifact not found in local and remote repository"); + } + + public Response serveLocal(IHTTPSession httpSession) throws IOException { if (configuration.isFullAuthEnabled()) { Result authResult = this.authenticator.authUri(httpSession); @@ -64,7 +104,7 @@ public NanoHTTPD.Response serve(ReposiliteHttpServer server, NanoHTTPD.IHTTPSess } } - String[] path = normalizeUri(reposilite.getConfiguration(), httpSession.getUri()).split("/"); + String[] path = RepositoryUtils.normalizeUri(configuration, httpSession.getUri()).split("/"); if (path.length == 0) { return notFound(reposilite, "Unsupported request"); @@ -95,7 +135,7 @@ public NanoHTTPD.Response serve(ReposiliteHttpServer server, NanoHTTPD.IHTTPSess return notFound(reposilite, "Metadata not found"); } - return NanoHTTPD.newFixedLengthResponse(Status.OK, "text/xml", metadataService.generateMetadata(repository, requestPath)); + return NanoHTTPD.newFixedLengthResponse(Status.OK, "text/xml", result); } if (requestedFileName.contains("-SNAPSHOT")) { @@ -121,7 +161,7 @@ public NanoHTTPD.Response serve(ReposiliteHttpServer server, NanoHTTPD.IHTTPSess content = new FileInputStream(file); String mimeType = Files.probeContentType(file.toPath()); - NanoHTTPD.Response response = NanoHTTPD.newChunkedResponse(NanoHTTPD.Response.Status.OK, mimeType, content); + Response response = NanoHTTPD.newChunkedResponse(Response.Status.OK, mimeType, content); response.addHeader("Content-Disposition", "attachment; filename=\"" + MetadataUtils.getLast(path) +"\""); response.addHeader("Content-Length", String.valueOf(file.length())); @@ -136,34 +176,8 @@ public NanoHTTPD.Response serve(ReposiliteHttpServer server, NanoHTTPD.IHTTPSess } } - private String normalizeUri(Configuration configuration, String uri) { - if (uri.startsWith("/")) { - uri = uri.substring(1); - } - - if (uri.contains("..")) { - return StringUtils.EMPTY; - } - - if (!configuration.isRewritePathsEnabled()) { - return uri; - } - - if (StringUtils.countOccurrences(uri, "/") <= 1) { - return uri; - } - - for (String repositoryName : configuration.getRepositories()) { - if (uri.startsWith(repositoryName)) { - return uri; - } - } - - return configuration.getRepositories().get(0) + "/" + uri; - } - - private NanoHTTPD.Response notFound(Reposilite reposilite, String message) { - return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.NOT_FOUND, "text/html", reposilite.getFrontend().forMessage(message)); + private Response notFound(Reposilite reposilite, String message) { + return NanoHTTPD.newFixedLengthResponse(Response.Status.NOT_FOUND, "text/html", reposilite.getFrontend().forMessage(message)); } } diff --git a/src/main/java/org/panda_lang/reposilite/repository/RepositoryUtils.java b/src/main/java/org/panda_lang/reposilite/repository/RepositoryUtils.java index 2266bc00e..642e37cac 100644 --- a/src/main/java/org/panda_lang/reposilite/repository/RepositoryUtils.java +++ b/src/main/java/org/panda_lang/reposilite/repository/RepositoryUtils.java @@ -16,6 +16,8 @@ package org.panda_lang.reposilite.repository; +import org.panda_lang.reposilite.Configuration; +import org.panda_lang.utilities.commons.StringUtils; import org.panda_lang.utilities.commons.text.ContentJoiner; import java.io.File; @@ -28,4 +30,30 @@ public static File toRequestedFile(Repository repository, String[] requestPath) return new File(repository.getLocalPath() + File.separator + ContentJoiner.on(File.separator).join(requestPath)); } + protected static String normalizeUri(Configuration configuration, String uri) { + if (uri.startsWith("/")) { + uri = uri.substring(1); + } + + if (uri.contains("..")) { + return StringUtils.EMPTY; + } + + if (!configuration.isRewritePathsEnabled()) { + return uri; + } + + if (StringUtils.countOccurrences(uri, "/") <= 1) { + return uri; + } + + for (String repositoryName : configuration.getRepositories()) { + if (uri.startsWith(repositoryName)) { + return uri; + } + } + + return configuration.getRepositories().get(0) + "/" + uri; + } + } diff --git a/src/main/resources/reposilite.yml b/src/main/resources/reposilite.yml index 5e016b19e..bd06ccc83 100644 --- a/src/main/resources/reposilite.yml +++ b/src/main/resources/reposilite.yml @@ -13,6 +13,12 @@ repositories: - releases - snapshots +# List of proxied repositories. +# Reposilite will search for an artifact in remote repositories listed below, if the requested artifact was not found. +# Note: URL cannot contains / at the end +proxied: [] +# - https://repo.panda-lang.org + # Accept deployment connections deployEnabled: true # Allow to omit name of the main repository in request