Rack ‘em, Stack ‘em Web Apps
CC Attribution - ShareAlike 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this
Clark, Jason
While Rails is the most common Ruby web framework, it’s not the only option. Rack is a simple, elegant HTTP library, ideal for microservices and high performance applications. In this talk, you’ll see Rack from top to bottom. Starting from the simplest app, we’ll grow our code into a RESTful HTTP API. We’ll test our code, write reusable middleware, and dig through what Rack provides out of the box. Throughout, we’ll balance when Rack is a good fit, and when larger tools are needed. If you’ve heard of Rack but wondered where it fits in the Ruby web stack, here’s your chance!
 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 back end 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 will fight each other. We're probably not going to get down to demoing it, but if you're like me and you kind of drift a little here and there during a presentation, feel free to go hit fight.robotlikes.com. This is Rails comp. We have a code of conduct. Keep it clean. Keep it professional. But go have fun with that if you'd like. 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. 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 WebRick 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 want to 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 interact 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 got to 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. 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 that stands for Rack up. We define a class called application, and in that class we define one method. The entirety of Rack protocol is a single method. This method is called call, and it takes one parameter called inv. 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. 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, you know, 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 want to hand a string back, right? 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, you know, 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 got to get the thing 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 the 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 going to 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 rack test. You include this into your gem file, just like any other gem. You would require rack test to then make that available, and that pulls in the classes that you're going to use. In these examples, I'm going to be showing many tests because that's kind of my flavor for things. 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 from many test tests. The first thing that we do is we include the rack test method. So what this does is it 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 rack test 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 rack test 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 rack test will put the responses from that, what happened, what it got back into an object called lastResponse. 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 going to 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 going to 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 know, 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, that's, you know, when you type something into your browser and just hit go, that's the method that's going to 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. And 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 going to 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 going to see here. It's not going to 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. That's the only thing that requests we're going to 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, you know, 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. 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 know, you can botch the key value and it looks like something's not there, hand in things that don't exist. So, you know, 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 method call to path info instead. Now this, if we botched the typing on it and said pat info, it's going to give us a no method error rather than just returning us a nil value in 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 tidies 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 Journey and Musterman are both gyms that are independent gyms that Ruby, Rails and Sinatra use for doing routing. So, you know, you could mix those in yourself. In this case, we're using a regular expression to match against the, the route that we're running. The percent R 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 the 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. 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 1, look up in our database, there we have a funky little ASCII robot with the ID 1 on it as the response that 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 stream 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. If 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 post, it's, 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, you know, 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 that 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 RAC has a request class, RAC 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, you know, 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. It also, RAC 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 RAC response by instantiating a new one. It doesn't take any parameters, so 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, you know, 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, you know, progressively build up that response how you want to. And then when you're done, you call response.finish, and it generates a valid RAC 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 RAC 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, RAC has wrapped up in an object. And this object actually takes care of some more complicated scenarios in 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 RAC 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 RAC 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 that the middleware is going to 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 munge 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 HTTPX 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 RackStatic. 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? I 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, use a middleware just like you did in our config.ru, but it also provides these insert 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 hand back 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 Sites. 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. APPLAUSE
 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 back end 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 will fight each other. We're probably not going to get down to demoing it, but if you're like me and you kind of drift a little here and there during a presentation, feel free to go hit fight.robotlikes.com.  This is Rails comp. We have a code of conduct. Keep it clean. Keep it professional. But go have fun with that if you'd like.  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.  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 WebRick 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 want to 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 interact 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 got to 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.  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 that stands for Rack up.  We define a class called application, and in that class we define one method.  The entirety of Rack protocol is a single method.  This method is called call, and it takes one parameter called inv.  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.  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, you know, 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 want to hand a string back, right?  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, you know, 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 got to get the thing 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 the 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 going to 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 rack test.  You include this into your gem file, just like any other gem.  You would require rack test to then make that available,  and that pulls in the classes that you're going to use.  In these examples, I'm going to be showing many tests because that's kind of my flavor for things.  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 from many test tests.  The first thing that we do is we include the rack test method.  So what this does is it 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 rack test 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 rack test 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 rack test will put the responses from that,  what happened, what it got back into an object called lastResponse.  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 going to 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 going to 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 know, 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, that's, you know, when you type something into your browser and just hit go,  that's the method that's going to 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.  And 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 going to 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 going to see here.  It's not going to 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.  That's the only thing that requests we're going to 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, you know, 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.  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 know, you can botch the key value  and it looks like something's not there, hand in things that don't exist.  So, you know, 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  method call to path info instead. Now this, if we botched the typing on it and said  pat info, it's going to give us a no method error rather than just returning us a nil  value in 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 tidies 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 Journey and Musterman are both gyms that are  independent gyms that Ruby, Rails and Sinatra use for doing routing.  So, you know, you could mix those in yourself.  In this case, we're using a regular expression to match against the, the route that we're  running. The percent R 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 the 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. 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 1, look up in  our database, there we have a funky little ASCII robot with the ID 1 on it as  the response that 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 stream 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. If 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 post, it's,  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, you know, 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 that 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 RAC has a request class, RAC 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,  you know, 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. It also, RAC  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 RAC response by instantiating a new one. It doesn't  take any parameters, so 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, you know, 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, you know,  progressively build up that response how you want to. And then when you're done,  you call response.finish, and it generates a valid RAC 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 RAC 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, RAC has wrapped up in an object. And this object  actually takes care of some more complicated scenarios in 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 RAC  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 RAC 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 that the  middleware is going to 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 munge 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 HTTPX 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 RackStatic.  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? I 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,  use a middleware just like you did in our config.ru, but it also provides  these insert 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 hand back 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 Sites. 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.  APPLAUSE
Rack____em__Stack____em_Web_Apps_2_0_4391.png Rack____em__Stack____em_Web_Apps_2_4392_7787.png Rack____em__Stack____em_Web_Apps_2_7788_7835.png Rack____em__Stack____em_Web_Apps_2_7836_8987.png Rack____em__Stack____em_Web_Apps_2_8988_9491.png Rack____em__Stack____em_Web_Apps_2_9492_11123.png Rack____em__Stack____em_Web_Apps_2_11124_11903.png Rack____em__Stack____em_Web_Apps_2_11904_11999.png Rack____em__Stack____em_Web_Apps_2_12000_12311.png Rack____em__Stack____em_Web_Apps_2_12312_13007.png Rack____em__Stack____em_Web_Apps_2_13008_13451.png Rack____em__Stack____em_Web_Apps_2_13452_16751.png Rack____em__Stack____em_Web_Apps_2_16752_17159.png Rack____em__Stack____em_Web_Apps_2_17160_20087.png Rack____em__Stack____em_Web_Apps_2_20088_20447.png Rack____em__Stack____em_Web_Apps_2_20448_21107.png Rack____em__Stack____em_Web_Apps_2_21108_21983.png Rack____em__Stack____em_Web_Apps_2_21984_23267.png Rack____em__Stack____em_Web_Apps_2_23268_23915.png Rack____em__Stack____em_Web_Apps_2_23916_25031.png Rack____em__Stack____em_Web_Apps_2_25032_27287.png Rack____em__Stack____em_Web_Apps_2_27288_28355.png Rack____em__Stack____em_Web_Apps_2_28356_28967.png Rack____em__Stack____em_Web_Apps_2_28968_29051.png Rack____em__Stack____em_Web_Apps_2_29052_29495.png Rack____em__Stack____em_Web_Apps_2_29496_30167.png Rack____em__Stack____em_Web_Apps_2_30168_33287.png Rack____em__Stack____em_Web_Apps_2_33288_33695.png Rack____em__Stack____em_Web_Apps_2_33696_33995.png Rack____em__Stack____em_Web_Apps_2_33996_37403.png Rack____em__Stack____em_Web_Apps_2_37404_37919.png Rack____em__Stack____em_Web_Apps_2_37920_38051.png Rack____em__Stack____em_Web_Apps_2_38052_40739.png Rack____em__Stack____em_Web_Apps_2_40740_41759.png Rack____em__Stack____em_Web_Apps_2_41760_42347.png Rack____em__Stack____em_Web_Apps_2_42348_43715.png Rack____em__Stack____em_Web_Apps_2_43716_49091.png Rack____em__Stack____em_Web_Apps_2_49092_49112.png