diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..30bab2a --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/flash/config/sidebar.json b/flash/config/sidebar.json index a590fa1..6e054c4 100644 --- a/flash/config/sidebar.json +++ b/flash/config/sidebar.json @@ -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" } ] } ] diff --git a/flash/handler-default-implementation.md b/flash/handler-default-implementation.md new file mode 100644 index 0000000..6f67f22 --- /dev/null +++ b/flash/handler-default-implementation.md @@ -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() + "\"}"; + } +} +``` \ No newline at end of file diff --git a/flash/installation.md b/flash/installation.md index adda764..902bb8f 100644 --- a/flash/installation.md +++ b/flash/installation.md @@ -71,7 +71,7 @@ This page provides installation instructions for the latest version of the `flas
⚡ Latest version: - + {{ latestVersion }}
\ No newline at end of file diff --git a/flash/request-handler.md b/flash/request-handler.md index 6562c0a..ff88096 100644 --- a/flash/request-handler.md +++ b/flash/request-handler.md @@ -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); diff --git a/flash/request-response.md b/flash/request-response.md index 6c2a3f4..0d2f34f 100644 --- a/flash/request-response.md +++ b/flash/request-response.md @@ -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; diff --git a/flash/server-router.md b/flash/server-router.md index 3f6cb11..7ec1988 100644 --- a/flash/server-router.md +++ b/flash/server-router.md @@ -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); diff --git a/flash/server-types.md b/flash/server-types.md deleted file mode 100644 index d769e7d..0000000 --- a/flash/server-types.md +++ /dev/null @@ -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. -::: diff --git a/flash/websockets.md b/flash/websockets.md new file mode 100644 index 0000000..36b19a6 --- /dev/null +++ b/flash/websockets.md @@ -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.