Files
coapwithudp/static/index.html
Md Imran Hossain 4906ca7652 Add HTTP bridge so the CoAP server can be checked in a browser
Browsers speak HTTP over TCP and cannot open coap:// URLs, which made the
server awkward to try without the CLI client. The bridge accepts an ordinary
HTTP request, reissues it as CoAP over UDP, and maps the response code back
onto an HTTP status (the raw code is also returned in X-Coap-Code).

It serves a test console at / with a name field and a button per route, so
GET, POST, 4.04 and 4.05 can all be exercised by clicking.

The CoAP request logic moves from src/bin/client.rs into src/lib.rs so the
client and the bridge share one implementation.

The bridge is a development convenience: a small HTTP/1.1 subset with no
keep-alive, chunked bodies or TLS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 10:01:44 +06:00

135 lines
5.1 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CoAP Hello — Test Console</title>
<style>
:root {
color-scheme: light dark;
--bg: #fbfbfa; --fg: #1a1a19; --muted: #6b6b68;
--card: #ffffff; --border: #e4e4e1; --accent: #b05730;
--ok: #2f7d4f; --err: #b3402f; --code-bg: #f4f4f2;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #1a1a19; --fg: #edecea; --muted: #9a9a96;
--card: #232322; --border: #353533; --accent: #e08a5f;
--ok: #6bbd8a; --err: #e8806c; --code-bg: #2b2b29;
}
}
* { box-sizing: border-box; }
body {
margin: 0; padding: 2rem 1.25rem; background: var(--bg); color: var(--fg);
font: 15px/1.55 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
}
main { max-width: 640px; margin: 0 auto; }
h1 { font-size: 1.4rem; margin: 0 0 .35rem; letter-spacing: -.01em; }
.sub { color: var(--muted); margin: 0 0 1.75rem; font-size: .9rem; }
.card {
background: var(--card); border: 1px solid var(--border);
border-radius: 10px; padding: 1.25rem; margin-bottom: 1rem;
}
label { display: block; font-weight: 600; font-size: .82rem; margin-bottom: .4rem; }
input {
width: 100%; padding: .6rem .7rem; font: inherit; border-radius: 7px;
border: 1px solid var(--border); background: var(--bg); color: var(--fg);
}
input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.row { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .85rem; }
button {
font: inherit; font-weight: 600; padding: .55rem 1rem; border-radius: 7px;
border: 1px solid var(--border); background: var(--bg); color: var(--fg); cursor: pointer;
}
button:hover { border-color: var(--accent); color: var(--accent); }
button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
button.primary:hover { opacity: .88; color: #fff; }
pre {
background: var(--code-bg); border: 1px solid var(--border); border-radius: 7px;
padding: .8rem; overflow-x: auto; margin: 0;
font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
}
.status { font-weight: 600; }
.status.ok { color: var(--ok); }
.status.err { color: var(--err); }
h2 { font-size: .82rem; text-transform: uppercase; letter-spacing: .06em;
color: var(--muted); margin: 0 0 .7rem; }
.hint { color: var(--muted); font-size: .82rem; margin: .8rem 0 0; }
code { background: var(--code-bg); padding: .1em .35em; border-radius: 4px;
font: 13px ui-monospace, SFMono-Regular, Menlo, monospace; }
</style>
</head>
<body>
<main>
<h1>CoAP Hello — Test Console</h1>
<p class="sub">This page talks HTTP to a local bridge, which reissues each
call as CoAP over UDP and shows you the raw response code.</p>
<div class="card">
<label for="name">Name</label>
<input id="name" value="Imran" autocomplete="off" placeholder="leave blank for &quot;World&quot;">
<div class="row">
<button class="primary" data-act="get">GET /hello</button>
<button data-act="post">POST /hello</button>
<button data-act="json">POST (JSON body)</button>
<button data-act="health">GET /health</button>
<button data-act="notfound">GET /nope</button>
</div>
<p class="hint">Or hit an endpoint directly:
<a href="/hello?name=Imran">/hello?name=Imran</a> ·
<a href="/health">/health</a></p>
</div>
<div class="card">
<h2>Response</h2>
<p id="status" class="status">—</p>
<pre id="out">Pick an action above.</pre>
</div>
</main>
<script>
const $ = (id) => document.getElementById(id);
async function call(method, path, body) {
$('status').textContent = '…';
$('status').className = 'status';
$('out').textContent = '';
try {
const res = await fetch(path, {
method,
body,
headers: body ? { 'Content-Type': 'text/plain' } : undefined,
});
const text = await res.text();
const coap = res.headers.get('X-Coap-Code') || '—';
$('status').textContent = `CoAP ${coap} · HTTP ${res.status}`;
$('status').className = 'status ' + (res.ok ? 'ok' : 'err');
$('out').textContent = `${method} ${path}\n${body ? 'body: ' + body + '\n' : ''}\n${text || '(empty)'}`;
} catch (err) {
$('status').textContent = 'Request failed';
$('status').className = 'status err';
$('out').textContent = String(err) + '\n\nIs the bridge still running?';
}
}
document.querySelectorAll('button[data-act]').forEach((btn) => {
btn.addEventListener('click', () => {
const name = $('name').value.trim();
switch (btn.dataset.act) {
case 'get':
return call('GET', '/hello' + (name ? '?name=' + encodeURIComponent(name) : ''));
case 'post':
return call('POST', '/hello', name || '');
case 'json':
return call('POST', '/hello', JSON.stringify({ name }));
case 'health':
return call('GET', '/health');
case 'notfound':
return call('GET', '/nope');
}
});
});
</script>
</body>
</html>