1// a pretend media server. it serves requests on its own, and the prompt is
2// there to poke at it meanwhile: try `server.`, `server.bs`,
3// `await delay(500)`, `/encode 3`, and `/deploy`.
4const console = log.scoped("http");
5const server = {
6 requests: 0,
7 bytesSent: 0,
8 visitors: new Map<string, number>(),
9 startedAt: new Date(),
10};
11
12new repl.Repl({
13 language: repl.javascript({ globals: { server, delay: async.delay } }),
14 commands: {
15 // "/encode 3" encodes three videos at once, each a bar under the prompt
16 async encode(args, node) {
17 const files = Array.from({ length: Number(args) || 2 }, (_, i) => `video-${i + 1}.mov`);
18 await Promise.all(files.map((file) => encode(node, file)));
19 },
20 // a blocking command has the terminal to itself until it is done
21 deploy: {
22 block: true,
23 complete: () => ({ from: 0, options: ["production", "staging", "preview"] }),
24 async execute(target, node) {
25 for (const step of ["build", "upload", "restart"]) {
26 using task = node.start(step);
27 for (const part of ["pages", "styles", "scripts"]) {
28 using _ = task.start(`${step} ${part}`);
29 await async.delay(250 + Math.random() * 250);
30 task.log.info(`${part} ok`);
31 }
32 }
33 node.log.info(`deployed to ${target || "production"}`);
34 },
35 },
36 },
37 prompt: "$ ",
38 style: "lines",
39 title: "media server",
40 placeholder: "javascript, or / for a command",
41 history: ".clover/repl-history",
42});
43
44async function encode(command: progress.Node, file: string) {
45 const size = (40 + Math.random() * 80) * 1e6;
46 using node = command.start(file, { total: size, units: "bytes" });
47 while (node.value < size) {
48 await async.delay(40 + Math.random() * 80);
49 node.value = Math.min(size, node.value + size * Math.random() / 25);
50 }
51 command.log.info(`encoded ${file}`);
52}
53
54// requests come in whether or not anyone is typing. `progress.start` is the
55// global progress display, which is drawn under the prompt as well.
56(async () => {
57 for (const path of cycle(["/", "/videos/7", "/blog/feed.xml", "/videos/7/stream", "/q+a"])) {
58 await async.delay(1200 + Math.random() * 1800);
59 const visitor = `10.0.0.${1 + Math.floor(Math.random() * 4)}`;
60 server.visitors.set(visitor, (server.visitors.get(visitor) ?? 0) + 1);
61 server.requests += 1;
62 server.bytesSent += Math.floor(Math.random() * 2e6);
63
64 const started = performance.now();
65 {
66 using node = progress.start(`GET ${path}`, { total: 12, showTotal: path.endsWith("stream") });
67 while (node.value < 11) {
68 await async.delay(30 + Math.random() * 60);
69 node.value += 1;
70 }
71 }
72 console.info(`GET ${path} 200 in ${Math.round(performance.now() - started)}ms`);
73 }
74})();
75
76function* cycle<T>(items: T[]) {
77 while (true) yield* items;
78}
79
80import * as async from "../async.ts";
81import * as log from "../log.ts";
82import * as progress from "../progress.ts";
83import * as repl from "../repl.ts";