 All right, so I'm going to be talking a bit about RAC and middleware and how to build a web framework. Now, obviously, there's no way I can get everything in in a half hour. So effectively, what I'm going to be trying to do is I'm going to be trying to get a 45-minute talk in in a half hour. So let's just go ahead and breeze through this. My name is Charles Max Wood, and this GitHub link is where you can go and get the code that I'm going to show off. About me, again, you can get the source code. I run TeachMeToCode.com. We being primarily, I put together screencasts, tutorial screencasts, and we have a podcast. We have articles about Agile and Ruby, Ruby on Rails. So go ahead and check out any of these links. You can follow me on Twitter and Facebook. You can follow TeachMeToCode as well if you're just interested in site updates and things like that. So let's go into what RAC is. Basically, RAC is a library that helps you handle HTTP. And so what it does is you basically provide it with some object that responds to the call method. And what it passes is a hash that describes things that came in on the request. And then what it does is it returns an array. And there are three objects in the array. And we're going to see all this in a minute. I just want to get it out of the way so you can kind of understand where things are going. But the array has the status as the first element. It has the headers and a hash as the second element. And then the body or multiple bodies in the response. And the body has to be some object that responds to dot each like an array. And each element should be a string or should yield to it as a string. And it provides a middleware framework that allows you to set up your functionality. So here's a basic object. And what it does is it does basic, in fact, I have a little laser pointer that I'll just use here. So basically, we have a lambda. And the lambda will respond to dot call. And so we just set up a lambda. It takes one argument, env, and that's just kind of the environment that HTTP has created. And then what we do is we take the environment and RAC gives us this convenient object that's called a RAC request that you can pull the environment in and it'll actually create an object that's a little more pleasant to deal with as opposed to guessing what the keys might be or going to try and look up the documentation. So then you have, you can pull the parameters out. And it's just like using Rails' params at this point, except that they're stringified keys instead of symbolized keys. And then you have the dividend and you put it to a float and the divisor and you put that to a float. And I fixed this in the code that we're gonna run, but I didn't fix it in my slide. The dividend should be on the other side of the division. And then we return the content and we have a 200 status, which is success. This is our header and it'll complain at you if you don't put the content type in. And then we return the quotient in an array and then this run tells rack what to do. And so then all you have to do is you just call rackup and then config.ru, r-u stands for rackup and it'll just know what to do. So I'm gonna switch really quickly and we have our application here, our rack application. I don't see my mouse, so there it is. So you can see that I've already run it, 10 divided by four is 2.5. And this is actually up and running right now. So if we change it to five, then it should change to 2.0. And all that's running, like I showed before, is this rackup. And I'll show you kind of what I'm running over here. This code is actually, here. This code is actually running, I've put the branch that it's running out of. This is the basic rack branch. And so if you check it out, you go into the directory and you run rackup, space, config.ru, space, dashp 3000. The dashp just specifies the port and it'll start right up for you. So then we get to middleware. And middleware is just several layers of filters that go between the request and the actual execution of that application, which is the Lambda that we saw before. So you can set up filters before it gets to your application. You can set up filters after. So it gets to your application and bubbles back out. You can do both in the same piece of middleware and the middleware can actually be an endpoint. So let's go ahead and see an inbound filter. And again, there's the branch name right here in the bottom right. So this is in the inbound filter branch. So basically this is our piece of middleware. Now there are a few things I need to point out. First off, there's the initialize method that takes one argument. You can set it up to take more arguments, but you have to be very specific about where you're calling that middleware from. So it'll set up initialize, it passes in the app and this is just basically a reference to where you are in the middleware stack. And you need to be able to reference it from call so you assign it to an instance variable. And then here we're doing the same thing, we're getting the parameters. And then what we're doing here is we're setting it up so that we can get pass in hex values and it'll convert them to decimal and then give us the answer. So dividend and divisor, we just take them. And since we called, this was something that fouled me up a little bit and I'll just point it out. If you haven't called rackrequest.new on your environment, then this end rack request query hash is empty. But if you have called it, then this is set and all subsequent pieces of middleware will reference this for your params and not the query string. So since my stack is really short and I know that, and I've called this, I just know that this is what has been set and that's what I need to reference. And there's an example later where I manipulate the query string instead because I know that I haven't called this. So anyway, so what we do is we just do a regex match for 0x and then a hex number. If that's the case, if you call eval on the string, it'll convert it to decimal for you, it'll convert it to a fixed num. So then we can just put it back into this hash and then the next thing down the chain will pick it up and convert it to decimal. So let's go ahead and see that one real quick if I can find my mouse. So here what I've done is you see divisor is 10.0 and the dividend is 0xA, A is 10. So 10 divided by 10 is one. If we add more onto it, then whatever this is, 160, 171 divided by, that's not right. I think this is doing, I think this has them swapped. So I think it's 10 divided by 171. I'll have to double check that. But it is doing the math and returning a float. So let's come back over here to an outbound filter. And what this is doing is it takes the result that we've gotten and it basically rounds it to two places. So it takes the float and it changes it from 3.0 to 3.00 or 1.33 if you do four divided by three. And all it's doing here, this is the whole thing. It's just, it picks up the body and it passes the body as a float to sprint F and then you get the rounded body. Rounded body is a string, so it just passes it back. That's all you have to do. You can manipulate any of these, you can manipulate the status or the header or any of this on the way back out. So if we come back over here, you can see that four divided by 10 is 0.4. Four divided by 16 is 0.25. So you can see I have those backwards as well. No, that is correct. Anyway, let's do one where it'll have to round. So four divided by three, 1.33. So it's cutting off after two decimal places. And that's being done in the filter and not in the middleware because we're using the same division middleware that we were using before. So let's go to an endpoint. The wrap around kind of middleware, all you have to do is do what we did before. You just do your before stuff before you call at app.call and you do your after stuff after at app.call. So the endpoint here is it's checking to see if we have a decimal whole number for the divisor and the dividend. And if either of these have illegal characters and I left the period out, so it's actually gonna be integer values. If it doesn't match, then you're gonna get a 500 error. And you'll notice that this has a content type and body as well. And since we're passing in a body, it will actually render the body. So, I'm just come back here. And so if we try to divide munchy by gumbo, it says that your parameters are not valid. But if we change these to three and four, then it executes properly. Now we haven't added the other middleware in there, so we're getting 1.3333. But you kind of get the idea there. So this is basically the framework that we're gonna use to build some routing for our middleware. Since that seems to be what people are most interested in, is how do I get that path and give back this content? So there are a couple of ways that I've seen to do it. And basically what I did is I just looked at things like Rails and Sinatra and I decided, okay, well, if I wanna do what they do, then how do I do it? So map to a class. That was something that I kind of came up with that looks a little bit like Rails using params. And another piece of middleware is something that I came up with that also looks like Rails. And then the middleware in blocks is something that I came up with that looks a lot more like Sinatra. So we'll just run through these really quickly. So you can map to a class. And so basically what I did is I created a router class. And you can see here that it just generates a map for get, post, put, and delete. All you really need to map to a class in this instance is the HTTP method and you need the path. So what we do is we've gone ahead and we've said, okay, well, if you call get, in fact, we just did map and with a block, and then it just evaluates the block against a new instance of router. And so within the block, if you did like get, path, comma, another string that's formatted the way we want, or any object for that matter, this router's pretty permissive, then it'll just come down here and I'm actually using method missing. And so it says if the map has a key key, which would be like get, so if you call get and you have two arguments, so your second argument is where your route should go, then you just stuff it into the map. My laser pointer is dying, but that's okay. So then anyway, when you do dot get route, then it goes in and it just references the hash. And I know there are better ways to do this, but this is quick and dirty and kind of illustrates the point and then you can put in whatever data structure you want. So here's the setup for the middleware. So we go in and we do my app and then we have app and router and initialize. And what that does right here is this part generates our routes. Now what Rails does, oh thanks. Oh, it's green, all right. Isn't that the way it works in the Star Wars? Yeah. Yeah, that's right. I'm a Jedi now, all right. So anyway, so you can see the get method and that goes into our method missing and comes back with that symbol. And then here's the path and here's what we're gonna do with the handler. So then what we have over here is our class handler and it has def one, def two, def four. I'm actually only using def one because we only set up a route for that. But then what we do is we come down here and we do router, get route, and then we just pass in the environment and that strips out the HTTP request method and the path. And then it hands back this handler.one and we split that and then we say, okay, well, handler and again, I'm using eval. It wasn't my favorite way to go but I ran out of time and so I just crammed it in. But anyway, so this route zero is handler because we split on the period. So handler.new generates a new object here and then we come down here and if we have the handler and if we have a public method called whatever the second part was, which is one, then it just, it sends a message back to handler and says, hey, evaluate this, convert it to a string, whatever it is and then we're gonna stick it in the body and then we just pass it along to the chain. And so what we have here is basically an app that if we go to slash one, we should get back one. And so if we come over here, I've already run it but we can refresh, it'll come back with the same thing. And anyway, there you go. The bottom levels in this case actually returns a 404 error and I just copied the 404.html out of Rails. Oh, nope, I didn't handle that case, my bad. Anyway, so you kind of get the idea there on how this would work. So if we come back to the next method and again, these are all in their own branches and you can find them here at the bottom and I'll have to post these slides when I get back to my seat. But anyway, so then we have the params. We're gonna use the same router that we used before. The difference is that for this router, what we're gonna pass in is we're gonna pass in hashes that define some parameters that we want to actually add to the query string. And you can see that that's what we're doing here. So you come down here, you get the route and again, that gives you back this hash, handler and method. And then if you come down here, you can see that we're just tacking it onto the query string. Because the router request.new hasn't been called and so if you try and append it to the rack request params, it'll actually tell you that you can't add something like that to a nil object. So we just tack this into the params and then we call into our stack and you can see here that we're using, this is something I neglected to point out but right over here you have useMyApp, use Rubius, use Python East does. So basically what this does is this sets the route and then we include some more middleware, this Rubius and Python East does to, and we just include those in the stack. So what happens is if we, yeah, that's right. So what happens is we hit this and it pulls the route, it puts these into the query string so that when we come into this handler here, then what happens is this is some class out of this so it just finds the handler and if the handler matches the class name, which in this case would be Rubius, then it just evaluates the method call. And that's pretty much it and so what I've done here is I've actually built in a render and render just returns the classic rack response. So if it's Rubius then it renders awesome and if it's Python then it renders slimy. So we can go ahead and come over here. So Ruby gives us awesome and Python is loading. That is interesting. All right, well, I don't have time to debug it so we'll just move on. No, I'm running them on different ports so that I can show the different versions. Let's see. No, it's this path right here. It's Python and my middleware just may not have loaded that. I'm not sure I'll have to debug it, but anyway, you can see that it worked for Ruby and you can set up as many routes as you want that way. To handle that, I had it working last night so anyway, sorry my voice is cracking. I think it's a mix between being excited to be here and not having any water in front of me. So anyway, let's go ahead and move on to the middleware blocks. Now this was actually kind of fun to write because this involved quite a bit of metaprogramming. So let's go ahead and look at this real quick. What we're gonna do is we're actually gonna set something up that defines a piece of middleware and this actually was too long to put in one place. This is actually the rest of the method and you can see here at the end that we're actually including this piece of middleware into our stack. So if we come here, we set up a class, it doesn't need a name and then we just define these methods Action, Initialize and the block here is what's passed in. So if you think of things, so if you think about the way Sinatra does things, so it does get and then it passes a string for the path and then it passes a block, do, stuff, end. So what you have here is you have the verb which is get, you have path which is like slash whatever and in the example it slash chuck because I wanted to have the last word and then the block which is that do and all that stuff. So we set up this action which just takes that block. So if we call this class dot new, we set up a new instance and then we call dot action, it would execute that block. Initialize, that's just our standard assign the app to add app. We need to be able to access the verb and the path so I just set up getters and setters and I know there are better ways to do that but again I was being a little bit lazy and then you have the middleware send and so this sets up our call method and so what we're checking here is we're saying, okay well if the request method matches the verb and the path info matches the path then call action otherwise pass it along so somebody else can pick it up and then we set the verb, set the path and then we put it into our stack. And so what this looks like is we require the API that we've just set up there for our rack application and so then all we do is we do get which we set it up so that I set up methods so that it get called the define middleware and just pass get is the first parameter. So get slash chuck do and then it sends back the success as text HTML with an appropriate method or an appropriate body and then here this is the this is the default backend and you can see that it just returns a 404 with whatever's in this 404.html and so if we come over here to our last piece of middleware you can see that it's returned what we expect and if you go somewhere else well sorry Joe you're undefined so you're not found. All right so anyway I have like a minute or two left so I'm just gonna point out I don't have time to go into views and layouts. You can use a standard ERB or Hamel and those libraries have the methods for putting the information into them but then if you set up render then you can actually build the string out of ERB or builder or whatever stuff it into body you just send that back and that would work. You could set up downstream middleware you may have to serialize the assignment variables that you're trying to pass into your view so that it can handle them properly or there may be a way to put them into the header and then pull them back out in a subsequent middleware. I didn't have time to play with it but you can use any form of markup as long as it gives you a string back so that's pretty much all I have. I did stuff it into 30 minutes I'm impressed. Anyway if you have any questions I'll take them now and then go check it out go get the source code. Tav? Where do you just a piece of middleware and then bumping it up to using something like Sinatra? What do you mean? So writing your own? Well it seems like you could yeah or just using something that like a framework like Sinatra. Well Sinatra does a lot of things but the core that most people use it for I mean it's not too much different from what I just did. Sure. You know so I mean if you need the functionality that's in Sinatra I mean some of the niceties that I didn't handle like if you want to give it a path that has like colon ID or something in it and you don't want to deal with building that in yourself then you can handle it that way. You could also use regular expressions to handle some of that. It really depends on the level of work you want to get into. If you're just defining a simple API and you can literally just switch through a couple of cases then Rack might be the simpler way to go because all it's gonna do is load the one library and then spit back a text back at you. Right so I don't know if the recording picked up what Dave said. Basically what he said was Sinatra makes a really good endpoint but not necessarily a great middleware. So you know you can use Sinatra to define your underlying stuff but if you need something that will delegate under certain circumstances then you probably want to use Rack. So yeah I'd be really interested to see how that works. Sinatra like a lot of the other ones are based on Rack so you would assume that it would have that capability. Yeah I think Rails in theory should be able to but I don't know how you would do that. Any other questions? Just the entire response goes. I've always felt like the honest answer is the best one I don't know. Yeah for those that didn't hear it it was a question about streaming media and things like that through Rack. Anything else? Anyone else? All right well thanks very much.