Add CoAP-over-UDP hello server in Rust
A minimal CoAP (RFC 7252) server built on coap-lite for message encoding and a tokio UdpSocket for transport. The socket, read loop and routing are kept in src/main.rs rather than behind a framework. Routes: GET /hello[?name=X] -> 2.05 "Hello, X!" (defaults to World) POST /hello -> 2.01, name from a raw text or JSON body GET /health -> 2.05 status + uptime Unknown paths return 4.04, known paths with the wrong method 4.05. Includes a CLI client (src/bin/client.rs) for smoke tests and unit tests covering the greeting, payload parsing and query decoding. Verified against node-coap as an independent implementation to confirm wire compatibility. The earlier Node.js prototype is kept under nodejs/ for reference. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
102
nodejs/server.js
Normal file
102
nodejs/server.js
Normal file
@@ -0,0 +1,102 @@
|
||||
'use strict';
|
||||
|
||||
const coap = require('coap');
|
||||
|
||||
const PORT = Number(process.env.COAP_PORT) || 5683;
|
||||
const HOST = process.env.COAP_HOST || '0.0.0.0';
|
||||
|
||||
// --- helpers ---------------------------------------------------------------
|
||||
|
||||
const TEXT = 'text/plain';
|
||||
const JSON_FMT = 'application/json';
|
||||
|
||||
/** Split "/hello?name=Bob" into ["/hello", "name=Bob"]. */
|
||||
function splitUrl(url) {
|
||||
const i = url.indexOf('?');
|
||||
const path = (i === -1 ? url : url.slice(0, i)).replace(/\/+$/, '') || '/';
|
||||
return [path, i === -1 ? '' : url.slice(i + 1)];
|
||||
}
|
||||
|
||||
/** Pull a name out of "?name=Bob" style queries. */
|
||||
function nameFromQuery(query) {
|
||||
return new URLSearchParams(query || '').get('name');
|
||||
}
|
||||
|
||||
/** Pull a name out of a POST body: raw text or {"name":"Bob"}. */
|
||||
function nameFromBody(buf) {
|
||||
const body = buf.toString('utf8').trim();
|
||||
if (!body) return null;
|
||||
if (body.startsWith('{')) {
|
||||
try {
|
||||
const parsed = JSON.parse(body);
|
||||
return typeof parsed.name === 'string' ? parsed.name.trim() : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
function greet(name) {
|
||||
return `Hello, ${name && name.length ? name : 'World'}!`;
|
||||
}
|
||||
|
||||
function send(res, code, payload, format = TEXT) {
|
||||
res.code = code;
|
||||
res.setOption('Content-Format', format);
|
||||
res.end(typeof payload === 'string' ? payload : JSON.stringify(payload));
|
||||
}
|
||||
|
||||
// --- routes ----------------------------------------------------------------
|
||||
// key: "METHOD /path"
|
||||
|
||||
const routes = {
|
||||
'GET /hello': (req, res) => {
|
||||
send(res, '2.05', greet(nameFromQuery(req.query)));
|
||||
},
|
||||
|
||||
'POST /hello': (req, res) => {
|
||||
const name = nameFromBody(req.payload) || nameFromQuery(req.query);
|
||||
if (!name) {
|
||||
return send(res, '4.00', 'Bad Request: send a name as the payload');
|
||||
}
|
||||
send(res, '2.01', greet(name));
|
||||
},
|
||||
|
||||
'GET /health': (_req, res) => {
|
||||
send(res, '2.05', { status: 'ok', uptime: Math.round(process.uptime()) }, JSON_FMT);
|
||||
},
|
||||
};
|
||||
|
||||
// --- server ----------------------------------------------------------------
|
||||
|
||||
const server = coap.createServer({ type: 'udp4' });
|
||||
|
||||
server.on('request', (req, res) => {
|
||||
const [path, query] = splitUrl(req.url);
|
||||
req.query = query; // node-coap only gives us req.url on the server side
|
||||
const key = `${req.method} ${path}`;
|
||||
const handler = routes[key];
|
||||
|
||||
console.log(`${req.method} ${req.url} from ${req.rsinfo.address}:${req.rsinfo.port}`);
|
||||
|
||||
if (!handler) return send(res, '4.04', `Not Found: ${path}`);
|
||||
|
||||
try {
|
||||
handler(req, res);
|
||||
} catch (err) {
|
||||
console.error('handler error:', err);
|
||||
send(res, '5.00', 'Internal Server Error');
|
||||
}
|
||||
});
|
||||
|
||||
server.on('error', (err) => console.error('server error:', err));
|
||||
|
||||
server.listen(PORT, HOST, () => {
|
||||
console.log(`CoAP server (UDP) listening on coap://${HOST}:${PORT}`);
|
||||
console.log(' GET /hello?name=Imran');
|
||||
console.log(' POST /hello (payload: Imran | {"name":"Imran"})');
|
||||
console.log(' GET /health');
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => server.close(() => process.exit(0)));
|
||||
Reference in New Issue
Block a user