Talks
Rack ‘em, Stack ‘em Web Apps
Summarized using AI

Rack ‘em, Stack ‘em Web Apps

by Jason Clark

The video titled "Rack ‘em, Stack ‘em Web Apps" features Jason Clark at RailsConf 2017, where he explores the Rack library, a minimal yet powerful HTTP interface for building web applications in Ruby. This presentation aims to clarify Rack's role in the Ruby ecosystem, demonstrating both its simplicity and performance benefits compared to using traditional frameworks like Rails.

Key points discussed in the video include:
- Definition of Rack: Rack is introduced as a gem that serves as an interface for web servers and Ruby applications, allowing developers to swap out web servers without altering their application code.
- Basic Structure of a Rack Application: Clark outlines how to create a simple Rack application using a class and the call method, which is fundamental for processing web requests.
- Testing Rack Applications: The video highlights the importance of tests in Ruby applications and how to use the Rack Test gem to facilitate testing of Rack applications effectively.
- Dynamic Routing and Handling Requests: The speaker discusses how to handle various HTTP methods, build dynamic routes, and manage user requests based on URL patterns using regex.
- Middleware in Rack: A significant focus is on middleware, showcasing how it can manage cross-cutting concerns such as authentication and logging, ultimately enhancing application functionality.
- Integration with Other Frameworks: The relationship between Rack and popular frameworks like Rails and Sinatra is explored, emphasizing that both frameworks are built upon Rack, and demonstrating how Rack applications can be embedded within them.

Throughout the talk, practical examples illustrate the discussed concepts, including a project where users can interact with a Rack-based API that handles bot data via GET and POST methods. By the end of the presentation, Clark encourages further learning through links to additional resources and courses for deeper engagement with Rack and its functionalities.

The key takeaways from the video are:
- Rack provides a versatile framework for building web applications with the potential for high performance.
- Understanding Rack can enhance flexibility in application architecture, making it easier to manage requests and middleware.
- Developers can leverage Rack for both simple applications and more complex architectures, integrating it easily with established Ruby frameworks.

00:00:11.960 Welcome everyone! Thank you for coming this afternoon. I hope you've had a good first day of RailsConf so far. My name is Jason Clark, and I work for New Relic, previously on the Ruby agent. Now I'm working with some other teams on some backend services.
00:00:19.410 Today, I'm here to talk to you about Rack. Rack is a library that you may have heard a little bit about. But before we dig into it, I’ve got a couple of links for you.
00:00:32.279 The first one is the slides for this presentation, so if it's easier for you to follow along on your computer or you want to reference it, that link will take you there. The second link is just a fun little Rack application that I built to demonstrate some of the principles we’re discussing. It lets you post text-based robots that will fight each other.
00:00:45.059 We probably won't get to demoing it today, but if you're like me and tend to drift a little during a presentation, feel free to go visit fightrobotlikes.com. This is meant to be a professional talk, but go ahead and have fun with that if you'd like.
00:01:03.840 Now, let’s get down to the meat of what we're here to talk about today. If you've been around Ruby and Rails, you've probably heard Rack mentioned before. But what is it? It's a bit ambiguous until you dig in and see what part it plays in the ecosystem.
00:01:18.060 The simple answer is that Rack is a gem, like so many other things in Ruby. It provides a minimal, modular, adaptable interface for developing web applications. If you're like me, that doesn’t really tell you much. However, we can draw a picture that illustrates what Rack is about and where it fits in our stack.
00:01:35.820 When a user comes to a website from a browser, they make a request. That request goes across the internet and gets handled by some sort of web server, typically something like Unicorn or Puma. This web server then figures out what to do with that request—and this is where Rack comes into play.
00:02:09.690 Rack is the interface that those web servers communicate with to signify that a web request just happened. Rack then passes that request along to a web framework of your choice, like Rails or Sinatra. So, Rack is sort of the glue that sits between your web server and your application code.
00:02:28.829 This is really cool because it means you can swap your web servers without changing your application code. You can change from Puma to Unicorn and back again, or even run on Webrick, and all of that is okay because everyone talks through this common interface, Rack.
00:02:52.470 However, Rack is also available for you to use directly. There's no reason you can't leverage what it provides to write your own code against it, instead of relying on a framework.
00:03:06.359 Now, you might wonder why you would want to do something like that. I mean, I’m here at RailsConf, and I'm telling you you can do things other than use Rails. However, there are a couple of scenarios where knowledge of Rack really comes in handy.
00:03:29.699 The first is when you need the absolute utmost speed from your Ruby code. Because the web server makes one method call into Rack to hand you a web request, there's nothing else in between. There are no additional layers, abstractions, or logic that could slow things down. If you need something to be as fast as possible, getting down to the Rack layer will strip everything else away.
00:03:50.099 The second reason is simplicity—although I have to put an asterisk on this. This is a specific sort of simplicity. The protocol for how you communicate in Rack is quite basic: it's just method calls, arrays, and hashes—things you're likely really familiar with. However, this simplicity comes with its own trade-offs.
00:04:02.879 While it may simplify some aspects, it might complicate others, and we can talk about where that’s the case. Another nice feature of Rack is the high degree of reuse of some of its components, because Rack is such a standard part of the Ruby web ecosystem.
00:04:27.150 Things written to work with one Rack-based application can often work with another, or even with applications written against entirely different frameworks. So, there are nice advantages if you're writing against Rack that you may not have when creating code as a before filter in your controller.
00:04:56.010 Alright, that’s a lot of yammering about what Rack is. Now, let’s take a look at some code. This is actually one of my favorite snippets of Ruby.
00:05:10.320 This is a fully functioning Rack web application. You would typically put this in a config.ru file. Don't let the .ru fool you; it's still just a Ruby file. The .ru stands for Rack Up.
00:05:21.990 We define a class called Application, and in that class, we define one method. The entirety of Rack's protocol is a single method, called call. It takes one parameter called env, which is a hash. We’ll look closer at what's in that hash shortly.
00:05:37.229 On the response side, Rack expects the call method to return an array with three parts. The first value in that array is the HTTP status code to return. In this case, we are saying 200, which means everything is okay.
00:05:52.289 The second parameter in this array is a hash of HTTP headers that will be sent back to the client browser. I've left this empty for now; typically, you would include something like the content type or some basic values.
00:06:07.020 However, any HTTP header you need to communicate back to the client can simply go in that hash. The last part of this three-part array is the content you want to send back.
00:06:25.320 This is slightly different from what you might expect. You might think you’d just want to hand a string back—like your HTML or the JSON response. However, Rack expects the last content object to respond to 'each.' So, using an array is the easiest way to achieve that.
00:06:39.690 The server will take whatever comes out of that enumeration and send it down the wire to clients. So, that’s it! That’s the protocol by which the web server communicates to Rack for the incoming request and gets the response back from the Rack application.
00:07:13.889 In config.ru, we need to get the server started, and there is a run method that Rack provides at that level. You pass it an instance of a Rack application, an instance that responds to this call method.
00:07:26.069 When we go to our terminal, we can say 'rackup.' That is an executable installed by the Rack gem, and it will output some front matter, telling us about versions and what port number it's running on. Now it’s ready to receive requests.
00:07:50.550 We can access it in the simplest way by going to our browser and entering localhost:9292. It informs us that this is all good.
00:08:09.750 There are other ways to access this, though. I like to experiment with them because some later cases will be easier with different tools.
00:08:16.340 One is curl, a command-line tool readily available across most UNIX-based systems.
00:08:21.449 You can use curl in the most basic way by typing curl followed by the same address you would have typed into your browser. This command will print the response right back in your terminal.
00:08:38.520 There are all sorts of flags, but we’ll only focus on a couple for now, as they’ll come in handy later when we explore more complex scenarios that cannot be easily typed into a browser.
00:09:01.890 As Rubyists, a significant part of Ruby culture is testing. We have an app ready, so let’s see what it would take to get some tests wrapped around it. Unfortunately, as you might expect, the community provides a gem called Rack Test.
00:09:20.220 You include this in your Gemfile, just like any other gem, and require Rack Test to make it available. This pulls in the classes you'll be using in the examples I’m about to show.
00:09:35.980 I'll be showing many tests because that's my preferred style. This works seamlessly with RSpec and is well-supported across that testing framework.
00:09:57.980 A test for our application would look something like this: we include the Rack Test method, which provides a whole bunch of helpers that will be available throughout the rest of your test class.
00:10:17.050 You can do this for everything if you know you're going to be using it everywhere, or you can apply it point by point like we're doing here. One expectation that Rack Test has of your test class is that it will provide a method called app.
00:10:35.980 This app method should return an instance of your Rack application. This allows us to control what we are testing directly. The methods and helpers that Rack Test provides will interact with this app method to perform their operations.
00:10:54.410 As we’ll see later, this setup plays well because you can also do things like instantiate middleware or configure as needed to return a valid Rack application to test.
00:11:12.760 The tests themselves are quite straightforward, providing methods that allow you to invoke the typical HTTP verbs you would expect. For instance, if we simply want to 'get' what's at the root, we just call get '/'. When that is executed, it runs against our Rack app.
00:11:32.380 Rack Test will store the responses from the app in an object called last_response, allowing us to interrogate it for things like the status code, the content that was returned, and the headers.
00:11:54.690 You can see how this enables a nice unit testing cycle, where you can feed various inputs, make requests against your Rack app, and analyze its responses.
00:12:06.180 Now, while this is all well and good, I’m sure someone would pay you a lot of money to create a web app that simply returns a static string. However, most of us will require some form of more dynamic, interactive behavior.
00:12:34.360 And that parameter we looked at, 'env,' is crucial for all of the dynamic behavior you’ll want to incorporate into your application. Everything you need to know about the incoming web request is wrapped in that 'env' hash.
00:12:46.790 Let’s examine some of the important information contained in there. It includes the HTTP request method, so in our case this is a 'GET' method. This method is the one sent to the server when you type something into your browser and hit enter.
00:13:01.990 Additionally, it provides various path information, allowing us to know what was requested and where. It also wraps up any input that's coming in, though with a 'GET' request, there's no real input being sent, just the requested URL.
00:13:32.270 However, with a POST request, you will send data along with the request, and this will be accessible through the 'env' hash.
00:13:39.490 Now, let’s modify our basic app to start digging into 'env' and implement some more complex functionality, although we’ll keep it simple for now.
00:13:55.160 We’ll check the path information that was provided in the 'env' hash, specifically looking to see if it matches '/bot.' If it does, we’ll return a 403 HTTP status code for forbidden access.
00:14:19.890 We’ll also include a nice little message to let the user know that we've detected their attempt to access a restricted area.
00:14:36.310 This process of examining the incoming URL and translating that into executable code is a fundamental aspect of web architecture, often referred to as routing.
00:14:56.800 In fact, many web frameworks tout their routing capabilities, making it easy to manage and configure routes cleanly. That said, when you take a bare-bones approach using Rack, you’ll likely find yourself creating your own abstractions to avoid deeply nested if statements.
00:15:15.490 While it won't get too complicated with a small amount of routes, imagine dealing with ten or more routes without a structured way to handle them. It could become quite challenging.
00:15:40.430 However, Rack does offer some assistance in this area. It allows you to provide leading prefixes to URLs and send calls to specific application objects, which helps you manage routes.
00:15:56.840 In our config.ru, where we initially called run on the main application, we can also support routing prefixes by creating a new Rack-compatible application object called status.
00:16:18.390 This status app will now receive calls meant for the '/status' URL while ensuring that they don't pass through the main application object.
00:16:31.220 If you have more than one or two routes, Rack provides a URL map class that lets you hand it a hash of prefixes and their corresponding application instances. Depending on how many you manage, this can be a more organized approach than standard map calls in your configure.ru.
00:16:56.080 The 'env' hash contains all possible request data, but it can be messy to work with raw hashes. That's where Rack helps us with the Rack::Request class.
00:17:04.420 You pass it the incoming 'env', and it provides a lot of helper methods that give you cleaner access to what's expected inside that Rack request.
00:17:19.410 For example, instead of referencing 'env['PATH_INFO']' directly, you can use the 'path_info' method, and if you make a typo, it’ll throw a no-method error rather than just returning nil.
00:17:32.840 I recommend using Rack::Request whenever you're doing more than a couple of straightforward accesses from 'env', as it provides a robust abstraction.
00:17:50.630 Let's take this a step further. Instead of simply returning a static string from '/bot', let’s make it more like a Rails show route with a dynamic ID.
00:18:07.990 We’ll still return a forbidden status, but we’ll indicate what user ID they were attempting to access. While this approach might not be the cleanest, it effectively illustrates the routing possibilities within Rack.
00:18:20.799 That being said, using regex for routing can be cumbersome. While it's compact, if you want more user-friendly or elegant routing, frameworks like Sinatra provide DSLs that allow you to define routes with much less boilerplate.
00:18:38.079 When we match a route successfully, instead of just returning a static string, we can now execute any kind of code we desire. For example, we can set up a fictional database class that looks up items by ID.
00:18:54.699 This class returns the corresponding bot object that we can then render as our output.
00:19:09.740 Now we can even curl against bot/1, which will interact with our imaginary database and return a visual representation of the object with ID 1 as the response.
00:19:31.020 In this case, we’re dealing with an HTTP GET request, so no additional parameters are expected apart from the URL and query strings. You could add headers, but we’re not reading any here.
00:19:51.890 Let’s transform this application to support POST requests, allowing users to write data into our database. For that, we simply adapt our call method a bit further.
00:20:05.120 Don’t worry; this is as complex as our method will get. Here, we check if the request is a GET method. If it is, we proceed as we did before.
00:20:25.010 However, if the request is a POST, we read the request body to get the data being sent from the client.
00:20:42.550 This data often originates from web forms but can also come directly from API clients. After capturing the content, we proceed to write it into our database as needed.
00:20:55.620 We can then return a response to the client, indicating that we successfully saved the record they sent. This is effectively how a basic RESTful API is structured.
00:21:13.820 This example shows that building simple yet functional applications is indeed straightforward in Rack.
00:21:28.890 Once you get the hang of it, you can see how removing excessive frameworks simplifies the model.
00:21:40.120 To test this lifecycle, you can use curl with '-X' to specify HTTP methods other than GET, providing the full URL with the ID of the item you want.
00:21:58.390 Using '--data' lets you send the necessary data along with the POST request, demonstrating the full process of sending and receiving data.
00:22:12.780 Unsurprisingly, since Rack has a request class, it also has a response class that helps in constructing valid responses. This class is pretty useful.
00:22:39.230 For one, it ensures the response is formatted correctly without enforcing the strictness of producing exactly those three components every time.
00:22:54.580 Additionally, Rack::Response allows for a more fluid approach in building responses rather than just returning the array inline. You can set various properties incrementally.
00:23:07.330 This means you can create more readable and maintainable code, especially if responding with multiple dynamic pieces of content.
00:23:21.660 When you’re done setting up your response, you simply need to call finish, which generates a valid Rack response to be handed back across the wire.
00:23:34.240 If you print the response and look at what it returns, you'll find it also maintains the three-part array structure: the status code, headers, and body.
00:23:51.380 However, with Rack::Response, you'll notice that it also sets the content length based on the content you added—something browsers may not immediately break for, but it’s definitely in the HTTP specs.
00:24:06.410 Moreover, the content you previously sent as an array of strings will be encapsulated in an object, allowing Rack to manage any intricate scenarios more effectively.
00:24:23.420 Now that we have a valid app returning data, we can interact with it and make requests to receive responses. However, an important aspect of how Rack functions is middleware.
00:24:43.650 This pattern is incredibly powerful and frequently leveraged in other frameworks. In our config.ru, where we previously called our application, we can also use a method called use to install middleware.
00:25:01.650 When you install middleware, they get activated in the sequence they were set, creating a call chain for requests. The first middleware processes the request, followed by subsequent middleware, all leading to the app.
00:25:19.020 Responses follow the same route back through the chain, allowing you to handle pre-processing and post-processing of requests and responses.
00:25:37.060 Here’s an example of a basic middleware that checks for an API key. If the provided key matches a predetermined string, the request will pass through; otherwise, it returns an error response.
00:25:50.660 Visually, it looks like this: the middleware reads and validates the API key. If it’s valid, the request continues; if not, it stops right there.
00:26:08.600 Middleware is advantageous for applying cross-cutting concerns like logging or authentication. These behaviors can smoothly be integrated into multiple endpoints across your application.
00:26:26.230 Rails is built massively on middleware, utilizing this pattern extensively for straightforward behavior across various application aspects.
00:26:46.580 Once the authentication middleware is installed, we can observe the result of our operations. If we try a request without a valid API key, we will receive a forbidden message.
00:27:03.030 However, if we do provide a valid API key using the HTTP header format, the request is eventually allowed through.
00:27:18.300 Rack comes equipped with numerous middleware components, including rack-static for serving static files and session support, both server-backed and cookie-based.
00:27:34.170 It also provides debugging tools, such as exception pages in development, alongside code reloading features, which bring many comforts often seen in Rails into Rack.
00:27:50.130 That leads us to our final discussion: how Rack intersects with various frameworks.
00:28:07.110 We noted earlier that Rails and Sinatra and others are built atop Rack, but what does this mean in practical terms?
00:28:25.490 In a Rails application, it is a valid Rack application. It has its own call method that processes incoming requests, dispatching them to controllers, routing, and views.
00:28:41.810 But Rails does all this for you, so you don’t need to write a custom call method yourself.
00:28:54.640 You can also embed Rack applications directly inside a Rails application using the mount command, delegating to that Rack application for specific URL paths.
00:29:08.660 Rails also permits the use of middleware similar to how we handled in our config.ru. You can utilize the use method in the application config to introduce middleware.
00:29:25.390 You can also strategically control middleware sequencing with 'insert_before' and 'insert_after' commands, something you cannot do directly in Rack.
00:29:43.520 That said, Rails has its own internal middleware stack. When you configure the use of middleware in a Rails application, it interacts with that internal stack, which won't show up in Rack’s middleware list.
00:30:02.050 This distinction can be a little tricky when searching for installed middleware, as it won’t show up if you try to query with something like 'rake middleware'.
00:30:19.080 Sinatra operates similarly to Rails; at the end of the day, when you call 'Sinatra::Base' and derive your class from it, you’re creating a Rack application.
00:30:37.120 While you’ll normally use the helpers and output strings easily, you also have the option to return a valid Rack response directly, as it’s fundamentally just a Rack app.
00:30:56.760 In conclusion, we’ve gone over the core aspects of Rack and how to build simple applications, examining the integral components such as request handling, response generation, and middleware.
00:31:12.210 We've also explored how Rack fits within various frameworks. I’ve barely scratched the surface, and for those interested, I’ve created a course for Pluralsight.
00:31:32.650 I have several tokens available for a free month of access to Pluralsight, where you’ll find more detailed content explaining Rack.
00:31:49.680 I hope you’ve found this talk useful, and I’d love to hear any questions you may have.
Explore all talks recorded at RailsConf 2017
+105