DEV Community

Paul Rumkin
Paul Rumkin

Posted on • Updated on

Plant: HTTP2 and P2P web server for Node.js and browsers.

Plant is a new WebAPI compatible HTTP2 web server. It's transport agnostic, highly modular, very secure by default and small: Plant's size is 8 KiB + optional node.js HTTP transport is 38 KiB (minified, gzipped) ; 74 KiB and 125 KiB respectively (unminified, ungzipped).

Plant was designed to use bleeding edge technologies, reduce complexity and make server portable. This portability gives you ability to write and test server-side APIs right in browser using only text editor. Plant has additional packages like http-adapter, router and bunch of http transports.

    const Plant = require('@plant/plant')
    const {createServer} = require('@plant/http')

    const plant = new Plant()

    plant.use('/greet', ({res}) => {
        res.body = 'Hello, World!'
    })

    createServer(plant)
    .listen(8080)

In-browser example

This is a very simple example of how it could work. It's just renders request into inlined iframe. It doesn't emulate browser. It's goal to show how to produce requests and emulate network connection with Plant.

Codesandbox · Preview

Details

HTTP/2-ready

Plant can push responses to the client using HTTP/2 resource push mechanics.

    plant.use(({res}) => {
        res.push('/js/index.js')
        res.push('/css/style.css')

        res.html('<!DOCTYPE html><html><head>...')
    }

WebAPI compatible

Objects like Request, Response, Headers and streams have the same or familiar interfaces that already exists in WebAPI. Plant's Request and Response are mirrored from the Client, that's why Request object has Response's method json().

    plant.use(({req, res}) => {
      req.url.pathname // "/"
      req.headers.get('content-type')
      res.headers.set('content-length', 5)
      res.body = 'Hello'
    })

    // Retrieve JSON with one single command
    plant.use('/echo', async ({req, res}) => {
      const body = await req.json()

      res.json(json)
    })

Plant is using ReadableStreams instead of Node streams. That's why it can work seamlessly in browser. For example in ServiceWorker.

Transport agnostic

Plant isn't tightly coupled with the Node.js http module server, instead Plant is using it as external dependency. You can easely create your own transport. That's why you able to deliver requests via anything: WebSockets, MessageChannel, raw TCP, WebRTC or even email (why not). It makes things extremely simple, especially your tests.

    const Plant = require('@plant/plant');
    const {createServer} = require('@plant/http2');

    const plant = new Plant();
    plant.use(({res, socket}) => {
      res.body = 'Hello, World!'
    })

    createServer(plant, {
      key: '...', 
      cert: '...',
    })
    .listen(443)

Create requests manually:

    const plant = new Plant()

    plant.use(({res}) => {
        res.body = 'Hi'
    })

    const url = new URL('http://localhost:8080/')

    // Create HTTP context's params
    const req = new Plant.Request({
      url,
    });
    const res = new Plant.Response({
      url,
    });

    // Request peer. Peer represents other side of connection.
    const peer = new Plant.Peer({
      uri: new Plant.URI({
        protocol: 'ws:',
        hostname: window.location.hostname,
        port: window.location.port,
      }),
    });

    // Create connection socket
    const socket = new Plant.Socket({
        peer,
        // If socket allows write upstream, then onPush method could be defined to handle pushes.
        // onPush should return Promise which resolves when response sending completes.
        onPush(response) {},
    });

    const handleRequest = plant.getHandler()

    handleRequest({req, res, socket})
    .then(() => {
        // Request handled. All requests (even faulty) should get there.
    }, (error) => {
        // Something went wrong
    })

Modular

Plant is trying to separate responsibility between modules and not to bloat own size. Everything, especially transport related, is moved out of server package and could be replaced with you own code.

Additional packages:

http Node.js native http module transport
https Node.js native https module transport
http2 Node.js native http2 module transport
https2 Node.js native http2 module with TLS transport
router Router package

Secure by default

Plant is using the most strict Content-Security-Policy out of the box. And this is the only web server which bring security first and doesn't sacrifice it. This policy doesn't allow webpage to do anything, even run a single piece of JS. Default value of Content-Security-Policy header is very denial and should be used in production to protect client and server from accidents. Developers should specify exact permissions which their site requires.

    const plant = new Plant({
      csp: Plant.CSP.STRICT,
    })

For development should be used Plant.CSP.LOCAL policy.

Router example

const Plant = require('@plant/plant')
const Router = require('@plant/router')
const {createServer} = require('@plant/http')

// User API router
const router = new Router()

router.post('/', () => {})
router.get('/:id', () => {})
router.put('/:id', () => {})
router.delete('/:id', () => {})

plant.use('/api/users/*', router)

createServer(plant)
.listen(8080)

References

Github · NPM

P.S.

I'm an author of this package, so you could AMA. Also, notify me about grammatical errors. I would very appreciate it.

Top comments (1)

Collapse
 
artydev profile image
artydev

Hy Paul

Thank you fro LPant.

I have no sucess when downloading zip project from CodeSanbox.
I got an error 'require string property" get null in router/index.js

Regards