From 6dd4f8fd0f6abd98229b57badeac439631241fac Mon Sep 17 00:00:00 2001 From: Relism Date: Thu, 27 Feb 2025 21:46:56 +0000 Subject: [PATCH] deploy: e9ddd43a46c770bd8040da968a29ba89adeb1f38 --- 404.html | 2 +- assets/flash-fullstack.D96XNhET.png | Bin 0 -> 30646 bytes ...anced_fullstack-development.md.B124NtWL.js | 29 ++++ ..._fullstack-development.md.B124NtWL.lean.js | 29 ++++ ...ler-default-implementations.md.BSoFk9xD.js | 93 +++++++++++++ ...efault-implementations.md.BSoFk9xD.lean.js | 93 +++++++++++++ ...lash_core-concepts_handlers.md.ChVWECmb.js | 3 + ...core-concepts_handlers.md.ChVWECmb.lean.js | 3 + ...re-concepts_request-handler.md.fjZWLpOw.js | 12 ++ ...ncepts_request-handler.md.fjZWLpOw.lean.js | 12 ++ ...e-concepts_request-response.md.3C-3EYgj.js | 67 ++++++++++ ...cepts_request-response.md.3C-3EYgj.lean.js | 67 ++++++++++ ...core-concepts_server-router.md.BtmNSZRo.js | 24 ++++ ...concepts_server-router.md.BtmNSZRo.lean.js | 24 ++++ ...sh_core-concepts_websockets.md.ByGVX96c.js | 33 +++++ ...re-concepts_websockets.md.ByGVX96c.lean.js | 33 +++++ ...serving_dynamic-file-server.md.BxDWqJ2P.js | 28 ++++ ...ng_dynamic-file-server.md.BxDWqJ2P.lean.js | 28 ++++ ...-serving_static-file-server.md.BvN0FZB2.js | 28 ++++ ...ing_static-file-server.md.BvN0FZB2.lean.js | 28 ++++ ...h_introduction_installation.md.BEOKwAAp.js | 19 +++ ...roduction_installation.md.BEOKwAAp.lean.js | 19 +++ flash/advanced/fullstack-development.html | 60 +++++++++ .../handler-default-implementations.html | 124 ++++++++++++++++++ flash/core-concepts/handlers.html | 34 +++++ flash/core-concepts/request-handler.html | 43 ++++++ flash/core-concepts/request-response.html | 98 ++++++++++++++ flash/core-concepts/server-router.html | 55 ++++++++ flash/core-concepts/websockets.html | 64 +++++++++ flash/file-serving/dynamic-file-server.html | 59 +++++++++ flash/file-serving/static-file-server.html | 59 +++++++++ flash/index.html | 4 +- flash/introduction/installation.html | 50 +++++++ hashmap.json | 2 +- index.html | 2 +- mobot/index.html | 2 +- serverlibraries/index.html | 2 +- 37 files changed, 1325 insertions(+), 7 deletions(-) create mode 100644 assets/flash-fullstack.D96XNhET.png create mode 100644 assets/flash_advanced_fullstack-development.md.B124NtWL.js create mode 100644 assets/flash_advanced_fullstack-development.md.B124NtWL.lean.js create mode 100644 assets/flash_advanced_handler-default-implementations.md.BSoFk9xD.js create mode 100644 assets/flash_advanced_handler-default-implementations.md.BSoFk9xD.lean.js create mode 100644 assets/flash_core-concepts_handlers.md.ChVWECmb.js create mode 100644 assets/flash_core-concepts_handlers.md.ChVWECmb.lean.js create mode 100644 assets/flash_core-concepts_request-handler.md.fjZWLpOw.js create mode 100644 assets/flash_core-concepts_request-handler.md.fjZWLpOw.lean.js create mode 100644 assets/flash_core-concepts_request-response.md.3C-3EYgj.js create mode 100644 assets/flash_core-concepts_request-response.md.3C-3EYgj.lean.js create mode 100644 assets/flash_core-concepts_server-router.md.BtmNSZRo.js create mode 100644 assets/flash_core-concepts_server-router.md.BtmNSZRo.lean.js create mode 100644 assets/flash_core-concepts_websockets.md.ByGVX96c.js create mode 100644 assets/flash_core-concepts_websockets.md.ByGVX96c.lean.js create mode 100644 assets/flash_file-serving_dynamic-file-server.md.BxDWqJ2P.js create mode 100644 assets/flash_file-serving_dynamic-file-server.md.BxDWqJ2P.lean.js create mode 100644 assets/flash_file-serving_static-file-server.md.BvN0FZB2.js create mode 100644 assets/flash_file-serving_static-file-server.md.BvN0FZB2.lean.js create mode 100644 assets/flash_introduction_installation.md.BEOKwAAp.js create mode 100644 assets/flash_introduction_installation.md.BEOKwAAp.lean.js create mode 100644 flash/advanced/fullstack-development.html create mode 100644 flash/advanced/handler-default-implementations.html create mode 100644 flash/core-concepts/handlers.html create mode 100644 flash/core-concepts/request-handler.html create mode 100644 flash/core-concepts/request-response.html create mode 100644 flash/core-concepts/server-router.html create mode 100644 flash/core-concepts/websockets.html create mode 100644 flash/file-serving/dynamic-file-server.html create mode 100644 flash/file-serving/static-file-server.html create mode 100644 flash/introduction/installation.html diff --git a/404.html b/404.html index 142111c..fdfded4 100644 --- a/404.html +++ b/404.html @@ -17,7 +17,7 @@
- + \ No newline at end of file diff --git a/assets/flash-fullstack.D96XNhET.png b/assets/flash-fullstack.D96XNhET.png new file mode 100644 index 0000000000000000000000000000000000000000..6257133a2054a4a847d15ac3f1f181658ed2704e GIT binary patch literal 30646 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 Ykdl6mHN?n-@*U>Fc`I1Ox$D3DFRZ^`ng9R* literal 0 HcmV?d00001 diff --git a/assets/flash_advanced_fullstack-development.md.B124NtWL.js b/assets/flash_advanced_fullstack-development.md.B124NtWL.js new file mode 100644 index 0000000..4731e64 --- /dev/null +++ b/assets/flash_advanced_fullstack-development.md.B124NtWL.js @@ -0,0 +1,29 @@ +import{_ as e,c as a,a0 as i,o as t}from"./chunks/framework.p2VkXzrt.js";const n="/assets/flash-fullstack.D96XNhET.png",u=JSON.parse('{"title":"🌐 Fullstack Development with Flash","description":"","frontmatter":{"banner_title":"Flash - Fullstack Development","banner_description":"Build fullstack apps with Flash, frontend and backend in one jar.","head":[["meta",{"name":"twitter:image","content":"/assets/banner-cards/flash-advanced-fullstack-development.png"}],["meta",{"name":"twitter:image:src","content":"https://docs.pixel-services.com/assets/banner-cards/flash-advanced-fullstack-development.png"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:height","content":"1280"}],["meta",{"name":"twitter:image:width","content":"669"}],["meta",{"name":"twitter:description","content":""}]]},"headers":[],"relativePath":"flash/advanced/fullstack-development.md","filePath":"flash/advanced/fullstack-development.md"}'),l={name:"flash/advanced/fullstack-development.md"};function o(r,s,p,h,d,c){return t(),a("div",null,s[0]||(s[0]=[i('

🌐 Fullstack Development with Flash ​

Fullstack development involves building both the front-end (user interface) and back-end (server logic, database) of a web application. A fullstack developer is responsible for the entire application, ensuring a smooth connection between the two layers.

Popular fullstack frameworks include:

⚑ Fullstack Development with Flash ​

Developing and packaging a fullstack application can be complex, but Flash simplifies the process with built-in tools and quality-of-life features. With Flash, you can:

  • Serve both frontend and backend from a single application.
  • Bundle everything into a single JAR file for easy deployment.
  • Leverage HDIs for clean, modular and maintainable route logic.

Flash Fullstack Development

πŸš€ Serving Frontend with Flash ​

Flash’s dynamic file server enables seamless fullstack development by allowing you to serve frontend assets alongside backend logic. This is made possible by the RESOURCESTREAM source type, which serves files from the JAR’s resources folder.

βœ… Deployment Workflow ​

The recommended workflow for packaging a fullstack Flash application:

  1. Compile the frontend application (React, Vue, Angular, etc.).
  2. Place the compiled files inside the resources folder.
  3. Build the JAR file with both frontend and backend code.
  4. Deploy the JAR, serving both frontend and backend seamlessly.

This approach works for any frontend framework that compiles to static files, such as:

  • React (npm run build)
  • Vue.js (npm run build)
  • Angular (ng build --prod)

❔ Setting up a Fullstack Flash Application ​

To get started with fullstack development using Flash, you will first need to choose a frontend framework and set up the build process to compile the frontend assets. This guide will cover only some of the most popular javascript frontend build tools, but the process is similar for others.

πŸ› οΈ Setting up the Frontend Build Process ​

WARNING

When building a frontend application with a javascript framework that will be served from a subdirectory (e.g., /app), you need to specify the homepage in the package.json file. This ensures that the frontend assets are correctly loaded from the subdirectory. Using a subdirectory and forgetting to set the homepage will result in broken asset links and a potentially non-functional frontend.

json
{
+    "homepage": "/your-subdirectory"
+}
  • Vite :

    Click to expand
    1. Install Vite on your project if it's not already installed or scaffold a new Vite project (follow the official guide).
    2. Edit the vite.config.js or vite.config.ts build section to output the compiled files to the resources/frontend folder of your Flash project (see example below).
    typescript
        // ...
    +    import path from 'path'
    +    export default defineConfig({
    +        // ...
    +
    +        build: { 
    +            outDir: path.resolve(__dirname, '..path/to/your/src/main/resources/frontend'), 
    +            emptyOutDir: true
    +        }, 
    +    });
    1. The output of your Vite build will be placed in the resources/frontend folder of your Flash project, ready to be packaged into the JAR file.
  • Parcel

    Click to expand
    1. Install Parcel on your project if it's not already installed or scaffold a new Parcel project (follow the official guide).
    2. Edit the Parcel build command to output the compiled files to the resources/frontend folder of your Flash project (see example below).
      json
      {
      +    "scripts": {
      +        "build": "parcel build src/index.html --out-dir ../path/to/your/src/main/resources/frontend"
      +    }
      +}
    3. The output of your Parcel build will be placed in the resources/frontend folder of your Flash project, ready to be packaged into the JAR file.

πŸ›œ Serve the Frontend with Flash ​

Once you have compiled the frontend assets and placed them in the resources/frontend folder, you can serve them using Flash's dynamic file server. This server will serve the frontend assets from the JAR's resources folder.

INFO

NOTE: The following example is assuming you plan to serve the frontend from the root path (/). If you plan to serve the frontend from a subdirectory (e.g., /app), you will need to adjust the endpoint path accordingly by adding a trailing /*.

java
public class Example {
+    public static void main(String[] args) {
+        FlashServer server = new FlashServer(8080);
+
+        server.serveDynamic("/*", new DynamicFileServerConfiguration(
+            true,
+            "frontend", // points to the resources/frontend folder
+            "index.html",
+            SourceType.RESOURCESTREAM
+        ));
+        
+        server.start();
+    }
+}

πŸš€ Package and Deploy your app! ​

With the frontend and backend code in place, you can now build the JAR file and deploy it to your server. The JAR file will contain both the frontend and backend code, making it easy to deploy and run your fullstack application.

All you've left to do is run the jarfile on any machine that has Java installed, and your fullstack application will be up and running!

`,27)]))}const g=e(l,[["render",o]]);export{u as __pageData,g as default}; diff --git a/assets/flash_advanced_fullstack-development.md.B124NtWL.lean.js b/assets/flash_advanced_fullstack-development.md.B124NtWL.lean.js new file mode 100644 index 0000000..4731e64 --- /dev/null +++ b/assets/flash_advanced_fullstack-development.md.B124NtWL.lean.js @@ -0,0 +1,29 @@ +import{_ as e,c as a,a0 as i,o as t}from"./chunks/framework.p2VkXzrt.js";const n="/assets/flash-fullstack.D96XNhET.png",u=JSON.parse('{"title":"🌐 Fullstack Development with Flash","description":"","frontmatter":{"banner_title":"Flash - Fullstack Development","banner_description":"Build fullstack apps with Flash, frontend and backend in one jar.","head":[["meta",{"name":"twitter:image","content":"/assets/banner-cards/flash-advanced-fullstack-development.png"}],["meta",{"name":"twitter:image:src","content":"https://docs.pixel-services.com/assets/banner-cards/flash-advanced-fullstack-development.png"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:height","content":"1280"}],["meta",{"name":"twitter:image:width","content":"669"}],["meta",{"name":"twitter:description","content":""}]]},"headers":[],"relativePath":"flash/advanced/fullstack-development.md","filePath":"flash/advanced/fullstack-development.md"}'),l={name:"flash/advanced/fullstack-development.md"};function o(r,s,p,h,d,c){return t(),a("div",null,s[0]||(s[0]=[i('

🌐 Fullstack Development with Flash ​

Fullstack development involves building both the front-end (user interface) and back-end (server logic, database) of a web application. A fullstack developer is responsible for the entire application, ensuring a smooth connection between the two layers.

Popular fullstack frameworks include:

⚑ Fullstack Development with Flash ​

Developing and packaging a fullstack application can be complex, but Flash simplifies the process with built-in tools and quality-of-life features. With Flash, you can:

  • Serve both frontend and backend from a single application.
  • Bundle everything into a single JAR file for easy deployment.
  • Leverage HDIs for clean, modular and maintainable route logic.

Flash Fullstack Development

πŸš€ Serving Frontend with Flash ​

Flash’s dynamic file server enables seamless fullstack development by allowing you to serve frontend assets alongside backend logic. This is made possible by the RESOURCESTREAM source type, which serves files from the JAR’s resources folder.

βœ… Deployment Workflow ​

The recommended workflow for packaging a fullstack Flash application:

  1. Compile the frontend application (React, Vue, Angular, etc.).
  2. Place the compiled files inside the resources folder.
  3. Build the JAR file with both frontend and backend code.
  4. Deploy the JAR, serving both frontend and backend seamlessly.

This approach works for any frontend framework that compiles to static files, such as:

  • React (npm run build)
  • Vue.js (npm run build)
  • Angular (ng build --prod)

❔ Setting up a Fullstack Flash Application ​

To get started with fullstack development using Flash, you will first need to choose a frontend framework and set up the build process to compile the frontend assets. This guide will cover only some of the most popular javascript frontend build tools, but the process is similar for others.

πŸ› οΈ Setting up the Frontend Build Process ​

WARNING

When building a frontend application with a javascript framework that will be served from a subdirectory (e.g., /app), you need to specify the homepage in the package.json file. This ensures that the frontend assets are correctly loaded from the subdirectory. Using a subdirectory and forgetting to set the homepage will result in broken asset links and a potentially non-functional frontend.

json
{
+    "homepage": "/your-subdirectory"
+}
  • Vite :

    Click to expand
    1. Install Vite on your project if it's not already installed or scaffold a new Vite project (follow the official guide).
    2. Edit the vite.config.js or vite.config.ts build section to output the compiled files to the resources/frontend folder of your Flash project (see example below).
    typescript
        // ...
    +    import path from 'path'
    +    export default defineConfig({
    +        // ...
    +
    +        build: { 
    +            outDir: path.resolve(__dirname, '..path/to/your/src/main/resources/frontend'), 
    +            emptyOutDir: true
    +        }, 
    +    });
    1. The output of your Vite build will be placed in the resources/frontend folder of your Flash project, ready to be packaged into the JAR file.
  • Parcel

    Click to expand
    1. Install Parcel on your project if it's not already installed or scaffold a new Parcel project (follow the official guide).
    2. Edit the Parcel build command to output the compiled files to the resources/frontend folder of your Flash project (see example below).
      json
      {
      +    "scripts": {
      +        "build": "parcel build src/index.html --out-dir ../path/to/your/src/main/resources/frontend"
      +    }
      +}
    3. The output of your Parcel build will be placed in the resources/frontend folder of your Flash project, ready to be packaged into the JAR file.

πŸ›œ Serve the Frontend with Flash ​

Once you have compiled the frontend assets and placed them in the resources/frontend folder, you can serve them using Flash's dynamic file server. This server will serve the frontend assets from the JAR's resources folder.

INFO

NOTE: The following example is assuming you plan to serve the frontend from the root path (/). If you plan to serve the frontend from a subdirectory (e.g., /app), you will need to adjust the endpoint path accordingly by adding a trailing /*.

java
public class Example {
+    public static void main(String[] args) {
+        FlashServer server = new FlashServer(8080);
+
+        server.serveDynamic("/*", new DynamicFileServerConfiguration(
+            true,
+            "frontend", // points to the resources/frontend folder
+            "index.html",
+            SourceType.RESOURCESTREAM
+        ));
+        
+        server.start();
+    }
+}

πŸš€ Package and Deploy your app! ​

With the frontend and backend code in place, you can now build the JAR file and deploy it to your server. The JAR file will contain both the frontend and backend code, making it easy to deploy and run your fullstack application.

All you've left to do is run the jarfile on any machine that has Java installed, and your fullstack application will be up and running!

`,27)]))}const g=e(l,[["render",o]]);export{u as __pageData,g as default}; diff --git a/assets/flash_advanced_handler-default-implementations.md.BSoFk9xD.js b/assets/flash_advanced_handler-default-implementations.md.BSoFk9xD.js new file mode 100644 index 0000000..a7d040f --- /dev/null +++ b/assets/flash_advanced_handler-default-implementations.md.BSoFk9xD.js @@ -0,0 +1,93 @@ +import{_ as i,c as a,a0 as n,o as t}from"./chunks/framework.p2VkXzrt.js";const g=JSON.parse(`{"title":"⚑ Handler Default Implementations (HDI)","description":"","frontmatter":{"banner_title":"Flash - Handler Default Implementations","banner_description":"Leverage HDI's for cleaner and more maintainable route logic.","head":[["meta",{"name":"twitter:image","content":"/assets/banner-cards/flash-advanced-handler-default-implementations.png"}],["meta",{"name":"twitter:image:src","content":"https://docs.pixel-services.com/assets/banner-cards/flash-advanced-handler-default-implementations.png"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:height","content":"1280"}],["meta",{"name":"twitter:image:width","content":"669"}],["meta",{"name":"twitter:description","content":""}]]},"headers":[],"relativePath":"flash/advanced/handler-default-implementations.md","filePath":"flash/advanced/handler-default-implementations.md"}`),h={name:"flash/advanced/handler-default-implementations.md"};function e(l,s,k,p,r,d){return t(),a("div",null,s[0]||(s[0]=[n(`

⚑ 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 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;
+
+    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);
+            res.type("application/json");
+            return "{\\"error\\":\\"Invalid API Key\\"}";
+        }
+
+        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;
+    }
+}

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")
+public class GetDataHandler extends APIKeyProtectedHandler {
+    public GetDataHandler(Request req, Response res) {
+        super(req, res);
+    }
+
+    @Override
+    protected Object handleAuthorized() {
+        res.type("application/json");
+        return "{\\"data\\":\\"Your API response here\\"}";
+    }
+}

πŸ—οΈ Chaining HDIs for Modular Logic ​

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;
+
+    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, 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);
+    }
+
+    @Override
+    protected Object handleAuthenticated() {
+        user = getUserFromDatabase(authToken);
+        if (user == null) {
+            res.status(403);
+            res.type("application/json");
+            return "{\\"error\\":\\"User not found\\"}";
+        }
+        return handleWithUser();
+    }
+
+    protected abstract Object handleWithUser();
+}
  • 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(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() {
+        res.type("application/json");
+        return "{\\"username\\":\\"" + username + "\\"}";
+    }
+}
`,17)]))}const y=i(h,[["render",e]]);export{g as __pageData,y as default}; diff --git a/assets/flash_advanced_handler-default-implementations.md.BSoFk9xD.lean.js b/assets/flash_advanced_handler-default-implementations.md.BSoFk9xD.lean.js new file mode 100644 index 0000000..a7d040f --- /dev/null +++ b/assets/flash_advanced_handler-default-implementations.md.BSoFk9xD.lean.js @@ -0,0 +1,93 @@ +import{_ as i,c as a,a0 as n,o as t}from"./chunks/framework.p2VkXzrt.js";const g=JSON.parse(`{"title":"⚑ Handler Default Implementations (HDI)","description":"","frontmatter":{"banner_title":"Flash - Handler Default Implementations","banner_description":"Leverage HDI's for cleaner and more maintainable route logic.","head":[["meta",{"name":"twitter:image","content":"/assets/banner-cards/flash-advanced-handler-default-implementations.png"}],["meta",{"name":"twitter:image:src","content":"https://docs.pixel-services.com/assets/banner-cards/flash-advanced-handler-default-implementations.png"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:height","content":"1280"}],["meta",{"name":"twitter:image:width","content":"669"}],["meta",{"name":"twitter:description","content":""}]]},"headers":[],"relativePath":"flash/advanced/handler-default-implementations.md","filePath":"flash/advanced/handler-default-implementations.md"}`),h={name:"flash/advanced/handler-default-implementations.md"};function e(l,s,k,p,r,d){return t(),a("div",null,s[0]||(s[0]=[n(`

⚑ 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 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;
+
+    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);
+            res.type("application/json");
+            return "{\\"error\\":\\"Invalid API Key\\"}";
+        }
+
+        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;
+    }
+}

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")
+public class GetDataHandler extends APIKeyProtectedHandler {
+    public GetDataHandler(Request req, Response res) {
+        super(req, res);
+    }
+
+    @Override
+    protected Object handleAuthorized() {
+        res.type("application/json");
+        return "{\\"data\\":\\"Your API response here\\"}";
+    }
+}

πŸ—οΈ Chaining HDIs for Modular Logic ​

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;
+
+    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, 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);
+    }
+
+    @Override
+    protected Object handleAuthenticated() {
+        user = getUserFromDatabase(authToken);
+        if (user == null) {
+            res.status(403);
+            res.type("application/json");
+            return "{\\"error\\":\\"User not found\\"}";
+        }
+        return handleWithUser();
+    }
+
+    protected abstract Object handleWithUser();
+}
  • 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(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() {
+        res.type("application/json");
+        return "{\\"username\\":\\"" + username + "\\"}";
+    }
+}
`,17)]))}const y=i(h,[["render",e]]);export{g as __pageData,y as default}; diff --git a/assets/flash_core-concepts_handlers.md.ChVWECmb.js b/assets/flash_core-concepts_handlers.md.ChVWECmb.js new file mode 100644 index 0000000..8d69de4 --- /dev/null +++ b/assets/flash_core-concepts_handlers.md.ChVWECmb.js @@ -0,0 +1,3 @@ +import{_ as t,c as a,a0 as s,o as n}from"./chunks/framework.p2VkXzrt.js";const u=JSON.parse('{"title":"πŸ“š Handlers","description":"","frontmatter":{"banner-title":"Flash - Handlers","banner-description":"Learn about handlers in Flash and the different types available.","head":[["meta",{"name":"twitter:image","content":"/assets/banner-cards/flash-core-concepts-handlers.png"}],["meta",{"name":"twitter:image:src","content":"https://docs.pixel-services.com/assets/banner-cards/flash-core-concepts-handlers.png"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:height","content":"1280"}],["meta",{"name":"twitter:image:width","content":"669"}],["meta",{"name":"twitter:description","content":""}]]},"headers":[],"relativePath":"flash/core-concepts/handlers.md","filePath":"flash/core-concepts/handlers.md"}'),i={name:"flash/core-concepts/handlers.md"};function r(o,e,l,d,h,c){return n(),a("div",null,e[0]||(e[0]=[s(`

πŸ“š 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 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
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
    Will match exactly /hello

  • Parametrized Routing: /hello/:name
    Will match /hello/John, /hello/Alice, etc.

  • Dynamic Routing: /hello/*
    Will match /hello/../..

`,20)]))}const g=t(i,[["render",r]]);export{u as __pageData,g as default}; diff --git a/assets/flash_core-concepts_handlers.md.ChVWECmb.lean.js b/assets/flash_core-concepts_handlers.md.ChVWECmb.lean.js new file mode 100644 index 0000000..8d69de4 --- /dev/null +++ b/assets/flash_core-concepts_handlers.md.ChVWECmb.lean.js @@ -0,0 +1,3 @@ +import{_ as t,c as a,a0 as s,o as n}from"./chunks/framework.p2VkXzrt.js";const u=JSON.parse('{"title":"πŸ“š Handlers","description":"","frontmatter":{"banner-title":"Flash - Handlers","banner-description":"Learn about handlers in Flash and the different types available.","head":[["meta",{"name":"twitter:image","content":"/assets/banner-cards/flash-core-concepts-handlers.png"}],["meta",{"name":"twitter:image:src","content":"https://docs.pixel-services.com/assets/banner-cards/flash-core-concepts-handlers.png"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:height","content":"1280"}],["meta",{"name":"twitter:image:width","content":"669"}],["meta",{"name":"twitter:description","content":""}]]},"headers":[],"relativePath":"flash/core-concepts/handlers.md","filePath":"flash/core-concepts/handlers.md"}'),i={name:"flash/core-concepts/handlers.md"};function r(o,e,l,d,h,c){return n(),a("div",null,e[0]||(e[0]=[s(`

πŸ“š 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 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
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
    Will match exactly /hello

  • Parametrized Routing: /hello/:name
    Will match /hello/John, /hello/Alice, etc.

  • Dynamic Routing: /hello/*
    Will match /hello/../..

`,20)]))}const g=t(i,[["render",r]]);export{u as __pageData,g as default}; diff --git a/assets/flash_core-concepts_request-handler.md.fjZWLpOw.js b/assets/flash_core-concepts_request-handler.md.fjZWLpOw.js new file mode 100644 index 0000000..86a175c --- /dev/null +++ b/assets/flash_core-concepts_request-handler.md.fjZWLpOw.js @@ -0,0 +1,12 @@ +import{_ as s,c as t,a0 as a,o as i}from"./chunks/framework.p2VkXzrt.js";const k=JSON.parse('{"title":"βš™οΈ Request Handler","description":"","frontmatter":{"banner_title":"Flash - Request Handler","banner_description":"Learn how to create and manage Request Handlers in Flash.","head":[["meta",{"name":"twitter:image","content":"/assets/banner-cards/flash-core-concepts-request-handler.png"}],["meta",{"name":"twitter:image:src","content":"https://docs.pixel-services.com/assets/banner-cards/flash-core-concepts-request-handler.png"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:height","content":"1280"}],["meta",{"name":"twitter:image:width","content":"669"}],["meta",{"name":"twitter:description","content":""}]]},"headers":[],"relativePath":"flash/core-concepts/request-handler.md","filePath":"flash/core-concepts/request-handler.md"}'),n={name:"flash/core-concepts/request-handler.md"};function d(r,e,h,l,o,c){return i(),t("div",null,e[0]||(e[0]=[a(`

βš™οΈ 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
@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:

MethodParamsDescription
getRequestBody()noneReturns a JSONObject representation of the request body.
getSpecification()noneReturns an instance of HandlerSpecification containing all sorts of information about the handler.
expectedRequestParameter()String name, descriptionReturns an instance of ExpectedRequestParameter for the specified parameter name.
expectedBodyField()String name, descriptionReturns an instance of ExpectedBodyField for the specified field name.
expectedBodyFile()String name, descriptionReturns an instance of ExpectedBodyFile for the specified file name.

(More on the ExpectedRequestParameter, ExpectedBodyField, and ExpectedBodyFile classes in the next section).

`,11)]))}const u=s(n,[["render",d]]);export{k as __pageData,u as default}; diff --git a/assets/flash_core-concepts_request-handler.md.fjZWLpOw.lean.js b/assets/flash_core-concepts_request-handler.md.fjZWLpOw.lean.js new file mode 100644 index 0000000..86a175c --- /dev/null +++ b/assets/flash_core-concepts_request-handler.md.fjZWLpOw.lean.js @@ -0,0 +1,12 @@ +import{_ as s,c as t,a0 as a,o as i}from"./chunks/framework.p2VkXzrt.js";const k=JSON.parse('{"title":"βš™οΈ Request Handler","description":"","frontmatter":{"banner_title":"Flash - Request Handler","banner_description":"Learn how to create and manage Request Handlers in Flash.","head":[["meta",{"name":"twitter:image","content":"/assets/banner-cards/flash-core-concepts-request-handler.png"}],["meta",{"name":"twitter:image:src","content":"https://docs.pixel-services.com/assets/banner-cards/flash-core-concepts-request-handler.png"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:height","content":"1280"}],["meta",{"name":"twitter:image:width","content":"669"}],["meta",{"name":"twitter:description","content":""}]]},"headers":[],"relativePath":"flash/core-concepts/request-handler.md","filePath":"flash/core-concepts/request-handler.md"}'),n={name:"flash/core-concepts/request-handler.md"};function d(r,e,h,l,o,c){return i(),t("div",null,e[0]||(e[0]=[a(`

βš™οΈ 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
@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:

MethodParamsDescription
getRequestBody()noneReturns a JSONObject representation of the request body.
getSpecification()noneReturns an instance of HandlerSpecification containing all sorts of information about the handler.
expectedRequestParameter()String name, descriptionReturns an instance of ExpectedRequestParameter for the specified parameter name.
expectedBodyField()String name, descriptionReturns an instance of ExpectedBodyField for the specified field name.
expectedBodyFile()String name, descriptionReturns an instance of ExpectedBodyFile for the specified file name.

(More on the ExpectedRequestParameter, ExpectedBodyField, and ExpectedBodyFile classes in the next section).

`,11)]))}const u=s(n,[["render",d]]);export{k as __pageData,u as default}; diff --git a/assets/flash_core-concepts_request-response.md.3C-3EYgj.js b/assets/flash_core-concepts_request-response.md.3C-3EYgj.js new file mode 100644 index 0000000..a027ed9 --- /dev/null +++ b/assets/flash_core-concepts_request-response.md.3C-3EYgj.js @@ -0,0 +1,67 @@ +import{_ as i,c as e,a0 as a,o as t}from"./chunks/framework.p2VkXzrt.js";const c=JSON.parse('{"title":"πŸ“₯ Request and Response","description":"","frontmatter":{"banner_title":"Flash - Request and Response","banner_description":"Unlock the power of the Request and Response objects in Flash.","head":[["meta",{"name":"twitter:image","content":"/assets/banner-cards/flash-core-concepts-request-response.png"}],["meta",{"name":"twitter:image:src","content":"https://docs.pixel-services.com/assets/banner-cards/flash-core-concepts-request-response.png"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:height","content":"1280"}],["meta",{"name":"twitter:image:width","content":"669"}],["meta",{"name":"twitter:description","content":""}]]},"headers":[],"relativePath":"flash/core-concepts/request-response.md","filePath":"flash/core-concepts/request-response.md"}'),n={name:"flash/core-concepts/request-response.md"};function h(l,s,p,k,d,r){return t(),e("div",null,s[0]||(s[0]=[a(`

πŸ“₯ 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.

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.

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.

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
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
@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
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
@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
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
@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.

`,19)]))}const E=i(n,[["render",h]]);export{c as __pageData,E as default}; diff --git a/assets/flash_core-concepts_request-response.md.3C-3EYgj.lean.js b/assets/flash_core-concepts_request-response.md.3C-3EYgj.lean.js new file mode 100644 index 0000000..a027ed9 --- /dev/null +++ b/assets/flash_core-concepts_request-response.md.3C-3EYgj.lean.js @@ -0,0 +1,67 @@ +import{_ as i,c as e,a0 as a,o as t}from"./chunks/framework.p2VkXzrt.js";const c=JSON.parse('{"title":"πŸ“₯ Request and Response","description":"","frontmatter":{"banner_title":"Flash - Request and Response","banner_description":"Unlock the power of the Request and Response objects in Flash.","head":[["meta",{"name":"twitter:image","content":"/assets/banner-cards/flash-core-concepts-request-response.png"}],["meta",{"name":"twitter:image:src","content":"https://docs.pixel-services.com/assets/banner-cards/flash-core-concepts-request-response.png"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:height","content":"1280"}],["meta",{"name":"twitter:image:width","content":"669"}],["meta",{"name":"twitter:description","content":""}]]},"headers":[],"relativePath":"flash/core-concepts/request-response.md","filePath":"flash/core-concepts/request-response.md"}'),n={name:"flash/core-concepts/request-response.md"};function h(l,s,p,k,d,r){return t(),e("div",null,s[0]||(s[0]=[a(`

πŸ“₯ 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.

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.

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.

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
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
@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
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
@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
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
@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.

`,19)]))}const E=i(n,[["render",h]]);export{c as __pageData,E as default}; diff --git a/assets/flash_core-concepts_server-router.md.BtmNSZRo.js b/assets/flash_core-concepts_server-router.md.BtmNSZRo.js new file mode 100644 index 0000000..6d4d8e5 --- /dev/null +++ b/assets/flash_core-concepts_server-router.md.BtmNSZRo.js @@ -0,0 +1,24 @@ +import{_ as i,c as a,a0 as e,o as n}from"./chunks/framework.p2VkXzrt.js";const c=JSON.parse('{"title":"πŸ›£οΈ Server Router","description":"","frontmatter":{"banner_title":"Flash - Server Router","banner_description":"Learn how to use the FlashServer router to create and manage RouteHandlers.","head":[["meta",{"name":"twitter:image","content":"/assets/banner-cards/flash-core-concepts-server-router.png"}],["meta",{"name":"twitter:image:src","content":"https://docs.pixel-services.com/assets/banner-cards/flash-core-concepts-server-router.png"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:height","content":"1280"}],["meta",{"name":"twitter:image:width","content":"669"}],["meta",{"name":"twitter:description","content":""}]]},"headers":[],"relativePath":"flash/core-concepts/server-router.md","filePath":"flash/core-concepts/server-router.md"}'),t={name:"flash/core-concepts/server-router.md"};function h(l,s,r,p,k,o){return n(),a("div",null,s[0]||(s[0]=[e(`

πŸ›£οΈ 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
// 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
// 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!.

`,10)]))}const E=i(t,[["render",h]]);export{c as __pageData,E as default}; diff --git a/assets/flash_core-concepts_server-router.md.BtmNSZRo.lean.js b/assets/flash_core-concepts_server-router.md.BtmNSZRo.lean.js new file mode 100644 index 0000000..6d4d8e5 --- /dev/null +++ b/assets/flash_core-concepts_server-router.md.BtmNSZRo.lean.js @@ -0,0 +1,24 @@ +import{_ as i,c as a,a0 as e,o as n}from"./chunks/framework.p2VkXzrt.js";const c=JSON.parse('{"title":"πŸ›£οΈ Server Router","description":"","frontmatter":{"banner_title":"Flash - Server Router","banner_description":"Learn how to use the FlashServer router to create and manage RouteHandlers.","head":[["meta",{"name":"twitter:image","content":"/assets/banner-cards/flash-core-concepts-server-router.png"}],["meta",{"name":"twitter:image:src","content":"https://docs.pixel-services.com/assets/banner-cards/flash-core-concepts-server-router.png"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:height","content":"1280"}],["meta",{"name":"twitter:image:width","content":"669"}],["meta",{"name":"twitter:description","content":""}]]},"headers":[],"relativePath":"flash/core-concepts/server-router.md","filePath":"flash/core-concepts/server-router.md"}'),t={name:"flash/core-concepts/server-router.md"};function h(l,s,r,p,k,o){return n(),a("div",null,s[0]||(s[0]=[e(`

πŸ›£οΈ 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
// 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
// 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!.

`,10)]))}const E=i(t,[["render",h]]);export{c as __pageData,E as default}; diff --git a/assets/flash_core-concepts_websockets.md.ByGVX96c.js b/assets/flash_core-concepts_websockets.md.ByGVX96c.js new file mode 100644 index 0000000..50bae7c --- /dev/null +++ b/assets/flash_core-concepts_websockets.md.ByGVX96c.js @@ -0,0 +1,33 @@ +import{_ as i,c as e,a0 as a,o as t}from"./chunks/framework.p2VkXzrt.js";const c=JSON.parse('{"title":"🌐 Websockets","description":"","frontmatter":{"banner_title":"Flash - Websockets","banner_description":"Learn how to create and manage server-side Websockets in Flash.","head":[["meta",{"name":"twitter:image","content":"/assets/banner-cards/flash-core-concepts-websockets.png"}],["meta",{"name":"twitter:image:src","content":"https://docs.pixel-services.com/assets/banner-cards/flash-core-concepts-websockets.png"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:height","content":"1280"}],["meta",{"name":"twitter:image:width","content":"669"}],["meta",{"name":"twitter:description","content":""}]]},"headers":[],"relativePath":"flash/core-concepts/websockets.md","filePath":"flash/core-concepts/websockets.md"}'),n={name:"flash/core-concepts/websockets.md"};function h(l,s,k,p,o,d){return t(),e("div",null,s[0]||(s[0]=[a(`

🌐 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.

MethodParamsDescription
getChannel()noneReturns an instance of AsynchronousSocketChannel useful for retrieving info about the client .
getRequestInfo()noneReturns an instance of RequestInfo containing all sorts of information about the request (headers, method, path etc.) .
getPath()noneReturns the path to the websocket endpoint as a String.
getId()noneReturns 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()noneReturns the ByteBuffer for that session.
sendMessage()String messageSends 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()noneCloses the websocket session.

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.

`,14)]))}const E=i(n,[["render",h]]);export{c as __pageData,E as default}; diff --git a/assets/flash_core-concepts_websockets.md.ByGVX96c.lean.js b/assets/flash_core-concepts_websockets.md.ByGVX96c.lean.js new file mode 100644 index 0000000..50bae7c --- /dev/null +++ b/assets/flash_core-concepts_websockets.md.ByGVX96c.lean.js @@ -0,0 +1,33 @@ +import{_ as i,c as e,a0 as a,o as t}from"./chunks/framework.p2VkXzrt.js";const c=JSON.parse('{"title":"🌐 Websockets","description":"","frontmatter":{"banner_title":"Flash - Websockets","banner_description":"Learn how to create and manage server-side Websockets in Flash.","head":[["meta",{"name":"twitter:image","content":"/assets/banner-cards/flash-core-concepts-websockets.png"}],["meta",{"name":"twitter:image:src","content":"https://docs.pixel-services.com/assets/banner-cards/flash-core-concepts-websockets.png"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:height","content":"1280"}],["meta",{"name":"twitter:image:width","content":"669"}],["meta",{"name":"twitter:description","content":""}]]},"headers":[],"relativePath":"flash/core-concepts/websockets.md","filePath":"flash/core-concepts/websockets.md"}'),n={name:"flash/core-concepts/websockets.md"};function h(l,s,k,p,o,d){return t(),e("div",null,s[0]||(s[0]=[a(`

🌐 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.

MethodParamsDescription
getChannel()noneReturns an instance of AsynchronousSocketChannel useful for retrieving info about the client .
getRequestInfo()noneReturns an instance of RequestInfo containing all sorts of information about the request (headers, method, path etc.) .
getPath()noneReturns the path to the websocket endpoint as a String.
getId()noneReturns 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()noneReturns the ByteBuffer for that session.
sendMessage()String messageSends 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()noneCloses the websocket session.

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.

`,14)]))}const E=i(n,[["render",h]]);export{c as __pageData,E as default}; diff --git a/assets/flash_file-serving_dynamic-file-server.md.BxDWqJ2P.js b/assets/flash_file-serving_dynamic-file-server.md.BxDWqJ2P.js new file mode 100644 index 0000000..9e73ccb --- /dev/null +++ b/assets/flash_file-serving_dynamic-file-server.md.BxDWqJ2P.js @@ -0,0 +1,28 @@ +import{_ as i,c as a,a0 as e,o as n}from"./chunks/framework.p2VkXzrt.js";const c=JSON.parse('{"title":"πŸ“ Dynamic File Server","description":"","frontmatter":{"banner_title":"Flash - Dynamic File Server","banner_description":"Learn how to serve files in a dynamic context.","head":[["meta",{"name":"twitter:image","content":"/assets/banner-cards/flash-file-serving-dynamic-file-server.png"}],["meta",{"name":"twitter:image:src","content":"https://docs.pixel-services.com/assets/banner-cards/flash-file-serving-dynamic-file-server.png"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:height","content":"1280"}],["meta",{"name":"twitter:image:width","content":"669"}],["meta",{"name":"twitter:description","content":""}]]},"headers":[],"relativePath":"flash/file-serving/dynamic-file-server.md","filePath":"flash/file-serving/dynamic-file-server.md"}'),t={name:"flash/file-serving/dynamic-file-server.md"};function l(h,s,p,r,k,d){return n(),a("div",null,s[0]||(s[0]=[e(`

πŸ“ Dynamic File Server ​

Sometimes we need to serve files in a dynamic context, in this sense a static file server is limiting. Imagine we have a frontend application that is compiled down to a single index.html file, and we want to serve it with Flash alongside it's javascript and css bundles. The way these compiled applications work is that they rely heavily on client-side routing, so when the user navigates to a different page, the frontend application will try to fetch the corresponding file from the server. This is where a dynamic file server comes in handy, as no route is pre-registered.

WARNING

The dynamic file server relies heavily on the concept of dynamic handlers, which are handlers that will resolve for any subpath of the endpoint they are registered to (see Handler Types for more info).

Usage ​

To serve static files in Flash, you need to call the server.serveDynamic() method with the endpoint path and an instance of DynamicFileServerConfig. The configuration object is instanced like so :

java
DynamicFileServerConfiguration(
+    boolean enableFileWatcher,
+    String destinationPath,
+    String dynamicEntrypoint,
+    SourceType sourceType
+)
  • enableFileWatcher : If set to true, the server will watch for changes in the served files and reload them automatically.
  • destinationPath : The path to the directory containing the files to be served.
  • dynamicEntrypoint : The path to the file that will be served when the client navigates to the endpoint eg. index.html.
  • sourceType : The type of source to serve files from. It can be either FILESYSTEM or RESOURCESTREAM.

Registering the dynamic file server is as simple as calling the server.serveDynamic() method with the desired path and configuration object:

java
public class Example {
+    public static void main(String[] args) {
+        FlashServer server = new FlashServer(8080);
+
+        server.serveDynamic("/*", new DynamicFileServerConfiguration(
+            true,
+            "path/to/my/files",
+            "index.html",
+            SourceType.FILESYSTEM
+        ));
+    }
+}

Now you can access the files (or frontend) in the specified directory by navigating to http://localhost:8080/<file-name>.

Similarly, you can serve the same content 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 DynamicFileServerConfiguration(
+            true,
+            "path/to/my/files",
+            "index.html",
+            SourceType.RESOURCESTREAM
+        ));
+    }
+}
`,12)]))}const E=i(t,[["render",l]]);export{c as __pageData,E as default}; diff --git a/assets/flash_file-serving_dynamic-file-server.md.BxDWqJ2P.lean.js b/assets/flash_file-serving_dynamic-file-server.md.BxDWqJ2P.lean.js new file mode 100644 index 0000000..9e73ccb --- /dev/null +++ b/assets/flash_file-serving_dynamic-file-server.md.BxDWqJ2P.lean.js @@ -0,0 +1,28 @@ +import{_ as i,c as a,a0 as e,o as n}from"./chunks/framework.p2VkXzrt.js";const c=JSON.parse('{"title":"πŸ“ Dynamic File Server","description":"","frontmatter":{"banner_title":"Flash - Dynamic File Server","banner_description":"Learn how to serve files in a dynamic context.","head":[["meta",{"name":"twitter:image","content":"/assets/banner-cards/flash-file-serving-dynamic-file-server.png"}],["meta",{"name":"twitter:image:src","content":"https://docs.pixel-services.com/assets/banner-cards/flash-file-serving-dynamic-file-server.png"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:height","content":"1280"}],["meta",{"name":"twitter:image:width","content":"669"}],["meta",{"name":"twitter:description","content":""}]]},"headers":[],"relativePath":"flash/file-serving/dynamic-file-server.md","filePath":"flash/file-serving/dynamic-file-server.md"}'),t={name:"flash/file-serving/dynamic-file-server.md"};function l(h,s,p,r,k,d){return n(),a("div",null,s[0]||(s[0]=[e(`

πŸ“ Dynamic File Server ​

Sometimes we need to serve files in a dynamic context, in this sense a static file server is limiting. Imagine we have a frontend application that is compiled down to a single index.html file, and we want to serve it with Flash alongside it's javascript and css bundles. The way these compiled applications work is that they rely heavily on client-side routing, so when the user navigates to a different page, the frontend application will try to fetch the corresponding file from the server. This is where a dynamic file server comes in handy, as no route is pre-registered.

WARNING

The dynamic file server relies heavily on the concept of dynamic handlers, which are handlers that will resolve for any subpath of the endpoint they are registered to (see Handler Types for more info).

Usage ​

To serve static files in Flash, you need to call the server.serveDynamic() method with the endpoint path and an instance of DynamicFileServerConfig. The configuration object is instanced like so :

java
DynamicFileServerConfiguration(
+    boolean enableFileWatcher,
+    String destinationPath,
+    String dynamicEntrypoint,
+    SourceType sourceType
+)
  • enableFileWatcher : If set to true, the server will watch for changes in the served files and reload them automatically.
  • destinationPath : The path to the directory containing the files to be served.
  • dynamicEntrypoint : The path to the file that will be served when the client navigates to the endpoint eg. index.html.
  • sourceType : The type of source to serve files from. It can be either FILESYSTEM or RESOURCESTREAM.

Registering the dynamic file server is as simple as calling the server.serveDynamic() method with the desired path and configuration object:

java
public class Example {
+    public static void main(String[] args) {
+        FlashServer server = new FlashServer(8080);
+
+        server.serveDynamic("/*", new DynamicFileServerConfiguration(
+            true,
+            "path/to/my/files",
+            "index.html",
+            SourceType.FILESYSTEM
+        ));
+    }
+}

Now you can access the files (or frontend) in the specified directory by navigating to http://localhost:8080/<file-name>.

Similarly, you can serve the same content 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 DynamicFileServerConfiguration(
+            true,
+            "path/to/my/files",
+            "index.html",
+            SourceType.RESOURCESTREAM
+        ));
+    }
+}
`,12)]))}const E=i(t,[["render",l]]);export{c as __pageData,E as default}; diff --git a/assets/flash_file-serving_static-file-server.md.BvN0FZB2.js b/assets/flash_file-serving_static-file-server.md.BvN0FZB2.js new file mode 100644 index 0000000..e987b53 --- /dev/null +++ b/assets/flash_file-serving_static-file-server.md.BvN0FZB2.js @@ -0,0 +1,28 @@ +import{_ as i,c as a,a0 as e,o as t}from"./chunks/framework.p2VkXzrt.js";const o=JSON.parse('{"title":"πŸ“ Static File Server","description":"","frontmatter":{"banner_title":"Flash - Static File Server","banner_description":"Learn how to serve static files in Flash.","head":[["meta",{"name":"twitter:image","content":"/assets/banner-cards/flash-file-serving-static-file-server.png"}],["meta",{"name":"twitter:image:src","content":"https://docs.pixel-services.com/assets/banner-cards/flash-file-serving-static-file-server.png"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:height","content":"1280"}],["meta",{"name":"twitter:image:width","content":"669"}],["meta",{"name":"twitter:description","content":""}]]},"headers":[],"relativePath":"flash/file-serving/static-file-server.md","filePath":"flash/file-serving/static-file-server.md"}'),n={name:"flash/file-serving/static-file-server.md"};function l(h,s,p,r,k,d){return t(),a("div",null,s[0]||(s[0]=[e(`

πŸ“ 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
+        ));
+    }
+}
`,12)]))}const E=i(n,[["render",l]]);export{o as __pageData,E as default}; diff --git a/assets/flash_file-serving_static-file-server.md.BvN0FZB2.lean.js b/assets/flash_file-serving_static-file-server.md.BvN0FZB2.lean.js new file mode 100644 index 0000000..e987b53 --- /dev/null +++ b/assets/flash_file-serving_static-file-server.md.BvN0FZB2.lean.js @@ -0,0 +1,28 @@ +import{_ as i,c as a,a0 as e,o as t}from"./chunks/framework.p2VkXzrt.js";const o=JSON.parse('{"title":"πŸ“ Static File Server","description":"","frontmatter":{"banner_title":"Flash - Static File Server","banner_description":"Learn how to serve static files in Flash.","head":[["meta",{"name":"twitter:image","content":"/assets/banner-cards/flash-file-serving-static-file-server.png"}],["meta",{"name":"twitter:image:src","content":"https://docs.pixel-services.com/assets/banner-cards/flash-file-serving-static-file-server.png"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:height","content":"1280"}],["meta",{"name":"twitter:image:width","content":"669"}],["meta",{"name":"twitter:description","content":""}]]},"headers":[],"relativePath":"flash/file-serving/static-file-server.md","filePath":"flash/file-serving/static-file-server.md"}'),n={name:"flash/file-serving/static-file-server.md"};function l(h,s,p,r,k,d){return t(),a("div",null,s[0]||(s[0]=[e(`

πŸ“ 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
+        ));
+    }
+}
`,12)]))}const E=i(n,[["render",l]]);export{o as __pageData,E as default}; diff --git a/assets/flash_introduction_installation.md.BEOKwAAp.js b/assets/flash_introduction_installation.md.BEOKwAAp.js new file mode 100644 index 0000000..2128b98 --- /dev/null +++ b/assets/flash_introduction_installation.md.BEOKwAAp.js @@ -0,0 +1,19 @@ +import{p as l,v as p,c as h,a0 as r,j as n,a as k,t as o,o as E}from"./chunks/framework.p2VkXzrt.js";const d={href:"https://maven.pixel-services.com/#/releases/com/pixelservices/flash",style:{"text-decoration":"underline",color:"#007bff"}},m=JSON.parse('{"title":"πŸ“² Installation","description":"","frontmatter":{"banner_title":"Flash - Installation","banner_description":"A guide on how to install the Flash library in your project.","head":[["meta",{"name":"twitter:image","content":"/assets/banner-cards/flash-introduction-installation.png"}],["meta",{"name":"twitter:image:src","content":"https://docs.pixel-services.com/assets/banner-cards/flash-introduction-installation.png"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:height","content":"1280"}],["meta",{"name":"twitter:image:width","content":"669"}],["meta",{"name":"twitter:description","content":""}]]},"headers":[],"relativePath":"flash/introduction/installation.md","filePath":"flash/introduction/installation.md"}'),c={name:"flash/introduction/installation.md"},u=Object.assign(c,{setup(g){const i=l("");return p(async()=>{try{const e=(await(await fetch("https://maven.pixel-services.com/api/maven/details/releases/com/pixelservices/flash")).json()).files.filter(t=>t.type==="DIRECTORY").map(t=>t.name);i.value=e.sort().pop()}catch(a){console.error("Error fetching latest version:",a),i.value="Error fetching version"}}),(a,s)=>(E(),h("div",null,[s[1]||(s[1]=r(`

πŸ“² Installation ​

This page provides installation instructions for the latest version of the flash library from Pixel Services.

Installation ​

Maven (pom.xml) ​

  1. Add the repository :

    xml
    <repositories>
    +  <repository>
    +    <id>pixel-services</id>
    +    <name>Pixel Services</name>
    +    <url>https://maven.pixel-services.com/repository</url>
    +  </repository>
    +</repositories>
  2. And the dependency :

    xml
    <dependencies>
    +  <dependency>
    +    <groupId>com.pixelservices</groupId>
    +    <artifactId>flash</artifactId>
    +    <version>{{ latestVersion }}</version>
    +  </dependency>
    +</dependencies>

Gradle (build.gradle) ​

  1. Add the repository :

    groovy
    repositories {
    +    maven {
    +        url "https://maven.pixel-services.com/repository"
    +    }
    +}
  2. And the dependency :

    groovy
    dependencies {
    +    implementation 'com.pixelservices:flash:{{ latestVersion }}'
    +}
`,7)),n("div",null,[s[0]||(s[0]=k(" ⚑ Latest version: ")),n("a",d,[n("strong",null,o(i.value),1)])])]))}});export{m as __pageData,u as default}; diff --git a/assets/flash_introduction_installation.md.BEOKwAAp.lean.js b/assets/flash_introduction_installation.md.BEOKwAAp.lean.js new file mode 100644 index 0000000..2128b98 --- /dev/null +++ b/assets/flash_introduction_installation.md.BEOKwAAp.lean.js @@ -0,0 +1,19 @@ +import{p as l,v as p,c as h,a0 as r,j as n,a as k,t as o,o as E}from"./chunks/framework.p2VkXzrt.js";const d={href:"https://maven.pixel-services.com/#/releases/com/pixelservices/flash",style:{"text-decoration":"underline",color:"#007bff"}},m=JSON.parse('{"title":"πŸ“² Installation","description":"","frontmatter":{"banner_title":"Flash - Installation","banner_description":"A guide on how to install the Flash library in your project.","head":[["meta",{"name":"twitter:image","content":"/assets/banner-cards/flash-introduction-installation.png"}],["meta",{"name":"twitter:image:src","content":"https://docs.pixel-services.com/assets/banner-cards/flash-introduction-installation.png"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:height","content":"1280"}],["meta",{"name":"twitter:image:width","content":"669"}],["meta",{"name":"twitter:description","content":""}]]},"headers":[],"relativePath":"flash/introduction/installation.md","filePath":"flash/introduction/installation.md"}'),c={name:"flash/introduction/installation.md"},u=Object.assign(c,{setup(g){const i=l("");return p(async()=>{try{const e=(await(await fetch("https://maven.pixel-services.com/api/maven/details/releases/com/pixelservices/flash")).json()).files.filter(t=>t.type==="DIRECTORY").map(t=>t.name);i.value=e.sort().pop()}catch(a){console.error("Error fetching latest version:",a),i.value="Error fetching version"}}),(a,s)=>(E(),h("div",null,[s[1]||(s[1]=r(`

πŸ“² Installation ​

This page provides installation instructions for the latest version of the flash library from Pixel Services.

Installation ​

Maven (pom.xml) ​

  1. Add the repository :

    xml
    <repositories>
    +  <repository>
    +    <id>pixel-services</id>
    +    <name>Pixel Services</name>
    +    <url>https://maven.pixel-services.com/repository</url>
    +  </repository>
    +</repositories>
  2. And the dependency :

    xml
    <dependencies>
    +  <dependency>
    +    <groupId>com.pixelservices</groupId>
    +    <artifactId>flash</artifactId>
    +    <version>{{ latestVersion }}</version>
    +  </dependency>
    +</dependencies>

Gradle (build.gradle) ​

  1. Add the repository :

    groovy
    repositories {
    +    maven {
    +        url "https://maven.pixel-services.com/repository"
    +    }
    +}
  2. And the dependency :

    groovy
    dependencies {
    +    implementation 'com.pixelservices:flash:{{ latestVersion }}'
    +}
`,7)),n("div",null,[s[0]||(s[0]=k(" ⚑ Latest version: ")),n("a",d,[n("strong",null,o(i.value),1)])])]))}});export{m as __pageData,u as default}; diff --git a/flash/advanced/fullstack-development.html b/flash/advanced/fullstack-development.html new file mode 100644 index 0000000..1521280 --- /dev/null +++ b/flash/advanced/fullstack-development.html @@ -0,0 +1,60 @@ + + + + + + 🌐 Fullstack Development with Flash | Pixel Services Docs + + + + + + + + + + + + + + + + + + + + + +
Skip to content

🌐 Fullstack Development with Flash ​

Fullstack development involves building both the front-end (user interface) and back-end (server logic, database) of a web application. A fullstack developer is responsible for the entire application, ensuring a smooth connection between the two layers.

Popular fullstack frameworks include:

⚑ Fullstack Development with Flash ​

Developing and packaging a fullstack application can be complex, but Flash simplifies the process with built-in tools and quality-of-life features. With Flash, you can:

  • Serve both frontend and backend from a single application.
  • Bundle everything into a single JAR file for easy deployment.
  • Leverage HDIs for clean, modular and maintainable route logic.

Flash Fullstack Development

πŸš€ Serving Frontend with Flash ​

Flash’s dynamic file server enables seamless fullstack development by allowing you to serve frontend assets alongside backend logic. This is made possible by the RESOURCESTREAM source type, which serves files from the JAR’s resources folder.

βœ… Deployment Workflow ​

The recommended workflow for packaging a fullstack Flash application:

  1. Compile the frontend application (React, Vue, Angular, etc.).
  2. Place the compiled files inside the resources folder.
  3. Build the JAR file with both frontend and backend code.
  4. Deploy the JAR, serving both frontend and backend seamlessly.

This approach works for any frontend framework that compiles to static files, such as:

  • React (npm run build)
  • Vue.js (npm run build)
  • Angular (ng build --prod)

❔ Setting up a Fullstack Flash Application ​

To get started with fullstack development using Flash, you will first need to choose a frontend framework and set up the build process to compile the frontend assets. This guide will cover only some of the most popular javascript frontend build tools, but the process is similar for others.

πŸ› οΈ Setting up the Frontend Build Process ​

WARNING

When building a frontend application with a javascript framework that will be served from a subdirectory (e.g., /app), you need to specify the homepage in the package.json file. This ensures that the frontend assets are correctly loaded from the subdirectory. Using a subdirectory and forgetting to set the homepage will result in broken asset links and a potentially non-functional frontend.

json
{
+    "homepage": "/your-subdirectory"
+}
  • Vite :

    Click to expand
    1. Install Vite on your project if it's not already installed or scaffold a new Vite project (follow the official guide).
    2. Edit the vite.config.js or vite.config.ts build section to output the compiled files to the resources/frontend folder of your Flash project (see example below).
    typescript
        // ...
    +    import path from 'path'
    +    export default defineConfig({
    +        // ...
    +
    +        build: { 
    +            outDir: path.resolve(__dirname, '..path/to/your/src/main/resources/frontend'), 
    +            emptyOutDir: true
    +        }, 
    +    });
    1. The output of your Vite build will be placed in the resources/frontend folder of your Flash project, ready to be packaged into the JAR file.
  • Parcel

    Click to expand
    1. Install Parcel on your project if it's not already installed or scaffold a new Parcel project (follow the official guide).
    2. Edit the Parcel build command to output the compiled files to the resources/frontend folder of your Flash project (see example below).
      json
      {
      +    "scripts": {
      +        "build": "parcel build src/index.html --out-dir ../path/to/your/src/main/resources/frontend"
      +    }
      +}
    3. The output of your Parcel build will be placed in the resources/frontend folder of your Flash project, ready to be packaged into the JAR file.

πŸ›œ Serve the Frontend with Flash ​

Once you have compiled the frontend assets and placed them in the resources/frontend folder, you can serve them using Flash's dynamic file server. This server will serve the frontend assets from the JAR's resources folder.

INFO

NOTE: The following example is assuming you plan to serve the frontend from the root path (/). If you plan to serve the frontend from a subdirectory (e.g., /app), you will need to adjust the endpoint path accordingly by adding a trailing /*.

java
public class Example {
+    public static void main(String[] args) {
+        FlashServer server = new FlashServer(8080);
+
+        server.serveDynamic("/*", new DynamicFileServerConfiguration(
+            true,
+            "frontend", // points to the resources/frontend folder
+            "index.html",
+            SourceType.RESOURCESTREAM
+        ));
+        
+        server.start();
+    }
+}

πŸš€ Package and Deploy your app! ​

With the frontend and backend code in place, you can now build the JAR file and deploy it to your server. The JAR file will contain both the frontend and backend code, making it easy to deploy and run your fullstack application.

All you've left to do is run the jarfile on any machine that has Java installed, and your fullstack application will be up and running!

+ + + + \ No newline at end of file diff --git a/flash/advanced/handler-default-implementations.html b/flash/advanced/handler-default-implementations.html new file mode 100644 index 0000000..34d97de --- /dev/null +++ b/flash/advanced/handler-default-implementations.html @@ -0,0 +1,124 @@ + + + + + + ⚑ Handler Default Implementations (HDI) | Pixel Services Docs + + + + + + + + + + + + + + + + + + + + + +
Skip to content

⚑ 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 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;
+
+    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);
+            res.type("application/json");
+            return "{\"error\":\"Invalid API Key\"}";
+        }
+
+        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;
+    }
+}

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")
+public class GetDataHandler extends APIKeyProtectedHandler {
+    public GetDataHandler(Request req, Response res) {
+        super(req, res);
+    }
+
+    @Override
+    protected Object handleAuthorized() {
+        res.type("application/json");
+        return "{\"data\":\"Your API response here\"}";
+    }
+}

πŸ—οΈ Chaining HDIs for Modular Logic ​

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;
+
+    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, 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);
+    }
+
+    @Override
+    protected Object handleAuthenticated() {
+        user = getUserFromDatabase(authToken);
+        if (user == null) {
+            res.status(403);
+            res.type("application/json");
+            return "{\"error\":\"User not found\"}";
+        }
+        return handleWithUser();
+    }
+
+    protected abstract Object handleWithUser();
+}
  • 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(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() {
+        res.type("application/json");
+        return "{\"username\":\"" + username + "\"}";
+    }
+}
+ + + + \ No newline at end of file diff --git a/flash/core-concepts/handlers.html b/flash/core-concepts/handlers.html new file mode 100644 index 0000000..80a7695 --- /dev/null +++ b/flash/core-concepts/handlers.html @@ -0,0 +1,34 @@ + + + + + + πŸ“š Handlers | Pixel Services Docs + + + + + + + + + + + + + + + + + + + + + +
Skip to content

πŸ“š 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 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
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
    Will match exactly /hello

  • Parametrized Routing: /hello/:name
    Will match /hello/John, /hello/Alice, etc.

  • Dynamic Routing: /hello/*
    Will match /hello/../..

+ + + + \ No newline at end of file diff --git a/flash/core-concepts/request-handler.html b/flash/core-concepts/request-handler.html new file mode 100644 index 0000000..316b403 --- /dev/null +++ b/flash/core-concepts/request-handler.html @@ -0,0 +1,43 @@ + + + + + + βš™οΈ Request Handler | Pixel Services Docs + + + + + + + + + + + + + + + + + + + + + +
Skip to content

βš™οΈ 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
@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:

MethodParamsDescription
getRequestBody()noneReturns a JSONObject representation of the request body.
getSpecification()noneReturns an instance of HandlerSpecification containing all sorts of information about the handler.
expectedRequestParameter()String name, descriptionReturns an instance of ExpectedRequestParameter for the specified parameter name.
expectedBodyField()String name, descriptionReturns an instance of ExpectedBodyField for the specified field name.
expectedBodyFile()String name, descriptionReturns an instance of ExpectedBodyFile for the specified file name.

(More on the ExpectedRequestParameter, ExpectedBodyField, and ExpectedBodyFile classes in the next section).

+ + + + \ No newline at end of file diff --git a/flash/core-concepts/request-response.html b/flash/core-concepts/request-response.html new file mode 100644 index 0000000..e43d81e --- /dev/null +++ b/flash/core-concepts/request-response.html @@ -0,0 +1,98 @@ + + + + + + πŸ“₯ Request and Response | Pixel Services Docs + + + + + + + + + + + + + + + + + + + + + +
Skip to content

πŸ“₯ 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.

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.

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.

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
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
@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
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
@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
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
@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.

+ + + + \ No newline at end of file diff --git a/flash/core-concepts/server-router.html b/flash/core-concepts/server-router.html new file mode 100644 index 0000000..dfa9b2b --- /dev/null +++ b/flash/core-concepts/server-router.html @@ -0,0 +1,55 @@ + + + + + + πŸ›£οΈ Server Router | Pixel Services Docs + + + + + + + + + + + + + + + + + + + + + +
Skip to content

πŸ›£οΈ 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
// 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
// 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!.

+ + + + \ No newline at end of file diff --git a/flash/core-concepts/websockets.html b/flash/core-concepts/websockets.html new file mode 100644 index 0000000..20b4b56 --- /dev/null +++ b/flash/core-concepts/websockets.html @@ -0,0 +1,64 @@ + + + + + + 🌐 Websockets | Pixel Services Docs + + + + + + + + + + + + + + + + + + + + + +
Skip to content

🌐 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.

MethodParamsDescription
getChannel()noneReturns an instance of AsynchronousSocketChannel useful for retrieving info about the client .
getRequestInfo()noneReturns an instance of RequestInfo containing all sorts of information about the request (headers, method, path etc.) .
getPath()noneReturns the path to the websocket endpoint as a String.
getId()noneReturns 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()noneReturns the ByteBuffer for that session.
sendMessage()String messageSends 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()noneCloses the websocket session.

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.

+ + + + \ No newline at end of file diff --git a/flash/file-serving/dynamic-file-server.html b/flash/file-serving/dynamic-file-server.html new file mode 100644 index 0000000..0754576 --- /dev/null +++ b/flash/file-serving/dynamic-file-server.html @@ -0,0 +1,59 @@ + + + + + + πŸ“ Dynamic File Server | Pixel Services Docs + + + + + + + + + + + + + + + + + + + + + +
Skip to content

πŸ“ Dynamic File Server ​

Sometimes we need to serve files in a dynamic context, in this sense a static file server is limiting. Imagine we have a frontend application that is compiled down to a single index.html file, and we want to serve it with Flash alongside it's javascript and css bundles. The way these compiled applications work is that they rely heavily on client-side routing, so when the user navigates to a different page, the frontend application will try to fetch the corresponding file from the server. This is where a dynamic file server comes in handy, as no route is pre-registered.

WARNING

The dynamic file server relies heavily on the concept of dynamic handlers, which are handlers that will resolve for any subpath of the endpoint they are registered to (see Handler Types for more info).

Usage ​

To serve static files in Flash, you need to call the server.serveDynamic() method with the endpoint path and an instance of DynamicFileServerConfig. The configuration object is instanced like so :

java
DynamicFileServerConfiguration(
+    boolean enableFileWatcher,
+    String destinationPath,
+    String dynamicEntrypoint,
+    SourceType sourceType
+)
  • enableFileWatcher : If set to true, the server will watch for changes in the served files and reload them automatically.
  • destinationPath : The path to the directory containing the files to be served.
  • dynamicEntrypoint : The path to the file that will be served when the client navigates to the endpoint eg. index.html.
  • sourceType : The type of source to serve files from. It can be either FILESYSTEM or RESOURCESTREAM.

Registering the dynamic file server is as simple as calling the server.serveDynamic() method with the desired path and configuration object:

java
public class Example {
+    public static void main(String[] args) {
+        FlashServer server = new FlashServer(8080);
+
+        server.serveDynamic("/*", new DynamicFileServerConfiguration(
+            true,
+            "path/to/my/files",
+            "index.html",
+            SourceType.FILESYSTEM
+        ));
+    }
+}

Now you can access the files (or frontend) in the specified directory by navigating to http://localhost:8080/<file-name>.

Similarly, you can serve the same content 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 DynamicFileServerConfiguration(
+            true,
+            "path/to/my/files",
+            "index.html",
+            SourceType.RESOURCESTREAM
+        ));
+    }
+}
+ + + + \ No newline at end of file diff --git a/flash/file-serving/static-file-server.html b/flash/file-serving/static-file-server.html new file mode 100644 index 0000000..993e1ab --- /dev/null +++ b/flash/file-serving/static-file-server.html @@ -0,0 +1,59 @@ + + + + + + πŸ“ Static File Server | Pixel Services Docs + + + + + + + + + + + + + + + + + + + + + +
Skip to content

πŸ“ 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
+        ));
+    }
+}
+ + + + \ No newline at end of file diff --git a/flash/index.html b/flash/index.html index e2c4e89..c0457f1 100644 --- a/flash/index.html +++ b/flash/index.html @@ -25,8 +25,8 @@ -
Skip to content

⚑ Flash ​

Flash is a simple, modern and fast expressive web framework written in Java. The project is maintained by Pixel Services and the open-source community.

Our Team ​

Say hello to the Pixel Services team !

Relism

Relism

Developer

Sieadev

Sieadev

Developer

- +
Skip to content

⚑ Flash ​

Flash is a simple, modern and fast expressive web framework written in Java. The project is maintained by Pixel Services and the open-source community.

Our Team ​

Say hello to the Pixel Services team !

Relism

Relism

Developer

Sieadev

Sieadev

Developer

+ \ No newline at end of file diff --git a/flash/introduction/installation.html b/flash/introduction/installation.html new file mode 100644 index 0000000..6dc5ec7 --- /dev/null +++ b/flash/introduction/installation.html @@ -0,0 +1,50 @@ + + + + + + πŸ“² Installation | Pixel Services Docs + + + + + + + + + + + + + + + + + + + + + +
Skip to content

πŸ“² Installation ​

This page provides installation instructions for the latest version of the flash library from Pixel Services.

Installation ​

Maven (pom.xml) ​

  1. Add the repository :

    xml
    <repositories>
    +  <repository>
    +    <id>pixel-services</id>
    +    <name>Pixel Services</name>
    +    <url>https://maven.pixel-services.com/repository</url>
    +  </repository>
    +</repositories>
  2. And the dependency :

    xml
    <dependencies>
    +  <dependency>
    +    <groupId>com.pixelservices</groupId>
    +    <artifactId>flash</artifactId>
    +    <version>{{ latestVersion }}</version>
    +  </dependency>
    +</dependencies>

Gradle (build.gradle) ​

  1. Add the repository :

    groovy
    repositories {
    +    maven {
    +        url "https://maven.pixel-services.com/repository"
    +    }
    +}
  2. And the dependency :

    groovy
    dependencies {
    +    implementation 'com.pixelservices:flash:{{ latestVersion }}'
    +}
⚑ Latest version:
+ + + + \ No newline at end of file diff --git a/hashmap.json b/hashmap.json index 8ac41fc..1929c03 100644 --- a/hashmap.json +++ b/hashmap.json @@ -1 +1 @@ -{"flash_handler-default-implementations.md":"BEJ-A1cJ","flash_index.md":"E5zPbZtg","flash_installation.md":"6Deie__C","flash_request-handler.md":"D-YKsT8g","flash_request-response.md":"AHTHQW77","flash_server-router.md":"bhWEamQT","flash_static-file-server.md":"C3QKsWSO","flash_websockets.md":"CYiVOCzA","index.md":"CgmTRI0Q","mobot_index.md":"Be0Zoetq","serverlibraries_index.md":"CeIqSPIs"} +{"flash_advanced_fullstack-development.md":"B124NtWL","flash_advanced_handler-default-implementations.md":"BSoFk9xD","flash_core-concepts_handlers.md":"ChVWECmb","flash_core-concepts_request-handler.md":"fjZWLpOw","flash_core-concepts_request-response.md":"3C-3EYgj","flash_core-concepts_server-router.md":"BtmNSZRo","flash_core-concepts_websockets.md":"ByGVX96c","flash_file-serving_dynamic-file-server.md":"BxDWqJ2P","flash_file-serving_static-file-server.md":"BvN0FZB2","flash_index.md":"E5zPbZtg","flash_introduction_installation.md":"BEOKwAAp","index.md":"CgmTRI0Q","mobot_index.md":"Be0Zoetq","serverlibraries_index.md":"CeIqSPIs"} diff --git a/index.html b/index.html index 52b6c8f..ffec7ca 100644 --- a/index.html +++ b/index.html @@ -26,7 +26,7 @@ - + \ No newline at end of file diff --git a/mobot/index.html b/mobot/index.html index e4f2456..f6f2b90 100644 --- a/mobot/index.html +++ b/mobot/index.html @@ -26,7 +26,7 @@
Skip to content
- + \ No newline at end of file diff --git a/serverlibraries/index.html b/serverlibraries/index.html index 1156c19..44d9d2d 100644 --- a/serverlibraries/index.html +++ b/serverlibraries/index.html @@ -26,7 +26,7 @@
Skip to content
- + \ No newline at end of file