diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 0000000..0080322 --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,14 @@ +name: Run Gradle on PRs +on: pull_request +jobs: + gradle: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: 8 + - uses: eskatos/gradle-command-action@v1 + with: + gradle-version: 6.6.1 + arguments: build diff --git a/.gitignore b/.gitignore index 0e9d70a..7f9414a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,31 +1,12 @@ -# Created by .ignore support plugin (hsz.mobi) -### Java template -# Compiled class file *.class - -# Log file *.log -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* - *.idea *.iml -/_psd -/.gradle -/target -_tmp \ No newline at end of file + +build/ +.gradle/ + +gradle/ +gradlew* diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7782223..0000000 --- a/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ -install: gradle wrapper --gradle-version 5.4.1 - -language: java - -jdk: - - openjdk11 diff --git a/README.md b/README.md index 5c1fadb..e5376ce 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,22 @@ - -

- Logo -

+
+ Logo +

- Simple and fast HTTP-Framework with the touch of expressjs + Simple and fast HTTP-Framework with the touch of expressjs

- License MIT - Current version - Support me - Build with gradle - Build Status + + License MIT + + + Support me +

-
- -

- !! This project is looking for a new maintainer! Please comment here if you're interested !! -

- -
+# Getting Started -#### Getting Started ```java Express app = new Express(); @@ -44,59 +27,49 @@ app.get("/", (req, res) -> { ## Installation -### Maven -> Add repository: -```xml - - jitpack.io - https://jitpack.io - -``` - -> Add dependency: -```xml - - com.github.Simonwep - java-express - 0.1.1 - -``` - -### Gradle -> Add this to your build.gradle -```xml -repositories { - maven { url "https://jitpack.io/" } -} +Add the following dependency coordinate from JCenter on your favorite build system: + +[![Download](https://api.bintray.com/packages/vaccovecrana/vacco-oss/java-express/images/download.svg) ](https://bintray.com/vaccovecrana/vacco-oss/java-express/_latestVersion) + +``` +io.vacco.java-express:java-express: +``` + +- [Getting Started](#getting-started) + - [Installation](#installation) +- [Routing](#routing) + - [DynExpress](#dynexpress) + - [Direct](#direct) + - [With Router](#with-router) + - [URL Basics](#url-basics) + - [URL Parameter](#url-parameter) + - [URL Parameter Listener](#url-parameter-listener) + - [URL Querys](#url-querys) + - [Cookies](#cookies) + - [Form data](#form-data) + - [HTTP Relevant classes](#http-relevant-classes) + - [Express](#express) + - [Response Object](#response-object) + - [Request Object](#request-object) +- [Middleware](#middleware) + - [Create own middleware](#create-own-middleware) + - [Existing Middlewares](#existing-middlewares) + - [CORS](#cors) + - [Provide static Files](#provide-static-files) + - [Cookie Session](#cookie-session) + - [Global Variables](#global-variables) + - [Examples](#examples) + - [Very simple static-website](#very-simple-static-website) + - [File download](#file-download) + - [Send cookies](#send-cookies) + - [License](#license) + +# Routing -dependencies { - implementation 'com.github.Simonwep:java-express:0.1.1' -} -``` - -## Docs: -* [Routing](#routing) - * [DynExpress](#dynexpress) - * [Direct](#direct) - * [With Router](#with-router) -* [URL Basics](#url-basics) - * [URL Parameter](#url-parameter) - * [URL Parameter Listener](#url-parameter-listener) - * [URL Querys](#url-querys) - * [Cookies](#cookies) - * [Form Data](#form-data) -* [HTTP Relevant classes](#http-relevant-classes) - * [Response Object](#response-object) - * [Request Object](#request-object) -* [Middleware](#middleware) - * [Create own middleware](#create-own-middleware) -* [Using global variables](#global-variables) -* [License](#license) -* [Examples](#examples) - -## Routing ### DynExpress + Express allows the attaching of request-handler to instance methods via the DynExpress annotation: + ```java // Your main class @@ -136,13 +109,12 @@ public class Bindings { res.send("POST to index"); } } - - ``` - ### Direct + You can add routes (And middlewares) directly to the Express object to handle requests: + ```java Express app = new Express(); @@ -157,7 +129,9 @@ app.get("/user/register", (req, res) -> res.send("Join now!")); app.listen(); ``` + Directly it also supports methods like `POST` `PATCH` `DELETE` and `PUT`, others need to be created manually: + ```java Express app = new Express(); @@ -174,7 +148,9 @@ app.listen(); ``` ### With Router + But it's better to split your code, right? With the `ExpressRouter` you can create routes and add it later to the `Express` object: + ```java Express app = new Express() {{ @@ -201,7 +177,9 @@ Express app = new Express() {{ ``` ## URL Basics + Over the express object you can create handler for all [request-methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) and contexts. Some examples: + ```java app.get("/home", (req, res) -> { // Will match every request which uses the 'GET' method and matches the '/home' path @@ -213,10 +191,12 @@ app.post("/login", (req, res) -> { ``` ### URL Parameter + Sometimes you want to create dynamic URL where some parts of the URL's are not static. With the `:` operator you can create variables in the URL which will be saved later in a HashMap. Example request: `GET` `/posts/john/all`: + ```java app.get("/posts/:user/:description", (req, res) -> { String user = req.getParam("user"); // Contains 'john' @@ -226,6 +206,7 @@ app.get("/posts/:user/:description", (req, res) -> { ``` #### URL Parameter Listener + You can also add an event listener when the user called an route which contains an certain parameter. ```java @@ -234,17 +215,21 @@ app.get("/posts/:user/:id", (req, res) -> { }); ``` For example, if we want to check every `id` before the associated get post etc. handler will be fired, we can use the `app.onParam([PARAM])` function: + ```java app.onParam("id", (req, res) -> { // Do something with the id parameter, eg. check if it's valid. }); ``` + Now, this function will be called every time when an context is requested which contains the `id` parameter placeholder. ### URL Querys + If you make an request which contains querys, you can access the querys over `req.getQuery(NAME)`. Example request: `GET` `/posts?page=12&from=john`: + ```java app.get("/posts", (req, res) -> { String page = req.getQuery("page"); // Contains '12' @@ -254,9 +239,11 @@ app.get("/posts", (req, res) -> { ``` ## Cookies + With `req.getCookie(NAME)` you can get an cookie by his name, and with `res.setCookie(NAME, VALUE)` you can easily set an cookie. Example request: `GET` `/setcookie`: + ```java app.get("/setcookie", (req, res) -> { Cookie cookie = new Cookie("username", "john"); @@ -266,6 +253,7 @@ app.get("/setcookie", (req, res) -> { ``` Example request: `GET` `/showcookie`: + ```java app.get("/showcookie", (req, res) -> { Cookie cookie = req.getCookie("username"); @@ -275,8 +263,10 @@ app.get("/showcookie", (req, res) -> { ``` ### Form data + Over `req.getFormQuery(NAME)` you receive the values from the input elements of an HTML-Form. Example HTML-Form: + ```html
@@ -284,9 +274,11 @@ Example HTML-Form:
``` + **Attention: Currently, File-inputs don't work, if there is an File-input the data won't get parsed!** Now description, for the example below, `john` in username and `john@gmail.com` in the email field. Java code to handle the post request and access the form elements: + ```java app.post("/register", (req, res) -> { String email = req.getFormQuery("email"); @@ -299,8 +291,11 @@ app.post("/register", (req, res) -> { ``` ## HTTP Relevant classes + ### Express + This class represents the entire HTTP-Server, the available methods are: + ```java app.get(String context, HttpRequest handler); // Add an GET request handler app.post(String context, HttpRequest handler); // Add an POST request handler @@ -329,6 +324,7 @@ app.stop(); // Stop the serv ``` ### Response Object + Over the response object, you have serveral possibility like setting cookies, send an file and more. Below is an short explanation what methods exists: (We assume that `res` is the `Response` object) @@ -351,9 +347,11 @@ res.getStatus(); // Returns the current status res.setStatus(Status status); // Set the repose status res.streamFrom(long contentLength, InputStream is, MediaType mediaType) // Send a inputstream with known length and type ``` + The response object calls are comments because **you can only call the .send(xy) once each request!** ### Request Object + Over the `Request` Object you have access to serveral request stuff (We assume that `req` is the `Request` object): ```java @@ -391,6 +389,7 @@ req.getBody(); // Returns the request inputstream ``` # Middleware + Middleware are one of the most important features of JavaExpress, with middleware you can handle a request before it reaches any other request handler. To create an own middleware you have serveral interfaces: * `HttpRequest` - Is **required** to handle an request. * `ExpressFilter` - Is **required** to put data on the request listener. @@ -405,23 +404,29 @@ app.use((req, res) -> { // Handle data }); ``` + You can also filter by [request-methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) and contexts: + ```java // Global context, you can also pass an context if you want app.use("/home", "POST", (req, res) -> { // Handle request by context '/home' and method 'POST' }); ``` + In addition to that yo can use `*` which stands for every **context** or **request-method**: + ```java // Global context, you can also pass an context if you want app.use("/home", "*", (req, res) -> { // Handle request which matches the context '/home' and all methods. }); ``` + ## Create own middleware Now we take a look how we can create own middlewares. Here we create an simple PortParser which parse / extract the port-number for us. We only used `HttpRequest` and `ExpressFilter` because we don't need any background thread. + ```java public class PortMiddleware implements HttpRequest, ExpressFilter { @@ -458,13 +463,15 @@ public class PortMiddleware implements HttpRequest, ExpressFilter { } ``` -No we can, as we learned above, include it with: +Now we can, as we learned above, include it with: + ```java // Global context, you can also pass an context if you want app.use(new PortMiddleware()); ``` And use it: + ```java app.get("/port-test", (req, res) -> { @@ -477,14 +484,18 @@ app.get("/port-test", (req, res) -> { ``` ## Existing Middlewares + There are already some basic middlewares included, you can access these via static methods provided from `Middleware`. #### CORS + To realize a cors api yu can use the cors middleware. + ```java app.use(Middleware.cors()); ``` You can use CorsOptions to specify origin, methods and more: + ```java CorsOptions corsOptions = new CorsOptions(); corsOptions.setOrigin("https://mypage.com"); @@ -496,12 +507,17 @@ app.use(Middleware.cors()); ``` #### Provide static Files + If you want to allocate some files, like librarys, css, images etc. you can use the [static](https://github.com/Simonwep/java-express/blob/master/src/express/middleware/Middleware.java) middleware. But you can also provide other files like mp4 etc. + Example: + ```java app.use(Middleware.statics("examplepath\\myfiles")); ``` + Now you can access every files in the `test_statics` over the root adress `\`. I'ts also possible to set an configuration for the FileProvider: + ```java FileProviderOptionsoptions = new FileProviderOptions(); options.setExtensions("html", "css", "js"); // By default, all are allowed. @@ -521,14 +537,19 @@ options.setMaxAge(10000); // Send the Cache-Control header, by options.setDotFiles(DotFiles.DENY); // Deny access to dot-files. Default is IGNORE. app.use(Middleware.statics("examplepath\\myfiles", new FileProviderOptions())); // Using with StaticOptions ``` + #### Cookie Session + There is also an simple cookie-session implementation: + ```java // You should use an meaningless cookie name for serveral security reasons, here f3v4. // Also you can specify the maximum age of the cookie from the creation date and the file types wich are actually allowed. app.use(Middleware.cookieSession("f3v4", 9000)); ``` + To use a session cookie we need to get the data from the middleware which is actually an `SessionCookie`: + ```java // Cookie session example app.get("/session", (req, res) -> { @@ -555,18 +576,21 @@ Send an info message res.send("You take use of your session cookie " + count + " times."); }); ``` + ### Global Variables -Java-express also supports to save and read global variables over the Express instance: -Example: + +Java-express also supports to save and read global variables over the Express instance. + ```java app.set("my-data", "Hello World"); app.get("my-data"); // Returns "Hello World" ``` ## Examples + #### Very simple static-website -```java +```java // Create instance new Express() {{ @@ -576,8 +600,8 @@ new Express() {{ ``` #### File download -```java +```java // Your file Path downloadFile = Paths.get("my-big-file"); @@ -588,7 +612,9 @@ new Express() {{ get("/download-me", (req, res) -> res.sendAttachment(downloadFile)); }}; ``` + #### Send cookies + ```java new Express() {{ diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 43b4ae3..0000000 --- a/build.gradle +++ /dev/null @@ -1,2 +0,0 @@ -apply plugin: 'java' -apply plugin: 'idea' \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..6d26844 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { id("io.vacco.common-build") version "0.5.3" } + +group = "io.vacco.java-express" +version = "0.2.1" + +configure { + addJ8Spec(); addPmd(); addSpotBugs() + setPublishingUrlTransform { repo -> "${repo.url}/${project.name}" } + sharedLibrary() +} + +configure { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +dependencies { + implementation("org.slf4j:slf4j-api:1.7.30") +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..eccb323 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,4 @@ +libDesc=Simple and fast HTTP-Framework with the touch of expressjs +libGitUrl=https://github.com/Simonwep/java-express +libLicense=MIT License +libLicenseUrl=https://github.com/Simonwep/java-express/blob/master/LICENSE diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..2124917 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,6 @@ +pluginManagement { + repositories { + jcenter(); gradlePluginPortal() + maven { name = "VaccoOss"; setUrl("https://dl.bintray.com/vaccovecrana/vacco-oss") } + } +} diff --git a/src/main/java/express/http/response/Response.java b/src/main/java/express/http/response/Response.java index 26ee7a4..937b5d9 100644 --- a/src/main/java/express/http/response/Response.java +++ b/src/main/java/express/http/response/Response.java @@ -6,6 +6,8 @@ import express.utils.MediaType; import express.utils.Status; import express.utils.Utils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; @@ -14,8 +16,6 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; /** * @author Simon Reinisch @@ -23,25 +23,16 @@ */ public class Response { + private static final Logger log = LoggerFactory.getLogger(Response.class); + private final HttpExchange httpExchange; private final OutputStream body; private final Headers headers; - private final Logger logger; - private String contentType; - private boolean isClose; - private long contentLength; - private int status; - - { - // Initialize with default data - this.contentType = MediaType._txt.getMIME(); - this.isClose = false; - this.contentLength = 0; - this.status = 200; - this.logger = Logger.getLogger(getClass().getSimpleName()); - this.logger.setUseParentHandlers(false); // Disable default console log - } + private String contentType = MediaType._txt.getMIME(); + private boolean isClose = false; + private long contentLength = 0; + private int status = 200; public Response(HttpExchange exchange) { this.httpExchange = exchange; @@ -178,7 +169,7 @@ public void send(String s) { try { this.body.write(s.getBytes()); } catch (IOException e) { - logger.log(Level.INFO, "Failed to write charsequence to client.", e); + log.error("Failed to write char sequence to client.",e ); } close(); @@ -237,7 +228,7 @@ public boolean send(Path file) { fis.close(); } catch (IOException e) { - logger.log(Level.INFO, "Failed to pipe file to outputstream.", e); + log.error("Failed to pipe file to output stream.", e); return false; } finally { close(); @@ -250,7 +241,7 @@ public boolean send(Path file) { * Send a byte array as response. Content type will be * set to application/octet-streamFrom * - * @param bytes Byte arraa + * @param bytes Byte array * @return If operation was successful */ public boolean sendBytes(byte[] bytes) { @@ -271,7 +262,7 @@ public boolean sendBytes(byte[] bytes) { // Write bytes to body this.body.write(bytes); } catch (IOException e) { - logger.log(Level.INFO, "Failed to pipe file to outputstream.", e); + log.error("Failed to pipe file to output stream.", e); return false; } finally { close(); @@ -281,7 +272,7 @@ public boolean sendBytes(byte[] bytes) { } /** - * Streams a inputstream to the client. + * Streams an input stream to the client. * Requires a contentLength as well as a MediaType * * @param contentLength Total size @@ -313,7 +304,7 @@ public boolean streamFrom(long contentLength, InputStream is, MediaType mediaTyp is.close(); } catch (IOException e) { - logger.log(Level.INFO, "Failed to pipe file to outputstream.", e); + log.error("Failed to pipe file to output stream.", e); return false; } finally { close(); @@ -323,22 +314,12 @@ public boolean streamFrom(long contentLength, InputStream is, MediaType mediaTyp } /** - * @return If the response is already closed (headers are send). + * @return If the response is already closed (headers have been sent). */ public boolean isClosed() { return this.isClose; } - /** - * Returns the logger which is concered for this Response object. - * There is no default-handler active, if you want to log it you need to set an handler. - * - * @return The logger from this Response object. - */ - public Logger getLogger() { - return logger; - } - private void sendHeaders() { try { @@ -349,7 +330,7 @@ private void sendHeaders() { this.headers.set("Content-Type", contentType); this.httpExchange.sendResponseHeaders(status, contentLength); } catch (IOException e) { - logger.log(Level.INFO, "Failed to send headers.", e); + log.error("Failed to send headers.", e); } } @@ -358,7 +339,7 @@ private void close() { this.body.close(); this.isClose = true; } catch (IOException e) { - logger.log(Level.INFO, "Failed to close outputstream.", e); + log.error("Failed to close output stream.", e); } } diff --git a/src/main/java/express/middleware/FileProvider.java b/src/main/java/express/middleware/FileProvider.java index 8cdfaf2..e761591 100644 --- a/src/main/java/express/middleware/FileProvider.java +++ b/src/main/java/express/middleware/FileProvider.java @@ -5,6 +5,8 @@ import express.http.response.Response; import express.utils.Status; import express.utils.Utils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; @@ -15,23 +17,18 @@ import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.util.Optional; -import java.util.logging.Level; -import java.util.logging.Logger; /** * @author Simon Reinisch * An middleware to provide access to static server-files. */ public final class FileProvider implements HttpRequestHandler { - private final Logger logger; + + private static final Logger log = LoggerFactory.getLogger(FileProvider.class); + private FileProviderOptions options; private String root; - { - this.logger = Logger.getLogger(this.getClass().getSimpleName()); - this.logger.setUseParentHandlers(false); // Disable default console log - } - FileProvider(String root, FileProviderOptions options) throws IOException { Path rootDir = Paths.get(root); @@ -83,7 +80,7 @@ public void handle(Request req, Response res) { } } } catch (IOException e) { - this.logger.log(Level.WARNING, "Cannot walk file tree.", e); + log.error("Cannot walk file tree.", e); } } @@ -143,7 +140,7 @@ private void finish(Path file, Request req, Response res) { } catch (IOException e) { res.sendStatus(Status._500); - this.logger.log(Level.WARNING, "Cannot read LastModifiedTime from file " + file.toString(), e); + log.error("Cannot read LastModifiedTime from file [{}]", file.toString(), e); return; } @@ -157,14 +154,4 @@ private String getBaseName(Path path) { return index == -1 ? name : name.substring(0, index); } - - /** - * Returns the logger which is concered for this FileProvilder object. - * There is no default-handler active, if you want to log it you need to set an handler. - * - * @return The logger from this FileProvilder object. - */ - public Logger getLogger() { - return logger; - } }