|
| 1 | +<link rel="stylesheet" href="./docs.css"> |
| 2 | +<a href="https://modula.dev/weaver"><header> |
| 3 | + <img class="logo" src="https://modula.dev/weaver.png"/> |
| 4 | + <h1>Weaver</h1> |
| 5 | + 0.3.0 Documentation <br> |
| 6 | + © Modula, 2025 |
| 7 | +</header></a> |
| 8 | + |
| 9 | +# Table Of Contents |
| 10 | + |
| 11 | +1. [Weaver in cPanel](#1) |
| 12 | +2. [A simple Application](#2) |
| 13 | +3. [Processing the Request](#3) |
| 14 | +4. [Application Routing](#4) |
| 15 | +5. [User IDs](#5) |
| 16 | +6. [Honeypotting Bad Requests](#6) |
| 17 | + |
| 18 | +<h1 class="pagebreak" id="1">Weaver in cPanel</h1> |
| 19 | + |
| 20 | +We recommend using `git` to manage your projects, |
| 21 | +and the instructions herein will include some helpful |
| 22 | +instructions on building your project so that it's as easy |
| 23 | +as possible to build and update your website using the |
| 24 | +git client. |
| 25 | + |
| 26 | +Make sure that the `Git™ Version Control` is enabled in your cPanel. |
| 27 | +Initalize your server-side app directory by clicking `Create`, |
| 28 | +make sure `Clone a Repository` is toggled off, |
| 29 | +then set the `Repository Path` to wherever you want |
| 30 | +the source files to physically exist on the server. |
| 31 | +After doing this, click the `Manage` button next to the |
| 32 | +newly created Git Repository and copy the `Clone URL`. |
| 33 | +On your local development machine, |
| 34 | +you'll want to `git clone` the url you just copied |
| 35 | +to link your local development directory with the service running on the server. |
| 36 | + |
| 37 | +After making sure the `Setup Node.js App` tool is also enabled in your cPanel, |
| 38 | +Click on the `Create Application` button and |
| 39 | +select the latest version of Node (currently tested against `22.8.0`). |
| 40 | +Set the Application Mode to `Production`, |
| 41 | +point `Application Root` to the path |
| 42 | +you set the server-side app directory earlier, |
| 43 | +and name your `Application Startup File`. |
| 44 | + |
| 45 | +Once you've setup both the Git Repo and the Node app, |
| 46 | +you'll want to run `git pull` on your local repository, |
| 47 | +which should give you a default `app.js` (or whatever you named it) |
| 48 | +you chose in the last step. |
| 49 | +Download the `weaver.js` source file from |
| 50 | +[modula.dev/weaver.js](https://modula.dev/weaver.js) |
| 51 | +in the local directory, and then include it like |
| 52 | +``` |
| 53 | +const weaver = require("./weaver.js"); |
| 54 | +``` |
| 55 | + |
| 56 | +You'll need to define a function to handle the routing |
| 57 | +of your application, and then tell weaver to use it |
| 58 | +to handle incoming requests by passing it into the |
| 59 | +`weaver.router` function. |
| 60 | +And after all the other application logic, you'll call `weaver.listen`. |
| 61 | +Do not pass the `port` argument into `weaver.listen` when using it on cPanel. |
| 62 | + |
| 63 | +<h1 class="pagebreak" id="2">A simple application</h1> |
| 64 | + |
| 65 | +First, make sure to download and include the Weaver source like |
| 66 | +``` |
| 67 | +const weaver = require("./weaver.js") |
| 68 | +``` |
| 69 | + |
| 70 | +Weaver applications need to define a router – |
| 71 | +a function which takes in a `request` object, and a `handler` function, |
| 72 | +and will respond to the request using the `weaver.respond` function. |
| 73 | + |
| 74 | +The `weaver.respond` function expects to be given an object that contains |
| 75 | +the HTTP`code` we're sending back, the `mime` type of the data we're going to return, |
| 76 | +and the `body` containing the content of our response. |
| 77 | +Weaver also gives us a few helper functions like `weaver.serveFile` |
| 78 | +and `weaver.serveRedirect` which can create these objects for us, |
| 79 | +but for now we're going to make the response object ourselves. |
| 80 | + |
| 81 | +Let's define a `main` function that we will use as our router. |
| 82 | +We're just going to do a simple "Hello World" for now: |
| 83 | +``` |
| 84 | +function main(request, handler) { |
| 85 | + return { |
| 86 | + code: 200, |
| 87 | + mime: "text/raw", |
| 88 | + body: "Hello, world!\n" |
| 89 | + } |
| 90 | +} |
| 91 | +``` |
| 92 | +To tell Weaver we want this to be our router function, |
| 93 | +we just pass like |
| 94 | +``` |
| 95 | +weaver.router(main) |
| 96 | +``` |
| 97 | +and then wrap up by calling the listener like |
| 98 | +``` |
| 99 | +weaver.listen() |
| 100 | +``` |
| 101 | + |
| 102 | +<h1 class="pagebreak" id="3">Processing the Request</h1> |
| 103 | + |
| 104 | +Weaver also gives us some helper functions to processing incoming requests. |
| 105 | + |
| 106 | +`weaver.hashId` will take in the `request` and turn the headers into a unique |
| 107 | +integer for each client/user-agent connecting to your service. |
| 108 | +In cases where there is not enough information in the headers to do so, |
| 109 | +this function will return `undefined`. |
| 110 | + |
| 111 | +`weaver.routeMethod` will take in the `request` and return the `method` |
| 112 | +the client is using, such as `GET`. |
| 113 | + |
| 114 | +`weaver.routeUri` will take in the `request` and return just the `url` part |
| 115 | +with any queries stripped out. |
| 116 | + |
| 117 | +`weaver.routeQueryObject` will take in the `request` and return a dictionary/object |
| 118 | +of the key-value pairs of the query. |
| 119 | + |
| 120 | +`weaver.routeMatch` will match the `request` url against a list of |
| 121 | +string `regexes`, and tell us which was the first that matched |
| 122 | +given a set of `flags`. Generally, we recommend just setting |
| 123 | +flags to `"i"` (case-insensitive matches). |
| 124 | +If you need help making regexes, the tool |
| 125 | +[regex101.com](https://regex101.com) is incredible. |
| 126 | + |
| 127 | +<h1 class="pagebreak" id="4">Application Routing</h1> |
| 128 | + |
| 129 | +For apps that are a little more complex than a Hello World, |
| 130 | +you're going to want to define your router so that it |
| 131 | +processes incoming requests, and then passes them off |
| 132 | +to appropriate functions for each route. |
| 133 | + |
| 134 | +Let's make a simple router that gives any requests |
| 135 | +for the root to function `main` which returns an `index.html`, |
| 136 | +any requests for the subdirectory `bar` to the function `foo` which will redirect, |
| 137 | +and everything else to the function `bad`. |
| 138 | + |
| 139 | +Our `app.js` now will look something like |
| 140 | +``` |
| 141 | +const weaver = require("./weaver.js"); |
| 142 | +function main() { return weaver.serveFile("text/html", "index.html"); } |
| 143 | +function foo() { return weaver.serveRedirect("/"); } |
| 144 | +function bad() { return { |
| 145 | + code: 404, |
| 146 | + mime: "text/raw", |
| 147 | + body: "HTTP 404: Route does not exist" } |
| 148 | +} |
| 149 | +const foo_route = "^(https?:\/\/)?[^\/]*\/bar(\/[^\/]*)*$"; |
| 150 | +const main_route = "^(https?:\/\/)?[^\/]*(\/)?$"; |
| 151 | +function router(request, handler){ |
| 152 | + const path = weaver.routeUri(request); |
| 153 | + const routes = [foo_route, main_route]; |
| 154 | + const use = weaver.routeMatch(request, routes); |
| 155 | + var response; |
| 156 | + switch(use) { |
| 157 | + case 0: response = foo(); break; |
| 158 | + case 1: response = main(); break; |
| 159 | + default: response = bad(); break; |
| 160 | + } |
| 161 | + weaver.respond(handler, response); |
| 162 | +} |
| 163 | +weaver.router(router) |
| 164 | +weaver.listen() |
| 165 | +``` |
| 166 | +<h1 class="pagebreak" id="5">User IDs</h1> |
| 167 | + |
| 168 | +It's very likely you're not just serving static content, |
| 169 | +but instead that you want to provide some service to remote |
| 170 | +users over the internet. To do that, you usually need to keep track |
| 171 | +of who made a given request. |
| 172 | + |
| 173 | +We personally believe that fingerprinting users is |
| 174 | +invasive and impolite, and we don't like asking users |
| 175 | +to keep cookies or other identifying marks on their system, |
| 176 | +so instead Weaver provides a very simple `hashID` function that |
| 177 | +gives users a unique number ID based on their self-reported |
| 178 | +`user-agent` and connecting IP address, |
| 179 | +meaning they're temporary and non-identifying but still |
| 180 | +usable for things like allowing remote clients to log into a service. |
| 181 | + |
| 182 | +To get this unique ID, |
| 183 | +all we have to do is pass the `request` into `hashID` somewhere |
| 184 | +in our app's router like so |
| 185 | +``` |
| 186 | +function router(request, handler){ |
| 187 | + const client = weaver.hashId(request); |
| 188 | + // router logic |
| 189 | +} |
| 190 | +``` |
| 191 | + |
| 192 | +Since the `app.js` is run independently between connections, |
| 193 | +you'll likely want to store what IDs correspond to what user |
| 194 | +somewhere on disk, and then have those sessions expire |
| 195 | +either on a timer or when that user logs in another session. |
| 196 | + |
| 197 | +<h1 class="pagebreak" id="6">Honeypotting Bad Requests</h1> |
| 198 | + |
| 199 | +In our previous example, we assumed all incoming requests |
| 200 | +were valid and genuine, but any internet-connected service |
| 201 | +is inevitably going to receive malicious traffic. |
| 202 | +While Weaver does not provide any anti-malware or |
| 203 | +security services directly, it does provide a very |
| 204 | +minimal anti-spam feature using honeypots. |
| 205 | + |
| 206 | +We can modify our previous router function by adding in a |
| 207 | +quick filter and check like so |
| 208 | +like so |
| 209 | +``` |
| 210 | +function router(request, handler){ |
| 211 | + const path = weaver.routeUri(request); |
| 212 | + const routes = [foo_route, main_route]; |
| 213 | + const filter = weaver.routeHoneypot(request); |
| 214 | + if (filter != undefined) { response = filter; } |
| 215 | + else { |
| 216 | + const use = weaver.routeMatch(request, routes); |
| 217 | + var response; |
| 218 | + switch(use) { |
| 219 | + case 0: response = foo(); break; |
| 220 | + case 1: response = main(); break; |
| 221 | + default: response = bad(); break; |
| 222 | + } |
| 223 | + } |
| 224 | + weaver.respond(handler, response); |
| 225 | +} |
| 226 | +``` |
| 227 | + |
| 228 | +The `routeHoneypot` function either returns |
| 229 | +the response for a `404: Resource Not Found` |
| 230 | +if the user is attempting to access something in our |
| 231 | +honeypot, or it returns `undefined` in which case we |
| 232 | +carry out with our normal routing logic. |
| 233 | + |
| 234 | +We could go a step farther and add that user-agent |
| 235 | +to a blacklist and serve all incoming requests from them |
| 236 | +going forward with that same 404 error, if we wished, |
| 237 | +but we'll leave that as an excercise for you. |
| 238 | + |
| 239 | +You can optionally add additional regexes to the |
| 240 | +honeyput using the `pushHoneypot` function. |
0 commit comments