diff --git a/README.adoc b/README.adoc index 107578b..f9420dc 100644 --- a/README.adoc +++ b/README.adoc @@ -12,30 +12,21 @@ A minimal implementation of Nexus repository to practice Vert.x * [x] Jitpack. .Snapshots -* [x] Enable snapshots. +* [x] Enable snapshot repositories of above mirrors. .Publishing artifacts -* [ ] Publish artifacts to Kumoru. (_Contemplating_) +* [x] Publish artifacts to Kumoru. .HTTP methods supported * [x] GET +* [x] PUT == Pre-Requisites -1. JDK 11 +1. Java 11 2. Docker -== Building and Running Locally - -To build your application: -``` -./gradlew clean build -``` - -To package your application into Docker container: -``` -./gradlew jibDockerBuild -``` +== Running the server To run your application: ``` @@ -52,4 +43,73 @@ docker run -e KUMORU_ACCESS_LOG=true -p 8888:8888 -v /tmp/repo:/srv/repo rishabh > All logs are written to the `STDOUT`. +== Configuring Maven + +.Mirroring +Edit Maven's `settings.xml` and add a ``: + +``` + + + kumoru + * + Kumoru - A minimal Nexus repository + http://localhost:8888 + + + +``` + +.Publishing + +In your `pom.xml` add + +``` + + + kumoru + Kumoru - A minimal Nexus repository + http://localhost:8888 + + +``` + +== Configuring Gradle +.Mirroring + +Add to you `build.gradle` + +``` +repositories { + maven { + url: "http://localhost:8080" + } +} +``` + +.Publishing + +Add to your `build.gradle` + +``` +publishing { + repositories { + maven { + url: "http://localhost:8080" + } + } +} +``` + +== Building the code + +To build your application: +``` +./gradlew clean build +``` + +To package your application into Docker container: +``` +./gradlew jibDockerBuild +``` diff --git a/src/main/java/com/github/rishabh9/kumoru/MainVerticle.java b/src/main/java/com/github/rishabh9/kumoru/MainVerticle.java index 8dc86a6..1b6efac 100644 --- a/src/main/java/com/github/rishabh9/kumoru/MainVerticle.java +++ b/src/main/java/com/github/rishabh9/kumoru/MainVerticle.java @@ -6,6 +6,7 @@ import com.github.rishabh9.kumoru.handlers.LocalResourceHandler; import com.github.rishabh9.kumoru.handlers.MavenMirrorHandler; import com.github.rishabh9.kumoru.handlers.SendFileHandler; +import com.github.rishabh9.kumoru.handlers.UploadHandler; import com.github.rishabh9.kumoru.handlers.ValidRequestHandler; import io.vertx.core.AbstractVerticle; import io.vertx.core.Promise; @@ -13,6 +14,7 @@ import io.vertx.core.http.HttpServerOptions; import io.vertx.ext.web.Route; import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.BodyHandler; import io.vertx.ext.web.handler.LoggerFormat; import io.vertx.ext.web.handler.LoggerHandler; import lombok.extern.log4j.Log4j2; @@ -25,7 +27,28 @@ public void start(final Promise startFuture) { // Get all environment variables final int port = getKumoruPort(); - final boolean enableAccessLog = enableAccessLog(); + final Router router = configureRoutes(enableAccessLog(), getBodyLimit()); + + // Logging network server activity + final HttpServerOptions options = new HttpServerOptions().setLogActivity(true); + final HttpServer httpServer = vertx.createHttpServer(options); + httpServer.requestHandler(router); + httpServer.listen( + port, + asyncResult -> { + if (asyncResult.succeeded()) { + startFuture.complete(); + log.info( + "Kumoru server [v{}] started on port {}", + VersionProperties.INSTANCE.getVersion(), + port); + } else { + startFuture.fail(asyncResult.cause()); + } + }); + } + + private Router configureRoutes(final boolean enableAccessLog, final long bodyLimit) { // All handlers final ValidRequestHandler validRequestHandler = new ValidRequestHandler(); @@ -35,6 +58,7 @@ public void start(final Promise startFuture) { final JitPackMirrorHandler jitPackMirror = new JitPackMirrorHandler(vertx); final SendFileHandler sendFileHandler = new SendFileHandler(); final FinalHandler finalHandler = new FinalHandler(); + final UploadHandler uploadHandler = new UploadHandler(vertx); final Router router = Router.router(vertx); // for all routes do @@ -43,6 +67,7 @@ public void start(final Promise startFuture) { route.handler(LoggerHandler.create(LoggerFormat.DEFAULT)); } route.handler(validRequestHandler); + // for GET method do router .get() @@ -53,23 +78,26 @@ public void start(final Promise startFuture) { .handler(sendFileHandler) .handler(finalHandler); - // Logging network server activity - final HttpServerOptions options = new HttpServerOptions().setLogActivity(true); - final HttpServer httpServer = vertx.createHttpServer(options); - httpServer.requestHandler(router); - httpServer.listen( - port, - asyncResult -> { - if (asyncResult.succeeded()) { - startFuture.complete(); - log.info( - "Kumoru server [v{}] started on port {}", - VersionProperties.INSTANCE.getVersion(), - port); - } else { - startFuture.fail(asyncResult.cause()); - } - }); + // for PUT method do + router + .put() + .handler(BodyHandler.create().setBodyLimit(bodyLimit).setDeleteUploadedFilesOnEnd(true)) + .handler(uploadHandler); + + return router; + } + + private int getBodyLimit() { + // Body limited to 50MB + final int defaultBodyLimit = 50000000; + final String bodyLimit = System.getenv("KUMORU_BODY_LIMIT"); + log.debug("Body limit configured as {} bytes", bodyLimit); + if (null != bodyLimit && !bodyLimit.isEmpty() && !bodyLimit.matches(".*\\D.*")) { + return Integer.parseInt(bodyLimit); + } else { + log.debug("Setting body limit as {} bytes", defaultBodyLimit); + return defaultBodyLimit; + } } private boolean enableAccessLog() { diff --git a/src/main/java/com/github/rishabh9/kumoru/handlers/UploadHandler.java b/src/main/java/com/github/rishabh9/kumoru/handlers/UploadHandler.java new file mode 100644 index 0000000..d1112d3 --- /dev/null +++ b/src/main/java/com/github/rishabh9/kumoru/handlers/UploadHandler.java @@ -0,0 +1,79 @@ +package com.github.rishabh9.kumoru.handlers; + +import io.vertx.core.Vertx; +import io.vertx.ext.web.RoutingContext; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +public class UploadHandler extends KumoruHandler { + + private final Vertx vertx; + + public UploadHandler(final Vertx vertx) { + this.vertx = vertx; + } + + @Override + public void handle(final RoutingContext routingContext) { + final String path = routingContext.normalisedPath(); + final String subPath = path.substring(0, path.lastIndexOf("/")); + final String directory = REPO_ROOT + subPath; + vertx + .fileSystem() + .exists( + directory, + existsResult -> { + if (existsResult.succeeded() + && null != existsResult.result() + && existsResult.result()) { + log.debug("The directory {} already exists", subPath); + writeFile(routingContext); + } else { + log.debug("The directory {} does not exists", subPath); + createDirectory(routingContext, directory); + } + }); + } + + private void createDirectory(final RoutingContext routingContext, final String directory) { + vertx + .fileSystem() + .mkdirs( + directory, + mkdirsResult -> { + if (mkdirsResult.succeeded()) { + log.debug("Created directory {}", directory); + writeFile(routingContext); + } else { + log.error("Failed to create directory {}", directory, mkdirsResult.cause()); + routingContext + .response() + .setStatusCode(INTERNAL_ERROR) + .setStatusMessage(mkdirsResult.cause().getMessage()) + .end(); + } + }); + } + + private void writeFile(final RoutingContext routingContext) { + final String path = routingContext.normalisedPath(); + vertx + .fileSystem() + .writeFile( + REPO_ROOT + path, + routingContext.getBody(), + writeResult -> { + if (writeResult.succeeded()) { + log.debug("Saved {} to disk", path); + routingContext.response().end(); + } else { + log.error("There was error writing {} to disk", path, writeResult.cause()); + routingContext + .response() + .setStatusCode(INTERNAL_ERROR) + .setStatusMessage(writeResult.cause().getMessage()) + .end(); + } + }); + } +} diff --git a/src/main/java/com/github/rishabh9/kumoru/handlers/ValidRequestHandler.java b/src/main/java/com/github/rishabh9/kumoru/handlers/ValidRequestHandler.java index fd9515f..c09f390 100644 --- a/src/main/java/com/github/rishabh9/kumoru/handlers/ValidRequestHandler.java +++ b/src/main/java/com/github/rishabh9/kumoru/handlers/ValidRequestHandler.java @@ -1,6 +1,7 @@ package com.github.rishabh9.kumoru.handlers; import static io.vertx.core.http.HttpMethod.GET; +import static io.vertx.core.http.HttpMethod.PUT; import io.vertx.core.http.HttpMethod; import io.vertx.ext.web.RoutingContext; @@ -18,22 +19,23 @@ public void handle(final RoutingContext routingContext) { .setStatusCode(METHOD_NOT_ALLOWED) .setStatusMessage("Method Not Allowed") .end(); - } - final String path = routingContext.normalisedPath(); - if (isValidPath(path)) { - routingContext.next(); } else { - log.error("Path has invalid characters: {}", path); - routingContext - .response() - .setStatusCode(BAD_REQUEST) - .setStatusMessage("Path has invalid characters") - .end(); + final String path = routingContext.normalisedPath(); + if (isValidPath(path)) { + routingContext.next(); + } else { + log.error("Path has invalid characters: {}", path); + routingContext + .response() + .setStatusCode(BAD_REQUEST) + .setStatusMessage("Path has invalid characters") + .end(); + } } } private boolean isSupportedMethod(final HttpMethod method) { - return GET.equals(method); + return GET.equals(method) || PUT.equals(method); } private boolean isValidPath(final String path) {