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 @@
-
-
-
-
+
+

+
- Simple and fast HTTP-Framework with the touch of expressjs
+ Simple and fast HTTP-Framework with the touch of expressjs
-
-
-
-
-
+
+
+
+
+
+
-
-
-
- !! 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:
+
+[ ](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
```
+
**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;
- }
}