diff --git a/flash/config/sidebar.json b/flash/config/sidebar.json index 6e054c4..4daa59f 100644 --- a/flash/config/sidebar.json +++ b/flash/config/sidebar.json @@ -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" } ] } ] diff --git a/flash/handler-default-implementation.md b/flash/handler-default-implementations.md similarity index 74% rename from flash/handler-default-implementation.md rename to flash/handler-default-implementations.md index 6f67f22..85ae6b4 100644 --- a/flash/handler-default-implementation.md +++ b/flash/handler-default-implementations.md @@ -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 + "\"}"; } } ``` \ No newline at end of file diff --git a/flash/static-file-server.md b/flash/static-file-server.md new file mode 100644 index 0000000..89021dc --- /dev/null +++ b/flash/static-file-server.md @@ -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/`. + +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 + )); + } +} +``` \ No newline at end of file diff --git a/flash/websockets.md b/flash/websockets.md index 36b19a6..d1bc981 100644 --- a/flash/websockets.md +++ b/flash/websockets.md @@ -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. +::: +