Added websockets.md and handler-default-implementation.md

This commit is contained in:
Relism
2025-02-27 00:59:41 +01:00
parent f07554408d
commit a5eac9e6a0
9 changed files with 233 additions and 143 deletions
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="ASK" />
<option name="description" value="" />
</component>
</project>
+3 -2
View File
@@ -5,10 +5,11 @@
"items": [
{ "text": "Getting Started", "link": "/flash" },
{ "text": "Installation", "link": "/flash/installation" },
{ "text": "Server Types", "link": "/flash/server-types" },
{ "text": "RequestHandler", "link": "/flash/request-handler"},
{ "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": "Websockets", "link": "/flash/websockets" }
]
}
]
+143
View File
@@ -0,0 +1,143 @@
---
banner_title: "Flash - Handler Default Implementations (HDI)"
banner_description: "Leverage HDI's for cleaner and more maintainable route logic."
---
# ⚡ Handler Default Implementations (HDI)
HDI's provide an elegant and reliable way to standardize common behaviors across multiple request handlers.
By defining base handlers that extend `RequestHandler` (or even chaining multiple base handlers), you can modularize your logic for aspects like authentication, user data retrieval, and rate limiting.
## 🔗 How It Works
Instead of implementing repeated logic in every handler, you create abstract handler classes that encapsulate common functionality. Your actual route handlers then extend these base classes, inheriting their behavior while focusing purely on request-specific logic.
### 🛠 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:
```java
public abstract class APIKeyProtectedHandler extends RequestHandler {
protected String apiKey;
protected int remainingQuota;
public APIKeyProtectedHandler(Request req, Response res) {
super(req, res);
}
@Override
public Object handle() {
apiKey = req.header("X-API-Key");
if (apiKey == null || !isValidApiKey(apiKey)) {
res.status(403);
return "{\"error\":\"Invalid API Key\"}";
}
remainingQuota = getRemainingQuota(apiKey);
if (remainingQuota <= 0) {
res.status(429);
return "{\"error\":\"API quota exceeded\"}";
}
return handleAuthorized();
}
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:
```java
@RouteInfo(method = HttpMethod.GET, path = "/data")
public class GetDataHandler extends APIKeyProtectedHandler {
public GetDataHandler(Request req, Response res) {
super(req, res);
}
@Override
protected Object handleAuthorized() {
return "{\"data\":\"Your API response here\"}";
}
}
```
## 🏗️ Chaining HDIs for Modular Logic
HDIs can be chained together to compose multiple layers of behavior. For example:
- `ProtectedHandler` ensures authentication.
```java
public abstract class ProtectedHandler extends RequestHandler {
protected String authToken;
public ProtectedHandler(Request req, Response res) {
super(req, res);
}
@Override
public Object handle() {
authToken = req.header("Authorization");
if (authToken == null || !isValidToken(authToken)) {
res.status(401);
res.type("application/json");
return "{\"error\":\"Unauthorized\"}";
}
return handleAuthenticated();
}
protected abstract Object handleAuthenticated();
}
```
- `AuthenticatedHandler` extends ProtectedHandler to fetch user data from the database.
```java
public abstract class AuthenticatedHandler extends ProtectedHandler {
protected User user;
public AuthenticatedHandler(Request req, Response res) {
super(req, res);
}
@Override
protected Object handleAuthenticated() {
user = getUserFromDatabase(authToken);
if (user == null) {
res.status(403);
return "{\"error\":\"User not found\"}";
}
return handleWithUser();
}
protected abstract Object handleWithUser();
}
```
- Your actual implementation handler extends AuthenticatedHandler to work directly with user data.
```java
@RouteInfo(method = HttpMethod.GET, path = "/profile")
public class UserProfileHandler extends AuthenticatedHandler {
public UserProfileHandler(Request req, Response res) {
super(req, res);
}
@Override
protected Object handleWithUser() {
return "{\"username\":\"" + user.getUsername() + "\"}";
}
}
```
+1 -1
View File
@@ -71,7 +71,7 @@ This page provides installation instructions for the latest version of the `flas
<div>
⚡ Latest version:
<a href="'https://maven.pixel-services.com/#/releases/com/pixelservices/flash'" style="text-decoration: underline; color: #007bff;">
<a href="https://maven.pixel-services.com/#/releases/com/pixelservices/flash" style="text-decoration: underline; color: #007bff;">
<strong>{{ latestVersion }}</strong>
</a>
</div>
+2 -7
View File
@@ -11,19 +11,14 @@ In this section, we illustrate the powerful concept of `RequestHandler` in Flash
To create a custom request handler, you need to extend the `RequestHandler` class and annotate the class with the `RouteInfo` annotation,
specifying the HTTP method that the handler will respond to and the relative path that the handler will be registered to.
The `enforceNonNullBody` attribute is used to specify whether the handler expects a non-null request body: by default, it is set to `false`.
After that, you must override the `handle` method;
The `handle` method is where you define the logic for processing the request and generating the response.
The `req` (request) and `res` (response) objects are available in the handler to access the request data and send the response back to the client.
You must call the super constructor with the `req` and `res` objects to initialize the handler.
```java{5,8}
import flash.Request;
import flash.Response;
import flash.RequestHandler;
@RouteInfo(method = HttpMethod.GET, path = "/hello", enforceNonNullBody = false)
```java{1,4}
@RouteInfo(method = HttpMethod.GET, path = "/hello")
public class MyHandler extends RequestHandler {
public MyHandler(Request req, Response res) {
super(req, res);
+6 -23
View File
@@ -50,13 +50,8 @@ The `ExpectedRequestParameter`, `ExpectedBodyField`, and `ExpectedBodyFile` inst
The `ExpectedRequestParameter` object is used to get the expected parameters of the request.
You can use the getter methods to safely get the parameter value, such as `getString`, `getInt`, `getDouble`, and `getBoolean` methods to safely cast the parameter to the expected type.
```java{8,11,16}
import flash.Request;
import flash.Response;
import flash.models.RequestHandler;
import flash.models.ExpectedRequestParameter;
@RouteInfo(method = HttpMethod.GET, path = "/hello", enforceNonNullBody = false)
```java{4,8,18,21}
@RouteInfo(method = HttpMethod.GET, path = "/hello")
public class MyHandler extends RequestHandler {
// Store the expected parameter in a private field
private final ExpectedRequestParameter myExpectedReqParam;
@@ -89,14 +84,8 @@ Visiting `/hello?myParam=John` from your browser, will return `Hello, John!`.
The `ExpectedBodyField` object is used to get the expected fields of the request body.
You can use the getter methods to safely get the field value, such as `getString`, `getInt`, `getDouble`, and `getBoolean` methods to safely cast the field to the expected type.
```java{8,11,16}
import flash.Request;
import flash.Response;
import flash.models.RequestHandler;
import flash.models.ExpectedBodyField;
// Make sure to set enforceNonNullBody to true
@RouteInfo(method = HttpMethod.GET, path = "/helloBody", enforceNonNullBody = true)
```java{4,8,18,21}
@RouteInfo(method = HttpMethod.GET, path = "/helloBody")
public class MyHandler extends RequestHandler {
// Store the expected field in a private field
private final ExpectedBodyField myExpectedBodyField;
@@ -134,14 +123,8 @@ The methods provided by this object are slightly different from the other two, b
- `getFileName()` simply returns the name of the file specified by the client.
- `getInputStream()` returns an `InputStream` object containing the file's contents.
```java{8,11,16}
import flash.Request;
import flash.Response;
import flash.models.RequestHandler;
import flash.models.ExpectedBodyFile;
// Make sure to set enforceNonNullBody to true
@RouteInfo(method = HttpMethod.POST, path = "/helloFile", enforceNonNullBody = true)
```java{4,8,18,21}
@RouteInfo(method = HttpMethod.POST, path = "/helloFile")
public class MyHandler extends RequestHandler {
// Store the expected file in a private field
private final ExpectedBodyFile myExpectedBodyFile;
+7 -11
View File
@@ -9,27 +9,23 @@ In this section, we discuss how to use the `FlashServer` router to manage our `R
The router is used to define route endpoints and their corresponding handler, which are executed when a request is made to the server.
The `FlashServer` router is an instance of the `RouteController` class, each server instance has its own router instance.
To access the router instance, you can call the `route()` method on either the internal server or your `FlashServer` instance.
To access the router instance, you can call the `route()` method on the `FlashServer` instance.
## Creating a Route
To create a route, you need to call the `route()` method on your server's instance (in this case for simplicity, on the InternalFlashServer)
and specify the base path of the route, followed by your handler class and optionally a `RouteInterceptor` instance.
and specify the base path of the route, followed by your handler class,
(More on the concept of `RouteInterceptor` in the following sections).
```java{8,9}
```java{9}
// Example.java
import static flash.InternalFlashServer.*;
public class Example {
public static void main(String[] args) {
port(8080);
FlashServer server = new FlashServer(8080);
route("/api")
server.route("/api")
.register(MyHandler.class);
start();
server.start();
}
}
```
@@ -40,7 +36,7 @@ import flash.Request;
import flash.Response;
import flash.models.RequestHandler;
@RouteInfo(method = HttpMethod.GET, path = "/hello", enforceNonNullBody = false)
@RouteInfo(method = HttpMethod.GET, path = "/hello")
public class MyHandler extends RequestHandler {
public MyHandler(Request req, Response res) {
super(req, res);
-99
View File
@@ -1,99 +0,0 @@
---
banner_title: "Flash - Server Types"
banner_description: "Illustrating the main differences between the internal server instance and the FlashServer instance."
---
# Server Types
In this section, we discuss the differences between the internal server instance `InternalFlashServer` and the `FlashServer` instance, and whether you should use one or the other. We will also provide examples of how to use each type of server.
## Internal Server
The internal server is a singleton instance that can be accessed by statically importing
methods from `InternalFlashServer`. This means that for each application, only one `InternalFlashServer` instance is created.
This allows you to use the server's functionality without creating and managing an instance of the server.
### Usage
To use the internal server, you can statically import the methods from `InternalFlashServer`:
```java{1}
import static flash.InternalFlashServer.*;
public class Example {
public static void main(String[] args) {
port(8080);
get("/hello", (req, res) -> {
res.status(200);
return "Hello, world!";
});
post("/submit", (req, res) -> {
// Handle POST request
});
// Other routes and configurations
}
}
```
## Server Instance
The `FlashServer` class is used to create an instance of the server.
Unlike the internal server, you can create multiple instances of the `FlashServer` class with different configurations.
### Usage
To create a server instance, you can simply call `new FlashServer()` and configure the server using the instance methods:
```java{5}
import flash.FlashServer;
public class Example {
public static void main(String[] args) {
FlashServer server = new FlashServer("My Server Instance");
server.port(8080);
server.get("/hello", (req, res) -> {
res.status(200);
return "Hello, world!";
});
server.post("/submit", (req, res) -> {
// Handle POST request
});
// Other routes and configurations
}
}
```
The `InternalFlashServer` is a **pre-configured instance** of `FlashServer` that is managed internally by the library. Under the hood, it is instantiated as:
```java
new FlashServer("Internal");
```
This means:
- Its **name** is set to `"Internal"` for logging and internal management purposes.
- It serves as the default server used by the library, which is automatically initialized when the library is loaded.
Both the `InternalFlashServer` and any user-created `FlashServer` instances are of the same type (`FlashServer`). However, if you want to create and manage your own server instance, you must provide a **name** during initialization, e.g.:
```java
FlashServer server = new FlashServer("My Server Instance");
```
## Which Server Type Should You Use?
It all depends on your use case.
If you only need one server instance and want to keep your code concise, you can use the internal server.
However, if your application needs to handle multiple server instances each with different ports and configurations,
you should use `FlashServer` instances.
::: warning
From now on, and unless otherwise specified, every example in the documentation will use the `InternalFlashServer` to illustrate the usage of the library.
However, for your specific implementation, you can call the same methods on your `FlashServer` instance to achieve the same results.
:::
+64
View File
@@ -0,0 +1,64 @@
---
banner_title: "Flash - Websockets"
banner_description: "Learn how to create and manage server-side Websockets in Flash."
---
# 🌐 Websockets
In this section, we illustrate how to create and manage Websockets in Flash, which are used to establish a bidirectional communication channel between the client and the server.
## Understanding Websockets
Websockets are a communication protocol that provides full-duplex communication channels over a single TCP connection.
Unlike HTTP, which is a request-response protocol, Websockets allow for real-time, low-latency communication between the client and the server.
Imagine a scenario where you need to send real-time updates to the client, such as a chat application or a live feed: if you were to use HTTP,
you would need to poll the server at regular intervals to check for updates, which is inefficient and resource-intensive.
## 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.
```java
public class MyWebsocketHandler extends WebsocketHandler {
@Override
public void onOpen(WebSocketSession session) {
System.out.println("WebSocket connection opened");
}
@Override
public void onClose(WebSocketSession session, int statusCode, String reason) {
System.out.println("WebSocket connection closed");
}
@Override
public void onMessage(WebSocketSession session, String message) {
System.out.println("Received message: " + message);
session.sendMessage("I received your message!");
}
@Override
public void onError(WebSocketSession session, Throwable error) {
System.out.println("WebSocket error: " + error.getMessage());
}
}
```
To register your Websocket handler with the server, you can use the `server.ws()` method:
```java
public class Example {
public static void main(String[] args) {
FlashServer server = new FlashServer(8080);
server.ws("/ws")
.register(MyWebsocketHandler.class);
server.start();
}
}
```
## 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.