You may only deploy this edge function using CDN-as-Code due to Node.js dependencies. It will not work when deployed through the Edgio Console.
A waiting room can be used to restrict access to a website by means of queueing requests during high traffic periods. This can be useful for preventing a website from becoming overloaded during a traffic spike, or for restricting access to a website during a limited-time event.
The example is derived from an Upstash blog post using an edge database to store the number of active sessions, and to determine whether the current user is active. If the number of active sessions is less than the maximum allowed, or if the current user is already active, the request will be allowed to proceed. Otherwise, the request will be queued and the user will be shown a waiting room page.
Router Configuration
In the Edgio router, you can use the
edge_function
feature to specify the path to the edge function that will handle the waiting room.JavaScriptroutes.js
1import {Router, edgioRoutes} from '@edgio/core';23export default new Router()4 .use(edgioRoutes)56 .match('/:path*', {7 edge_function: './edge-functions/main.js',8 });
Edge Function
The edge function will be responsible for determining whether the request should be allowed to proceed, or whether it should be queued for later processing. In this example, we will use Upstash to store the number of active sessions, and to determine whether the current user is active.
The following sample code contains
import
declarations that are not documented in this guide. View the full source code for these functions from within the edgio-examples repository.JavaScriptedge-functions/main.js
1import createFetchForOrigin from '../../../utils/createFetchForOrigin';2import {3 getCookiesFromRequest,4 setCookieToResponse,5} from '../../../utils/cookies';6import {setEnvFromContext} from '../../../utils/polyfills/process.env';7import waitingPage from './waitingPage';89// Constants10const COOKIE_NAME_ID = '__sessiong_id';11const COOKIE_NAME_TIME = '__session_last_update_time';12const TOTAL_ACTIVE_USERS = 2;13const SESSION_DURATION_SECONDS = 15;1415// Setup fetch function for Upstash16const fetch = createFetchForOrigin('upstash');1718/**19 * Main handler for the edge request.20 */21export async function handleHttpRequest(request, context) {22 let resp;2324 // Set context environment variables to process.env25 setEnvFromContext(context);2627 const cookies = getCookiesFromRequest(request);2829 // Get user ID from cookie or generate a new one30 const userId = cookies[COOKIE_NAME_ID] ?? generateId();3132 // Get the current number of active sessions and the active user33 const size = await getRecordCount();34 const isActiveUser = (await getRecord(userId)) === '1';3536 console.log('Current number of active sessions: ', size);3738 // Check capacity39 if (size < TOTAL_ACTIVE_USERS || isActiveUser) {40 // User is able to access the website41 resp = await getDefaultResponse(request, userId);42 } else {43 // User is not able to access the website, hold them in the waiting room44 resp = await getWaitingRoomResponse();45 }4647 return resp;48}4950/**51 * Generate a random ID52 */53function generateId(len = 10) {54 return Array.from({length: len}, () =>55 ((Math.random() * 36) | 0).toString(36)56 ).join('');57}5859/**60 * Handle the default response.61 */62async function getDefaultResponse(request, userId) {63 const cookiesToSet = [[COOKIE_NAME_ID, userId]];6465 // Read the session cookie and update the expiry time66 const cookies = getCookiesFromRequest(request);67 const now = Date.now();68 const lastUpdate = cookies[COOKIE_NAME_TIME];69 let lastUpdateTime = 0;7071 if (lastUpdate) {72 lastUpdateTime = parseInt(lastUpdate);73 }7475 const diff = now - lastUpdateTime;76 const updateInterval = (SESSION_DURATION_SECONDS * 1000) / 2;77 if (diff > updateInterval) {78 await setExpiryRecord(userId, '1', SESSION_DURATION_SECONDS);79 cookiesToSet.push([COOKIE_NAME_TIME, now.toString()]);80 }8182 // Fetch the response from the origin83 const response = await fetch(request);8485 // Set the cookies86 setCookieToResponse(response, cookiesToSet);8788 return response;89}9091/**92 * Send a REST request to Upstash.93 */94async function sendUpstashRequest(cmd) {95 cmd = Array.isArray(cmd) ? cmd.join('/') : cmd;9697 return (98 await fetch(`${process.env.UPSTASH_REDIS_REST_URL}`, {99 method: 'POST',100 body: JSON.stringify(cmd.split('/')),101 headers: {102 Authorization: `Bearer ${process.env.UPSTASH_REDIS_REST_TOKEN}`,103 },104 })105 ).json();106}107108/**109 * Get the current number of records.110 */111async function getRecordCount() {112 const data = await sendUpstashRequest('DBSIZE');113 return data.result;114}115116/**117 * Fetch a record from Upstash by key.118 */119async function getRecord(key) {120 const data = await sendUpstashRequest(['GET', key]);121 return data.result;122}123124/**125 * Set a record with an expiry time in Upstash.126 */127async function setExpiryRecord(key, value, seconds) {128 return sendUpstashRequest(['SET', key, value, 'EX', seconds]);129}130131/**132 * Response for the waiting room.133 */134async function getWaitingRoomResponse() {135 const response = new Response(waitingPage);136 response.headers.set('content-type', 'text/html;charset=UTF-8');137 return response;138}
JavaScriptedge-functions/waitingPage.js
1export default `2<!DOCTYPE html>3<html lang="en">45<head>6 <meta charset="UTF-8">7 <meta http-equiv='refresh' content='5'>8 <title>Waiting Room</title>9 <style>10 * {11 box-sizing: border-box;12 margin: 0;13 padding: 0;14 }1516 body {17 line-height: 1.4;18 font-size: 1rem;19 font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;20 padding: 2rem;21 display: grid;22 place-items: center;23 min-height: 100vh;24 background-color: #f3f4f6;25 color: #333;26 }2728 .container {29 width: 100%;30 max-width: 800px;31 text-align: center;32 background-color: #fff;33 padding: 2rem;34 border-radius: 10px;35 box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);36 }3738 p {39 margin-top: .5rem;40 }4142 .loader {43 border: 6px solid #f3f3f3;44 border-top: 6px solid #3498db;45 border-radius: 50%;46 width: 50px;47 height: 50px;48 animation: spin 1s linear infinite;49 }5051 @keyframes spin {52 0% {53 transform: rotate(0deg);54 }55 100% {56 transform: rotate(360deg);57 }58 }5960 h1 {61 margin-top: 20px;62 margin-bottom: 20px;63 }6465 </style>66</head>6768<body>69 <div class='container'>70 <div class="loader"></div>71 <h1>Almost There!</h1>72 <p>Our site is currently at full capacity. Thanks for your patience.</p>73 <p>You'll be redirected shortly. Please do not close your browser.</p>74 </div>75</body>7677</html>78`;