Merge pull request #4 from Pixel-Services/flash/add/server-router
This commit is contained in:
@@ -8,8 +8,9 @@
|
|||||||
{ "text": "RequestHandler", "link": "/flash/request-handler"},
|
{ "text": "RequestHandler", "link": "/flash/request-handler"},
|
||||||
{ "text": "Request and Response", "link": "/flash/request-response"},
|
{ "text": "Request and Response", "link": "/flash/request-response"},
|
||||||
{ "text": "Server Router", "link": "/flash/server-router" },
|
{ "text": "Server Router", "link": "/flash/server-router" },
|
||||||
{ "text": "Handler Default Implementation", "link": "/flash/handler-default-implementation" },
|
{ "text": "Handler Default Implementations", "link": "/flash/handler-default-implementations" },
|
||||||
{ "text": "Websockets", "link": "/flash/websockets" }
|
{ "text": "Websockets", "link": "/flash/websockets" },
|
||||||
|
{ "text": "Static Files Server", "link": "/flash/static-file-server" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
+16
-21
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
banner_title: "Flash - Handler Default Implementations (HDI)"
|
banner_title: "Flash - Handler Default Implementations"
|
||||||
banner_description: "Leverage HDI's for cleaner and more maintainable route logic."
|
banner_description: "Leverage HDI's for cleaner and more maintainable route logic."
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -14,12 +14,11 @@ Instead of implementing repeated logic in every handler, you create abstract han
|
|||||||
|
|
||||||
### 🛠 Example: API Key Authentication
|
### 🛠 Example: API Key Authentication
|
||||||
|
|
||||||
Imagine you want to protect API endpoints with an authentication key and track API usage in a database. You can create an abstract base handler that enforces API key validation:
|
Imagine you want to protect API endpoints with an authentication key by checking it against a database. You can create an abstract `APIKeyProtectedHandler` that extends `RequestHandler` and implements the authentication logic:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public abstract class APIKeyProtectedHandler extends RequestHandler {
|
public abstract class APIKeyProtectedHandler extends RequestHandler {
|
||||||
protected String apiKey;
|
protected String apiKey;
|
||||||
protected int remainingQuota;
|
|
||||||
|
|
||||||
public APIKeyProtectedHandler(Request req, Response res) {
|
public APIKeyProtectedHandler(Request req, Response res) {
|
||||||
super(req, res);
|
super(req, res);
|
||||||
@@ -34,30 +33,21 @@ public abstract class APIKeyProtectedHandler extends RequestHandler {
|
|||||||
return "{\"error\":\"Invalid API Key\"}";
|
return "{\"error\":\"Invalid API Key\"}";
|
||||||
}
|
}
|
||||||
|
|
||||||
remainingQuota = getRemainingQuota(apiKey);
|
|
||||||
if (remainingQuota <= 0) {
|
|
||||||
res.status(429);
|
|
||||||
return "{\"error\":\"API quota exceeded\"}";
|
|
||||||
}
|
|
||||||
|
|
||||||
return handleAuthorized();
|
return handleAuthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implement this method in your actual handlers
|
||||||
protected abstract Object handleAuthorized();
|
protected abstract Object handleAuthorized();
|
||||||
|
|
||||||
private boolean isValidApiKey(String key) {
|
private boolean isValidApiKey(String key) {
|
||||||
// Implement key validation logic, e.g., checking against a database
|
// Implement key validation logic, e.g., checking against a database
|
||||||
|
// ...
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getRemainingQuota(String key) {
|
|
||||||
// Retrieve remaining quota from a database
|
|
||||||
return 100;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, your actual API handlers only need to extend APIKeyProtectedHandler, ensuring every request has a valid API key and available quota before executing its logic:
|
Now, your actual API handlers only need to extend APIKeyProtectedHandler, ensuring every request has a valid API key before executing its logic:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@RouteInfo(method = HttpMethod.GET, path = "/data")
|
@RouteInfo(method = HttpMethod.GET, path = "/data")
|
||||||
@@ -75,13 +65,14 @@ public class GetDataHandler extends APIKeyProtectedHandler {
|
|||||||
|
|
||||||
## 🏗️ Chaining HDIs for Modular Logic
|
## 🏗️ Chaining HDIs for Modular Logic
|
||||||
|
|
||||||
HDIs can be chained together to compose multiple layers of behavior. For example:
|
HDIs can be chained together to compose multiple layers of behavior. For example,
|
||||||
|
imagine you want to authenticate users with a token and fetch their data from a database. You can create two abstract handlers that extend one another:
|
||||||
|
|
||||||
- `ProtectedHandler` ensures authentication.
|
- `ProtectedHandler` ensures authentication.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public abstract class ProtectedHandler extends RequestHandler {
|
public abstract class ProtectedHandler extends RequestHandler {
|
||||||
protected String authToken;
|
protected String authToken;
|
||||||
|
|
||||||
public ProtectedHandler(Request req, Response res) {
|
public ProtectedHandler(Request req, Response res) {
|
||||||
super(req, res);
|
super(req, res);
|
||||||
@@ -102,11 +93,12 @@ protected String authToken;
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `AuthenticatedHandler` extends ProtectedHandler to fetch user data from the database.
|
- `AuthenticatedHandler` extends ProtectedHandler to fetch user data from the database, and overrides `handleAuthenticated` to ensure the user is authenticated before proceeding.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public abstract class AuthenticatedHandler extends ProtectedHandler {
|
public abstract class AuthenticatedHandler extends ProtectedHandler {
|
||||||
protected User user;
|
protected User user;
|
||||||
|
private String authToken; // inherited from ProtectedHandler
|
||||||
|
|
||||||
public AuthenticatedHandler(Request req, Response res) {
|
public AuthenticatedHandler(Request req, Response res) {
|
||||||
super(req, res);
|
super(req, res);
|
||||||
@@ -126,18 +118,21 @@ public abstract class AuthenticatedHandler extends ProtectedHandler {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- Your actual implementation handler extends AuthenticatedHandler to work directly with user data.
|
- Your actual implementation handler `UserProfileHandler` extends `AuthenticatedHandler` and implements `handleWithUser` to ensure the user is authenticated and their profile data has been fetched before proceeding.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@RouteInfo(method = HttpMethod.GET, path = "/profile")
|
@RouteInfo(endpoint = "/profile", method = HttpMethod.GET)
|
||||||
public class UserProfileHandler extends AuthenticatedHandler {
|
public class UserProfileHandler extends AuthenticatedHandler {
|
||||||
|
// inherited from AuthenticatedHandler
|
||||||
|
private String username = user.getUsername();
|
||||||
|
|
||||||
public UserProfileHandler(Request req, Response res) {
|
public UserProfileHandler(Request req, Response res) {
|
||||||
super(req, res);
|
super(req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Object handleWithUser() {
|
protected Object handleWithUser() {
|
||||||
return "{\"username\":\"" + user.getUsername() + "\"}";
|
return "{\"username\":\"" + username + "\"}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
banner_title: "Flash - Static File Server"
|
||||||
|
banner_description: "Learn how to serve static files in Flash."
|
||||||
|
---
|
||||||
|
|
||||||
|
# 📁 Static File Server
|
||||||
|
|
||||||
|
Flash provides a built-in static file server that allows you to serve static files such with autoresolving MIME types and caching.
|
||||||
|
|
||||||
|
::: warning
|
||||||
|
The static file server pre-registers literal routes for every file in the specified target directory.
|
||||||
|
Creating/deleting files in the target directory will trigger the internal route registry to update accordingly.
|
||||||
|
If you are planning to serve a compiled frontend application with a client-side router (think of react-router-dom),
|
||||||
|
it is reccomended to use the Dynamic File Server instead,
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
To serve static files in Flash, you need to call the `server.serveStatic()` method with the endpoint path and an instance of `StaticFileServerConfig`.
|
||||||
|
The configuration object is instanced like so :
|
||||||
|
|
||||||
|
```java
|
||||||
|
public StaticFileServerConfiguration(
|
||||||
|
boolean enableFileWatcher,
|
||||||
|
boolean enableIndexRedirect,
|
||||||
|
String destinationPath,
|
||||||
|
SourceType sourceType
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `enableFileWatcher` : If set to `true`, the server will watch for changes in the served files and reload them automatically.
|
||||||
|
- `enableIndexRedirect` : If set to `true`, the server will redirect requests to directories to the `index.html` file.
|
||||||
|
- `destinationPath` : The path to the directory containing the files to be served.
|
||||||
|
- `sourceType` : The type of source to serve files from. It can be either `FILESYSTEM` or `RESOURCESTREAM`.
|
||||||
|
|
||||||
|
Registering the static file server is as simple as calling the `server.serveStatic()` method with the desired path and configuration object:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class Example {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
FlashServer server = new FlashServer(8080);
|
||||||
|
|
||||||
|
server.serveStatic("/static", new StaticFileServerConfiguration(
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"path/to/static/files",
|
||||||
|
SourceType.FILESYSTEM
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you can access the files in the specified directory by navigating to `http://localhost:8080/static/<file-name>`.
|
||||||
|
|
||||||
|
Similarly, you can serve files from the jar's resources folder by setting the `sourceType` to `RESOURCESTREAM`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class Example {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
FlashServer server = new FlashServer(8080);
|
||||||
|
|
||||||
|
server.serveStatic("/static", new StaticFileServerConfiguration(
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"path/to/static/files",
|
||||||
|
SourceType.RESOURCESTREAM
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
+22
-1
@@ -18,6 +18,8 @@ you would need to poll the server at regular intervals to check for updates, whi
|
|||||||
## Creating a Websocket
|
## Creating a Websocket
|
||||||
|
|
||||||
To create a Websocket in Flash, you need to extend the `WebsocketHandler` class and override the `onOpen`, `onMessage`, `onClose`, and `onError` methods.
|
To create a Websocket in Flash, you need to extend the `WebsocketHandler` class and override the `onOpen`, `onMessage`, `onClose`, and `onError` methods.
|
||||||
|
These methods are called when the Websocket connection is opened, a message is received, the connection is closed, and an error occurs, respectively,
|
||||||
|
and they provide you with an instance of the `WebSocketSession` object to be able to interact with it.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
public class MyWebsocketHandler extends WebsocketHandler {
|
public class MyWebsocketHandler extends WebsocketHandler {
|
||||||
@@ -34,6 +36,8 @@ public class MyWebsocketHandler extends WebsocketHandler {
|
|||||||
@Override
|
@Override
|
||||||
public void onMessage(WebSocketSession session, String message) {
|
public void onMessage(WebSocketSession session, String message) {
|
||||||
System.out.println("Received message: " + message);
|
System.out.println("Received message: " + message);
|
||||||
|
|
||||||
|
//optionally send a reponse back to the client
|
||||||
session.sendMessage("I received your message!");
|
session.sendMessage("I received your message!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +56,7 @@ public class Example {
|
|||||||
FlashServer server = new FlashServer(8080);
|
FlashServer server = new FlashServer(8080);
|
||||||
|
|
||||||
server.ws("/ws")
|
server.ws("/ws")
|
||||||
.register(MyWebsocketHandler.class);
|
.register(new MyWebsocketHandler());
|
||||||
|
|
||||||
server.start();
|
server.start();
|
||||||
}
|
}
|
||||||
@@ -62,3 +66,20 @@ public class Example {
|
|||||||
## Interacting with Websockets sessions
|
## Interacting with Websockets sessions
|
||||||
|
|
||||||
The `WebSocketSession` object provides methods to interact with the Websocket session, such as sending messages, closing the connection, and getting the remote address and session ID.
|
The `WebSocketSession` object provides methods to interact with the Websocket session, such as sending messages, closing the connection, and getting the remote address and session ID.
|
||||||
|
|
||||||
|
| Method | Params | Description |
|
||||||
|
|--------------------|------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `getChannel()` | `none` | Returns an instance of `AsynchronousSocketChannel` useful for retrieving info about the client . |
|
||||||
|
| `getRequestInfo()` | `none` | Returns an instance of `RequestInfo` containing all sorts of information about the request (headers, method, path etc.) . |
|
||||||
|
| `getPath()` | `none` | Returns the path to the websocket endpoint as a `String`. |
|
||||||
|
| `getId()` | `none` | Returns the id of the websocket session as a `String`, useful if you want to keep track of the connected clients in a custom manager. |
|
||||||
|
| `getBuffer()` | `none` | Returns the ByteBuffer for that session. |
|
||||||
|
| `sendMessage()` | `String message` | Sends the `message` to the client as a `String`. it's up to the developer to stringify and de-stringify any data you want to send back and forth |
|
||||||
|
| `close()` | `none` | Closes the websocket session. |
|
||||||
|
|
||||||
|
::: warning NOTE
|
||||||
|
`WebsocketHandler` includes a `setId(String id)` method for overriding the default session ID. Unless you have a specific reason to change it, it's best to leave it as is.
|
||||||
|
|
||||||
|
Similarly, the `setBuffer(ByteBuffer buffer)` method allows you to override the default buffer. If you're unsure about this, it's recommended to keep the default setting.
|
||||||
|
:::
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user