Skip to content

Instantly share code, notes, and snippets.

@worotyns
Last active November 29, 2024 21:43
Show Gist options
  • Save worotyns/0d812d5051af8140cedc5325405177c9 to your computer and use it in GitHub Desktop.
Save worotyns/0d812d5051af8140cedc5325405177c9 to your computer and use it in GitHub Desktop.
request bin alternative self hosted with deno deploy
type Client = {
namespace: string;
writer: WritableStreamDefaultWriter<Uint8Array>;
};
const sseClients = new Map<string, Set<Client>>();
function randomString(length = 8) {
const characters = "abcdefghijklmnopqrstuvwxyz0123456789";
return Array.from({ length }, () => characters[Math.floor(Math.random() * characters.length)]).join("");
}
function sendToClients(namespace: string, message: string) {
const clients = sseClients.get(namespace);
if (!clients) return;
const encodedMessage = new TextEncoder().encode(`data: ${message}\n\n`);
clients.forEach(async (client) => {
try {
await client.writer.write(encodedMessage);
} catch (error) {
console.error("Error sending to client:", error);
client.writer.releaseLock();
client.writer.close();
}
});
}
async function handler(req: Request): Promise<Response> {
const { pathname, searchParams } = new URL(req.url);
const [, namespace, ssePath] = pathname.split("/");
if (pathname === "/") {
const indexContent = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTTP Rqeusts Debugger</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
pre { background: #f3f3f3; padding: 10px; border: 1px solid #ddd; }
.btn { padding: 8px 16px; background-color: #007bff; color: white; border: none; cursor: pointer; }
</style>
</head>
<body>
<h1>HTTP Rqeusts Debugger - SSE Log Server</h1>
<p>Create a unique logging namespace to start viewing real-time logs.</p>
<p>Use the following instructions to send and view HTTP logs:</p>
<h2>How to Use</h2>
<p>1. Start listening to logs in a <code>:namespace</code> by opening the <code>__sse</code> endpoint:</p>
<pre>curl -N https://request-bin.deno.dev/your_namespace/__sse</pre>
<p>2. Send a request to log an entry under the same <code>:namespace</code>:</p>
<pre>curl -X POST https://request-bin.deno.dev/your_namespace -d "Example log entry"</pre>
<p>3. Customize headers, method, or query params to see different log formats:</p>
<pre>curl -X GET https://request-bin.deno.dev/your_namespace?foo=bar</pre>
<button class="btn" onclick="createNamespace()">Create New with Random String</button>
<script>
function createNamespace() {
const randomNamespace = "${randomString()}";
window.location.href = "/" + randomNamespace + "/__sse";
}
</script>
</body>
</html>
`;
return new Response(indexContent, {
headers: { "Content-Type": "text/html" },
});
}
if (!namespace) {
return new Response("Namespace is required", { status: 400 });
}
if (ssePath === "__sse" && req.method === "GET" && !req.headers.get("accept")?.includes("text/event-stream")) {
const htmlContent = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Live Logs - ${namespace}</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
#logs { white-space: pre-wrap; background: #f3f3f3; padding: 10px; border: 1px solid #ddd; max-height: 400px; overflow-y: scroll; }
.btn { padding: 8px 16px; background-color: #007bff; color: white; border: none; cursor: pointer; }
.input-container { display: flex; align-items: center; margin-bottom: 10px; }
input[type="text"] { width: 80%; padding: 5px; margin-right: 10px; }
</style>
</head>
<body>
<h1>Live Logs for Namespace: ${namespace}</h1>
<div class="input-container">
<input type="text" value="https://request-bin.deno.dev/${namespace}" id="logEndpoint" disabled>
<button class="btn" onclick="copyToClipboard()">Copy</button>
</div>
<div id="logs"></div>
<script>
const eventSource = new EventSource('/${namespace}/__sse');
const logsDiv = document.getElementById('logs');
eventSource.onmessage = function(event) {
const logEntry = document.createElement('div');
logEntry.innerHTML = event.data;
logsDiv.appendChild(logEntry);
logsDiv.scrollTop = logsDiv.scrollHeight;
};
eventSource.onerror = function() {
logsDiv.innerHTML += "<div style='color: red;'>Connection lost. Trying to reconnect...</div>";
};
function copyToClipboard() {
const logEndpoint = document.getElementById("logEndpoint");
logEndpoint.select();
document.execCommand("copy");
}
</script>
</body>
</html>
`;
return new Response(htmlContent, {
headers: { "Content-Type": "text/html" },
});
}
if (ssePath === "__sse") {
const { readable, writable } = new TransformStream();
const writer = writable.getWriter();
const client: Client = {
namespace,
writer,
};
if (!sseClients.has(namespace)) {
sseClients.set(namespace, new Set());
}
sseClients.get(namespace)?.add(client);
req.signal.addEventListener("abort", () => {
sseClients.get(namespace)?.delete(client);
if (!writer.closed) {
writer.close();
}
});
return new Response(readable, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
},
});
}
const method = req.method;
const headers = [...req.headers.entries()].map(([key, value]) => `${key}: ${value}`).join("<br/>");
const queryParams = [...searchParams.entries()].map(([key, value]) => `${key}: ${value}`).join("<br/>");
const body = await req.text();
const logMessage = `<code><br/>Request ${method.toUpperCase()} @ ${new Date().toLocaleString()}<br/>path: ${pathname}<br/>query:<br/>${queryParams || "(none)"}<br/>headers:<br/>${headers}<br/>body:${body || "(none)"}<br/></code>`;
sendToClients(namespace, logMessage);
return new Response("Logged", { status: 200 });
}
console.log("SSE Server running at https://request-bin.deno.dev");
Deno.serve({port: 8000}, handler);
// save to main.ts
// deploy using deno deploy like deployctl deploy main.ts
@worotyns
Copy link
Author

Instructions for
selfhosters:
deno compile main.ts # make executable you can run everywhere

for deploy on deno deploy (save this gist in directory and run)
deploy like deployctl deploy .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment