Playing
01. How middleware works

Episodes

0%
Your progress
  • Total: 20m
  • Played: 0m
  • Remaining: 20m
Join or sign in to track your progress

Transcript

00:00
In this snippet, we're going to look at a simplified version of how middleware works
00:04
behind the scenes and we're going to do this in the context of some kind of framework, so think like Slim 4 or in Laravel. Now if you want to follow along, you can go ahead and grab the initial code that I've got set up here.
00:17
I'll go through this in just a second and if you go ahead and download this and just run composer install, that will pull in the dependencies that you need and also you'll want to run a local PHP web server within this directory, so you can just use the PHP web server built-in web server with the S flag and the port that you want to run this
00:36
on and if you open this up, you should see something like this in your browser. So let's take a quick look at the code here. We're requiring in the autoloader from composer, we're newing up an app and we're running the app.
00:48
All that's happening in here at the moment is we're just dumping using the symphony var dumper function which is pulled in inside of composer and that's pretty much all that's happening at the moment. The goal that we want to achieve here is down here somewhere to find out the list of middleware
01:04
we want to run before we get to the point where we run our app and with this middleware, we can modify objects along the way like a request object or a response object and we'll be looking at a kind of fake version of that in a minute. Now this course isn't going to cover what middleware is.
01:20
In essence, middleware is a list of actions or things that run before you get to a point in your application, almost like the layers of an onion. But if you are familiar with middleware and you've worked with middleware before, you'll know exactly what it does and how it works and in this snippet, you'll see kind of how
01:36
it works behind the scenes. So the first thing that we want to do is look at a couple of middleware classes. So inside of the directory structure here, I've got this core which will be all of our core functionality.
01:49
Imagine that's our kind of framework stuff and then anything outside of this core directory will be things like the actual middleware classes that we want to implement. So let's go ahead and just create out a piece of middleware here and I'm going to call this first middleware.php and let's start to define out how we might want middleware to look and
02:06
this varies from framework to framework but you'll kind of get the idea. So this first middleware here, we just define this out really quickly and give this a namespace. So this is now under app and middleware, is going to have an invoke method. So you can do this with a handle method or, you know, anything you like but in this case,
02:26
we're going to have an invoke method which means that when this is called, whatever happens in here will be run. So let's just say first middleware like this and let's go ahead and grab this and create another piece of middleware as well.
02:40
Just create two out, they're not quite complete yet but this will give us a good idea as to how this works. So the way that we want this to work is over an index.php, we want to say something like app add.
02:51
This is the way that slim works, the slim framework works but of course, you could do something like add middleware if you want to do, you could pass it into your app. It really doesn't matter how this works. So you're going to want a new first middleware like so and you're going to want a new second
03:10
middleware like so and let's just make sure that we pull the namespaces for both of them in. Now of course, this isn't going to work at the moment because the first thing we have is there's no add method over on app but we need to discuss where we're adding these new
03:22
middleware classes to and that is going to be a middleware stack. So in this case, the way that I've got things set up, we've got this overall app class and what we probably want to do is accept in a middleware stack objects. Now the middleware stack will be responsible for keeping the list of middleware that we
03:40
want to run and also then running that middleware as well and returning the final result and we'll take a look at that in just a minute. So let's go over to core just here and create a middleware directory. So remember this is part of our framework and let's create a middleware stack class
03:57
inside of here. So let's just create this out, middleware stack and of course, we'll go ahead and give this a namespace as well so we can put it in properly. So I'm going to put this in app and core and middleware.
04:09
Great. Okay. So our middleware stack then, what needs to happen when we knew this up and pass it into app?
04:15
Let's go ahead and accept this in first of all, so middleware stack, I'm going to type into here and I'm just going to call this middleware for now but of course, you can call that whatever you like and let's go ahead and assign it to a property inside of here and just go ahead and create that protected middleware property up there.
04:32
Great. Let's pull the namespace in for that and come over to our middleware stack. Now the way that middleware works is you typically have a tip or a starting point which is your kind of final middleware that is run.
04:44
This could be absolutely anything. It could be that something that registers things in your application, sets things up. It's basically the last piece of middleware that runs. Now with a list of middleware, let's just say we have our app which is a given, we want
04:57
this to run. You'll have your initial piece of middleware or your last piece of middleware added first then you'll have your middleware added in a certain order. So for example, if we were to add our first middleware, that would be added in here and
05:14
then when we add our second middleware, that's going to be added on to the top of the stack. So second middleware on here. So it will run in this order. Second middleware, first middleware, initial middleware and then our app will run using
05:28
any of the data that we have within our middleware. So let's start things out by creating out a constructor inside of our middleware stack and let's set some kind of tip or starting point for our middleware. So let's just call this start.
05:44
Now inside of here, we want to initialize this with the last piece of middleware that we want to run. This could be absolutely anything. So what I'm going to do is I'm going to set this to a function and we'll see this in just
05:55
a little bit. And in here, I'm going to do absolutely nothing. You could even dump something out in here if you wanted to. So let's just say dump start middleware.
06:06
So this in itself, this closure here is kind of like a piece of middleware. But what we want to do is give the ability to add more onto this and then call this after the piece of middleware that we've added has been called. So remember, it runs in a chain kind of like an onion.
06:22
So over in app, then we've got our middleware stack pulled in here. Let's create that add method that we saw a little bit earlier and all that's going to do is defer to this middleware object. So we've got in here some middleware like so and we're going to go ahead and call this
06:38
middleware add and we're going to add that middleware in there. Now, you'll probably at some point have this set to an interface. You'll probably have middleware.php as an interface. So let's go ahead and just really quickly create this out just so you can type in and
06:56
know exactly what you're passing in. So let's say namespace app core and middleware and then each of your pieces of middleware will implement this middleware interface and that's also useful if you have a named method in here for handling middleware.
07:12
So each of these things that we're going to create and each of these pieces of middleware are going to implement that middleware interface so we can do the same for our second middleware as well and all that means now is that over in our middleware stack or over in our app, when we add some middleware, we just make sure that it is of the type middleware so
07:31
it can be run and properly handled. So like I said, we're deferring to the middleware stack. So over in the middleware stack now, we also need a method called add and this is really where the magic happens.
07:43
So again, this middleware can be type-pinted to that interface and here's where we want to add middleware. Now there's a couple of ways to do this. What you can actually do is you can have a kind of stack which is usually an array of
07:59
pieces of middleware that you want to run but if we think about middleware, inside of the first middleware, we want to do something potentially with an object that's passed in here and then we want to return the next piece of middleware and call that next piece of middleware and we usually do this with a variable called next which will always be
08:18
passed into every piece of middleware that you run. So you invoke the next piece of middleware and you can potentially pass anything else through to there and you'd return this as well. Now we're not quite at that point yet so let's just get rid of this and let's go over to
08:33
our middleware stack and look at what happens when we add middleware to our chain. So what we want to do is essentially replace this middleware with a new piece of middleware but we want to keep this middleware here as the next callable middleware. So how are we going to do that?
08:50
Well let's go ahead and just assign a local variable in here next to this start. So we know now that if we dump out on next, what we should see is and if we just come over to here and make sure that we pass in a new middleware stack first of all, remember we pass that as a dependency through and we come over and give this a refresh.
09:14
So you can see this is dumped out a couple of times here, of course the reason it's dumped out twice is we're calling that add method twice from in here but essentially that is the next piece of callable middleware and this is that start anonymous function that we have to find out just inside of here.
09:30
Now rather than dump this out, what we want to do is replace that middleware with a new piece of middleware e.g. in our case just an anonymous function and inside of this anonymous function we want to return the middleware just inside of here so it can be called when we go through the list of middleware.
09:49
Now into this what we want to do is bring into scope the middleware that we're trying to add and the next callable middleware as well which is the previous piece of middleware that's been added e.g. the start one and from here we want to return that middleware that we've passed into this add method and we want to invoke that passing through the next piece
10:11
of callable middleware e.g. this just here. Now if we call this three times, this one would be replaced with the new one that we've added e.g. this one here and if we call this again, this one here would be replaced with this and although it seems like we're overwriting what we've already added, we're keeping a
10:32
reference to the next callable middleware e.g. the one before that. So essentially what we end up with is the initial middleware that we've defined here that's replaced out with the first middleware but then next in this context equals the initial middleware and then when we have the second middleware added, just add that in there really
10:52
quickly as a comment, the next piece of middleware is going to be the first middleware. So what we basically have is a chain now so when we call say the second middleware which will then become the first on the stack, we then call the next which is the first, then we call the next which is the initial and then we've basically run out of middleware
11:12
so we end the iteration of it there. So there's no looping here and it can be a little bit confusing to look at this and work out what's going on but essentially whenever we call the start middleware, that's the last piece of middleware that's been added, we always call the next middleware.
11:28
We call that middleware passing in next and when we invoke next, that will run what we just saw in this list. So hopefully that makes sense. So let's just go through this and have a look at what we've got here.
11:39
So if we just give this a refresh here, you can't see anything at the moment but if we go ahead and dump out here this start, that will prove to us that the last piece of middleware that we added is still in there. Now what we want to do is get to the point where inside of our app, before we run our
11:57
app, we call the first piece of middleware. So inside of the middleware stack, what we should probably end up with is some kind of handle method to basically kickstart the process that I explained down here with them comments. So basically all we want to do is just call the first piece of middleware, e.g. the last
12:16
piece of middleware that we've added. So we can do this by returning call user func. We can pass in this start which is remember a function and that will kickstart the process of calling all of our middleware.
12:30
So if we come over to our app just here, inside of run, before we run our app, we want to reference the middleware stack and we want to call handle. So let's come over and give this a refresh and you can see here we get run app and first middleware.
12:46
Now you're probably wondering where's the second middleware? Well in this case what we're doing is not calling the next in the stack. So we're calling the middleware that we've added but we're not calling the next piece of middleware.
12:57
So what we can do is inside of each piece of middleware, which I'm sure you've seen before if you've worked with middleware, is define the next middleware that needs to be called and then what we can do is we can return next like so. So we can do that for the first middleware and we can do that for the second piece of
13:16
middleware. So now when we run this we actually get first middleware dumped out a couple of times. So let's just have a look at why that might be and that's just because we named it first middleware or we dumped first middleware out twice but essentially what we're getting here
13:32
now is the last piece of middleware that we added being run. That's calling the next piece of middleware which is that first piece of middleware and then we finally end up with the start middleware which remember is that kind of default middleware that we added over in our middleware stack just here.
13:50
Now if we didn't do this, if we didn't define this out, what we'll basically end up with is we'll be trying to call something that doesn't work. So we could say this start equals null but we'll be trying to invoke null as the last piece of middleware so of course what we get is must be callable null given.
14:08
So you don't actually need to do anything inside of this first piece of initial middleware if you don't want to. If you're creating out a piece of or a middleware stack yourself, you could just add the start to an empty closure.
14:20
It doesn't really matter. You don't need to run anything here. So you can just kind of assume that this is just your kind of starting point. Okay, so with very little code, we've managed to actually create a middleware stack that
14:31
runs each piece of middleware one after another. So that's how simple it is to create this kind of middleware stack. You don't need a lot of code at all and the reason that this is so simple is again we're taking a reference point to the last piece of middleware and we're always passing it
14:46
through to the middleware that we're adding so we can continue to call it and of course in your middleware as long as you're always returning and invoking next, this is always going to work. Now let's take this a little bit further because although this is a very simplified example,
15:01
let's make it a little bit more complicated in terms of some kind of request object that could potentially be modified along the way middleware is called. So inside of the core directory, let's create out an HTTP directory and let's create a fake request object in here just to test this out.
15:18
So let's give this a namespace and of course that's now app core and HTTP and let's create this request class out here and let's just create a public code. Let's just say that the code by default is 200. Now this is a request object that when you are working within your app, you'll probably
15:36
want to always send through to your middleware handler so you can include it in each piece of middleware that you have. So I wouldn't normally do this but I'm just going to new this request up here. Of course it's just a really simple example class and what we're going to do is with that
15:52
request, we're going to accept that into our middleware stack, into handle just inside of here. So let's go ahead and accept that in here, making sure that we pull the namespace in and we're actually going to pass that through to the first piece of middleware that we've
16:07
defined as an argument and of course you can have multiple arguments passed to middleware as well. So now inside of our middleware, we're actually going to end up with this request object inside of each piece of middleware.
16:20
So let's go ahead and pull this in here and I'll grab the entire signature of the magic invoke method and just copy that over to here, like so, making sure again we pull that in and if we just give that a refresh, okay, so now we're on to another error where inside of second middleware which is remember the first piece of middleware that's going to
16:40
be run, we get too few arguments to function middleware stack closure and again that's that anonymous function. The reason that this isn't working is inside of first middleware and second middleware now, when we call the next function, this requires the request to be passed in because
16:58
over in our middleware stack, this is taking in a request object. So we need to come over to first middleware and we need to pass down the potentially modified things that we're passing into each piece of our middleware and again, if you've worked with middleware, you'll recognize this kind of thing, next request.
17:14
So if we come over now and give this a refresh, you can see we get second middleware, first middleware and then run app. Now if we just head over to our middleware stack inside of the first or the initial piece of middleware, we can actually accept this request in here and let's say that this was
17:33
an integral part of your application that you needed to then use inside of your main app class just here. We can actually return that request from this and then inside of app, you could go ahead and assign this the value you get back.
17:48
So we could just call this result, it doesn't really matter and then we could go ahead and dump on that result. Now if we come over to our middleware stack and we don't return this, so let's go ahead and comment that out and we give that a refresh, sure enough we get a null value.
18:01
But if we do return the last thing or the things that are important to us once our middleware is completed, we actually get that request inside of here. So we know that second middleware is run first, let's say that our second middleware was something to do with an authorization and we wanted to modify the request inside of this piece
18:22
of middleware. Well what we could do is really simply and because we have that public property over on request just here, we could just go ahead and set the status to something like 401. And when we come down to the end of our application, well in this case this is code but you get
18:37
the idea, you end up with the modified request object that then you're probably going to go ahead and send across to the browser, setting the status code and doing everything that you need within your routing. So that's basically it.
18:51
We've looked at a really, really simple middleware stack here. Of course things can get a lot more complicated but this is generally how things work inside of a framework, albeit a really simplified version. So we've started off by adding some middleware to our middleware stack which is passed into
19:07
our app as a dependency. We start off with a kind of plain or empty piece of middleware which is always run last and this is useful if you want to return things like the modified request object that's potentially been modified by other middleware.
19:22
Then with our middleware, we're not doing any looping, we're not looping through arrays. All we're doing is every time we add a piece of middleware, we're replacing the tip of our middleware with a new closure which can be invoked which returns the middleware that we've defined, passing through any objects that we want to send through to our middleware
19:40
and really important, the next callable middleware which is the one that we're replacing. Then when we handle this, all we need to do rather than looping or anything like that, we just call the first piece of middleware which is the last piece of middleware that's been defined, passing through anything we need inside of here.
19:56
And sure enough, that will go through each piece of your middleware that you've defined and eventually end up running your app and using anything that you've used in your middleware and potentially changed. So there we go.
20:08
That's a really simplified look at how middleware works with a very, very simple middleware stack. So next time you're working with your framework and you're wondering how middleware cycles through and passes objects to each other, this is generally how it works.
1 episode 20 mins

Overview

A low level overview of how middleware runs in your favourite framework. Starting with a simple app example, we'll build a middleware manager, add middleware to a stack, and run it.

Alex Garrett-Smith
Alex Garrett-Smith
Hey, I'm the founder of Codecourse!

Episode discussion

No comments, yet. Be the first!