 Welcome everyone. Thank you for coming this afternoon. I hope you've had a good first day of Rails comp so far. My name is Jason Clark. I worked for New Relic previously on the Ruby agent. Now I'm working with some other teams on some backend services. 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 in about it, I've got a couple of links for you. 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. And then the second link is just a fun little Rack application that I built to demonstrate a few of the principles that are going on here. It lets you post text-based robots that'll fight each other. We're probably not gonna get down to demoing it, but if you're like me and you kind of drift a little here and there during the presentation, feel free to go hit fight.robotlikes.com. This is RailsConf. We have a code of conduct. Keep it clean, keep it professional, but go have fun with that if you'd like. All right, so let's get down to the meat of what we're here to talk about today. So if you've been around in Ruby and in Rails, you've probably heard Rack mentioned before. But what is it? It's a little ambiguous until you dig in where this fits in the ecosystem and what part it plays. Well, the easy answer is that Rack is a gem, like so many other things in Ruby. This is a gem that provides a minimal modular, adaptable interface for developing web applications. If you're like me, that doesn't really tell you a whole lot, though, but we can draw a picture that kind of demonstrates what Rack is about and where it fits in our stack. So 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. That web server then has to figure out what to do with that request. And that is where Rack comes into play. Rack is the interface that those web servers talk to to communicate that a web request just happened. Rack then, in turn, is able to turn around and pass that request along generally to a web framework of your choice, something like Rails or Sinatra. So Rack is sort of the glue that sits between your web server and your application code. Now, this is really cool because this is the principle by which you can swap out your web servers without changing your application code. You can change Puma to Unicorn and back and forth. You could even run on Web Brick. And all of that is fine because everybody talks through this common interface of Rack. But Rack is also available for you to use directly. There's no reason that you can't leverage what it provides to write your own code against it. Instead of relying on a framework to take care of that for you. Now, why would you wanna do a crazy thing like that? I mean, I'm here at Rails Conf and I'm telling you, hey, you can do things other than use Rails. But there's a couple of places where I feel like knowledge of Rack really plays in and it's a good fit. The first is when you need the absolute uttermost speed out of your Ruby code. Because the web server is making one method call into Rack to hand you a web request that just happened. There's nothing else in between. There's no logic, there's no abstractions, there's no nothing happening to take time. So if you have something that you need it to be as fast as possible for Ruby to be handling it, getting down to the Rack layer takes everything else out of the way. The second reason I'd put forward is simplicity. And I gotta put an asterisk on this. This is a certain sort of simplicity. It makes something simpler. The protocol for how you communicate in Rack, as we'll see in a few minutes, is really basic. It's method calls, arrays, and hashes, things that you're very familiar with. But you're trading that for other sorts of complexity that then you have to handle yourself. So it's a trade off. And it might make some things more simple, might make other things a little harder and we'll talk about where that's true. The other piece that's nice about Rack is like I alluded to, there's a high degree of reuse to some of these components. Because Rack is such a standard part of the Ruby web ecosystem, things that are written to work with one Rack-based application can often work with another application or often with applications that are written against entirely different frameworks. So that's a nice part that you might be able to use code that you write. If you're writing against Rack, that if you wrote it as a before filter in your controller, you wouldn't be able to share as easily. All right, so that's a lot of yammering about what Rack is. Let's take a look at some code. And this is one of my favorite snippets of Ruby. Actually, pretty much in the world is this. This is a fully functioning Rack web application. Now you put this conventionally in a config.ru file. Don't let the .ru fool you at all. That's still just a Ruby file. It stands for Rack up. We define a class called application and in that class we define one method. The entirety of Rack's protocol is a single method. This method is called call and it takes one parameter called inf. It's a hash and we'll look closer at what's in that hash in a little bit. On the response side, Rack's expectation of a call method is that it will return back an array that has three parts. The first value in that array is the status code, the HTTP status code to return back. And so in this case we're saying 200, everything's all good. The second parameter in this array is a hash of HTTP headers that will be fed back to the client browser. Now I've left this empty in this case. More often you would have something like the content type or at least some basic values that you would set in it. I've excluded those in a lot of these slides just to keep the amount of text down. But any HTTP header that you need to communicate back to the client, you just put in that hash. And then the last thing in this three-part array is an array of the content that you're wanting to send back. Now this is slightly different than you might expect. You might think, oh, I just wanna hand a string back. My HTML or my text that I'm sending back across the wire of the JSON. But Rack expects that the content object in the last place is something that will respond to each. And so the array is the easiest way to accomplish that to have. Give it a thing that it will be able to enumerate across. And then it takes whatever it gets out of that enumeration and that's what it sends down the wire to clients. So that's it. That's the protocol for how the web server communicates to Rack the request that's coming in and then gets the response back from that Rack application. Now in config.ru, you've gotta get the things started. And so there is a run method that Rack makes available at that level. And you hand it an instance of a Rack application, an instance that responds to this call method. And now when we go to our terminal, we can say rackup. That is a executable that's installed by the Rack gem. And it will spit out a little bit of front matter here. Tell us about some versions. Tell us what port number it's running on. And now it's ready to receive requests. We can get a request to it in the simplest possible way just by going to our browser, going to localhost92.92 and it tells us that this is good enough. There are other ways of accessing this though. And I like to play with them because some of the later cases that we'll look at are a little easier with some of the other tools. And one of them is curl. So curl is a command line tool that's readily available across most Unix-based systems. And you can in the most basic setting, say curl and then the same address that you would have typed into your browser and it will spit back onto your terminal the text that it received. There's all sorts of flags. We're only gonna look at a couple of them, but this will come in useful a little later when we look at things that aren't as easy to just type into our browser. So we're Rubyists. A big part of Ruby culture is testing. So we have an app here already. Let's see what it would take to get some tests wrapped around that. Unfortunately, as you might expect, the Ruby community provides through a gem called RackTest. You include this into your gem file, just like any other gem. You would require RackTest to then make that available and that pulls in the classes that you're gonna use. In these examples, I'm gonna be showing many tests because that's kind of my flavor for things. This all works perfectly well with RSpec and is super well supported across that. So a test would look something like this. So we have our application test, drives for many test tests. The first thing that we do is we include the RackTest method. So what this does is makes available a whole bunch of helpers that you can then have access to throughout the rest of your test class. You can actually do things like open up the many test base class and do this for everything if you know that you're using it everywhere or do it on a point by point basis like we're doing here. One expectation that RackTest has of your test class though is that it will provide a method called app and that app method will return an instance of your Rack application. So this allows us to control what we are actually testing. The methods and helpers that RackTest provides will interact with this app method to figure things out. And as we'll see later, this plays nicely because you can also do things like middleware instantiation or configure this however you want to in returning a valid Rack application that you want to test. The tests then are pretty basic. It gives you methods where you can invoke all of the typical HTTP verbs that you would expect. So in our case, we just wanted to get what was at the root. So we just get slash. Once that's executed, it will run against our Rack app and RackTest will put the responses from that, what happened, what it got back into an object called last response. We'll look a little closer at what this response object is later, but we can interrogate it for things like was it a successful status code? What was the content that was in it? What sort of headers? And so you can see how you can get into a nice unit testing cycle here of being able to feed various things in, feed URLs, make requests against your Rack app and then see what it's gonna respond back to you with. All right, so that's all well and good. I'm sure somebody would pay you tons of money to make a web app that just returns a static string. But most of us will need to have some sort of more dynamic interactive thing going on. And that inf parameter that we looked at is the key to all of the dynamic behavior that you're gonna put into your application. Everything that you need to know about the incoming web request is in that inf. As I mentioned, it's a hash, and let's take a look at a little bit of what's in there. There's a lot of values, and you can have it print these out or do whatever to be able to dig deeper. But some of the important ones that you should know about are that includes the HTTP request method. So in our case, this is a get. As we referred to, when you type something into your browser and just hit go, that's the method that's gonna get sent there. It gives us a lot of different path information so we can know what got requested and where. And then it also wraps up any input that's coming in. So with a get, there's no real input that gets sent along. You're just requesting the URL. But as we'll see in a few minutes, there are ways that with a post message or some of the other HTTP methods that you're expected to actually send data along with the request. And that's available here in the inf. So this is a modification to our basic app to start digging into the inf and do something a little more sophisticated, although not that sophisticated yet. What we do is we look at that path info that was provided there. And that gives us sort of the relative path after the domain of what the user has requested. And in this case, we look to see if it matches exactly slash bought. And if it does, we're returning a 403, which is the HTTP status code for forbidden. And we give them a nice little message to let them know that we've spotted them trying to get somewhere that they're not supposed to. Very, very sophisticated sort of code here. This process of taking a look though at the URL that's being passed in and turning that into what code you're gonna execute is actually a really fundamental piece of web architecture. In fact, it's called routing. And this is one of the things that you get out of every single web framework. In fact, some of them, their claim to fame is how easy they make routing or how conventional they can do those sorts of things. This is really a place where you feel the lack of frameworks if you just try to go at your own in rack. You will end up building your own abstractions to keep from having deeply nested if statements, which are most of what you're gonna see here. It's not gonna get too deep, but you can imagine if you had even 10 routes in your application, it could get a little bit tough to manage without some help. Rack does provide you a little bit of assistance though. So you're not entirely trapped in a land of nested case statements to get some sort of routing. And it does it through a method called map. So in our config.ru, we originally showed that we just called run on that application. And that's the only thing that requests we're gonna be able to come into. Well, Rack will also support you providing the leading prefix of a URL and then send those calls to specific app objects. So here we've made another rack-compatible application object called status. And this will receive calls that come to a slash status URL on the app rather than them going to the main application object. So you can imagine that at a modest scale, if you had a small number of routes that you were trying to deal with, you could very nicely partition those off into their own separate application classes, which is also kind of nice for testing and single responsibility. And Rack will let you do some basic routing to get the requests that are supposed to go to those apps, where they belong. If you've got more than one or two of them, there is also a URL map class, where you can essentially hand it a hash of the strings for the prefixes and the instances that you want. And I don't know, depending on how many of them you have and how you're using it, it can be a little tidier than the map method calls in your config.ru. So the env is a hash. It has all the information that we could possibly want in it, but it's a hash. Like munging around in hashes, knowing the keys, you can botch the key value and it looks like something's not there, a hand in things that don't exist. It's kind of messy. And so Rack helps us out with that with a class called RackRequest. RackRequest, you hand it an env that's coming in and then it provides a lot of helper methods that give you cleaner access to the things that are expected to be inside of this RackRequest. So in this case, we've replaced our lookup in that env hash for path info in the uppercase with a nice method call to path info instead. Now this, if we botched the typing on it and said pat info, it's gonna give us a no method error rather than just returning us a nil value and behaving in kind of a mysterious way. So I highly recommend using RackRequest anytime that you're doing more than one or two very basic accesses of things that are in the env. It really tighties things up. So looking a little further at this, let's do a little bit more sophisticated things. So if you, rather than having slash bot return us just a static string, let's make it a little bit more like a Rails show route where you can say bot slash an ID that you're interested in and it will still deny you but at least it'll tell you what thing you were trying to get at. Now this is a little ugly and like I said, routing is one of those areas where frameworks help. There's actually a journey and musterman or both gyms that are independent gyms that Rails and Sinatra use for doing routing. So you could mix those in yourself. In this case we're using a regular expression to match against the route that we're running. The percent R from here with the curly braces is really nice when you're doing a regex that wants to match forward slashes. Because forward slashes then you don't have to like backslash forward slash to get them as literals into it. And then we have parentheses around that backslash D plus and that grabs as a capture the ID that we passed in. And the dollar one on the lower line below it pulls out the value that was matched when that regex went. Now there are lots of ways that this could be cleaner and tidier. This is the most concise way to fit it on a slide. So don't take this as best practice but as kind of illustrative of where you could go with parsing up your URLs and taking out the information that you want. So isn't there an easier way? Like I said, this is where you really win big with frameworks. I mean Sinatra's DSL for anybody who's used it where you just say get and then give it the string and you can put parameters in it just with colons on the names. Like they do so much to help you out here. So this is a place that you might want to look for that help. If we wanted to transform this into a little bit more of a real application though once we've done that match rather than just returning a string value we can do whatever sort of code we want inside that. So here we have some mythical database class that goes and looks up by ID the things that we have in a store. It returns back that bot and as an object and then we just 2S that in our output. So you can imagine if this database was storing things as JSON structures that this would be a way that you could have just sort of a typical restful API talking back and forth. Like there's no deep plumbing. You don't need anything here apart from the database class to be able to look up the data that you want to return back. So as we've seen before curl comes in handy for doing this and if we curl at bot slash one look up in our database there we have a funky little ASCII robot with the ID one on it as the response that it hands back. This is an HTTP get like we had mentioned so it's not expected to have any parameters really except for the URL in the query string that's provided there. You can send headers but we're not reading any of those but we want to make this a little bit more full featured like we have a database that we're reading out of we want to let people write into it and for that the HTTP method that you should use is post. Post allows us to send data along with. So we'll adapt our call method a little further and don't worry this is the most that this method is going to grow and you can see where the pain points are here in the routing that we're talking about. We had our match that we've seen before so as long as we're in the bot and now we have a branch in here and this branch says if the request is a get method which is provided for us by that rack request then we would do the stuff that we were already doing before of looking it up but if the request is a post then what we need to do is we go to that request and we read the body that comes in so this is the data that the client is sending to us. Now in a lot of web applications this would be something that would be coming from a web form that somebody had entered but it could also be from an API client calling it directly or as we'll see in a moment there's easy ways to make curl post data to a URL rather than just get from it. Receiving a post like we said we would like this to write into our database so having matched the ID and having read the content in we can do whatever we need to with that and return a response to the client indicating that we were successful in saving the record that they sent to us. So this is like the basic shell of a restful API here in 10 lines of code excluding the database or whatever you bring in to do that. It's pretty straightforward and the things that you get from your frameworks make this a little cleaner and make this tidier but these are all the moving parts that are really necessary to get this job done. Taking a look at how this works at the terminal if you say curl with a minus capital X then you can provide it an HTTP method other than get which is what it will default to. We give it the full URL with the ID of the thing that we're wanting in the minus minus data then allows you to send the data along that you want to actually post in. It gives us back the message that we wrote and if we then turn back around and do a get against that same URL we receive back the data that we had sent in. So we have the full life cycle of posting and reading. Unsurprisingly given that Rack has a request class Rack also has a response class which helps you in building up the valid responses. It's pretty useful and it fills a couple of gaps in that format. I mean for one it's pretty strict for you to need to make exactly those three elements and make sure that the contents the way that it's supposed to. Rack response also allows you to kind of have a little bit more of a flow in how you're building things up rather than making that array right at the point when you need to return it. It gives you an object to sort of tally up the information into and then at the time that you're finished ask it to write it out to the wire. So we interact with Rack response by instantiating a new one. It doesn't take any parameters to start it up necessarily. And then we can set various things on this response. It's sort of a stateful builder type of pattern. So one of them is write. You can write to it and that will write to the body. And you can do that as many times as you want. You can continue to append things. You can imagine if you were doing this with an HTML based thing, maybe you read something from a header file and write that in. And then you write some other piece of dynamic content and then you write a footer. You can progressively build up that response how you want to. And then when you're done you call response.finish and it generates a valid Rack response to be handed back across the wire. If we pride in at that point and took a look at the response finish what it returns back to us like we've experienced before is that three-part array, status code, headers and then the body. But two of them look a little different and this is part of why you want to use Rack response. One of them is that it's appended a content length based on the things that we wrote in. Now web browsers will act okay. Things don't break dramatically if you don't provide this but it is in the HTTP specs and it is better for you to provide that information if you want to make sure that you're compatible with all callers. Additionally that content, that last thing that we had been passing as an array with the strings in it, Rack has wrapped up in an object. And this object actually takes care of some more complicated scenarios when things are nested and there's certain closing behaviors on the response stream that it will take care of for you so you don't need to know about those things if you use Rack response. So at this point we have a valid app and it returns data and we can interact with it, make our requests, get our responses but there's another major component of how Rack works and that is middleware. And this is one of the most powerful patterns that this brings to play and one of the things that applies the most when you're in other frameworks. So back in our config.ru where we had our run statement for our application you can use a method that's provided there called use and install a middleware. And installing the middleware, they get installed in the sequence that gets called so we could have done multiple installations there and said use this middleware then use this one then use this one. And what that forms is that forms a call chain that's going to get flowed through. So the first request that comes from the web server will hit the first middleware that you said use in the config.ru then hit the next one, then hit the next one and eventually most of the time gets down to your app and then the response goes back up through that chain. Those middlewares take something shaped like this. So they have to have an initialize. Initialize is expected to take an app that is the next thing in the chain the middleware is gonna call. And you have to save that away so that you can make a call against it later. So in this form this middleware doesn't actually do anything useful. All it does is like takes the request and hands it along. But you can do your own work before and after that call on the app. You can munch the environment. You can deal with the response and change what's there. You can control what's going in and out. So here's an example of a very basic sort of key, API key validation as a middleware. We instantiate a request object to read it. We get the HTTP API key header which is a pretty standard value for passing an API key along. And if the key matches some predetermined string that we've chosen, I chose beep, then we'll go ahead and let the call through. They knew the key, everything's fine. If they don't, then we will immediately return from this middleware. That request will never make it to the application object itself to be handled. So in diagram form we come in, we get to the middleware and the middleware says, I'm responsible for this, returns, and nobody else further down the chain gets a chance to get involved with that. So middleware are very powerful for allowing you to apply these sorts of cross-cutting concerns, whether it's logging or authentication. Things where all of your endpoints across any portion of your app need to have the same behavior fit very well. And Rails actually is built on a ton of middleware. This is a huge pattern that's applied there for a lot of cross-cutting concerns. So if we look at this, once we've installed our authentication, we can curl against that. We'll get our forbidden message. If we looked at the status code, it would tell us a 403. If we curl with a minus H, minus capital H, we can give it headers in the HTTP format that they expect the key name colon and then the value. And once we pass a valid API key, that request then is allowed to go through. So rack itself comes with a number of middlewares that you can use. There are lots of them around. One of them is rack static. So if you need to serve static files out of a rack application, it allows for you to sort of declaratively set that up. It has some basic session support, both with backing stores on your server and with cookies that you can install. This actually often gets used with Sinatra if you want to have sessions because it's a middleware that can get installed. It has debugging help, things that will show you a nice exception page when you're in development, things that do code reloading that you can optionally install. A lot of the niceties that you have in Rails are available as middlewares with rack itself. And that brings us to the last little bit, which is to talk about where rack intersects with all of the other frameworks. So we mentioned early on that Rails and Sinatra and all of these things are built on top of it, but what does that really mean? For Rails, your Rails application is a rack app. There is something in Rails that has a call method that takes an M. And when those requests come in, that's getting dispatched to your controllers and your routing and your views and all of those other things, but all of that is downstream of a call method that you don't have to write because Rails does it for you. That's not the only place where Rails touches rack though. You can actually embed rack applications into the routing inside of a Rails application. So here in our routes file by using the mount command, you can hand it a rack app instance and then give it the URL where it should delegate to that rack app. And any request that gets there will get sent on to that rack application. Similarly, Rails allows for using middleware. There's a use method, so in your application config, you can just use a middleware just like you did in our config.ru, but it also provides these before and insert after. So if you have some concern about the sequencing of your middlewares, you can control that from in there. Now, you can't do that from rack itself, and so how is Rails accomplishing this? Well, it turns out that Rails actually has its own internal middleware stack. And so when you config use those in the Rails level, it's actually going into that middleware stack. You can install middlewares at the config.ru at the rack level just like you would, but you won't see those when you do things like rake middlewares and ask Rails to show you all of the things that it has installed. So that's a fun little tripping point if you're ever looking around for how things are plugged in. Sinatra as well, just like Rails, is just a rack app. At the end of the day, when you call Sinatra base and you derive your thing from it, it's just a rack application. And in fact, although you almost always use helpers and call render or handback strings and do all the nice things that Sinatra lets you do, you can always fall back to handing back a valid rack response. And Sinatra knows what to do with it and just hands it on back because it is just a rack app. So that's a quick tour through rack and where it fits. We've looked at how you would build a basic application, what the moving parts are that are there in the box. We've looked at the request and response life cycle and what plays into that. We've looked at middleware and how you can use those to kind of compose an app together and layer things in your rack applications. And we've looked at how this plugs into the various frameworks. Now, I really just touched on the surface of this and haven't given a lot of really concrete details, but I did a course for Plural Sites. So if this is something that is of interest to you, I actually have a bunch of tokens that people can get a free month worth of time on Plural Site and that screencast fills in a lot more detail. It goes into lots more of what's in the box with rack and a much more realistic example of building a JSON based API with it. So hopefully you've all found this useful and I can't see how much time we have left if there's any time for questions. All right, thank you very much. We'll see you in a minute.