Konstantin Haase

Real Time Rack

Real Time Rack

by Konstantin Haase

In this talk titled "Real Time Rack," Konstantin Haase explores the concept of real-time web responses using Rack, a web server interface for Ruby. He begins by acknowledging the support from Engine Yard for sponsoring his attendance at the Rocky Mountain Ruby 2011 event and shares his background, including his work on Sinatra and involvement with the Rack core team.

The discussion defines the evolution of web communication, highlighting the shift from traditional page requests (like HTTP) to modern asynchronous methods such as Ajax and Comet. However, he emphasizes a true real-time approach that involves the server pushing data to the client without repeated requests from the client, leveraging technologies like Server-Sent Events (SSE) and WebSockets.

Key points include:
- Understanding Rack: An overview of how Rack operates as a bridge between Ruby and HTTP, outlining the structure of a basic Rack application, which processes requests through a middleware chain.
- Middleware Demonstration: An example of middleware that modifies the body of a response to uppercase, showcasing how middleware can interact with Rack applications.
- Streaming Responses: An explanation of how to implement streaming in Rack applications, allowing for real-time data transmission, using a messaging service setup as an example.
- Evented Streaming: Introducing EventMachine for implementing event loops to manage multiple connections efficiently. This resolves scalability issues observed with non-evented approaches that block threads with each request.
- Async Sinatra: Discussion of how to enhance Rack applications by using a library that simplifies asynchronous processing without manual signaling.
- Service and Events: Exploration of a simpler HTTP standard for data streaming compared to WebSockets, focusing on the ease of implementation and automatic reconnections for clients.
- Websockets and HTTP/2: Brief mention of websockets as a two-way communication method and the upcoming HTTP/2 protocol, which allows multiple simultaneous requests over a single connection.

In conclusion, Haase stresses that while WebSockets are powerful, Service and Events provide a more manageable and straightforward alternative for pushing data to clients. His presentation encapsulates the potential of real-time communications in Ruby applications, demonstrating with practical examples how they can be implemented effectively.

00:00:10.480 Hi everyone. Oh, hi! Hi, Wayne. Okay, um, my name is Konstantin and I'm going to talk to you about real-time Rack.
00:00:15.880 But first, I want to thank Engine Yard for sponsoring my trip here. I'm really grateful for that; otherwise, I couldn't be here to talk to you.
00:00:29.800 A bit about me: I'm on GitHub and on Twitter, and I have a blog like probably many of you. Back in Germany, where I'm originally from, I'm a student at the Hasso Plattner Institute of IT Systems Engineering, which is in Potsdam, close to Berlin. If you ever want to visit, I'd be happy to show you around.
00:00:49.920 In my free time, I'm also maintaining Sinatra. I don't know if you've heard of it; it's quite popular. I'm also maintaining a fork or a clone called Almost Sinatra, which is probably going to be more popular than Sinatra soon because it's only eight lines.
00:01:33.920 I recently started working on a book about Sinatra, which is supposed to be released next month if I can continue writing on time. If you want to learn more about Sinatra, please get that book at 'Sinatra Up and Running.' I'm also a member of the Rack core team, and I'm going to talk a bit more about what Rack is in case you're not fully aware of that.
00:02:03.680 Currently, I'm doing an internship in Portland, also sponsored by Engine Yard, working full-time on Rinas, an alternative Ruby implementation, in case you haven't heard of that.
00:02:11.400 Okay, this talk is about breaking the laws of Rack. But why do we want to break the laws of Rack? Back in the day, the web worked like this: you put in a URL, requested a page, and got a response back from the site, which your fancy web browser displayed. But that's pretty boring.
00:02:25.519 So, along came Ajax, doing all those fancy requests where you don't really notice data being fetched while you're on the page. Ajax is really the client requesting data from the server, but in some cases, you want the server to push data to the client. People came up with something called Comet, which is basically Ajax but keeps polling the server to get messages.
00:03:06.000 However, this isn't what I mean when I talk about real-time. Real-time means streaming from the server to the client and pushing data at any time without the client having to request it each time. The basic idea is to decide what to send while streaming, not upfront. This is particularly useful for streaming APIs, like what Twitter does. If you've written a Twitter client, you've probably come across this, or Service-Sent Events.
00:03:45.400 You've probably heard about WebSockets, which are basically the foundations for implementing web circuits over Rack. But I'll start with a demo. You've all probably seen this prompt before, which can calculate stuff or write out code. This is basically a clone of IRB, and the cool thing is, it's running in my web browser.
00:04:00.960 What makes it really fancy is that it's sending the data to the server—the string to evaluate—and then the server evaluates it and returns the result to the client using Server-Sent Events, which is secure, but for a demo, it's fine. The real fun begins when I fire up a second browser, which could be on another machine. When I run some code here, it actually sends it out to both clients—or all clients if you'd connect to that port. I have a firewall on, so don't even try.
00:04:51.479 So, this is basically what I'm talking about when I mention real-time Rack, and I'm going to show you how to do this. First, let’s ensure we’re all on the same page regarding how Rack works internally. Rack is essentially a Ruby to HTTP to Ruby bridge, serving as a specification for translating HTTP requests into Ruby objects and back into HTTP responses.
00:05:41.400 It's also a middleware API and the foundation for basically every Ruby web framework out there, including Rails, Sinatra, and so on. The basic structure looks like this: you have the HTTP client, which is usually your browser but can be anything else, talking to what we call the Rack handler, essentially a web server like Thin or Mongrel.
00:06:01.680 The handler is responsible for translating from HTTP and then using the Rack protocol to communicate with any middleware. The so-called endpoint is what you typically define when writing your Rails application or Sinatra application, where Rails and Sinatra set up some middleware in front for all the routing and filtering.
00:06:36.039 What a basic Rack application looks like, if you're not using any framework, is essentially a proc that takes an ENV (an hash of the past HTTP headers and some environment information) and then returns an array with three elements: the first is the status code, the second their headers as a hash, and the last one is an object that loops through strings that will be sent to the client.
00:07:04.919 The object returned doesn't have to be a proc; it can be any object that responds to 'call' and takes in ENV. I'll use a bit of Sinatra during my talk to cut down on boilerplate, but Sinatra really embraces Rack. You could just return the response here for clarity.
00:07:31.759 Now, what the handler does is pause the HTTP response into an ENV and then calls 'call' on that. After that, it sends out the headers and calls each on the body object, then closes the connection. This overview reflects the general code structure you'll find in most Rack handlers.
00:08:11.560 Before I discuss streaming, let's take a look at middleware. This is a sample middleware that takes the body of your Rack endpoint application and converts everything into uppercase. Who doesn't want that, right?
00:08:51.480 What it does is store the endpoint or the next middleware in the middleware chain since you can use more than one middleware. When it gets a call, it will send that call to the application. It could modify the ENV or keep a list of applications to perform other nice actions, but that doesn’t matter for streaming.
00:09:33.040 Then it calls each on the body object, uppercases everything, and returns the result. Note that I'm not using 'map' here as that would violate the Rack specification since it's not guaranteed that the body object responds to 'map'.
00:09:36.480 This is how you would set up this middleware using a 'config.ru' file. If you have a Rack application, it's usually already there, or you could use some Rack or Rails interfaces to add middleware. The server's responsibility, instead of using the previous line from the Rack handler code, is now to call 'new' on the middleware, passing the endpoint to it, and then calls 'call'.
00:10:06.839 If you have any questions about this setup, please ask now. I want to ensure we're all on the same page. I once gave this talk at RubyConf and people mentioned they lost the thread halfway.
00:10:59.680 So let's dive into streaming. The body object must respond to 'each', but other than that, it can be anything. This allows us to implement some streaming. A simple application could send the current time 20 times, always waiting one second in between each send. If you use the browser or Curl, you can observe that the response comes in bit by bit with a one-second delay.
00:11:53.000 Once again, I’m using Sinatra for simplicity here. But let’s do something fancier: a messaging service where you have a list of clients to whom you can send data.
00:12:09.760 The goal is to maintain a list of subscribers. Whenever someone opens a connection, we keep it open, and whenever you post to the application, it should send a message to that connection.
00:12:32.240 Here's a sample approach: The subscriber will wait for new messages to come in using a sleep, which blocks the current thread. Then, whenever we send out data, we wake up that thread which we had stored when 'each' was called, and send the data.
00:13:03.760 This works fine on some servers, but there are issues. It can block the current thread with the sleep, which becomes problematic if you don't have a separate thread for every request.
00:13:50.120 Typically, if you have a server using a thread pool, it will handle requests simultaneously only based on the size of that thread pool, which can limit scalability.
00:14:11.840 It does not work well with certain middleware; for example, if we chain the uppercase middleware in front of it, it wouldn't work because the uppercase middleware waits until each completes before it sends its result to the handler.
00:14:56.120 This means you would never see any result. Additionally, it doesn't perform well with evented servers like EventMachine or Rainbows. So, let's explore how we can improve this scenario.
00:15:17.440 My presentation is actually running on Thin, so it must work somehow. The solution is to adopt evented streaming, avoiding a thread per request.
00:15:54.080 In this context, 'evented' means implementing an event loop, typically run by EventMachine. This is essentially a continuous loop that checks for registered callbacks triggered whenever new data or connections come in.
00:16:14.360 Data that needs to be sent out is registered, and you perform all actions in callbacks registered throughout the stream. This approach allows for scalability to handle a large number of connections.
00:16:45.920 If you take this non-evented application, you'll notice it waits 10 seconds, only firing the request to Rack afterward. In contrast, when using EventMachine, you can set up an event loop to trigger callbacks independently of delaying responses.
00:17:30.960 You can use timers to trigger callbacks after certain intervals, managing both the event loop and incoming requests seamlessly, allowing for concurrent processing in a single-threaded environment.
00:18:18.560 Now, bringing this back to our Rack application, we might leverage a nifty trick called the Async callback, where we send out our Rack response (the status code, headers, and body) whenever our timer callback is fired.
00:19:15.320 This requires a little magic to signal the Rack handler that we're postponing the response. The handler will be informed to proceed with the event loop while we handle other functionalities.
00:20:07.680 There are multiple ways to implement this: one is using 'throw' for skipping middleware and directly reaching the Rack handler or returning a status code of -1, which is generally cleaner.
00:20:41.600 Also, there's a nice library called Async Sinatra, which includes the necessary async methods (for GET, POST, etc.) to avoid manual signaling to the Rack handler for asynchronous execution.
00:21:56.080 When using Async callbacks, we can make calls to other systems such as querying a database or making HTTP requests while still processing requests concurrently.
00:22:39.560 Now, consider an application designed for service and events, which are essentially a new standard for HTTP from W3C. It's a one-way channel, allowing you to implement a socket-like functionality without the complexities of websocket settings.
00:23:21.680 The benefit of service and events over websockets is the simplicity of the implementation, especially in browsers like Firefox, Chrome, and even older ones like Internet Explorer 7, with automatic reconnections.
00:24:00.960 For client implementation, you can easily add JavaScript calls that react to data being sent from the server using HTTP headers, ensuring a straightforward stream. The connection remains open long enough for the server to decide when to send data, prefixed correctly to avoid confusion.
00:25:21.320 For tracking of sent data, you can also manage IDs which indicate the position in a stream for a reconnection process, besides accommodating multiline messages seamlessly.
00:26:09.200 Lastly, I want to touch on websockets, which provide a two-way communication channel effectively resembling event sources with an added send method. However, this creates additional complexity in the Rack implementation due to their nature and the need for browser support.
00:27:27.920 Remember, if your main goal is to send data to a client, service and events present a much simpler and more manageable solution.
00:28:00.720 Lastly, there's a new protocol called HTTP/2, which is spearheaded by Google. It offers multiple request channels over a single SSL connection, allowing for simultaneous data transfers back to clients.
00:28:34.760 Okay, that's it! Thank you!
00:28:37.279 My slides are up on GitHub.