clappski
Joined 10 years ago
Joined 10 years ago
1. Read the opcode
2. If PING, send PONG. If binary or text, continue
3. Read the header size
4. Read the payload size (which is a variable-width integer, hence getting the header size first)
5. Unmask the payload
6. If fin in the header is false, add the payload to a buffer. Else, either immediately dispatch (continuation buffer is empty) or buffer and dispatch (continuation buffer isn’t empty)
There are extensions like setting RSV1 to indicate you need to decompress the payload, but that’s the only one I’ve encountered building clients for >20 WebSocket based APIs.
On the other hand, you are going to struggle to implement bidirectional event based protocols without WebSockets that can be consumed by browser based clients or an application - WebSockets are very useful for that exact case, where you have a mixed client type consuming. It means you don’t need to offer web hooks and rest and some streaming protocol, you just have a WebSocket with some defined JSON message types without the rigmarole of distributing .proto or whatever.
In my experience this isn’t true; firstly you’re relying on an implementation detail of the platform that you’re executing on, of which you have no control over on the client side. Secondly, even if you aren’t opening a new connection per request, you’re still travelling through an entire HTTP stack implementation rather than the incredibly simple WebSocket protocol - effectively a length and a mask to get the contents, rather than some (in http1 land) fuzzy parser.
If you can guarantee you’re hitting http2 or http3 then you might be closer in latency, but due to the complexity of both I would imagine plain http1 negotiated persistent WebSockets provide the best latency.
I think a core thing that's missing is that code that performs well is (IME) also the simplest version of the thing. By that, I mean you'll be;
- Avoiding virtual/dynamic dispatch
- Moving what you can up to compile time
- Setting limits on sizing (e.g. if you know that you only need to handle N requests, you can allocate the right size at start up rather than dynamically sizing)
Realistically for a GC language these points are irrelevant w.r.t. performance, but by following them you'll still end up with a simpler application than one that has no constraints and hides everything behind a runtime-resolved interface.
> a pointer, whose size is always known
Yeah, this is exactly how it works. You work with a pointer that acts like a void* in your code, and the library with the definition is allowed to reach into the fields of that pointer. Normally you'd have a C API like
struct Op;
Op* init_op();
void free_op( Op* );
void do_something_with_op( Op* );
in the header provided by the library that you compile as part of your code, and the definition/implementation in some .a or .so/.dll that you'll link against.* f" something { my_dict['key'] } something else "
This works in Python already, allowing for nesting is a big QoL improvement- Templates (compile time), which are generic bits of code that are monomorphized over every combination of template parameters that they're used with.
- Virtualization (runtime), classic OO style polymorphism with a VTable (I think this similar to Rust's dyn trait?).
me <at> jakeclapp <dot> com