Skip to content

Commit

Permalink
feat: separate registerAPIHandler from registerURLHandler
Browse files Browse the repository at this point in the history
Also, make WebSockets a proper superset of the POST handlers.
  • Loading branch information
michaelfig committed Mar 16, 2020
1 parent 142b5c9 commit 7c670d9
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 74 deletions.
18 changes: 0 additions & 18 deletions packages/cosmic-swingset/lib/ag-solo/register-http.js

This file was deleted.

8 changes: 4 additions & 4 deletions packages/cosmic-swingset/lib/ag-solo/vats/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default function setup(syscall, state, helpers) {
}

async function setupWalletVat(commandDevice, httpVat, walletVat) {
await E(httpVat).registerCommandHandler(walletVat);
await E(httpVat).registerURLHandler(walletVat, '/vat');
await E(walletVat).setCommandDevice(commandDevice);
await E(walletVat).setPresences();
}
Expand Down Expand Up @@ -154,10 +154,10 @@ export default function setup(syscall, state, helpers) {
await E(wallet).deposit(pursePetnames.moola, moolaPayment);
await E(wallet).deposit(pursePetnames.simolean, simoleanPayment);

// This will allow Dapp developers to register in their dapp.js
// This will allow dApp developers to register in their api/deploy.js
const httpRegCallback = {
registerCommandHandler(handler) {
return E(vats.http).registerCommandHandler(handler);
registerAPIHandler(handler) {
return E(vats.http).registerURLHandler(handler, '/api');
},
};

Expand Down
13 changes: 3 additions & 10 deletions packages/cosmic-swingset/lib/ag-solo/vats/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function stringify(value, spaces, already = new WeakSet()) {
if (Array.isArray(value)) {
let ret = '[';
let sep = '';
for (let i = 0; i < value.length; i++) {
for (let i = 0; i < value.length; i += 1) {
ret += sep + stringify(value[i], spaces, already);
sep = ',';
}
Expand Down Expand Up @@ -57,7 +57,7 @@ export function getReplHandler(E, homeObjects, sendBroadcast) {
const display = {};
let highestHistory = -1;

function updateHistorySlot(histnum, s) {
function updateHistorySlot(histnum) {
// console.log(`sendBroadcast ${histnum}`);
sendBroadcast({
type: 'updateHistory',
Expand All @@ -67,13 +67,6 @@ export function getReplHandler(E, homeObjects, sendBroadcast) {
});
}

function addException(histnum, e) {
// We stringify so that exceptions do not leak.
const s = `${e}`;
display[histnum] = `Promise.reject(${stringify(s)})`;
updateHistorySlot(histnum);
}

const { evaluateProgram } = makeEvaluators({ sloppyGlobals: true });

return {
Expand All @@ -83,7 +76,7 @@ export function getReplHandler(E, homeObjects, sendBroadcast) {

rebroadcastHistory() {
// console.log(`rebroadcastHistory`, highestHistory);
for (let histnum = 0; histnum <= highestHistory; histnum++) {
for (let histnum = 0; histnum <= highestHistory; histnum += 1) {
updateHistorySlot(histnum);
}
},
Expand Down
44 changes: 28 additions & 16 deletions packages/cosmic-swingset/lib/ag-solo/vats/vat-http.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,18 @@ function build(E, D) {
});
harden(readyForClient);

let handler = {};
const registeredHandlers = [];
const handler = {};
const registeredURLHandlers = new Map();

// TODO: Don't leak memory.
async function registerCommandHandler(newHandler) {
async function registerURLHandler(newHandler, url) {
const commandHandler = await E(newHandler).getCommandHandler();
registeredHandlers.push(commandHandler);
let reg = registeredURLHandlers.get(url);
if (!reg) {
reg = [];
registeredURLHandlers.set(url, reg);
}
reg.push(commandHandler);
}

return {
Expand Down Expand Up @@ -133,7 +138,8 @@ function build(E, D) {
}
},

registerCommandHandler,
registerURLHandler,
registerAPIHandler: h => registerURLHandler(h, '/api'),

setProvisioner(p) {
provisioner = p;
Expand All @@ -160,30 +166,36 @@ function build(E, D) {
// JSON.stringify(obj, undefined, 2),
// );

const { type } = obj;
// Try on local handlers first.
let res;
// Try on registered handlers.
if (type in handler) {
res = await handler[type](obj);
} else {
const { type, requestContext: { url } = { url: '/vat' } } = obj;
if (url === '/vat' || url === '/captp') {
// Use our local handler object (compatibility with repl.js).
// TODO: standardise
if (handler[type]) {
D(commandDevice).sendResponse(count, false, handler[type](obj));
return;
}
}

const urlHandlers = registeredURLHandlers.get(url);
if (urlHandlers) {
// todo fixme avoid the loop
// For now, go from the end to beginning so that handlers
// override.
const hardObjects = harden({ ...homeObjects });
for (let i = registeredHandlers.length - 1; i >= 0; i -= 1) {
for (let i = urlHandlers.length - 1; i >= 0; i -= 1) {
// eslint-disable-next-line no-await-in-loop
res = await E(registeredHandlers[i]).processInbound(
const res = await E(urlHandlers[i]).processInbound(
obj,
hardObjects,
);
if (res) {
break;
D(commandDevice).sendResponse(count, false, harden(res));
return;
}
}
}

D(commandDevice).sendResponse(count, false, harden(res));
throw Error(`No handler for ${url} ${type}`);
} catch (rej) {
D(commandDevice).sendResponse(count, true, harden(rej));
}
Expand Down
78 changes: 52 additions & 26 deletions packages/cosmic-swingset/lib/ag-solo/web.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,22 @@ const send = (ws, msg) => {

export function makeHTTPListener(basedir, port, host, rawInboundCommand) {
// Enrich the inbound command with some metadata.
const inboundCommand = (body, { headers: { origin } = {} } = {}) => {
const requestContext = { origin, date: Date.now() };
return rawInboundCommand({ ...body, requestContext });
const inboundCommand = (
body,
{ url, headers: { origin } = {} } = {},
id = undefined,
) => {
const obj = { ...body, requestContext: { origin, url, date: Date.now() } };
return rawInboundCommand(obj).catch(err => {
const idpfx = id ? `${id} ` : '';
console.error(
`${idpfx}inbound error:`,
err,
'from',
JSON.stringify(obj, undefined, 2),
);
throw (err && err.message) || JSON.stringify(err);
});
};

const app = express();
Expand Down Expand Up @@ -80,19 +93,24 @@ export function makeHTTPListener(basedir, port, host, rawInboundCommand) {
return true;
};

// accept vat messages on /vat
app.post('/vat', (req, res) => {
if (!validateOrigin(req)) {
res.json({ ok: false, rej: 'Invalid Origin' });
return;
}
// accept messages on /vat and /api
// todo: later allow arbitrary endpoints?
for (const ep of ['/vat', '/api']) {
app.post(ep, (req, res) => {
if (ep !== '/api' && !validateOrigin(req)) {
res.json({ ok: false, rej: 'Invalid Origin' });
return;
}

// console.log(`POST /vat got`, req.body); // should be jsonable
inboundCommand(req.body, req).then(
r => res.json({ ok: true, res: r }),
rej => res.json({ ok: false, rej }),
);
});
// console.log(`POST /vat got`, req.body); // should be jsonable
inboundCommand(req.body, req)
.then(
r => res.json({ ok: true, res: r }),
rej => res.json({ ok: false, rej }),
)
.catch(_ => {});
});
}

// accept WebSocket connections at the root path.
// This senses the Connection-Upgrade header to distinguish between plain
Expand Down Expand Up @@ -132,36 +150,44 @@ export function makeHTTPListener(basedir, port, host, rawInboundCommand) {
console.log(id, 'client closed');
broadcasts.delete(ws);
if (req.url === '/captp') {
inboundCommand({ type: 'CTP_CLOSE', connectionID }, req).catch(_ => {});
inboundCommand(
{ type: 'CTP_CLOSE', connectionID },
req,
id,
).catch(_ => {});
points.delete(connectionID);
}
});

if (req.url === '/captp') {
// This is a point-to-point connection, not broadcast.
points.set(connectionID, ws);
inboundCommand({ type: 'CTP_OPEN', connectionID }, req).catch(err => {
console.log(id, `error establishing connection`, err);
});
inboundCommand(
{ type: 'CTP_OPEN', connectionID },
req,
id,
).catch(_ => {});
ws.on('message', async message => {
try {
// some things use inbound messages
const obj = JSON.parse(message);
obj.connectionID = connectionID;
await inboundCommand(obj, req);
} catch (e) {
console.log(id, 'client error', e);
const error = e.message || JSON.stringify(e);
await inboundCommand(obj, req, id);
} catch (error) {
// eslint-disable-next-line no-use-before-define
sendJSON({ type: 'CTP_ERROR', connectionID, error });
}
});
} else {
broadcasts.set(connectionID, ws);
ws.on('message', async message => {
// we ignore messages arriving on the default websocket port, it is only for
// outbound broadcasts
console.log(id, `ignoring message on WS port`, String(message));
try {
const obj = JSON.parse(message);
// eslint-disable-next-line no-use-before-define
sendJSON(await inboundCommand(obj, req, id));
} catch (error) {
// ignore
}
});
}
}
Expand Down

0 comments on commit 7c670d9

Please sign in to comment.