Skip to content

Commit

Permalink
GH-127 Web Interface returns 404 (Resolve #127)
Browse files Browse the repository at this point in the history
  • Loading branch information
dzikoysk committed Jul 15, 2020
1 parent 8919988 commit 159d043
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public final class ErrorDto implements Serializable {
private final int status;
private final String message;

ErrorDto(int status, String message) {
public ErrorDto(int status, String message) {
this.status = status;
this.message = message;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import io.javalin.http.Handler;
import org.apache.http.HttpStatus;
import org.panda_lang.reposilite.Reposilite;
import org.panda_lang.reposilite.api.ErrorDto;
import org.panda_lang.reposilite.frontend.FrontendService;
import org.panda_lang.reposilite.utils.Result;

Expand All @@ -36,21 +37,28 @@ public LookupController(FrontendService frontend, LookupService lookupService) {
@Override
public void handle(Context context) {
Reposilite.getLogger().info("LOOKUP " + context.req.getRequestURI() + " from " + context.req.getRemoteAddr());
Result<Context, ErrorDto> lookupResponse = lookupService.serveLocal(context);

Result<Context, String> lookupResponse = lookupService
.serveLocal(context)
.orElse(localError -> lookupService.serveProxied(context)
.map(context::result)
.orElse(proxiedError -> Result.error(localError)));
if (isProxied(lookupResponse)) {
if (lookupService.hasProxiedRepositories()) {
lookupResponse = lookupResponse.orElse(localError -> lookupService.serveProxied(context).map(context::result));
}

lookupResponse.onError(error -> {
Reposilite.getLogger().debug("error=" + error + "; uri=" + context.req.getRequestURI());
if (isProxied(lookupResponse)) {
lookupResponse = lookupResponse.mapError(proxiedError -> new ErrorDto(HttpStatus.SC_NOT_FOUND, proxiedError.getMessage()));
}
}

lookupResponse.onError(error -> {
context.res.setCharacterEncoding("UTF-8");
context.status(HttpStatus.SC_NOT_FOUND)
context.status(error.getStatus())
.contentType("text/html")
.result(frontend.forMessage(error));
.result(frontend.forMessage(error.getMessage()));
});
}

private boolean isProxied(Result<Context, ErrorDto> lookupResponse) {
return lookupResponse.containsError() && lookupResponse.getError().getStatus() == HttpStatus.SC_USE_PROXY;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpStatus;
import org.panda_lang.reposilite.Reposilite;
import org.panda_lang.reposilite.api.ErrorDto;
import org.panda_lang.reposilite.auth.Authenticator;
import org.panda_lang.reposilite.auth.Session;
import org.panda_lang.reposilite.config.Configuration;
import org.panda_lang.reposilite.frontend.FrontendService;
import org.panda_lang.reposilite.metadata.MetadataService;
Expand All @@ -37,13 +37,13 @@
import org.panda_lang.reposilite.utils.Result;
import org.panda_lang.utilities.commons.StringUtils;
import org.panda_lang.utilities.commons.collection.Pair;
import org.panda_lang.utilities.commons.text.ContentJoiner;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
Expand Down Expand Up @@ -71,11 +71,11 @@ public LookupService(Reposilite reposilite) {
this.proxiedExecutor = configuration.getProxied().isEmpty() ? null : Executors.newCachedThreadPool();
}

protected Result<Context, String> serveLocal(Context context) {
protected Result<Context, ErrorDto> serveLocal(Context context) {
Result<Pair<String[], Repository>, String> result = this.authenticator.authDefaultRepository(context, context.req.getRequestURI());

if (result.containsError()) {
return Result.error(result.getError());
return Result.error(new ErrorDto(HttpStatus.SC_OK, result.getError()));
}

String[] path = result.getValue().getKey();
Expand All @@ -84,16 +84,17 @@ protected Result<Context, String> serveLocal(Context context) {

// discard invalid requests (less than 'group/(artifact OR metadata)')
if (requestPath.length < 2) {
return Result.error("Missing artifact identifier");
return Result.error(new ErrorDto(HttpStatus.SC_OK, "Missing artifact identifier"));
}

Repository repository = result.getValue().getValue();
String requestedFileName = requestPath[requestPath.length - 1];
String requestedFileName = Objects.requireNonNull(ArrayUtils.getLast(requestPath));

if (requestedFileName.equals("maven-metadata.xml")) {
return metadataService
.generateMetadata(repository, requestPath)
.map(res -> context.contentType("text/xml").result(res));
.map(res -> context.contentType("text/xml").result(res))
.mapError(error -> new ErrorDto(HttpStatus.SC_NOT_FOUND, error));
}

// resolve requests for latest version of artifact
Expand All @@ -103,7 +104,7 @@ protected Result<Context, String> serveLocal(Context context) {
File version = ArrayUtils.getFirst(versions);

if (version == null) {
return Result.error("Latest version not found");
return Result.error(new ErrorDto(HttpStatus.SC_NOT_FOUND, "Latest version not found"));
}

return Result.ok(context.result(version.getName()));
Expand All @@ -116,10 +117,16 @@ protected Result<Context, String> serveLocal(Context context) {
requestedFileName = requestPath[requestPath.length - 1];
}

File repositoryFile = repository.getFile(requestPath);

if (repositoryFile.exists() && repositoryFile.isDirectory()) {
return Result.error(new ErrorDto(HttpStatus.SC_OK, "Directory access"));
}

Optional<Artifact> artifact = repository.find(requestPath);

if (!artifact.isPresent()) {
return Result.error("Artifact " + ContentJoiner.on("/").join(requestPath) + " not found");
return Result.error(new ErrorDto(HttpStatus.SC_USE_PROXY, "Artifact " + requestedFileName + " not found"));
}

File file = artifact.get().getFile(requestedFileName);
Expand All @@ -145,24 +152,21 @@ protected Result<Context, String> serveLocal(Context context) {
return Result.ok(context);
} catch (Exception exception) {
reposilite.throwException(context.req.getRequestURI(), exception);
return Result.error("Cannot read artifact");
return Result.error(new ErrorDto(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Cannot read artifact"));
} finally {
FilesUtils.close(content);
}
}

protected Result<CompletableFuture<Context>, String> serveProxied(Context context) {
if (proxiedExecutor == null) {
return Result.error("Proxied repositories are not enabled");
}

protected Result<CompletableFuture<Context>, ErrorDto> serveProxied(Context context) {
String uri = context.req.getRequestURI();

// /groupId/artifactId/<content>
if (StringUtils.countOccurrences(uri, "/") < 3) {
return Result.error("Invalid proxied request");
return Result.error(new ErrorDto(HttpStatus.SC_OK, "Invalid proxied request"));
}

return Result.ok(FutureUtils.submit(proxiedExecutor, future -> {
return Result.ok(FutureUtils.submit(reposilite, proxiedExecutor, future -> {
for (String proxied : configuration.getProxied()) {
try {
HttpRequest remoteRequest = requestFactory.buildGetRequest(new GenericUrl(proxied + uri));
Expand Down Expand Up @@ -203,4 +207,8 @@ protected Result<CompletableFuture<Context>, String> serveProxied(Context contex
}));
}

public boolean hasProxiedRepositories() {
return proxiedExecutor != null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.panda_lang.reposilite.utils;

import org.jetbrains.annotations.Nullable;
import org.panda_lang.reposilite.Reposilite;
import org.panda_lang.utilities.commons.function.ThrowingFunction;
import org.panda_lang.utilities.commons.function.ThrowingRunnable;
Expand All @@ -27,9 +28,9 @@ public final class FutureUtils {

private FutureUtils() { }

public static <T, E extends Exception> CompletableFuture<T> submit(ExecutorService service, ThrowingFunction<CompletableFuture<T>, ?, E> futureConsumer) {
public static <T, E extends Exception> CompletableFuture<T> submit(Reposilite reposilite, ExecutorService service, ThrowingFunction<CompletableFuture<T>, ?, E> futureConsumer) {
CompletableFuture<T> completableFuture = new CompletableFuture<>();
service.submit(() -> futureConsumer.apply(completableFuture));
service.submit(() -> run(reposilite, () -> futureConsumer.apply(completableFuture)));
return completableFuture;
}

Expand All @@ -41,11 +42,16 @@ public static <E extends Exception> void executeChecked(Reposilite reposilite, E
service.execute(() -> run(reposilite, runnable));
}

private static void run(Reposilite reposilite, ThrowingRunnable<?> runnable) {
private static void run(@Nullable Reposilite reposilite, ThrowingRunnable<?> runnable) {
try {
runnable.run();
} catch (Exception e) {
reposilite.throwException("Exception occurred during the task execution", e);
if (reposilite != null) {
reposilite.throwException("Exception occurred during the task execution", e);
}
else {
e.printStackTrace();
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public <R> Result<R, E> map(Function<V, R> function) {
return value != null ? Result.ok(function.apply(value)) : Result.error(error);
}

public <R> Result<V, R> mapError(Function<E, R> function) {
return isDefined() ? Result.ok(value) : Result.error(function.apply(error));
}

public Result<V, E> orElse(Function<E, Result<V, E>> orElse) {
return isDefined() ? this : orElse.apply(error);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import com.google.api.client.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.panda_lang.reposilite.Reposilite;
Expand All @@ -42,24 +41,24 @@ class LookupControllerTest extends ReposiliteIntegrationTest {
}

@Test
void shouldReturn404AndFrontendWithUnsupportedRequestMessage() throws IOException {
assert404WithMessage(get("/"), "Unsupported request");
void shouldReturn200AndFrontendWithUnsupportedRequestMessage() throws IOException {
assertResponseWithMessage(get("/"), HttpStatus.SC_OK, "Unsupported request");
}

@Test
void shouldReturn404AndFrontendWithRepositoryNotFoundMessage() throws IOException {
void shouldReturn200AndFrontendWithRepositoryNotFoundMessage() throws IOException {
super.reposilite.getConfiguration().setRewritePathsEnabled(false);
assert404WithMessage(get("/invalid_repository/groupId/artifactId"), "Repository invalid_repository not found");
assertResponseWithMessage(get("/invalid_repository/groupId/artifactId"), HttpStatus.SC_OK, "Repository invalid_repository not found");
}

@Test
void shouldReturn404AndFrontendWithMissingArtifactIdentifier() throws IOException {
assert404WithMessage(get("/releases/groupId"), "Missing artifact identifier");
void shouldReturn200AndFrontendWithMissingArtifactIdentifier() throws IOException {
assertResponseWithMessage(get("/releases/groupId"), HttpStatus.SC_OK, "Missing artifact identifier");
}

@Test
void shouldReturn404AndFrontendWithMissingArtifactPathMessage() throws IOException {
assert404WithMessage(get("/releases/groupId/artifactId"), "Artifact groupId/artifactId not found");
void shouldReturn404AndFrontendWithProxiedRepositoriesAreNotEnabledMessage() throws IOException {
assertResponseWithMessage(get("/releases/groupId/artifactId"), HttpStatus.SC_NOT_FOUND, "Artifact artifactId not found");
}

@Test
Expand All @@ -78,7 +77,11 @@ void shouldReturn200AndLatestVersion() throws IOException {

@Test
void shouldReturn404AndFrontendWithLatestVersionNotFound() throws IOException {
assert404WithMessage(get("/releases/org/panda-lang/reposilite-test/reposilite-test-1.0.0.jar/latest"), "Latest version not found");
assertResponseWithMessage(
get("/releases/org/panda-lang/reposilite-test/reposilite-test-1.0.0.jar/latest"),
HttpStatus.SC_NOT_FOUND,
"Latest version not found"
);
}

@Test
Expand All @@ -90,7 +93,11 @@ void shouldReturn200AndResolvedSnapshotFile() throws IOException {

@Test
void shouldReturn404AndArtifactNotFoundMessage() throws IOException {
assert404WithMessage(super.get("/releases/org/panda-lang/reposilite-test/1.0.0/artifactId"), "Artifact org/panda-lang/reposilite-test/1.0.0/artifactId not found");
assertResponseWithMessage(
super.get("/releases/org/panda-lang/reposilite-test/1.0.0/artifactId"),
HttpStatus.SC_NOT_FOUND,
"Artifact artifactId not found"
);
}

@Test
Expand All @@ -111,11 +118,12 @@ void shouldReturn200AndHeadRequestedFile() throws IOException {
}

@Test
void shouldReturn404WithUnauthorizedMessage() throws IOException {
assert404WithMessage(super.get("/private/a/b"), "Unauthorized request");
void shouldReturn200WithUnauthorizedMessage() throws IOException {
assertResponseWithMessage(super.get("/private/a/b"), HttpStatus.SC_OK, "Unauthorized request");
}

@Test
@SuppressWarnings("ResultOfMethodCallIgnored")
void shouldReturn200AndProxiedFile() throws Exception {
String proxyPort = String.valueOf(Integer.parseInt(PORT) + 1);
super.reposilite.getConfiguration().setProxied(Collections.singletonList("http://localhost:" + proxyPort));
Expand Down Expand Up @@ -143,8 +151,8 @@ void shouldReturn200AndProxiedFile() throws Exception {
}
}

static void assert404WithMessage(HttpResponse response, String message) throws IOException {
assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode());
static void assertResponseWithMessage(HttpResponse response, int status, String message) throws IOException {
assertEquals(status, response.getStatusCode());
String content = response.parseAsString();
System.out.println(content);
assertTrue(content.contains("REPOSILITE_MESSAGE = '" + message + "'"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.panda_lang.reposilite.ReposiliteIntegrationTest;
import org.panda_lang.reposilite.api.ErrorDto;
import org.panda_lang.reposilite.utils.FutureUtils;
import org.panda_lang.reposilite.utils.Result;
import org.panda_lang.utilities.commons.FileUtils;
Expand Down Expand Up @@ -54,6 +55,7 @@ class LookupServiceTest extends ReposiliteIntegrationTest {
private LookupService lookupService;

@BeforeEach
@SuppressWarnings("ResultOfMethodCallIgnored")
void configure() throws IOException {
super.reposilite.getConfiguration().setProxied(Collections.singletonList(url("").toString()));
this.lookupService = new LookupService(super.reposilite);
Expand All @@ -67,10 +69,10 @@ void configure() throws IOException {
@Test
void shouldReturnErrorForInvalidProxiedRequest() {
Context context = mockContext("/groupId/artifactId");
Result<CompletableFuture<Context>, String> result = lookupService.serveProxied(context);
Result<CompletableFuture<Context>, ErrorDto> result = lookupService.serveProxied(context);

assertTrue(result.containsError());
assertEquals("Invalid proxied request", result.getError());
assertEquals("Invalid proxied request", result.getError().getMessage());
}

@Test
Expand All @@ -82,7 +84,7 @@ void shouldReturn404AndArtifactNotFound() throws Exception {
return null;
}).when(context.res).setStatus(anyInt());

FutureUtils.submit(SERVICE, future -> {
FutureUtils.submit(reposilite, SERVICE, future -> {
return future.complete(lookupService.serveProxied(context).getValue().get());
}).get();

Expand All @@ -100,7 +102,7 @@ void shouldReturn200AndProxiedFile() throws Exception {
return null;
}).when(context.res).setStatus(anyInt());

FutureUtils.submit(SERVICE, future -> {
FutureUtils.submit(reposilite, SERVICE, future -> {
return future.complete(lookupService.serveProxied(context).getValue().get());
}).get();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class FutureUtilsTest {

@Test
void submit() throws ExecutionException, InterruptedException {
assertEquals("result", FutureUtils.submit(EXECUTOR_SERVICE, completableFuture -> completableFuture.complete("result")).get());
assertEquals("result", FutureUtils.submit(null, EXECUTOR_SERVICE, completableFuture -> completableFuture.complete("result")).get());
}

}

0 comments on commit 159d043

Please sign in to comment.