The web is great, but it was designed around showing hyperlinked documents. What if the web technologies were designed a) with the benefit of hindsight, and b) primarily for applications instead of documents?
This is a proof of concept exploring what an application-first web platform might look like.
You'll need a Rust development environment. See the Getting Started instructions on Rust website for details about setting that up.
Pinhole uses TLS for all connections. Before you can run anything, you'll need to generate some certificates.
For local development, there's a script that'll generate a self-signed certificate:
./scripts/generate_dev_cert.sh
This creates cert.pem and key.pem files that are valid for a year. They'll work fine for localhost development, but obviously don't use these in production.
If you want to deploy this for real (which you probably shouldn't, since it's just a proof of concept), you can use Let's Encrypt to get proper certificates. Install certbot, run certbot certonly --standalone -d yourdomain.com, and point your server at the certificate files it generates in /etc/letsencrypt/live/yourdomain.com/.
To run the TodoMVC server in a terminal:
cargo run --bin pinhole-todomvc-example
Currently it's hardcoded to always listens on port 8080.
In a separate terminal, run the Pinhole Client:
cargo run --bin pinhole-client
Currently it's hardcoded to connect to a server on 0.0.0.0:8080, so it should now connect to your server.
When the client connects, it will first show a login page. Entering an email and password then clicking Sign In will send you to a list page (authentication is faked, but you will see in the terminal that the server receives the information you enter).
On the list page you will see a couple todo items. You can click their checkboxes and an action will be sent to the server. It doesn't currently persist the changes.
If you install Cargo Watch with cargo install cargo-watch, then you can start a hot reloading server like this:
cargo watch -x 'run --bin pinhole-todomvc-example'
Now you can leave that running in a terminal. It will watch for code changes, and recompile and restart your server as necessary.
The goal is to explore what would happen if we took the best ideas out of the Web as a delivery platform, then started from scratch on a platform for delivering applications rather than documents.
Pinhole maintains a persistent TLS-encrypted connection to allow for bidirectional messaging. It does not have a request-response cycle: instead, either the client or the server can message each other at any time. The server can, for example, send multiple view updates as loading progresses or in response to server-side events.
The protocol is designed so that all state is maintained client-side so that this connection can be terminated and reconnected at any time with minimal user impact, and so that the server is compatible with load balancers without needing sticky sessions.
The messages are transported by length-prefixed CBOR (Concise Binary Object Representation) datagrams. This was chosen because it has flexible, JSON-like semantics but it's compact and fast to generate parse.
The protocol uses capability-based versioning to allow clients and servers to negotiate supported features and evolve gracefully over time.
Load: Request that the server send the UI state for a new URL. The server should then start processing that route and respond with a message such asRenderto update the display, orRedirectToto send the client to yet another URL. ALoadmessage is sent whenever a client reconnects.Action: Notify the server that an action has taken place, such as a button being clicked or other form element being changed.
Render: Tell the client to update its display to show a new document.RedirectTo: Request that the client switch to a new URL. The client will respond with aLoadmessage for the new URL. The current URL is persisted client-side.Store: Tell the client to update its storage with a key-value pair.
Some view components, such as buttons and input fields, have events for when they are clicked or modified. When these events occur, the client will send an Action message to the server.
Actions are used in Pinhole whenever you would use a POST, PUT, PATCH, DELETE request in HTTP. URL navigations are used whenever you would use a GET request in HTTP.
The client keeps the following state locally about its connection:
- Current URL
- Latest document to be rendered.
The storage system is modelled after the Cookie system in HTTP where keys are written by the server, stored on the client, and sent back to the server in requests. But it's significantly improved to fix some of the problems with cookies, and to also fill in the use cases for persistent cookies, session cookies, local storage, and form state.
- Data can be stored in one of three scopes: persistent (saved across app restarts using atomic writes for crash safety), session (cleared after app restart), or local (cleared on page navigation).
- Form elements persist their values in storage at the 'local' scope.
- Stored data is sent to the server in
Actionmessages. Actions can choose exactly which set they want sent, to avoid the problems HTTP has with cookie bloat.
Pinhole's client uses Iced for rendering its views. When the client receives a Render message, it updates its current document and from then on renders that document on each frame.
- Add more node types -- media, grouping, links (then again, we have buttons so maybe HTML-like links aren't necessary?).
- Add UI chrome -- a navigation bar? status bar?
- Embed an extension language so servers can be written in e.g. Javascript via Deno.
- Client-side action handlers by shipping Javascript bundles.
- Polling? Server asks client to refresh page at some point in the future.
- Subscriptions. Rough sketch: Server sends client a subscription list, which client then subscribes to. When events occur server-side on one of these channels, server asks client to refresh.
These are ideas that I specifically don't plan on implemementing.
- A React-like component model. I think any encapsulation like that can be done server-side on top of the existing model.
- Incremental page updates. Currently the whole-page update model is very simple and works a lot like Turbolinks, which is good enough for almost any page.