Refactoring and additions

This commit is contained in:
Relism
2025-02-27 22:44:18 +01:00
parent 06e6bfa13d
commit 8912f7e738
11 changed files with 152 additions and 26 deletions
+63
View File
@@ -0,0 +1,63 @@
---
banner-title: "Flash - Handlers"
banner-description: "Learn about handlers in Flash and the different types available."
---
# 📚 Handlers
In Flash, handlers are the building blocks of your application logic. They are responsible for processing incoming requests, executing the necessary logic, and generating the appropriate response.
There are several types of handlers in Flash, each serving a specific purpose and providing a different level of control over the request lifecycle. Understanding the different handler types will help you structure your application logic more effectively and make the most out of Flash's powerful routing system.
## 📦 Routing Behavior
Before diving into the different handler types, it's essential to understand how routing works in Flash. When a request is received by the server, Flash matches the request path and method against the registered routes to find the appropriate handler. The handler is then executed, and its response is sent back to the client.
Flash supports 3 main types of routing behaviors:
- **Literal Routing**: Matches the exact path specified in the route definition.
- **Parametrized Routing**: Matches paths with dynamic segments that are extracted as route parameters.
- **Dynamic Routing**: Matches any path that starts with the specified prefix and is flagged with a wildcard "*" character.
## 📌 Handler Types
### 1. RequestHandler
The `RequestHandler` is the "standard" type of handler in Flash, it provides the most control over the request lifecycle and allows you to define custom logic for handling requests.
You can extend the `RequestHandler` class to create custom handlers that process incoming requests and generate responses.
Because `RequestHandler` is an abstract class, you need to implement both the `handle()` method and the `super` constructor in your custom handler to define the logic that should be executed
when a request is received.
Since `RequestHandler` is an abstract class, you can leverage and chain HDI's to create cleaner and more maintainable route logic (more on that in the [Handler Default Implementations](/flash/handler-default-implementations) section).
### 2. SimpleHandler
The `SimpleHandler` is a lightweight handler that allows you to define request handling logic in a single method using lambda notation.
It is useful for simple request processing tasks that don't require the full lifecycle control provided by `RequestHandler`.
To create a `SimpleHandler`, you can use the `server.get()`, `server.post()`, `server.put()`, `server.delete()` etc.
in general, you can use the `server.<METHOD>()` methods to register the handler with the server.
The arguments for these methods are the route path and a lambda expression that provides
the request and response objects and defines the request handling logic.
```java[Example]
server.get("/hello", (req, res) -> {
return "Hello, World!";
});
```
---
Both `RequestHandler` and `SimpleHandler` can specify the router behavior
by the naming convention of the endpoint used to register the handler.
- Literal Routing: `/hello` <br>
Will match exactly `/hello`
- Parametrized Routing: `/hello/:name` <br>
Will match `/hello/John`, `/hello/Alice`, etc.
- Dynamic Routing: `/hello/*` <br>
Will match `/hello/../..`
+53
View File
@@ -0,0 +1,53 @@
---
banner_title: "Flash - Request Handler"
banner_description: "Learn how to create and manage Request Handlers in Flash."
---
# ⚙️ Request Handler
In this section, we illustrate the powerful concept of `RequestHandler` in Flash, which are used to handle incoming requests and generate responses.
RequestHandler classes provide the most control over the request lifecycle and allow you to use routing, expected operators and HDI's to create custom logic for handling requests.
## Creating a Request Handler
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.
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{1,4}
@RouteInfo(endpoint="/hello", method = HttpMethod.GET)
public class MyHandler extends RequestHandler {
public MyHandler(Request req, Response res) {
super(req, res);
}
@Override
public Object handle() {
String response = "Hello, world!";
return response;
}
}
```
::: warning
Any logic that needs to be executed before the request handler is registered must be done within the constructor.
:::
## Request Handler methods
The `RequestHandler` class provides several methods that can be used to interact with the request and response objects easily and safely.
Following are listed the methods available in the `RequestHandler` class, with a brief description of their purpose:
| Method | Params | Description |
|------------------------------|----------------------------|------------------------------------------------------------------------------------------------------|
| `getRequestBody()` | `none` | Returns a `JSONObject` representation of the request body. |
| `getSpecification()` | `none` | Returns an instance of `HandlerSpecification` containing all sorts of information about the handler. |
| `expectedRequestParameter()` | `String name, description` | Returns an instance of `ExpectedRequestParameter` for the specified parameter name. |
| `expectedBodyField()` | `String name, description` | Returns an instance of `ExpectedBodyField` for the specified field name. |
| `expectedBodyFile()` | `String name, description` | Returns an instance of `ExpectedBodyFile` for the specified file name. |
(More on the `ExpectedRequestParameter`, `ExpectedBodyField`, and `ExpectedBodyFile` classes in the next section).
+153
View File
@@ -0,0 +1,153 @@
---
banner_title: "Flash - Request and Response"
banner_description: "Unlock the power of the Request and Response objects in Flash."
---
# 📥 Request and Response
Generally speaking, in an HTTP request-response cycle, the client sends a request to the server, and the server generates a response based on the request.
In this section, we illustrate the `Request` and `Response` objects in Flash, which are used to interact with the request data and generate responses.
We will learn to read and interpret the request data, and model the correct response to send back to the client.
![](https://miro.medium.com/v2/resize:fit:696/1*wzzLb_xz5Mlykcd1bBqDig.png)
## Request and Response Objects
The `Request` and `Response` objects are passed to the handler constructor and provide access to the request data and response methods.
Under the hood, these objects are provided by the `RouteController` during the registration stage of the handler, and they are continously updated for each request on that specific handler's instance, but for now, you can consider them as magic.
These objects are a very powerful tool to interact with the lifecycle of the server's response, and they provide a wide range of methods that makes our lives as developers easier.
## RequestHandler context
Inside a `RequestHandler` class, you can access the `Request` and `Response` objects inside the `handle` method by simply typing `req` and `res`, respectively.
You can use the methods provided by these objects both outside of a handler, and inside of it, to interact with the request and response objects.
Although, the advantages of being inside a handler are that Flash supports out-of-the-box methods that can significantly clean up your code and improve
the overall readability of your handlers.
Specifically, the `ExpectedRequestParameter`, `ExpectedBodyField`, and `ExpectedBodyFile` objects are used to get the expected properties of the request,
and they are used by `Flash` to validate the request data before the `handle` method is even executed.
_**The developer can then simply assume that all the expected parameters are present and valid,
without having to write a single line of validation code: Flash will do it for you.**_
The three objects mentioned above are fairly similar in their usage, providing getter methods which safely return the data in the expected format and type,
thanks to Flash's built-in validation and casting system.
::: warning NOTE
`Flash` will take care of informing the client of any missing or invalid parameters,
parameters that are not in the expected format, or any other kind of error that might occur during the validation process.
:::
::: danger REMEMBER
The `ExpectedRequestParameter`, `ExpectedBodyField`, and `ExpectedBodyFile` instances are **_ONLY_** supposed to be retrieved by calling the respective
`expectedRequestParameter()`, `expectedBodyField()`, and `expectedBodyFile()` methods **_INSIDE_** of the super constructor of your handler class.
:::
## Example Usage
- `ExpectedRequestParameter`
::: details Click to expand
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{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;
public MyHandler(Request req, Response res) {
super(req, res);
// Get the expected parameter "myParam", and optionally provide a description
myExpectedReqParam = expectedRequestParameter("myParam", "A description of the parameter");
}
@Override
public Object handle() {
// OPTIONAL: specify the response status code and type
res.status(200);
res.type("text/plain");
// Safely get the parameter value as a String
String myParamValue = myExpectedReqParam.getString();
// Return the response to the client
return "Hello, " + myParamValue + "!";
}
}
```
Visiting `/hello?myParam=John` from your browser, will return `Hello, John!`.
:::
- `ExpectedBodyField`
::: details Click to expand
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{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;
public MyHandler(Request req, Response res) {
super(req, res);
// Get the expected field "myField", and optionally provide a description
myExpectedBodyField = expectedBodyField("myField", "A description of the field");
}
@Override
public Object handle() {
// OPTIONAL: specify the response status code and type
res.status(200);
res.type("text/plain");
// Safely get the field value as a String
String myFieldValue = myExpectedBodyField.getString();
// Return the response to the client
return "Field value: " + myFieldValue;
}
}
```
This time, since we are expecting a field in the request body, using our browser would not be enough to test the handler.
Instead, you can use a tool like `Postman` to send a GET request to `/helloBody` with a multipart form data body containing a field named `myField`.
You should receive a response like `Field value: <value>`.
:::
- `ExpectedBodyFile`
::: details Click to expand
The `ExpectedBodyFile` object is used to get the expected files of the request body.
The methods provided by this object are slightly different from the other two, but still extremely powerful and simple to use.
- `createFile(Path/String)` accepts either a Path or a String as input. It writes the file's contents to the specified location on the filesystem and returns a `File` object for further interaction.
- `getFileName()` simply returns the name of the file specified by the client.
- `getInputStream()` returns an `InputStream` object containing the file's contents.
```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;
public MyHandler(Request req, Response res) {
super(req, res);
// Get the expected file "myFile", and optionally provide a description
myExpectedBodyFile = expectedBodyFile("myFile", "A description of the file");
}
@Override
public Object handle() {
// OPTIONAL: specify the response status code and type
res.status(200);
res.type("text/plain");
// Write the file to filesystem and get the File object
File myFile = myExpectedBodyFile.createFile(Paths.get("path/to/save"));
// Return the response to the client
return "File saved at: " + myFile.getAbsolutePath();
}
}
```
This time, you will need to use a tool like `Postman` to send a POST request to `/helloFile` with a multipart form data body containing a file named `myFile`.
You should receive a response like `File saved at: <path>` where `<path>` is the location where the server saved the file.
:::
+54
View File
@@ -0,0 +1,54 @@
---
banner_title: "Flash - Server Router"
banner_description: "Learn how to use the FlashServer router to create and manage RouteHandlers."
---
# 🛣️ Server Router
In this section, we discuss how to use the `FlashServer` router to manage our `RequestHandler` instances.
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 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,
```java{6}
// Example.java
public class Example {
public static void main(String[] args) {
FlashServer server = new FlashServer(8080);
server.route("/api")
.register(MyHandler.class);
server.start();
}
}
```
```java{3}
// MyHandler.java
@RouteInfo(method = HttpMethod.GET, path = "/hello")
public class MyHandler extends RequestHandler {
public MyHandler(Request req, Response res) {
super(req, res);
}
@Override
public Object handle() {
String response = "Hello, world!";
return response;
}
}
```
In the example above, we create an `/api` router and register the `MyHandler` class to handle requests on the `/api/hello` endpoint.
This is because the `path` property of the `RouteInfo` annotation is relative to the base path of the router, which in this case is `/api`.
Visiting `/api/hello` from your browser will result in the response `Hello, world!`.
+85
View File
@@ -0,0 +1,85 @@
---
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.
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 {
@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);
//optionally send a reponse back to the client
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(new MyWebsocketHandler());
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.
| 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.
:::