Skip to content

Commit

Permalink
Merge pull request #90 from edgio-docs/ef-caching
Browse files Browse the repository at this point in the history
Add EF waiting room example
  • Loading branch information
tristanlee85 authored Nov 16, 2023
2 parents 0ae8a2c + fad306a commit 373ec54
Show file tree
Hide file tree
Showing 15 changed files with 643 additions and 232 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
node_modules/
**/node_modules/
**/.edgio/**
**/.yalc/**
yalc.lock
15 changes: 9 additions & 6 deletions examples/v7-edge-functions/edgio.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@
require('dotenv').config();

module.exports = {
// The name of the site in Edgio to which this app should be deployed.
name: 'edgio-functions-examples',

// The name of the team in Edgio to which this app should be deployed.
team: 'edge-functions-sandbox',

// Overrides the default path to the routes file. The path should be relative to the root of your app.
// routes: 'routes.js',

Expand All @@ -30,6 +24,15 @@ module.exports = {
},
],
},
{
name: 'echo',
override_host_header: 'http-echo.raees.me',
hosts: [
{
location: 'http-echo.raees.me',
},
],
},
{
name: 'planetscale',
override_host_header: 'aws.connect.psdb.cloud',
Expand Down
132 changes: 0 additions & 132 deletions examples/v7-edge-functions/functions/database/upstash/index.js

This file was deleted.

27 changes: 27 additions & 0 deletions examples/v7-edge-functions/functions/general/caching.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import createFetchForOrigin from '../../utils/createFetchForOrigin';
import '../../utils/polyfills/URL';

const fetch = createFetchForOrigin('echo');

export async function handleHttpRequest(request, context) {
const { method } = request;
const body = method === 'POST' ? await request.arrayBuffer() : null;

// get the headers from the incoming request, removing the content-length header
const headers = Object.fromEntries(
[...request.headers.entries()].filter(([key]) => key !== 'content-length')
);

const newRequest = new Request('https://http-echo.raees.me', {
method,
headers,
body,
});

const response = await fetch(newRequest);

// apply caching headers to the response for all HTTP methods
response.headers.set('cache-control', 's-maxage=600');

return response;
}
27 changes: 25 additions & 2 deletions examples/v7-edge-functions/functions/general/sample-html-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,34 @@ export async function handleHttpRequest(request, context) {
<p>Transactional queries with a PlanetScale database.</p>
</li>
<li>
<h4>Upstash Database (<a href="/example/upstash-database">View Example</a>)</h4>
<p>A waiting room example using Upstash + Redis.</p>
<h4>Waiting Room using PlanetScale (<a href="/example/waiting-room">View Example</a>)</h4>
<p>A waiting room example using PlanetScale for session tracking.</p>
</li>
</ul>
</section>
<section>
<h2>Caching</h2>
<p>Examples demonstrating caching for different request types. Observe unique caching for GET and POST w/ body requests.</p>
<ul>
<li>
<strong>GET Request</strong>
<pre><code>${createCURLCommand(
'/example/caching'
)}</code></pre>
</li>
<li>
<strong>POST Request with JSON payload 1</strong>
<pre><code>curl -i -X POST ${domain}/example/caching -d '{"key": "value1"}'</code></pre>
</li>
<li>
<strong>POST Request with JSON payload 2</strong>
<pre><code>curl -i -X POST ${domain}/example/caching -d '{"key": "value2"}'</code></pre>
</li>
</ul>
</section>
</div>
<div style="margin-top: 30px; text-align: center;">
<a href="https://docs.edg.io/guides/v7/edge-functions" target="_blank">Edge Functions Documentation</a> | <a href="https://github.com/edgio-docs/edgio-v7-edge-functions-example" target="_blank">View the demo code on GitHub</a>
Expand Down
77 changes: 77 additions & 0 deletions examples/v7-edge-functions/functions/waiting-room/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
export const COOKIE_NAME_ID = '__edgio_session_id';
export const COOKIE_NAME_TIME = '__edgio_session_last_update_time';
export const TOTAL_ACTIVE_SESSIONS = 2;

// Active sessions should persist longer than queued sessions
export const ACTIVE_SESSION_DURATION_SECONDS = 150;
export const QUEUED_SESSION_DURATION_SECONDS = 15;

// Queries
export const CREATE_SESSION_QUERY = `
-- Conditional insert if the session does not exist.
-- This is cheaper than a SELECT followed by an INSERT.
INSERT INTO sessions (sessionId, status, createdAt, updatedAt)
SELECT
:id,
IF((SELECT COUNT(*) FROM sessions WHERE status = 'active') >= :sessionsLimit, 'queued', 'active'),
NOW(),
NOW()
FROM dual
WHERE NOT EXISTS (
SELECT 1 FROM sessions WHERE sessionId = :id
);
`;

export const GET_SESSION_QUERY = `
-- Get the session data and the position in the queue.
SELECT
sessions_all.*,
IF(sessions_all.status = 'queued', queued_info.position, -1) AS position,
(SELECT COUNT(*) FROM sessions WHERE status = 'active') AS active_count,
(SELECT COUNT(*) FROM sessions WHERE status = 'queued') AS queued_count
FROM
(SELECT
sessionId,
status,
createdAt,
updatedAt
FROM sessions) AS sessions_all
LEFT JOIN
(SELECT
sessionId,
ROW_NUMBER() OVER (ORDER BY createdAt) AS position
FROM sessions
WHERE status = 'queued') AS queued_info
ON sessions_all.sessionId = queued_info.sessionId
WHERE sessions_all.sessionId = :id;
`;

export const EXPIRE_SESSIONS_QUERY = `
DELETE FROM sessions WHERE
(updatedAt < DATE_SUB(NOW(), INTERVAL :activeDuration SECOND) AND status = 'active')
OR (updatedAt < DATE_SUB(NOW(), INTERVAL :queuedDuration SECOND) AND status = 'queued');
`;

export const GET_SESSION_ID_QUERY = `
SELECT sessionId FROM (
SELECT sessionId FROM sessions WHERE sessionId = :id
UNION ALL
SELECT UUID() AS sessionId
ORDER BY (sessionId IS NOT NULL) DESC
LIMIT 1
) AS uuid_selection;
`;

export const REFRESH_SESSION_QUERY = `
UPDATE sessions
SET
updatedAt = :date,
status = :status
WHERE sessionId = :id;
`;

export const AVAILABLE_STATUS_QUERY = `
SELECT IF(COUNT(*) < :sessionsLimit, 'active', 'queued') as newStatus
FROM sessions
WHERE status = 'active';
`;
78 changes: 78 additions & 0 deletions examples/v7-edge-functions/functions/waiting-room/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {
getCookiesFromRequest,
setCookieToResponse,
} from '../../utils/cookies';
import { setEnvFromContext } from '../../utils/polyfills/process.env';
import waitingPage from './waiting-room-capacity.html';
import landingPage from './waiting-room-landing.html';
import { COOKIE_NAME_ID, COOKIE_NAME_TIME } from './constants';
import { getSessionData } from './planetscale';

/**
* Main handler for the edge request.
*/
export async function handleHttpRequest(request, context) {
let response;

// Set context environment variables to process.env
setEnvFromContext(context);

const cookies = getCookiesFromRequest(request);

// Get user ID from cookie or generate a new one
const sessionId = cookies[COOKIE_NAME_ID];

// Get the current number of active sessions and the active user
const { session, activeCount, queuedCount } = await getSessionData(sessionId);

// Check capacity
if (session.status === 'active') {
response = await getDefaultResponse(request, session);
} else {
response = await getWaitingRoomResponse(request, session);
}

// Update the session cookie with the latest timestamp
setSessionCookie(response, session.sessionId, session.updatedAt);

return response;
}

/**
* Handle the default response.
*/
async function getDefaultResponse(request, session) {
const response = new Response(landingPage({ requestUrl: request.url }));
response.headers.set('content-type', 'text/html;charset=UTF-8');

return response;
}
/**
* Response for the waiting room.
*/
async function getWaitingRoomResponse(request, session) {
// update the waiting page to show the position in the queue, replacing {{queuePosition}}
const body = waitingPage({
queuePosition: session.position,
});

const response = new Response(body);
response.headers.set('content-type', 'text/html;charset=UTF-8');

return response;
}

/**
* Sets the session cookie to the response.
*
* @param {Response} response
* @param {Object} session
* @param {number} date
* @returns {Promise<void>}
*/
export async function setSessionCookie(response, sessionId, date) {
const now = date || Date.now();

setCookieToResponse(response, [[COOKIE_NAME_TIME, now.toString()]]);
setCookieToResponse(response, [[COOKIE_NAME_ID, sessionId]]);
}
Loading

0 comments on commit 373ec54

Please sign in to comment.