Introducing Edgio Applications v7Find out what's new.
Edgio
Edgio

Waiting Room

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';
2
3export default new Router()
4 .use(edgioRoutes)
5
6 .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 '../../../utils/polyfills/URL';
8import waitingPage from './waitingPage';
9
10// Constants
11const COOKIE_NAME_ID = '__sessiong_id';
12const COOKIE_NAME_TIME = '__session_last_update_time';
13const TOTAL_ACTIVE_USERS = 2;
14const SESSION_DURATION_SECONDS = 15;
15
16// Setup fetch function for Upstash
17const fetch = createFetchForOrigin('upstash');
18
19/**
20 * Main handler for the edge request.
21 */
22export async function handleHttpRequest(request, context) {
23 let resp;
24
25 // Set context environment variables to process.env
26 setEnvFromContext(context);
27
28 const cookies = getCookiesFromRequest(request);
29
30 // Get user ID from cookie or generate a new one
31 const userId = cookies[COOKIE_NAME_ID] ?? generateId();
32
33 // Get the current number of active sessions and the active user
34 const size = await getRecordCount();
35 const isActiveUser = (await getRecord(userId)) === '1';
36
37 console.log('Current number of active sessions: ', size);
38
39 // Check capacity
40 if (size < TOTAL_ACTIVE_USERS || isActiveUser) {
41 // User is able to access the website
42 resp = await getDefaultResponse(request, userId);
43 } else {
44 // User is not able to access the website, hold them in the waiting room
45 resp = await getWaitingRoomResponse();
46 }
47
48 return resp;
49}
50
51/**
52 * Generate a random ID
53 */
54function generateId(len = 10) {
55 return Array.from({ length: len }, () =>
56 ((Math.random() * 36) | 0).toString(36)
57 ).join('');
58}
59
60/**
61 * Handle the default response.
62 */
63async function getDefaultResponse(request, userId) {
64 const cookiesToSet = [[COOKIE_NAME_ID, userId]];
65
66 // Read the session cookie and update the expiry time
67 const cookies = getCookiesFromRequest(request);
68 const now = Date.now();
69 const lastUpdate = cookies[COOKIE_NAME_TIME];
70 let lastUpdateTime = 0;
71
72 if (lastUpdate) {
73 lastUpdateTime = parseInt(lastUpdate);
74 }
75
76 const diff = now - lastUpdateTime;
77 const updateInterval = (SESSION_DURATION_SECONDS * 1000) / 2;
78 if (diff > updateInterval) {
79 await setExpiryRecord(userId, '1', SESSION_DURATION_SECONDS);
80 cookiesToSet.push([COOKIE_NAME_TIME, now.toString()]);
81 }
82
83 // Fetch the response from the origin
84 const response = await fetch(request);
85
86 // Set the cookies
87 setCookieToResponse(response, cookiesToSet);
88
89 return response;
90}
91
92/**
93 * Send a REST request to Upstash.
94 */
95async function sendUpstashRequest(cmd) {
96 cmd = Array.isArray(cmd) ? cmd.join('/') : cmd;
97
98 return (
99 await fetch(`${process.env.UPSTASH_REDIS_REST_URL}`, {
100 method: 'POST',
101 body: JSON.stringify(cmd.split('/')),
102 headers: {
103 Authorization: `Bearer ${process.env.UPSTASH_REDIS_REST_TOKEN}`,
104 },
105 })
106 ).json();
107}
108
109/**
110 * Get the current number of records.
111 */
112async function getRecordCount() {
113 const data = await sendUpstashRequest('DBSIZE');
114 return data.result;
115}
116
117/**
118 * Fetch a record from Upstash by key.
119 */
120async function getRecord(key) {
121 const data = await sendUpstashRequest(['GET', key]);
122 return data.result;
123}
124
125/**
126 * Set a record with an expiry time in Upstash.
127 */
128async function setExpiryRecord(key, value, seconds) {
129 return sendUpstashRequest(['SET', key, value, 'EX', seconds]);
130}
131
132/**
133 * Response for the waiting room.
134 */
135async function getWaitingRoomResponse() {
136 const response = new Response(waitingPage);
137 response.headers.set('content-type', 'text/html;charset=UTF-8');
138 return response;
139}
JavaScriptedge-functions/waitingPage.js
1export default `
2<!DOCTYPE html>
3<html lang="en">
4
5<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 }
15
16 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 }
27
28 .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 }
37
38 p {
39 margin-top: .5rem;
40 }
41
42 .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 }
50
51 @keyframes spin {
52 0% {
53 transform: rotate(0deg);
54 }
55 100% {
56 transform: rotate(360deg);
57 }
58 }
59
60 h1 {
61 margin-top: 20px;
62 margin-bottom: 20px;
63 }
64
65 </style>
66</head>
67
68<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>
76
77</html>
78`;