Last active
November 29, 2024 21:43
-
-
Save worotyns/0d812d5051af8140cedc5325405177c9 to your computer and use it in GitHub Desktop.
request bin alternative self hosted with deno deploy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 .