Merge pull request #4 from Pixel-Services/flash/add/server-router

This commit is contained in:
Relism
2025-02-27 14:30:02 +01:00
committed by GitHub
4 changed files with 111 additions and 24 deletions
+3 -2
View File
@@ -8,8 +8,9 @@
{ "text": "RequestHandler", "link": "/flash/request-handler"},
{ "text": "Request and Response", "link": "/flash/request-response"},
{ "text": "Server Router", "link": "/flash/server-router" },
{ "text": "Handler Default Implementation", "link": "/flash/handler-default-implementation" },
{ "text": "Websockets", "link": "/flash/websockets" }
{ "text": "Handler Default Implementations", "link": "/flash/handler-default-implementations" },
{ "text": "Websockets", "link": "/flash/websockets" },
{ "text": "Static Files Server", "link": "/flash/static-file-server" }
]
}
]
@@ -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."
---
@@ -14,12 +14,11 @@ Instead of implementing repeated logic in every handler, you create abstract han
### 🛠 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
public abstract class APIKeyProtectedHandler extends RequestHandler {
protected String apiKey;
protected int remainingQuota;
public APIKeyProtectedHandler(Request req, Response res) {
super(req, res);
@@ -34,30 +33,21 @@ public abstract class APIKeyProtectedHandler extends RequestHandler {
return "{\"error\":\"Invalid API Key\"}";
}
remainingQuota = getRemainingQuota(apiKey);
if (remainingQuota <= 0) {
res.status(429);
return "{\"error\":\"API quota exceeded\"}";
}
return handleAuthorized();
}
// Implement this method in your actual handlers
protected abstract Object handleAuthorized();
private boolean isValidApiKey(String key) {
// Implement key validation logic, e.g., checking against a database
// ...
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
@RouteInfo(method = HttpMethod.GET, path = "/data")
@@ -75,13 +65,14 @@ public class GetDataHandler extends APIKeyProtectedHandler {
## 🏗️ 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.
```java
public abstract class ProtectedHandler extends RequestHandler {
protected String authToken;
protected String authToken;
public ProtectedHandler(Request req, Response 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
public abstract class AuthenticatedHandler extends ProtectedHandler {
protected User user;
private String authToken; // inherited from ProtectedHandler
public AuthenticatedHandler(Request req, Response 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
@RouteInfo(method = HttpMethod.GET, path = "/profile")
@RouteInfo(endpoint = "/profile", method = HttpMethod.GET)
public class UserProfileHandler extends AuthenticatedHandler {
// inherited from AuthenticatedHandler
private String username = user.getUsername();
public UserProfileHandler(Request req, Response res) {
super(req, res);
}
@Override
protected Object handleWithUser() {
return "{\"username\":\"" + user.getUsername() + "\"}";
return "{\"username\":\"" + username + "\"}";
}
}
```
+70
View File
@@ -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
View File
@@ -18,6 +18,8 @@ you would need to poll the server at regular intervals to check for updates, whi
## 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.
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
public class MyWebsocketHandler extends WebsocketHandler {
@@ -34,6 +36,8 @@ public class MyWebsocketHandler extends WebsocketHandler {
@Override
public void onMessage(WebSocketSession session, String message) {
System.out.println("Received message: " + message);
//optionally send a reponse back to the client
session.sendMessage("I received your message!");
}
@@ -52,7 +56,7 @@ public class Example {
FlashServer server = new FlashServer(8080);
server.ws("/ws")
.register(MyWebsocketHandler.class);
.register(new MyWebsocketHandler());
server.start();
}
@@ -62,3 +66,20 @@ public class Example {
## 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.
| 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.
:::