[Node.js] HTTP2

https://btholt.github.io/complete-intro-to-realtime/intro-to-http2-push

  • long-running HTTP call
  • HTTP2 PUSH

 

FE:

async function getNewMsgs() {
  let reader;
  const utf8Decoder = new TextDecoder("utf-8");
  try {
    const res = await fetch("/msgs");
    reader = res.body.getReader();
  } catch (e) {
    console.log("connection error", e);
  }
  let done;
  presence.innerText = "🟢";
  do {
    let readerResponse;
    try {
      readerResponse = await reader.read();
    } catch (e) {
      console.error("reader failed", e);
      presence.innerText = "🔴";
      return;
    }
    done = readerResponse.done;
    const chunk = utf8Decoder.decode(readerResponse.value, { stream: true });
    if (chunk) {
      try {
        const json = JSON.parse(chunk);
        allChat = json.msg;
        render();
      } catch (e) {
        console.error("parse error", e);
      }
    }
    console.log("done", done);
  } while (!done);
  // in theory, if our http2 connection closed, `done` would come back
  // as true and we'd no longer be connected
  presence.innerText = "🔴";
}

A few things here:

  • We're still using fetch, but instead of just saying res.json(), we're opening a readable stream with getReader() and now we can expect multiple responses. Up front we're just logging out the first chunk we get back, but we can now expect that to respond multiple times.
  • We're using the green and red circle to show the user if they're still connected to the socket. If it's red, we know we've disconnected. If that happens, you just need to refresh the page. In a production app, you'd just need to reconnect a new socket and keep listening. But you can do that on your own time.
  • We need to decode the response that comes over the socket. That's what the utf8Decoder does.
  • I'm not making you do the POST again. It's the same logic as last time.
  • If you look at the network request in your network console, notice that there isn't a status code or anything. According to the browser, this request is still actually in flight.

 

BE:

import http2 from "http2";
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
import handler from "serve-handler";
import nanobuffer from "nanobuffer";

let connections = [];

const msg = new nanobuffer(50);
const getMsgs = () => Array.from(msg).reverse();

msg.push({
  user: "brian?",
  text: "hi",
  time: Date.now(),
});

// openssl req -new -newkey rsa:2048 -new -nodes -keyout key.pem -out csr.pem
// openssl x509 -req -days 365 -in csr.pem -signkey key.pem -out server.crt
// http2 only works over HTTPS
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const server = http2.createSecureServer({
  cert: fs.readFileSync(path.join(__dirname, "/../server.crt")),
  key: fs.readFileSync(path.join(__dirname, "/../key.pem")),
});

server.on("stream", (stream, headers) => {
  const method = headers[":method"];
  const path = headers[":path"];

  // streams will open for everything, we want just GETs on /msgs
  if (path === "/msgs" && method === "GET") {
    // immediately respond with 200 OK and encoding
    stream.respond({
      ":status": 200,
      "content-type": "text/plain; charset=utf-8",
    });

    // write the first response
    stream.write(JSON.stringify({ msg: getMsgs() }));

    // keep track of the connection
    connections.push(stream);

    // when the connection closes, stop keeping track of it
    stream.on("close", () => {
      connections = connections.filter((s) => s !== stream);
    });
  }
});

server.on("request", async (req, res) => {
  const path = req.headers[":path"];
  const method = req.headers[":method"];

  if (path !== "/msgs") {
    // handle the static assets
    return handler(req, res, {
      public: "./frontend",
    });
  } else if (method === "POST") {
    // get data out of post
    const buffers = [];
    for await (const chunk of req) {
      buffers.push(chunk);
    }
    const data = Buffer.concat(buffers).toString();
    const { user, text } = JSON.parse(data);
    msg.push({
      user,
      text,
      time: Date.now(),
    });

    // all done with the request
    res.end();

    // notify all connected users
    connections.forEach((stream) => {
      stream.write(JSON.stringify({ msg: getMsgs() }));
    });
  }
});

// start listening
const port = process.env.PORT || 8080;
server.listen(port, () =>
  console.log(
    `Server running at https://localhost:${port} - make sure you're on httpS, not http`
  )
);

 

posted @ 2022-07-15 19:43  Zhentiw  阅读(46)  评论(0编辑  收藏  举报