 Thank you for coming to this talk. My name is Amy Unger, and I'm here to talk, I'm here to learn that my slides are becoming sentient, but also I'm here to talk about a part of the Rails ecosystem that I ignored for quite some time. And I came to regret that. It's easy as a new Rails developer to ignore rack middleware. I mean it's got words like rack and middleware. But those words are scary, especially when you're still getting to the point where you're comfortable with the concepts of models, views, and controllers. And then as I became a more experienced developer, I ran into gems that relied on middleware and even small pieces of middleware in the code bases I was working in. And it's really easy to just pick out the parts of the middleware that seem relevant to the changes you need to make or the bug you're trying to track down. And so it's pretty easy to think that there's not much more to rack middleware. Because to be honest, there isn't. It's designed to be a simple but powerful interface. But I never really took the time to understand what was going on. So at the beginning of my career, rack middlewares seemed far too advanced for me. And then it jumped straight to being too boring and too obvious. Nami, I'll let you guys in on a secret. I wrote some pretty bad middleware because of that. Probably the obvious conclusion. But I wrote middleware that wasn't thread safe. I didn't push back when middleware that should never have been middleware was written. And I maintained some sprawling middleware that is pretty much unintelligible because it is so sprawling. And finally, I didn't write middleware when I should have. I didn't know that it was a tool that I could use. So today I want to address some things that I would have loved to know, and some of the mistakes that I made. So you don't have to make them. So first I'm going to talk about what is rack middleware. I'm going to go through some examples about how we build rack middleware. I'm going to cover why you might want to use rack middleware as a tool. And because I got on a theme of my section headings, this last one is called who, as in who did this mistake, who made this. Which is, I think it's when I step in one spot that it moves. Okay, so who is talking about things that your successors will try to find you and track you down and tell you why you shouldn't have done that. So let's start with what. I mean, I jumped into what rack and rack middleware are. Look at how they fit into Rails. And then take a brief look at some familiar examples of rack middleware. So what's this rack thing? So let's go to a world where rack doesn't exist. And you need a server, for instance. For this example, I'm just going to use a CGI server. So your user is going to make an HTTP request here through a browser. It's going to hit your CGI server. Now your CGI server is going to take that request and parse it into different parts of data that's going to shove onto the environment. So it sets about 20 or 30 environment variables. Some of these should be somewhat familiar. Path Info, HTTP except for your headers. All this sort of stuff, it's shoving into the environment. And then it will run your code. Now, your application code will then write to standard out, which the CGI server will pick up on, formulate that into an HTTP response, and send it back to your user. What that means is that your application needs to know that it's being run by a CGI server. It needs to know that it can pull out those environment variables to figure out, hey, someone's asking for index.html. And that's a bit of a problem for many of us, because we tend to develop with one server and then deploy to production with another. So you're using multiple servers in the course of a day, and your application has to figure out what's running it. So let's move to a world with rack. So when we shove rack in the middle of your server and your application, the situation looks a little bit different. Your user makes an HTTP request to your server. Here it's web brick, it can be anything. That server knows that it should talk to rack. Rack parses the information it gets from the server into a standard incoming request that is the same for any different server. And that means that no matter the server that is running your application, your application can write the same logic. And then coming back up the stack, it's the same thing. Your application just returns the response in a rack compliant way. And rack figures out the details of how it should then talk to the server. So what does rack look like to your application? To your application, it looks like there's an incoming request with the environment hash. We can see that rack took inspiration from CGI, because it essentially took those environment variables and wrapped them in a hash and called that the environment. But it's no longer setting those variables on the environment, it's passing that into your code. The outgoing response that your app is going to return includes the status code, the headers, and the content body. So let's take a look at the simplest rack app we can create. Rack apps need to follow three rules. We need something we can call, whether that's a class or an instance method, or the proc we have here. That thing needs to accept the environment so that the environment's going to have that hash that includes path info, headers, all that information. And then it needs to return an array with the following. So here we can see the status, which is 200, a hash of headers here we're just saying we're returning HTML, and an array of the content body. So those are the three rules that you need to follow to be a rack app. So what is rack middleware? So if we look at this diagram and then zoom in on rack, we can see, well, we're going to look at the three parts of working with rack. Now, rack took their logo inspiration from a server rack. But I'm going to use that to display the three different parts. So the first part is the handler for your server, Webrick, Mongrel, CJipuma, et cetera. There's a handler for it. The next part is the adapter that's at the bottom that will talk to your framework. And so far we have a pretty simple setup. The request is just going to flow through rack and get transformed for the server and the framework. What if we want to do things to that request before it hits the server coming back up as the response or hits your application coming in? Well, that's where middlewares come in. So middlewares allow you to work with the request or the response before it exits either at the bottom of the stack or the top of the rack stack. And just to show some of the power of what rack middleware can do, here's some examples of middleware that Rails provides to you. So first off, Rails uses middleware to serve up static files. It uses middleware to set up logging for each request, and then flush all logs at the end of the request. It uses middleware to set up a cookie for the request, to handle flash messages, and to parse out params. So the params that you're used to in controllers, that's done in middleware. In addition to middleware in the Rails core code base, within the Ruby Web App ecosystem, there are other notable gems that use middleware as a strategy. Some of them include being used for throttling, for security for honeypots, and for authentication with Warden. So let's take a quick look at how we're going to build our middleware. So first, we're going to write a very basic middleware. This is just going to be a ping setup. We're just going to be able to ask our application, hey, are you up and running? First, we'll create a file in lib middleware ping. We'll create a class called ping, and to make sure that there won't be any namespace clashes, we'll throw that in a module. So now we have this class middleware ping. We're going to write an initialize method. Now every piece of rack middleware needs to accept, when it's initialized, it needs to accept the app. Now the app could be your Rails app, but it could also be another piece of middleware. You can imagine middleware as a set of Russian nesting dolls. Each one calls down to the smaller one until it hits your app. So really what's happening here is our middleware is going to be initialized with the next middleware down the stack. If it's the very last middleware to be called, it will be initialized with our Rails app. So a middleware has to follow the same three rules as an app. We need something that responds to call, it must take the environment, and it must return the status, headers, and content body. So our call method here is going to look very much like a Rails app or any Rack-compliant app, except for the fact that it will need to call down the stack. So let's write the simplest thing that is Rack-compliant. So you can see our call method here is accepting the end, and it's returning a Rack-compliant response. This is really cool. This is going to work. When we hit our app, we will receive a responsive Pong. Unfortunately, we're going to see that in every single case. This will never call down the stack. Every request to your app will respond with 200 Pong. Probably not what we want to do. So let's fix that. So first, let's take a look at what that request is. We're going to take that end, parse it out into something that's a little bit better to work with. Request is going to have some of these environment variables that we can see here that we can look to see whether it's a get, a post, a put. We can see that path info. And so with that information, we're going to be able to finish this method. So in this first line, we parse out the end into the request object. We then check to see if the request path is the route that we want to match on. If it's not, we're just going to call down the stack. We're not going to do anything. We're just going to pass everything down. And we're going to immediately return what the calls down the stack want to respond with. If the user did request that ping route, we're going to return 200. All right, so let's look at a less basic middleware. Request, response, time logging. Now, this is actually one of the most common middlewares I've seen written for small pieces of middleware. It's amazing how often you do not have beautiful things like New Relic, and instead you're on a solution where you need to do a little bit of your own logging and monitoring. So what we're going to be doing is we're going to be tracking how much time it takes your app to complete a request to index.html to any route. So we'll start with the same pattern we saw for ping. We'll take a new file, lib middleware, request time logging. We'll make a new class, again, namespaced. We'll create this initialize method, which again has a reference to the app that it needs to call down the stack. Now we get to the meat of this middleware, the call method. So it's going to take the end, as we know it needs to. And it's going to call down the stack. Because we want this piece of middleware to always call down the stack, it's not intercepting and returning any requests. It's not dropping any requests on the floor. It's always going to call down. We know we need to do this. We know that we need to call down the stack. So let's get some timing in here. We can start with recording the start time of the call, and then we compute the elapsed time. Now this is nice. We're calling down the stack, and we know how long it takes. But the problem is we never actually return a rack-compliant response. As you can see here, we're returning just the number, the elapsed time in seconds. We probably actually want to return a rack-compliant response. So the way we do that, we know we want to return the status headers and the response, we know that we'll get that data from calling down the stack. And so we just save that data off as we get that response coming back up the stack into our middleware, and then we return it. So now our app is working fine. Our users are hitting various end points in our app, and they're getting responses. But we still haven't logged our elapsed time anywhere. We're computing it, sure. But it's not being sent anywhere. So let's get working on that. We're going to do that in another method. We'll call that logResponseTime, and that method will need to know how long it's taken to call down the stack. And because it's important for us to know what the path is, it's not really helpful for me to know that it took 54 seconds for this request to complete if I don't know what route the user was hitting. We'll also send in the request. Now let's get to writing that method. We want to make sure that it's a private method. There's really nothing on a middleware besides the initialized method and the call method that needs to be public. So it's a private method. It takes elapsed time and the request. We'll set up a JSON payload with that data to be sent to our logging. And because the most common reason to do this is that you're using something like Splunk, here's just an arbitrary implementation of sending it to Splunk with your instrumentation of request.ResponseTime and sending the payload off. And that middleware is now complete. I want to quickly review an example of middleware in a gem because it's often that you're going to be debugging something and it's good to just take a quick look at how you would deal with a larger code base than something you might write quickly for your own app. So the example here is for throttling. Middleware is a great option for throttling because it can drop requests on the floor or return them, hopefully in this case, we're going to be returning with an unauthorized response without actually putting any load on our application. Our application never actually sees this. Our server doesn't have to deal with the load of bringing that response all the way down to the application. We can return as soon as possible. So rack throttle here on GitHub. One of the interesting things you'll see when trying to debug middleware gems is that you need to find the core middleware class, the class that has the initialize method and the call method. And they're always named differently. For this one, it's the limiter class. But once you find it, you'll see that there's an initialize method that does take app. It also takes options, which is an incredibly useful thing for allowing people to configure your code if you are packaging up a middleware as a gem. But first, the important thing is the app and then a call method. This call method, as we know, takes the environment. And then on this second line of the call method, does some pretty short logic. It asks, is this request allowed? If it is, call down the stack and return the result of calling down the stack. If it's not, call this method, rate limit exceeded. And presumably we're going to return an unauthorized code response with some helpful message. And I want to show this to you because it shows how little rack middleware code you really need to write before you can really jump in to writing code that is specific to your application, to what you want to do. So you really don't need to understand too much of rack middleware here to get going with writing a throttler. Now, all you have to know is how to throttle, which is a totally different topic, one that would be very interesting by the not this talk. So why would we want to write pieces of rack middleware? What is this tool best used for? Middleware can simplify your application. Middleware is very good at dealing with requests that your application should never see. So a good example of this is a situation at my job at Heroku, where we used to support a website called addons.heroku.com. This would be a site where you could see all of our Heroku add-on offerings. It's essentially a catalog of products. Now, behind this application also lives an admin interface for managing those add-ons. But the product data, essentially the store, has moved to elements.heroku.com. Now, what this means is that we have a lot of people out there who still want to go to addons.heroku.com slash New Relic and see data about New Relic. And those routes are hitting addons.heroku.com. But addons.heroku.com currently has no idea what a front-facing add-on is. All it knows is how to administer it. So our route file ends up for addons.heroku.com ends up being hundreds of lines long because it needs to redirect users to our new marketing site. Even when addons really shouldn't know anything about selling add-ons. It just needs to know how to administer them. So the solution here is to pull out some of those routes from that route file, move them into middleware so that when you hit addons.heroku.com slash New Relic, you don't even need to hit the addons.heroku.com route file. As a developer, I can deal with a very simple route file and you guys get a response far faster, which says, hey, redirect to this new site. Moving on, middleware can protect your application. So continuing on the theme of handling requests before they get to your application. There are certain types of requests that you just don't want your application to deal with. There's no point if you know that a particular request is malicious. There's no need for it to hit your app. Your middleware can handle it. And along this line, the throttling example that we saw, if your app shouldn't ever receive that request, your throttler can stop it. Also similar is implementing honeypots. Middleware sees both the request and the response object. So when a request comes into your application, it comes in through the routes file and it can hit any number of controllers. And it often will exit, again, through any one of those controllers. The only singular piece of code and, in fact, singular method that you have easy access to that sees both that request object coming in and the response coming back up, is a piece of middleware. And so this is how we know we want to write that request response timer in middleware. Because that is, that one method can be confident that it will see any request coming in as well as the response coming back up. It can be a code sharing mechanism. So this may not be so interesting to you if you are writing middleware for your own personal apps. But if you do have a piece of code that you want to share as a gem, middleware can be an important tool to sharing that code and allowing users to easily just drop it in without needing to do much additional configuration. So now, who the things that will trigger your successors to get blame your code? Just want to talk about order. Order in rack middleware is important. Going back to the Russian nesting doll analogy, it wouldn't make sense to try to fit the largest Russian nesting doll into the smallest one. It's not going to happen. Similarly, with rack middleware, there's a necessary order. So Rails provides a really nice rake task to tell you which middleware are being configured to be used with your application right now and the order that they're in. So if you run rake middleware on any modern Rails app, on any modern Rails app, you'll see this output. And this is from top to bottom, the order in which your rack middleware will run as the request is coming in. And then as the response is coming back out, reverse order as the response comes up. So let's look at some examples of how order can be a difficult thing and cause bugs for you. So taking the example of returning immediately for static file requests. We can see at the top of the rake middleware command that Rails is going to immediately respond with static files. We can also see down below that Rails is setting up the request ID and logging for the individual request. Now what this means is that if you're looking in your logging for all the requests for static files, you're not necessarily going to see them. In fact, with this configuration, you certainly won't. So unless you have alternate monitoring, you're not going to know that you're being DDoS by someone asking for logo.ping. Now if you wanted to make sure that all of those static files do get logged, you can just move the order of how you load in your rack middleware so that you are logging those requests. So another example, configuring warden. So we're really excited, in this example, let's say, we're really excited to add warden. We add it right at the top. We're super excited. And we can see lower at the bottom, we're actually setting up that session. Now this is a problem if we read the warden documentation. Because warden must actually be downstream. It depends on having session variables set. And so for warden, we need to make sure that when we first start using warden, it gets the warden rack middleware gets installed and gets used at the bottom of the stack. Next, application logic. So earlier, I talked about how rack middleware can help you simplify your application by pulling irrelevant parts out of the app. But there's nothing, in theory, stopping you from obscuring your entire application in middleware. This could be your entire app for an incredibly complicated application where everything is just handled through middleware. That's actually something that's kind of interesting to think about if you wanted to compose your application of small services. But for most applications, you'll probably actually want a full application that handles most of your business logic. You'll want one place where people can look when they want to debug things. You don't want to be dealing with rack middleware and dealing with business logic in rack middleware as well as in your application. So some red flags for maybe you should consider moving this logic out of rack middleware and into your application. First of all, if you're modifying the request, now there are plenty of things that are going to add to the request, many things that are going to help set up your application to handle the request. But if you're modifying or overwriting things like post data, things like the request path, you're probably on the wrong path. Next, awareness of business logic. It's hard to search for bugs across multiple places. So you probably want to keep your business logic in the place that people expect it to. It's also not the easiest necessarily to test business logic in middleware, because you are mostly working with either unit tests for that middleware in integration testing, acceptance testing. So you're probably going to end up with more bugs if you split your business logic between your app and your middleware. Another thing that's very similar is if it has awareness of the models, if it has awareness of the data structure, you're probably on the same path of something that's not going to be as maintainable. I don't know your app. Maybe you're doing these things. And maybe they work for you. So what are some mitigation strategies if you're going to be adding in application logic to your rack middleware? So the first suggestion I have is to use app middlewares. So if you are going to have that app logic, make it very explicit that that's what you're doing. It makes it easier for someone who's coming in who's debugging something to search for some keyword in app and find that code. It also makes it clear to a new developer coming on that, yes, the middlewares are something I need to learn. They're not extra libraries that I can reduce my cognitive load by learning later when I might need them. If it's particularly hackish, use app hacks or libhacks to add that extra red flag of, hey, we know we're not super comfortable with this. But it's still something that we want to do. And then finally, use keyword comments. Make it easy for someone who's searching for this bug three years after you've left to find the code by using throwing in comments some keywords you think they might be searching. Always return a response. So be aware that you are in a stack of middleware and that you need to comply with the three rules of rack middleware. Exceptions are not part of that. If your middleware is returning, is throwing an exception that it never catches, that's probably not a good thing. What that's going to result in is a 500 error that's not pretty. You're not going to get the Rails, oh, I'm sorry, something's wrong and we're on it page, because your Ruby code has erred. And rack is going to try to do its best. But it's going to be ugly. So final thing, thread safety. Thread safety in rack middleware is only important if you are setting instance variables. Now that usually implies a far more complicated piece of middleware than what I've written with you guys today. So we'll make ping thread safe, even though it doesn't need to be thread safe, because we're not updating app. Please don't update app on the fly. Because we're not updating app, we don't need to make this thread safe, but as an example, let's do it. So we're going to take that call method. All we have to do is dupe the instance of middleware. We then move all the logic that we put into the call method into another private method. The convention is underscore call. And then we're done. So we reviewed today what rack and rack middleware are, some of the reasons why you would want to use a piece of rack middleware in your application, a little bit about to make sure you're doing smart things with rack middleware. And generally, I hope this talk has made you feel more excited about using rack middleware sometime down the line. So thank you.