Skip to content

Instantly share code, notes, and snippets.

@wesleytodd
Last active September 18, 2020 15:55
Show Gist options
  • Save wesleytodd/e5642c0d39fa71bdebf8ef31ddbd5e40 to your computer and use it in GitHub Desktop.
Save wesleytodd/e5642c0d39fa71bdebf8ef31ddbd5e40 to your computer and use it in GitHub Desktop.
Just noodling on the future Node.js and http
'use strict'
// Notice no certs, one thing I have thought
// for a long time is that frameworks should
// directly have support for spinning up with
// a cert if none was provided.
require('h3xt')()
.get('/', (req) => {
// Access the associated session
// req.session
// No need to check if can push, frameworks does this
// The framework has something like a ROOT so you can
// resolve static files, but the content type negotiation
// is something I think belongs in the underlying core api
req.pushFile('favicon.ico')
req.pushJSON('/foo.json', {
message: await req.body()
})
// Frameworks could do whatever templating they saw fit,
// delivering the resulting string/buffer to `req.respond()`
// In this example I am assuming a "return response" approach,
// and the `.sendFile` call would return a `Response`
// object, and the middleware/routing layer would use that
// object to actually send
return req.sendFile('index.html', {
// template data
})
})
.listen(8443)
'use strict'
const http = require('http-next')
const fs = require('fs')
http.createServer({
allowHTTP1: true,
allowHTTP2: true,
allowHTTP3: true,
key: fs.readFileSync('localhost-privkey.pem'),
cert: fs.readFileSync('localhost-cert.pem')
})
.on('session', (session) => {
// I am not even sure what an end user would do with session.
// Is it ok to store application state on? Like if a user cookie
// was present in a request, could you load the user metadata onto
// the session object?
// Seems like a very useful feature with session pinning on your LB
// and something like session.locals = Object.create(null) where users
// could load on to.
// And none of this would apply in http1, so there is also that to consider.
session.on('stream', (stream, headers, flags) => {
// Do a push stream if allowed
// This covers the cases for no support in http1, http2 settings
// and could even go from true to false when http3's max push is hit
if (stream.pushAllowed) {
stream.pushStream({
path: '/favicon.ico'
}, (err, pushStream, headers) => {
if (err) {
throw err
}
// Even in HTTP1 noone liked the way statusCode works!
// I honeslty think we can do it all in one method signature
// again with this, it is like 90% uf use cases
pushStream.respond(200, {
'content-type': 'image/x-icon'
}, ourPreloadedFaviconBuffer)
})
}
// A conviencence method for consuming the entire body
// This covers like 90% of use cases
// Also, we could add .consumeAsJSON to map to the new .respondWithJSON?
stream.consume((body) => {
// Always check again, because if we hit the max streams this would change
// Actually, I wonder if it would be better for `stream.pushStream` to do
// this check internally then just return false if it was not allowed?
if (stream.pushAllowed) {
stream.pushStream({
path: '/foo.json'
}, (err, pushStream, headers) => {
if (err) {
throw err
}
// Again with this, it is like 90% uf use cases
pushStream.respondWithJSON({
message: body
})
})
}
// The basic response with html
stream.respondWithFile('index.html', {
// This should be infered from the file name if not specified
// 'content-type': 'text/html; charset=utf-8'
})
})
})
})
.listen(8443)
const http = require('http-next')
module.exports = function (opts) {
return new Application()
}
class Application {
constructor () {
this.stack = []
for (const method of http.METHODS) {
this[method] = this.use.bind(this, method)
}
this.server = http.createServer()
this.server.onRequest(this.onRequest)
}
use (method, path, fnc) {
this.stack.push({ path, method, fnc })
}
async onRequest (ctx) {
// Accept and parse bodies
if (ctx.method !== 'HEAD' && ctx.method !== 'GET') {
// Read body
const _body = []
for await (const chunk of ctx.body) {
_body.push(chunk)
}
let body = Buffer.from(..._body)
if (ctx.headers['content-type'] === 'application/json') {
body = JSON.parse(body.toString())
}
ctx.body = body
}
for (const layer of this.stack) {
if (layer.method && ctx.method !== layer.method) {
continue
}
if (layer.path && ctx.path !== layer.path) {
continue
}
let statusCode = 200
let headers = {}
let body = null
const ret = await layer.fnc(ctx)
if (ret === undefined) {
continue
}
if (Number.isFinite(ret)) {
statusCode = ret
let msg = ''
switch (statusCode) {
case 200:
msg = 'Success'
break
case 404:
msg = 'Not Found'
break
case 500:
msg = 'Server Error'
break
}
body = Buffer.from(msg)
headers = {
'content-type': 'text/plain',
'content-length': body.length
}
} else if (typeof ret !== 'undefined' && typeof ret === 'object') {
body = Buffer.from(JSON.stringify(ret))
headers = {
'content-type': 'application/json',
'content-length': body.length
}
} else if (typeof ret === 'string') {
body = Buffer.from(ret, 'utf8')
headers = {
'content-type': 'text/plain',
'content-length': body.length
}
}
return ctx.respondWith(statusCode, headers, body)
}
}
listen (...args) {
return this.server.listen(...args)
}
}
@wesleytodd
Copy link
Author

A few proposed updates from our call:

transport protocol core working group user
tcp http quic compat api high level generic api framework api
tls https
quic
httpNext.createServer({})

http.createServer({
  protocol: 'quic+http1',
  port: 1234
}).on('session', (sess) => fmw.emit('session', sess))

http.createServer({
  protocol: 'https'
}).on('session', (sess) => fmw.emit('session', sess)) 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment