├── README.md ├── package.json └── src └── index.js /README.md: -------------------------------------------------------------------------------- 1 | # What is KittenRouter 2 | KittenRouter is a routing script for [Cloudflare Workers](https://www.cloudflare.com/products/cloudflare-workers/) that attempts to connect to a list of specified servers and redirect the request to whichever server that is currently 'alive' at that point of time. It is extremely useful when you have servers that may go down or are unavailable to process the request and KittenRouter can automatically attempt to redirect the request to the next configured URL for processing. 3 | 4 | At the same time, it can be configured to log down information to your ElasticSearch server for analytical purposes. Some of the information logged are the status of the servers, country of the request and etc. For the full details, see the `index.js` file. 5 | 6 | # How to use KittenRouter 7 | Ultimately, KittenRouter is used together with Cloudflare workers. There are two ways in which you can use KittenRouter on your Cloudflare worker script, 8 | 9 | 1. Using NPM modules 10 | 2. Adding KittenRouter manually 11 | 12 | 13 | ### 1) Using NPM modules 14 | If you are comfortable in building projects in NodeJS and wanted to deploy it serverlessly on Cloudflare, you can do that as well. 15 | 16 | You can use [NPM modules in Cloudflare](https://developers.cloudflare.com/workers/writing-workers/using-npm-modules/), like how we did for Inboxkitten API component, just that you will need webpack to help compact your scripts into a single `.js` file and this single `.js` file will be used in your Cloudflare worker. 17 | 18 | Similarly, if you want to use KittenRouter on your Cloudflare script, you can just follow the 7 steps below to achieve it. 19 | 20 | #### Step 1: Installing the necessary packages 21 | KittenRouter is published in npm repository and you can install it as an NPM module. 22 | ```bash 23 | # Change to your project directory 24 | $ cd 25 | # Install kittenrouter 26 | $ npm install --save kittenrouter 27 | # Install webpack, we will need it to compact all your files into a single js file 28 | $ npm install --save webpack 29 | ``` 30 | 31 | #### Step 2: Writing your code 32 | After installing KittenRouter, you can simply create a variable and use it immediately. You can store this script as your entrypoint script such as `index.js`. 33 | 34 | ```javascript 35 | const KittenRouter = require("kittenrouter"); 36 | let config = { 37 | // some configuration that you have for your KittenRouter 38 | }; 39 | let kittenrouter = new KittenRouter(confg); 40 | 41 | // Usual Cloudflare worker script 42 | addEventListener('fetch', event => { 43 | event.respondWith(handleFetchEvent(event)) 44 | }) 45 | 46 | // the async function to handle the request 47 | async function handleFetchEvent(event) { 48 | // Depends on your logic of how you want to handle the request 49 | // E.g. only GET requests will need to go through KittenRouter 50 | let req = event.request; 51 | if (req.method === "GET") { 52 | return kittenrouter.handleFetchEvent(event); 53 | } 54 | 55 | // Default behavior 56 | return await fetch(event); 57 | } 58 | ``` 59 | 60 | #### Step 3: Setup NPM run commands 61 | At this point, we can create a run command so that we can run it easily. In your `package.json` file, add in 62 | ```javascript 63 | { 64 | // other settings... 65 | "scripts": { 66 | // other settings... 67 | "build-cloudflare": "webpack --mode production index.js" 68 | // other settings... 69 | }, 70 | // other settings... 71 | } 72 | ``` 73 | 74 | #### Step 4: Configure target environment in webpack 75 | One thing to note in webpack is that we can specify the environment the Javascript code will be run in. `webworker` is the closest environment to Cloudflare Workers environment. So in your `webpack.config.js`, configure the target to point to `webworker`. 76 | 77 | ```javascript 78 | module.exports = { 79 | target: 'webworker' 80 | }; 81 | ``` 82 | 83 | 84 | #### Step 5 (Optional): Disable minification for your webpack 85 | In your `webpack.config.js`, add in 86 | ```javascript 87 | module.exports = { 88 | target: 'webworker', 89 | optimization: { 90 | // We no not want to minimize our code. 91 | minimize: false 92 | } 93 | } 94 | ``` 95 | 96 | #### Step 6: Run NPM command 97 | ```bash 98 | $ npm run build-cloudflare 99 | ``` 100 | 101 | #### Step 7: Deploy to Cloudflare manually 102 | `Step 6` should create a `main.js` inside the `dist` folder, you can then copy the entire `main.js` and paste into your Cloudflare Worker's Editor of your domain. 103 | 104 | 105 | ### 2) Adding KittenRouter manually 106 | 107 | #### Step 1: Your own Cloudflare worker script 108 | Given that you have a Cloudflare worker script such as 109 | ```javascript 110 | // Usual Cloudflare worker script 111 | addEventListener('fetch', event => { 112 | event.respondWith(handleFetchEvent(event)) 113 | }) 114 | 115 | // the async function to handle the request 116 | async function handleFetchEvent(event) { 117 | // Default behavior 118 | return await fetch(event); 119 | } 120 | ``` 121 | 122 | #### Step 2: Add in the entire KittenRouter script 123 | You can then add the entire `KittenRouter` class, which is basically the `index.js` file in this github project into your script 124 | ```javascript 125 | // methods of KittenRouter ... 126 | // ... 127 | class KittenRouter { 128 | // ... 129 | } 130 | 131 | // Usual Cloudflare worker script 132 | addEventListener('fetch', event => { 133 | event.respondWith(handleFetchEvent(event)) 134 | }) 135 | // ... 136 | ``` 137 | 138 | #### Step 3: Add in the configuration 139 | ```javascript 140 | let config = { 141 | // some configuration that you have for your KittenRouter 142 | } 143 | 144 | // methods of KittenRouter ... 145 | // ... 146 | class KittenRouter { 147 | // ... 148 | } 149 | 150 | // Usual Cloudflare worker script 151 | addEventListener('fetch', event => { 152 | event.respondWith(handleFetchEvent(event)) 153 | }) 154 | // ... 155 | ``` 156 | 157 | #### Step 4: Use and deploy 158 | Declare the KittenRouter object and use it in your request process function. You can then deploy this script to your Cloudflare Worker. 159 | ```javascript 160 | 161 | let config = { 162 | // some configuration you have for your KittenRouter 163 | } 164 | // methods of KittenRouter ... 165 | // ... 166 | class KittenRouter { 167 | // ... 168 | } 169 | 170 | // Usual Cloudflare worker script 171 | addEventListener('fetch', event => { 172 | event.respondWith(handleFetchEvent(event)) 173 | }) 174 | 175 | // Initialize a new KittenRouter object 176 | let kittenrouter = new KittenRouter(confg); 177 | 178 | // the async function to handle the request 179 | async function handleFetchEvent(event) { 180 | // Depends on your logic of how you want to handle the request 181 | // E.g. only GET requests will need to go through KittenRouter 182 | let req = event.request; 183 | if (req.method === "GET") { 184 | return kittenrouter.handleFetchEvent(event); 185 | } 186 | 187 | // Default behavior 188 | return await fetch(event); 189 | } 190 | ``` 191 | 192 | # How to configure KittenRouter 193 | KittenRouter is initialized with a configuration map that contains the routes and log server settings attributes. 194 | 195 | ### Configuration Map 196 | | Attribute | Description | 197 | |-----------------------|-------------------------------------------------------------------------------------------| 198 | | route | An array of servers' url for the KittenRouter to connect to. KittenRouter will attempt to access the urls in the order of the array that was set. | 199 | | log | A map that contains the settings for your ElasticSearch server | 200 | | disableOriginFallback | Set to true to disable fallback to origin host when all routes fails | 201 | 202 | A sample of the configuration map looks like this 203 | ```javascript 204 | // 205 | // Routing and logging options 206 | // 207 | module.exports = { 208 | 209 | // logging endpoint to use 210 | log : [ 211 | { 212 | // Currently only elasticsearch is supported, scoped here for future alternatives 213 | // One possible option is google analytics endpoint 214 | type : "elasticsearch", 215 | 216 | // 217 | // Elasticsearch index endpoint 218 | // 219 | url : "https://", 220 | 221 | // 222 | // Authorization header (if needed) 223 | // 224 | basicAuthToken : "username:password", 225 | 226 | // 227 | // Index prefix for storing data, this is before the "YYYY.MM" is attached 228 | // 229 | indexPrefix : "test-data-", 230 | 231 | // Enable logging of the full ipv4/6 232 | // 233 | // Else it mask (by default) the last digit of IPv4 address 234 | // or the "network" routing for IPv6 235 | // see : https://www.haproxy.com/blog/ip-masking-in-haproxy/ 236 | logTrueIP : false, 237 | 238 | // @TODO support 239 | // Additional cookies to log 240 | // 241 | // Be careful not to log "sensitive" cookies, that can compromise security 242 | // typically this would be seesion keys. 243 | // cookies : ["__cfduid", "_ga", "_gid", "account_id"] 244 | } 245 | ], 246 | 247 | // Routing rules to evaluate, starting from 0 index 248 | // these routes will always be processed in sequence 249 | route : [ 250 | // Lets load all requests to commonshost first 251 | "commonshost.inboxkitten.com" 252 | 253 | // If it fails, we fallback to firebase 254 | //"firebase.inboxkitten.com" 255 | ], 256 | 257 | // Set to true to disable fallback to origin host 258 | // when all routes fails 259 | disableOriginFallback : false, 260 | } 261 | ``` 262 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kittenrouter", 3 | "version": "1.0.3", 4 | "description": "Serves as a probe to a list of configured URLs to determine if the URL is alive and retry until a 'living' URL is found or list is exhausted and fallback to original request URL", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/uilicious/kittenrouter.git" 12 | }, 13 | "dependencies": {}, 14 | "keywords": [ 15 | "cloudflare", 16 | "serverless", 17 | "router" 18 | ], 19 | "author": "Uilicious", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/uilicious/kittenrouter/issues" 23 | }, 24 | "homepage": "https://github.com/uilicious/kittenrouter#readme" 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * KittenRouter is a utility class used to 3 | * 4 | * - log request, for monitoring purposes (with ip masking for GDPR) 5 | * - reroute fetch requests an alternative "origin" endpoint 6 | * - automatically failover on request failure / timeout 7 | * - logging of failed requests 8 | * - fallback to default cloudflare "origin" endpoint 9 | * 10 | * And as the name implies, is a sub project for InboxKitten. 11 | */ 12 | 13 | /** 14 | * Useful various refrence documentation, on how this whole class should be implemented 15 | * 16 | * Cloudflare refrence documentation 17 | * - https://blog.cloudflare.com/logs-from-the-edge/ 18 | * - https://developers.cloudflare.com/workers/reference/cache-api/ 19 | * - https://blog.cloudflare.com/introducing-the-workers-cache-api-giving-you-control-over-how-your-content-is-cached/ 20 | * - https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-Cloudflare-handle-HTTP-Request-headers- 21 | * - https://support.cloudflare.com/hc/en-us/articles/202494830-Pseudo-IPv4-Supporting-IPv6-addresses-in-legacy-IPv4-applications 22 | * 23 | * Webworker documentation (for fetch API) 24 | * - https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch 25 | * - https://developer.mozilla.org/en-US/docs/Web/API/Request 26 | * - https://developer.mozilla.org/en-US/docs/Web/API/Response 27 | */ 28 | 29 | //--------------------------------------------------------------------------------------------- 30 | // 31 | // The following is an example config object, used to setup KittenRouter 32 | // and kinda serve as a semi-functional spec of how KittenRouter is expected to work. 33 | // 34 | //--------------------------------------------------------------------------------------------- 35 | 36 | /* 37 | const exampleConfig = { 38 | 39 | // logging endpoint to use 40 | log : [ 41 | { 42 | // Currently only elasticsearch is supported, scoped here for future alternatives 43 | // One possible option is google analytics endpoint 44 | type : "elasticsearch", 45 | 46 | // 47 | // Elasticsearch index endpoint 48 | // 49 | url : "https://elasticsearch-server.secret-domain.com/", 50 | 51 | // 52 | // Authorization header (if needed) 53 | // 54 | authUser : "user", 55 | authPass : "pass", 56 | 57 | // 58 | // Index prefix for storing data, this is before the "YYYY.MM" is attached 59 | // 60 | indexPrefix : "test-data-", 61 | 62 | // Enable logging of the full ipv4/6 63 | // 64 | // Else it mask (by default) the last digit of IPv4 address 65 | // or the "network" routing for IPv6 66 | // see : https://www.haproxy.com/blog/ip-masking-in-haproxy/ 67 | logTrueIP : false, 68 | 69 | // @TODO support 70 | // Additional cookies to log 71 | // 72 | // Be careful not to log "sensitive" cookies, that can compromise security 73 | // typically this would be seesion keys. 74 | // cookies : ["__cfduid", "_ga", "_gid", "account_id"] 75 | } 76 | ], 77 | 78 | // Routing rules to evaluate, starting from 0 index 79 | // these routes will always be processed in sequence 80 | route : [ 81 | 82 | // Lets load all requests to commonshost first 83 | "commonshost.inboxkitten.com", 84 | 85 | // If it fails, we fallback to firebase 86 | "firebase.inboxkitten.com" 87 | 88 | // // Object based route definitions 89 | // // THE FOLLOWING BELOW IS NOT SUPPORTED AS OF NOW!!! 90 | // //----------------------------------------------------------------- 91 | // { 92 | // // "" host routing will match all 93 | // reqHost : [""], 94 | // // Routing prefix to check for, note that "" will match all 95 | // reqPrefix : [""], 96 | // 97 | // // Origin servers to route to 98 | // host : "host-endpoint-c", 99 | // port : 443, 100 | // protocol : "https", 101 | // // Matching by country 102 | // // Country codes : https://support.cloudflare.com/hc/en-us/articles/205072537-What-are-the-two-letter-country-codes-for-the-Access-Rules- 103 | // country : [ 104 | // "SG" 105 | // ], 106 | // // Matching by region 107 | // region : [ 108 | // "Europe" 109 | // ], 110 | // // Timeout to abort request on 111 | // timeout : 2000, 112 | // 113 | // // Fetching sub options (like cache overwrite) 114 | // // Cloudflare fetch config https://developers.cloudflare.com/workers/reference/cloudflare-features/ 115 | // fetchConfig : { cf: { cacheEverything: true } } 116 | 117 | // // @TODO (consider support for nested object based origin decleration?) 118 | // // Might be useful for some region based routing or maybe crazier? 119 | // // Racing origin requests and terminating the "slower copy" 120 | // //------------------------------------------------------------------------- 121 | // } 122 | ], 123 | 124 | // Set to true to disable fallback to origin host 125 | // when all routes fails 126 | disableOriginFallback : false, 127 | 128 | // Support default timeout to process a request in milliseconds 129 | defaultRouteTimeout : 2000, // 2,000 ms = 2 s 130 | 131 | // Support for cloudflare caching 132 | disableCloudflarePreRouteCache : false, 133 | 134 | // @TODO crazier caching options to consider 135 | // - KeyValue caching (probably pointless, cost wise) 136 | // - In memory caching (with a limit of 500 objects + 10 kb per object?) 137 | // See : https://developers.cloudflare.com/workers/writing-workers/storing-data/ 138 | } 139 | */ 140 | 141 | //--------------------------------------------------------------------------------------------- 142 | // 143 | // Logging internal logic 144 | // 145 | //--------------------------------------------------------------------------------------------- 146 | 147 | // Generate a random uuid to identify the instance 148 | let workerID = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); 149 | let reqCount = 0; 150 | 151 | // Simple regex to validate for an ipv4 152 | const ipv4_simpleRegex = /^[0-9a-z]{1,3}\.[0-9a-z]{1,3}\.[0-9a-z]{1,3}\.[0-9a-z]{1,3}$/i; 153 | 154 | /** 155 | * Extract from a request, various possible ip properties (in cludflare) 156 | * for a valid ipv4 address 157 | * 158 | * @param {Request} request object to extract from 159 | * @param {Boolean} logTrueIP (defaul false) used to log unmasked ip if true 160 | * 161 | * @return {String} ipv4 string if found, else a blank string of "" 162 | */ 163 | function getIPV4(request, logTrueIP = false) { 164 | let headers = request.headers; 165 | let ip = headers.get('cf-pseudo-ipv4') || headers.get('cf-connecting-ipv4') || headers.get('cf-connecting-ip') || ''; 166 | ip = ip.trim(); 167 | 168 | // If ipv4 validation failed, return blank 169 | if(ip === '' || !ipv4_simpleRegex.test(ip)) { 170 | return ""; 171 | } 172 | 173 | // Assume that its a valid ipv4 174 | // return immediately if no ipmasking is done 175 | if(logTrueIP == false) { 176 | return ip; 177 | } 178 | 179 | // Time to perform ip masking 180 | let ip_split = ip.split("."); 181 | ip_split[3] = "xxx"; 182 | return ip_split.join("."); 183 | } 184 | 185 | /** 186 | * Extract from a request, various possible ip properties (in cludflare) 187 | * for a valid ipv6 address 188 | * 189 | * @param {Request} request object to extract from 190 | * @param {Boolean} logTrueIP (defaul false) used to log unmasked ip if true 191 | * 192 | * @return {String} ipv6 string if found, else a blank string of "" 193 | */ 194 | function getIPV6(request, logTrueIP = false) { 195 | let headers = request.headers; 196 | let ip = headers.get('cf-connecting-ipv6') || headers.get('cf-connecting-ip') || ''; 197 | ip = ip.trim(); 198 | 199 | // If ipv4 validation passes, return blank 200 | if(ip === '' || ipv4_simpleRegex.test(ip)) { 201 | return ""; 202 | } 203 | 204 | // Assume that its an ipv6 205 | // return immediately if no ipmasking is done 206 | if(logTrueIP == false) { 207 | return ip; 208 | } 209 | 210 | // Time to perform ip masking 211 | let ip_split = ip.split(":"); 212 | for(let i=2; i 0 ) { 282 | // let cookieJar = request.headers.get("Cookie") 283 | // // @TODO : iterate cookies and log relevent keys 284 | // } 285 | 286 | // Lets prepare the headers 287 | let logHeaders = { 288 | 'Content-Type': 'application/json', 289 | }; 290 | 291 | // Lets handle authentication 292 | if( logConfig.basicAuthToken && logConfig.basicAuthToken.length > 0 ) { 293 | logHeaders["Authorization"] = "Basic "+btoa(logConfig.basicAuthToken); 294 | } 295 | 296 | // The elasticsearch POST request to perform for logging 297 | let logResult = await fetch( 298 | fullLoggingURL, { 299 | method: 'POST', 300 | body: JSON.stringify(data), 301 | headers: new Headers(logHeaders) 302 | }) 303 | 304 | // Log the log? 305 | if( logConfig.consoleDebug ) { 306 | console.log("KittenRouter logging response", logResult); 307 | } 308 | 309 | // Return the result 310 | return logResult; 311 | } 312 | 313 | // Log request with a config array 314 | async function logRequestWithConfigArray(configArr, request, response, routeType, routeCount, currentRequestCount) { 315 | // Does nothing if configArr is null 316 | if( configArr == null || configArr.length <= 0 ) { 317 | return null; 318 | } 319 | 320 | // Lets iterate the config 321 | let promiseArray = []; 322 | for(let i=0; i= 200 && resObj.status < 400; 387 | } 388 | 389 | /** 390 | * Check if the given response object is a KittenRouter exception 391 | * @param {Response} resObj to validate 392 | * 393 | * @return true if its a KittenRouter exception 394 | */ 395 | function isKittenRouterException(resObj) { 396 | return resObj != null && resObj._isKittenRouterException; 397 | // resObj.headers && resObj.headers.get("KittenRouterException") == "true"; 398 | } 399 | 400 | const fetchOptionsKey = [ 401 | "method", 402 | "headers", 403 | "body", 404 | "mode", 405 | "credentials", 406 | "cache", 407 | "redirect", 408 | "referrer", 409 | "referrerPolicy", 410 | "integrity", 411 | "keepalive", 412 | "signal" 413 | ]; 414 | 415 | const overwriteFetchOptionsKey = [ 416 | "method", 417 | // "headers", 418 | // "body", 419 | "mode", 420 | "credentials", 421 | "cache", 422 | "redirect", 423 | "referrer", 424 | "referrerPolicy", 425 | "integrity", 426 | "keepalive", 427 | "signal", 428 | "cf" // cloudflare custom settings 429 | ] 430 | 431 | /** 432 | * To extend and include additional configuration options into the original 433 | * fetchOptions 434 | * 435 | * Currently only extension of headers are supported 436 | * 437 | * @param {*} fetchOptions object to be clone 438 | * @param {*} extendHeaders that contains the attributes to be replaced 439 | * 440 | * @return cloneExtendedFetchOptions, cloning the original fetch options with extended fetch options 441 | */ 442 | function cloneAndExtendFetchOptions(fetchOptions, extendOptions){ 443 | // Init a new fetch options 444 | let ret = {} 445 | 446 | // Clone all keys inside fetch options 447 | for(let i=0; i < fetchOptionsKey.length; ++i){ 448 | let key = fetchOptionsKey[i] 449 | try { 450 | // Why try catch, and ignore here!!! 451 | // 452 | // Well it seems like some properties of a request object 453 | // cannot be safely checked for "if not null", without 454 | // an error. Hence this work around. Sad =() 455 | if( fetchOptions[key] != null ) { 456 | ret[key] = fetchOptions[key] 457 | } 458 | } catch(e) { 459 | // does nothing : hopefully someone can do a better if != null ? 460 | } 461 | } 462 | 463 | // Clone over all the overwriting options (if applicable) 464 | for(let i=0; i < overwriteFetchOptionsKey.length; ++i) { 465 | let key = overwriteFetchOptionsKey[i] 466 | if(extendOptions[key] !== null) { 467 | ret[key] = extendOptions[key] 468 | } 469 | } 470 | 471 | // Overwrite the headers if needed 472 | if(extendOptions["overwriteHeaders"] !== null) { 473 | ret.headers = Object.assign({}, extendOptions["overwriteHeaders"]); 474 | } 475 | 476 | // Lets get the headers to extend 477 | let extendHeaders = extendOptions["headers"]; 478 | if(extendHeaders !== null) { 479 | let newHeaders = ret.headers || {}; 480 | // For all the headers that needs to be overwritten, overwrite if it exists 481 | for (key in extendHeaders){ 482 | newHeaders[key] = extendHeaders[key] 483 | } 484 | ret.headers = newHeaders 485 | } 486 | 487 | // Function overwrites 488 | if( extendOptions["optionsFunction"] != null ) { 489 | extendOptions["optionsFunction"](ret, fetchOptions, extendOptions); 490 | } 491 | 492 | // Return back a cloned extended fetch options 493 | return ret 494 | } 495 | 496 | 497 | 498 | /** 499 | * Calling of fetch, with a timeout option which returns an error instead 500 | * See: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch 501 | * 502 | * @param {*} resource 503 | * @param {*} init 504 | * @param {Integer} timeout in approximate milliseconds to return a failure 505 | * 506 | * @return {Response} object of the request 507 | */ 508 | function fetchWithTimeout(resource, init, timeout = 2000) { 509 | // Calling the fetch request 510 | let fetchPromise = fetch(resource, init); 511 | 512 | // Timeout promise 513 | let timeoutPromise = new Promise(function(good,bad) { 514 | setTimeout(function() { 515 | good( setupResponseError("ROUTE_TIMEOUT", "Route failed due to timeout : "+timeout+" ms") ); 516 | }, timeout); 517 | }); 518 | 519 | // Return a racing promise 520 | return Promise.race([ fetchPromise, timeoutPromise ]); 521 | } 522 | 523 | //--------------------------------------------------------------------------------------------- 524 | // 525 | // Cloudflare Caching logic 526 | // 527 | //--------------------------------------------------------------------------------------------- 528 | 529 | // Cloudflare constant prerouting cache 530 | const preRouteCache = caches.default; 531 | 532 | /** 533 | * Returned cached response (if valid) 534 | * @param {Request} inRequest to use as key 535 | */ 536 | async function matchPrerouteCache(inRequest) { 537 | return preRouteCache.match(inRequest); 538 | } 539 | 540 | /** 541 | * Fill up the cloudflare cache, with a response object. 542 | * This will automaticaly handle the response expirary rules 543 | * 544 | * @param {Request} inRequest to use as key 545 | * @param {Response} outResponse to cach 546 | * @param {Boolean} enable (default true), configure to false to skip route cache fill 547 | * 548 | * @return the outResponse (to pass upwards) 549 | */ 550 | async function fillupPrerouteCache(inRequest, outResponse, enable = true) { 551 | if(enable) { 552 | preRouteCache.put(inRequest, outResponse.clone()); 553 | } 554 | return outResponse; 555 | } 556 | 557 | //--------------------------------------------------------------------------------------------- 558 | // 559 | // Routing internal logic 560 | // 561 | //--------------------------------------------------------------------------------------------- 562 | 563 | /** 564 | * Makes a request, with a different origin host 565 | * 566 | * @param {String} originHostStr to overwrite host with 567 | * @param {Request} inRequest to use 568 | * @param {Integer} defaultRouteTimeout in approximate milliseconds to return a failure 569 | * 570 | * @return {Response} object of the request 571 | */ 572 | // Process a routing request, and return its response object 573 | async function processOriginRoutingStr(originHostStr, inRequest, defaultRouteTimeout = 2000) { 574 | return fetchWithTimeout( // 575 | cloneUrlWithNewOriginHostString(inRequest.url,originHostStr), // 576 | inRequest, // 577 | defaultRouteTimeout 578 | ); 579 | } 580 | 581 | /** 582 | * Makes a request, with a different origin host configured by an object 583 | * 584 | * @param {Object} originObj to overwrite host with 585 | * @param {Request} inRequest to use 586 | * @param {Integer} defaultRouteTimeout in approximate milliseconds to return a failure 587 | * 588 | * @return {Response} object of the request 589 | */ 590 | async function processOriginRoutingObj(originObj, inRequest, defaultRouteTimeout = 2000) { 591 | return fetchWithTimeout( 592 | cloneUrlWithNewOriginHostString(inRequest.url, originObj.host), // 593 | cloneAndExtendFetchOptions(inRequest, originObj), // 594 | originObj.timeout || defaultRouteTimeout 595 | ) 596 | } 597 | 598 | // // Process a routing request, and return its response object 599 | // async function processOriginRouting(origin, inRequest) { 600 | // if( (typeof origin) === "string" ) { 601 | // return processOriginRoutingStr(origin, inRequest); 602 | // } 603 | // throw "Object based routing config is NOT yet supported"; 604 | // } 605 | 606 | /** 607 | * Process a request, and perform the required route request and logging 608 | * This DOES NOT handle the fetch fallback 609 | * 610 | * @param {Object} configObj conataining both the .route, and .log array config 611 | * @param {*} fetchEvent provided from cloudflare, attaches logging waitUntil 612 | * @param {Request} inRequest to process 613 | * @param {int} currentRequestCount that keep tracks of how many requests have been made to this instance 614 | * 615 | * @return {Response} if a valid route with result is found, else return final route request failure (if any), else return null 616 | */ 617 | async function processRoutingRequest( configObj, fetchEvent, inRequest, currentRequestCount ) { 618 | // Lets get the route, and log array first 619 | let routeArray = configObj.route; 620 | let logArray = configObj.log; 621 | let defaultRouteTimeout = configObj.defaultRouteTimeout || 2000; 622 | 623 | // Return null, on empty routeArray 624 | if( routeArray == null || routeArray.length <= 0 ) { 625 | return null; 626 | } 627 | 628 | // setup the variable for response object 629 | let resObj = null; 630 | 631 | // Lets iterate the routes 632 | for( let i=0; i { 862 | event.respondWith(router.handleFetchEvent(event)) 863 | }); 864 | */ 865 | --------------------------------------------------------------------------------