Settings

Theme

Show HN: WebSocket-first development

github.com

120 points by jasonl99 9 years ago · 59 comments

Reader

Matthias247 9 years ago

I did a lot of websocket first development, since I ported various plain TCP based communication frameworks to websockets. You can do lots of cool stuff there, from plain request/response protocols that are faster than HTTP/1 to advanced stuff like publish/subscribe mechanisms and even protocols that keep the clients state automatically synchronized to the server side.

For advanced use-cases that's certainly a way to explore. For the average user I'm not sure whether I would recommend to go the route. You lose a lot of stuff that HTTP gives you out of the box, e.g. a request/response abstraction, dozens of frameworks and mechanisms for authorization, logging, etc. HTTP is also easily load-balanceable, while websockets are not. HTTP/2 in principal makes small request/response exchanges as fast as you could implement them with a custom websocket protocol, so there's no more gain. And SSE allow to push server updates in a form that still fits the HTTP model and frameworks pretty good. There are still lots of ways in which websockets can improve reactivity, but you have to invest a lot of engineering effort into it, which might not be worth it depending on the application.

marknadal 9 years ago

This is cool, but I'm a bit confused. Websockets have been around for a long time now, and there are tons of popular libraries (like socket.io, but more importantly, you should check out the high performance https://github.com/uWebSockets/uWebSockets instead).

Is all this doing trying to encourage awareness that using websockets is good? If so, awesome. Else, what is it? Because it does not discuss how Websockets lose state on reload. Your game will be in a completely wrong state. Managing and maintaining state is the important thing (which is what https://github.com/amark/gun does, which is of course also "websockets-first"). When you reload your game, it should return/remain as it was, or else half your players are going to see a half-broken system!

  • jasonl99OP 9 years ago

    Author here...Well, I started down the websockets path because of how awesome I found crystal to be. I wasn't intending to do this a few months ago, it just sort of took on a life of its own :)

    The framework as I've written takes state into account; in fact it was/is a design goal. There are three types of server objects currently - WebObject, StaticBuffer and DynamicBuffer. A WebObject stays instantiated on the server even when no users on viewing it (at some point it will be garbage collected).

    At any time, rendering that object will produce the exact representation as seen by someone who has viewed it and been updated with websockets. In fact, the card game is a good example of this. Start a game, draw a few cards, start another game, and then go back to the first. It'll be in the same state you left it in, complete with the events that occurred while you were gone. This was one of the top goals.

    DynamicObjects are just arrays of WebObjects in a configurable buffer - think of a list of sports scores, where each item handles its own update.

    StaticObjects are an array of simple strings of html (think of tail to view a log file).

    • marknadal 9 years ago

      ahhhh! Okay, yeah then that is super awesome and very impressive. You definitely should highlight/emphasize that more in the piece then. Cause that is super exciting, and the ideal way to build/have things work. FRP reactive streaming stuff, as the buzzwords go.

      So next up, how do you handle conflicts? Two users write to the same thing at the same time? Great work!

  • jessaustin 9 years ago

    If the site is hardcore SPA, and has a single "mysite.com/" URL, then you're right that state is only represented in terms of websocket events. However, if we have URLs like "mysite.com/users/alice" or "mysite.com/conversations/alice-bob-23", those resources will stay current, so that reloading them will reflect websocket events that took place since the last load. If the events are coming really quickly, there might be a race condition, but that's easy enough to solve by including a "last event" token in the page itself, that can be included in the websocket URL.

  • nohant 9 years ago

    If state matters, shouldn't we consider using Server Sent Events (SSE)?

  • mememachine 9 years ago

    Thanks for linking that lib btw

kahnpro 9 years ago

For everyone here who seems to be working with websockets, how do you deal with the potential that a browser goes offline and misses messages? How do you reconcile changed offline state with more recent events on the server? An OT library?

Do you have a way to cluster websocket servers so that events are propagated to all clients?

  • jhgg 9 years ago

    At work, we dispatch messages to clients using websockets (on a pretty massive scale).

    >How do you deal with the potential that a browser goes offline and misses messages?

    It depends on how long the browser goes offline for is it a transient disconnect (< 2 minutes) or is it an extended period of being offline. We deal with those two specific cases differently. In the first case, we use a sequence based system wherein each message contains an incremented counter. The server also holds a buffer of the last N messages sent to the client (N calculated by the expected velocity of messages being sent to the client). If the client disconnects, the server will keep that buffer alive for a few minutes (accumulating new messages in the buffer as well). The client can then reconnect, telling the server what the sequence number of the last message it received was. The server can then replay missed messages. In the case of an extended disconnect - we'd treat this as a fresh connection, where we do a full re-sync of state. The client then relies on the real-time event stream to keep its state updated.

    >Do you have a way to cluster websocket servers so that events are propagated to all clients?

    Yes. Our cluster is built in Erlang/Elixir and consists of several components for fanning out messages. For example, we're able to fan-out 1 message to ~25,000 clients in <0.1ms. (The use-case here is in a massive chat-room - we're able to fan out a message to all the connected users quickly).

  • AwesomeBean 9 years ago

    I'm working on a browser game, that does all communication to the game server through WebSockets.

    Here I have the clients ping the server every 2 seconds, and if I haven't received a message with in 10 seconds (including other messages than ping) I consider the client dead.

    Each socket is assigned to an individual player, and if that player opens a second connections the old one dies.

    To be honest - In regards to the missed messages and order of events, I just cross my fingers and hope TCP does that for me.

    In regards to the clustering, I have my map split up into sections, so most of the messages that needs to be send, are only to the people in that sector. So I rarely send a message to all players.

    I'm still experimenting, so I'll probably still have a lot of edge cases that I'll have to cover. But for now it seem to work well.

  • jasonl99OP 9 years ago

    I answered this on a different thread, but I've specifically designed things for this scenario. The framework has a class named WebObject. Once instantiated, it stays persisted on the server, and can take on additional subscribers.

    At any time, "updates-applied" browser version of the current state of the object can be created with a call to the #content method.

    So when a "updatable" occurrence occurs on the browser, you send an update method withe the changes. The changes are packaged up into messages that sent to each subscriber, where they modify the dom.

    It's a bit of a reversal, probably, with how people use frameworks that dynamically change content. I make the assumption that the server's instance is the only keeper of object state. Changes made on clients either change object state or they don't; if they are not sure, they send an event back to the server which handles it.

    A @WebObject can also have properties that are themselves WebObjects, and the current card game actually has three: ChatRoom, GameStats, and GameObserver.

    This pushes the responsibility for rendering content directly where it belongs: on the object where the rendering occurred in the first place.

    But it has the side benefit of keep object state, too. The CardGame doesn't have to worry about the state of the ChatRoom (though it can observer and send events to and from it).

    The bottom line is that there are only two ways the browser rendering could be wrong: 1) Some packets were missed, or 2) The developer didn't send the correct events, or sent them in the wrong sequence.

    The first case could be solved by creating a delivery confirmation layer over the objects.

    The second case is probably a little more dicey. The more data contained in a single updatable chunk, the harder it is determine when state changed. That's helped tremendously by the idea of nesting WebObjects (A Room has CardGames which have Players and Games, which have Decks and Cards....each of which take care of updating themselves)

  • Matthias247 9 years ago

    That's a good and important question. I think the solution depends on your application requirements. If all you need to do is get the updated current state in the browser then it's enough if the browser resubscribes after each connect. It would then get pushed the new current state and can display it. If you are not only interested in a current state but all state transitions (or events that describe them) then it gets harder, since you would need to store all the events somewhere on server side, and only remove them once a browser has acknowledged that they have been consumed. In such a model the difference to fetching the events via HTTP would probably not be too big.

    • mr_luc 9 years ago

      Yeah - if I had to handle requirements more towards the painful end of the scale I would probably say Agent + Event Store. Ie, ephemeral process(es) representing the user on the server side, with the ability to cache up to a certain amount of outgoing messages, respond to user's acks, and also to be wound down if the client hasn't consumed for too long. The Agent is a natural place for decisions about how, or if, to attempt to get the user 'caught up.'

anilgulecha 9 years ago

I think websockets as you're using it is a stand-in for realtime-first development.

This territory is well explored -- services like firebase/parse are a testament to many usecases.

IMO, this isn't per-se a new paradigm in the likes of offline-first, which is advocating for local application cache/data synced with a remote store, this offering the benefit of offline apps, with the sync/realtime benefits of network connected apps.

  • k__ 9 years ago

    > this isn't per-se a new paradigm

    If you look how regular websites work, I think it is.

    In my last project, people were kinda baffled, why the changes they made, didn't propagate to the other users instantly.

    5 years ago everyone knew you had to refresh a page to see changes. Today everyone seems to expect these changes to be pushed to them.

    It also was a fight to get this whole stuff working with React and Redux. So I think, if you want to build a good realtime site, you have to think about this stuff before choosing libraries, if you want to get good results.

    For example, GraphQL has subscriptions that can be delivered via Websockets.

    • jon-wood 9 years ago

      It surprises me getting it to work with React and Redux was a struggle, they seem like the perfect tools to handle a stream of state updates coming in realtime. You could practically dispatch client side events straight from the websocket stream to update local state.

      • catshirt 9 years ago

        almost commented the same thing earlier. i was surprised how manual adding multiplayer support was for redux/react.

        the solution was still pretty simple, for my app: 1. middleware that sends all actions to a server 2. websocket client that dispatches actions when they receive them. this works, but doesn't actually reconcile or keep data on the server which isn't suitable for most applications.

        anyway yeah, i was surprised there was no robust or standard library...

      • k__ 9 years ago

        The problem was more the frequency of updates, than the integration.

        I don't know, after working a year with Redux, it never grew onto me. Always felt clunky :\

    • elcapitan 9 years ago

      Are there actually any patterns yet in how a stream of updates is being pushed to the frontend? In a way that would be like the frontend subscribing to an event queue through a websocket, I would imagine.

  • caleblloyd 9 years ago

    I think that realtime-first is correct. Websockets is just a protocol that support bidirectional communication. Plain old HTTP requests paired with Server Sent Events over H TTP/2 are technically bidirectional communication over the same connection too.

    Google is also working on GRPC for the browser. Tomorrow it could be a completely different protocol, but it's all in the pursuit of making the app more realtime.

  • jasonl99OP 9 years ago

    Yes, you are completely right. It should have been titled "Realtime-first development with Websockets".

  • djmashko2 9 years ago

    If you're interested in Realtime-First development, Meteor has been doing it since 2013 and is very production-ready at this point: https://www.meteor.com/

ocharles 9 years ago

It's a shame Server-Sent Events aren't universally supported. I find them a lot simpler than WebSockets - modifications are just normal HTTP calls, and then you can broadcast the actual result of the transactional call back down with a SSE. But Edge doesn't support them :(

  • drdaeman 9 years ago

    Based on http://caniuse.com/#feat=eventsource only IE/Edge doesn't have it, and there are polyfills for those, e.g. https://github.com/Yaffle/EventSource/blob/master/README.md

    Or there are more problems?

    Want to use SSE for one small project soon (haven't used it before, and don't want Websockets as they're an overkill for a basic event stream), would appreciate any info about how things could go wrong.

    • taf2 9 years ago

      Again imagine if MS focused on user experience and used blink / WebKit - instead of rolling their own.

      We'd have a more standard and open web with fewer set backs. More engineers working on the same engine. A larger team working on a shared native application for everyone to build on for them to sell ads too. It even makes good business sense for them now. One can dream right?

      • striking 9 years ago

        Edge has much better battery life than other browsers on Windows, much like Safari on Mac. Neither of those are the best browser, but when my choice is between six or ten hours of battery life, it's easy for me to decide.

        I dream that one day Webkit / Blink will actually be efficient.

        • nacs 9 years ago

          > I dream that one day Webkit / Blink will actually be efficient.

          Apple's Safari uses Webkit and does get increased battery life on Macs. Theres no reason MS couldn't do the same with Webkit and optimize for battery life using the same engine.

    • ssfak 9 years ago

      I believe in the majority of cases SSE are a better technology than web sockets (e.g. WS do not seem to conform to HTTP/2.0, SSE offer transparent (browser-supported) reconnects, SSE event ids allow for replay, etc). The downside is no support in IE/Edge (you can vote at https://goo.gl/bd8V1K), no binary content (but again usually JSON is enough) and no fully bidirectional communication.

      A good talk at the matter can be found at https://youtu.be/NDDp7BiSad4

    • ocharles 9 years ago

      Yea, it's only Edge, but if you look at WebSockets you'll see that everything except Opera Mini supports them - so you can get (some of) the same functionality without polyfills at all.

      • drdaeman 9 years ago

        But why willingly introduce extra complexity to the system?

        Websockets are way less trivial than SSE. Polyfills for a less popular browser seem to be a simpler solution to the problem, compared to websockets stack everywhere. Even if I don't have to implement websockets on my own and would use some mature library (because bugs happen even in such libraries).

  • api 9 years ago

    Speaking of web sockets and simplicity:

    I never understood why web sockets aren't just a straight break out of HTTP(S) to a naked TCP or SSL socket. Why all the extra complexity? All it does is degrade performance and bloat software.

    I hate the way protocols are designed by committee, virtually guaranteeing that everything has layers upon layers of cruft that have never actually been needed by anyone for anything but must be implemented because it's in the spec.

    • hackcasual 9 years ago

      For one you need some concept of same origin/CORS. Don't want web sockets to provide an easy DoS vector

      • api 9 years ago

        That would be in the headers and prior to protocol breakout. I'm talking about after that. Why does web sockets have an extra layer of framing and other nonsense? Not to mention other "modes of operation." It's a socket. Give me a socket.

        • toomim 9 years ago

          No kidding. It's just a new version of TCP, implemented on top of HTTP, which is implemented on top of TCP.

        • lisivka 9 years ago

          Because of firewalls, proxies, and NAT.

          • api 9 years ago

            You already have a TCP connection, so NAT and firewalls don't matter here. Proxies have to be web socket aware anyway.

  • hardwaresofton 9 years ago

    Does anyone know why Edge would make that kind of decision? SSE has been supported forever.

  • douche 9 years ago

    For this sort of thing, albeit only with a .NET backend, I've always used SignalR to handle these browser/server OS incompatibilities. It'll negotiate a common protocol, whether that be WebSockets, SSE, or falling back to long-polling.

    • Matthias247 9 years ago

      However SignalR does not give you access to a raw websocket, in the same sense that socket.io does not give you one. Both are higher level abstractions that can use websockets as a transport, but are not restricted to them. If you want to run a custom protocol on plain websockets, e.g. because it's already well-defined what the other peer speaks/understands, then both are not applicable. If you don't care whether your realtime application is portable to another framework then yes, SignalR and socket.io are very viable solutions.

    • oldmanhorton 9 years ago

      When I checked probably around 6 months ago, SignalR seemed to have no real story for ASP.NET Core and it also depended on jquery on the frontend, which I found to be simply ridiculous.

JangoSteve 9 years ago

Cool demo! I like that you can see all the events in real-time. I built a similar demo (https://os.alfajango.com/websockets-demo/ and https://github.com/JangoSteve/websockets-demo) a while ago to showcase using websockets with a node app for a presentation I was giving at a JavaScript user group (https://os.alfajango.com/websockets-slides/).

The presentation goes through a bit of the history and lead-up to websockets for context.

One of my favorite things about the demo for the presentation was that everyone in the room (probably about 50 people) was able to connect to the live demo in real-time and some people started finding XSS vulnerabilities in it right away, and using the app to send emojis and pictures to others in the room in real-time (all harmless stuff). It was crazy though how many events you could see streaming through the app just running from my laptop with so many people connected and interacting at once (including x,y coordinates for mouse movements a few times per second per user as well).

The presentation goes through alternatives as well, including long-polling, short-polling, and server-side events (the slides are laid out in two dimensions, so pay attention to when you can hit the "down" arrow instead of the "right" arrow for more slides). It also delves a bit into the actual websocket spec. It was a while ago though, so it doesn't go into anything HTTP2-related.

slindz 9 years ago

I have two projects on the go right now. Both have gone the web sockets first approach. I love it.

For me, the project I keep rebuilding to learn new languages/frameworks is Risk (though it's been quite awhile now).

After spending so much time with Phoenix channels on my main project, I couldn't resist the fun of reimplementing it over sockets. It'll probably wind up on GitHub when I'm done.

  • Kexoth 9 years ago

    But isn't channels the Phoenix abstraction (middleware) for websockets/sockets per se?

    What I mean is why not write a sockets Phoenix channels transports adapter to be used instead of the default one & continue using the existing codebase? :)

    • slindz 9 years ago

      Sorry! Reading my comment back, it's no wonder it was tough to follow.

      Instead of writing: "I couldn't resist the fun of reimplementing it over sockets."

      I should have wrote: "I couldn't resist building another version of Risk with Phoenix/channels/sockets because it should be mind-meltingly easier compared to all of the previous times I'd built it on other platforms"

    • lilactown 9 years ago

      I assume the OP is talking about reimplementing the channels client. Though now I'm not sure.

hardwaresofton 9 years ago

Is it wise to get hooked on websockets given that they're disappearing in HTTP2? I assume lattice is not tied to websocket as the implementation, and can switch to doing things over webrtc data (which would be even better for games since webrtc data can use UDP)

  • Matthias247 9 years ago

    Websockets won't disappear!

    It's just the case that you can't upgrade from a HTTP connect/request to a websocket connection, since at that point the connection is already in HTTP/2 mode and both HTTP/2 and websockets need complete control over the TCP stream.

    But: You can still make a HTTP/1 request to the server and upgrade to websockets. Both servers and clients will speak HTTP/1 in parallel to HTTP/2 for a loooong time - most likely forever.

  • jasonl99OP 9 years ago

    Short answer, I don't know. I don't know that they'll disappear, they are just so efficient.

    That said, lattice is completely event-driven. The #on_event and #emit_event methods act as entry and exit points. As long as I can have string come and go between the server and client, HTTP2 would be fine.

    This seems like a pretty good article covering it

    https://www.infoq.com/articles/websocket-and-http2-coexist

laser 9 years ago

Just a heads up the the crystal language link is mis-routed in the readme.

edibleEnergy 9 years ago

Very cool demo. We did a demonstration of PornHub using WebSockets to bypass ad blockers[1] a few months ago., though that's more WebSocket-fallback development ;)

http://blog.bugreplay.com/post/152579164219/pornhubdodgesadb...

fiatjaf 9 years ago

Can a normal web server (a Heroku dyno, for example) with a normal websocket library (https://github.com/gorilla/websocket, for example) support multiple users connected at the same time?

How many in comparison to the number of normal web clients that do some XHR call from time to time?

  • JangoSteve 9 years ago

    It can. I have a demo deployed on Heroku for this app (https://github.com/JangoSteve/websockets-demo), and I've seen it handle maybe about 40 or 50 clients at a time on a single dyno before (and each client streaming a bunch of events), even though it's intentionally implemented in the simplest (i.e. inefficient) fashion possible. The request overhead for normal HTTP request/response goes away, since you only need the request headers and handshakes once at the beginning for a persistent connection.

    • jasonl99OP 9 years ago

      I'm trying to get this demo working on an extremely inexpensive plain VPS (running Debian 7), and I'm getting "WebSocket connection to... failed: Invalid frame header". Is that what you saw when you as a result of hitting the 40-50 client limit? I am getting the error on a first connection, so it's probably something different.

      • jasonl99OP 9 years ago

        I'm going to answer my own question - I tracked it down to a bug in lattice-core that occurred when it couldn't parse incoming JSON, and for some reason it killed the socket server. I fixed it by rescue the JSON parse and all is good now.

  • jasonl99OP 9 years ago

    I haven't explored that a lot, but my thought was that the amount of setup/teardown for polling goes away, so it's at least an order of magnitude (guessing) better.

    I have written the framework to use a single socket connection for all updateable objects on the page.

arca_vorago 9 years ago

I think the key point is that web sockets are a TCP connection with a http prenogotiation. If you need UDP style connections (OK to miss state changes as long as it catches up) WebRTC is where it is at AFAIK.

vr46 9 years ago

Looks interesting - NB: Broken link in README that should go to https://crystal-lang.org/

nilved 9 years ago

WebSocket first seems to be the wrong way to go about this. How about build your app to accept stdin and stdout, then expose this as TCP, HTTP or whatever else?

Keyboard Shortcuts

j
Next item
k
Previous item
o / Enter
Open selected item
?
Show this help
Esc
Close modal / clear selection