
Lesson Description
The "Socket Exercise" Lesson is part of the full, C Fundamentals course featured in this preview video. Here's what you'd learn in this lesson:
Students are instructed to refactor the HTTP server so that the server sends the error response codes through the socket back to the browser instead of the console.
Transcript from the "Socket Exercise" Lesson
[00:00:00]
>> Richard Feldman: All right, now you might notice that we have some more includes than last time. So one of the clues that I want to kinda shout out here is this is how old [LAUGH] C is. This is arpa/inet.h. So, the ARPANET, these like the original internet, like the precursor to the worldwide web, this is how old school this stuff was.
[00:00:18]
Like they were using C in fact. On the arpa/inet. [LAUGH] that's how old this stuff is. And guess what? It still works in 2025. Very cool, all right. So as usual, we're just gonna start by building and running the program. There we go. And now it just says listening on port 8080.
[00:00:37]
So, let's see what is actually happening on port 8080. So, if I go to local host 80 80, it says, hey, this is the root /page. There's nothing here except a blog. Now, if you may recall what was actually happening here is where was that? Index.html, right? So, this is the root/page.
[00:00:58]
There's nothing here except a blog. Hello everyone. I'm gonna just do that, not gonna restart the server. I just saved that file and we go back here, refresh the page, and bam, there it is. So that is what we've built up to today [LAUGH]. We've got a real static HTTP server.
[00:01:13]
We're gonna make it better in the next and final part of the course. But, but hey, it actually works, and it's not actually that much C code. Like if we look at this file, this is only like 220 lines of C code. Now granted, there's a lot of edge cases we're not handling here.
[00:01:26]
This actually doesn't support images yet. We're going to get to that in the next section, but it's pretty impressive. Like that this is all it takes to build a really, really high-performance, like C web server. Yes, some of the APIs are a little bit funky. Yes, we're doing a lot of weird stomping all over memory kind of tech.
[00:01:40]
Techniques in here, but if you want to get down to it and like really like get close to the operating system, this is what that looks like, all right. Now I do want to notice, sorry, that was from earlier. We are getting a number of HTTP 500 internal server errors here.
[00:01:57]
Now this might kind of surprise you because on the one hand, well, first of all, I didn't actually go over the. Part of the code where we are, in some cases we're turning at 500, so we'll look at that in a second. But also you might be, yeah, what's going on with that?
[00:02:07]
Because, I mean, clearly the browser doesn't think there was an error like this was, it's just like worked. So what's going on there? So, if I refresh this page, I might notice, what's that little thing down there? Let me zoom in for you. This is something that we don't actually have right now.
[00:02:26]
Support for images includes support for the favicon. So, favicon is a fun little one to debug. If you're not familiar with this, this is something the browser automatically goes and tries to get, to make the little icon in the browser tab. So, if you don't either have one or at least have your server able to be aware of the possibility that we're going to get one of these things, then by default, what you're going to get is some sort of error.
[00:02:48]
You might be wondering, like, why is it printing so many of those? Like, if I refresh the page, I get two, 500 internal server errors, not one. We'll talk about that in a second. All right, so going through this code in part five exercise. This is all just like exactly the same stuff that we went on the slides, except now it's all sort of spread out here.
[00:03:08]
We're doing the handle request thing. This is our little helper function that does take the request and the socket and like calls to path on it. This is basically just like I just split out a helper function, so we can sort of isolate the request quest handling code in here, just for code organization.
[00:03:21]
But otherwise the same exact stuff we did in the slides. We are doing a little like, okay, if I open this path and I get file descriptor is -1. Then we want to handle the case where it's a 404 differently and not just like, always do a 500.
[00:03:38]
The only issue is that this is not actually like printing a response to the socket. It's just printing it to standard out with a printf. So, what we're gonna do in this part of the exercise is actually like respond with one of these things. So instead of a 500 internal server error, we wanna return a 404 not found.
[00:03:57]
Down here, again, we're just like printf, if we fail to get the stats on this thing, then again, we do actually want to print a 500 error, but we wanna return that error to the browser, and once again, same pattern here. So basically, the theme of what we're doing here is we have a number of cases where what we're doing is currently just printing it to standard out.
[00:04:20]
And what we want to be doing instead is just actually sending it to the socket.
>> Richard Feldman: All right, let's go through the solution. Okay, so a bunch of the stuff unchanged from previous like two path works the same way as before, fundamentally. Handle requests is our little helper function here for just dealing with the request stuff.
[00:04:43]
The theme here is that we've got all these like different error scenarios that we want to handle properly. So for example, in this case, we've got HTTP 400 bad requests. So what does that mean? This means that when we called to path, to path, returned null. So here we've essentially done that same kind of C style error handling.
[00:05:01]
That we've seen in all these PIs we've been using, where basically you may recall, if we didn't have enough space to write this thing, meaning that we got a weird request that didn't have like enough space to write this thing. Or if we got something that didn't have the space where we expected it, like the file ended before we hit the spaces.
[00:05:20]
In either case, that means this is an invalid request, and two path would have returned null. And here we're just basically doing that same exact error handling case where we're like, yeah, let's see if what this function returned gave us an error code, in other words, a null address here, or a null pointer.
[00:05:36]
And basically, we're like, yeah, okay, now we need to propagate this error properly. So how are we going to do this? Well, we're gonna send this bad request header back to the browser and essentially say, okay, I'm gonna do a write and write takes as its first argument, the file descriptor.
[00:05:51]
So, this is gonna be socket FD. And then basically if we want, we can do like the know, sterling of that thing. Or if we wanna be like a little bit fancier about it, we can kind of put this up here and say like const char star, error 400 or something like that equals that.
[00:06:11]
And then down here, we can just do strlen of that one. There you go. So, this is basically a pretty straightforward pattern for just sending like a 400 error. If we have something that we are dealing with a lot, like we are with 500 errors, we might even wanna make a little helper function, which I'm gonna do in a sec, to just like do this right for me.
[00:06:32]
Okay, next one here. This one's actually kind of cool because it's pretty easy to reproduce. So, when we get this working, we will actually get a 404 Not Found. So, if we just do, let's say I open up my browser and I'm like, okay, I would like to open up localhost/foo.
[00:06:48]
And it's like, well, hang on a sec. That's like, Foo is not a thing, so it's like cool 404 Not Found for that. Now, by the way, I'm gonna not touch my keyboard for a second, and you might be a little bit surprised that even though. I am not touching my keyboard.
[00:07:01]
All right, I might need to, sorry, I might need to do that over again. Okay, I'm gonna local host for yeah /foo not touching the keyboard. And yet we're still getting like 404 coming in. So what's going on there? Why did we just get a 404 on my hands weren't even on the keyboard.
[00:07:17]
Does anyone have any guesses as to what's going on there?
>> Speaker 2: The browser retrying.
>> Richard Feldman: Yes, this is, this is easier to tell if you use Wget here, and Wget is basically gonna actually, like, tell us what it's doing. I need to actually be running the server. So run this on a different one.
[00:07:38]
It's basically, hey, HTTP request sent a rating response, no data received retrying, retrying, retrying, retrying. And the reason for that is that I'm printing out 404, to standard, out, but I'm not actually telling the server anything. So the server is not getting any response here. It's just like, hey, hello.
[00:07:53]
What's going on? And it's just like, times out. It's like, well, hang on, let me try again. Hang on, let me try again. So, the browser is the same thing, it just doesn't have as much of it. Delay as Wget does in between retry attempts. But not to worry, because once we actually like respond with this, then we're gonna be okay.
[00:08:08]
So, I'm going to basically just do the same like pattern that I did here. So, this is going to be 404 Not Found, and I'm just gonna follow exactly the same pattern that I did back there, where I'm just gonna write to but instead of error 404, it's gonna be, sorry, instead of error 400, it's gonna be 404.
[00:08:25]
So, we're gonna do that and then we do the same thing over here with 500, okay. And now, it's gone there. Yeah, I didn't actually define the error 500. Let's go ahead and do that. Okay, there we go, cool. I'm using Zed's fancy autocomplete there, so I to save myself some typing there, but you get the idea 400, 404, 500, etc.
[00:08:48]
And then basically we're just gonna write these here. Okay, so now when I restart the server, now we should get something much more semantically familiar. If I go to localhost.com, port 88/foo, we get, hey, page can't be found because the browser actually knows when I get a 404 back.
[00:09:04]
Here's what I do. I display, you know, HTTP error 404 and good stuff like that. And now we are not actually going to see all these things spamming us locally, because when the browser gets a 404 it knows what to do with that. It's like, yeah, got it.
[00:09:18]
This is a 404 error. I'm very familiar with these. I don't need to retry because we actually did send it a response in that scenario now. All right, looking good. So that's definitely an improvement. Let's keep going. So, at this point, we might notice a pattern here where it's like, okay, this one's gonna be a 500 error.
[00:09:34]
This is like if fsat failed. So, this means that like we were able to open the file because if we weren't able to open the file, then we would have 404. I forgot to mention, by the way, this is another beautiful C error-handling pattern. So, if fd=-1, that tells us it's an error, but it doesn't tell us what type of error we have.
[00:09:52]
That is the open function will set, will mutate this magical global variable called errno. This is not something that we wrote. This is just a magical global. And the way that errorno works, it's sort of like error number is when you call a function that can error in a particular way.
[00:10:10]
The way that you check whether this function failed was look at it as a string value and see if it was negative one. If it was negative one, then you go look at errorno before calling another function that does that, or else, you're gonna be in big trouble.
[00:10:19]
Because it's gonna stop all over it. And then in that magical little window between when you called one of these functions and you called another one, errno will be set to whatever the most recent way that one of these failed was. And this will tell you what the actual error was.
[00:10:33]
And ENOENT, and I'm not sure why exactly that. I remember at some point I knew the history behind why it was called that. But this is like, basically means file was not found. So, if you ever use no JS, you actually can see this. Sometimes you see, like, the console, like an error, like, no end, or something like that.
[00:10:48]
And that's because no JS is sort of uncritically passed along the the like C style error from this thing. So, if it's one of those errors, then we're like, okay, 404, meaning not found. So, this bit basically means like we attempted to open that path. So in this case, when I went to local host 8080/foo, we'd be coming through here.
[00:11:08]
The path would be foo/index.html. But since I don't have a directory called foo or a index.html within it, when I try to open that, this fails. FD is negative one errorno is set to Eno, end, meaning that it was file not found. And therefore we know to send, backup 404.
[00:11:23]
If it was anything else, like the file was there, it was found, but we couldn't open it, for some strange reason, like a permissions issue, or something, then we're gonna call that an internal server error, and send backup 500. And a lot of these are going to be 500s.
[00:11:34]
Like if we were able to open the file, but fstat on it failed, for whatever reason. Again, 500, internal server error. What happens if we attempted to write to the socket and writing to the socket failed, even though reading the file succeeded again, 500 internal server error. Now, there are so many of these things that at some point you start to be like, okay, maybe rather than, like, duplicating all of this write logic over and over.
[00:11:56]
Maybe I should just actually extract a little helper function for this, which I think is, like, perfectly reasonable. Let's say I wanted to just make a little helper function for this. One of the things we would probably wanna do is even though we're not using it here, is I would actually like to return, like, pass along the return value of the write.
[00:12:12]
So that's an size t. And I'll just call this like write 500. So, it takes socket, int, socket, good. And then we need the actual like, sorry, this is the 500, so we actually don't need that. We know that. So basically, what I'm gonna do is I'm gonna say return, whatever right return.
[00:12:34]
So that way if we do decide we want to handle that error, we have the ability to, and then it's just gonna be this exact thing. So now that we have. I can replace this with that socket-fd, okay, great. And then now every time I see one of these things, I can just do a nice little one of those.
[00:12:54]
Well, yeah, I guess we, right. We do want to early return when that happens. So, we wanna like exit instead of just printing this one's already like, returning negative one. Yeah, I guess if, okay, never mind. We do want to return negative one to indicate to the caller that we that we failed.
[00:13:09]
So that's fine. Okay, so we can do that. Or we could have write 500 return negative one and just return that. But I think it's probably more idiomatic to just like, propagate the error in case we do want to handle it separately, like maybe writing it to a log file or something like that.
[00:13:22]
But yeah, I mean, all of these being 500s, now we have our little helper function. Nice and easy and there we go, great. Now you might notice that over in this section of the code, like this is a actual main, like before we're doing the handle request stuff.
[00:13:35]
We do have error handling around all these things, which we didn't talk about in the slides. These do not actually send a 500 error. Can anyone think of why we don't do that?
>> Speaker 3: We didn't even get to start the server.
>> Richard Feldman: Yeah.
>> Speaker 3: Probably the most thing.
[00:13:46]
>> Richard Feldman: Yeah, right, exactly we would be sending into the void because until we actually make up through these boilerplates steps, all of which can fail. We're not actually talking to a browser yet, we're only talking to the browser inside this, after we've done the accept, we've done the read, and all that good stuff.
[00:14:03]
That's the only point where we can, there's one more of these, fine. Let's see if, yeah, this is the problem with AI auto-complete. Sometimes it hallucinates a helper function you didn't actually write. [LAUGH] but yeah, was that 413, it's always fun to, like, look up all the different error codes.
[00:14:24]
All right, I guess I need to ERR_404, ERR_413. Content too large.
>> Richard Feldman: Okay, and the right, great. Okay, and then I guess we actually so in this case, we don't necessarily need to early return, because we might want to just keep handling further requests. Just because this one was too big, that's no big deal.
[00:15:00]
But, yeah, this is basically like the difference between error handling when we don't have an active connection and don't have a browser connection that we can like actually send the response back to. And when we're just still kind of like starting up and like trying to start up the server.
[00:15:13]
So, if any of these fail, like for example, it's pretty easy to induce, say, like, addresses and use. So, if I leave this server running and then I try to, like, do this again. Was around 5, yeah examples exercise yeah. So, it says, like, bind failed address and use.
[00:15:33]
Would be a nicer example to give. But yeah, this is the way you can actually get this thing to not even make it to the while loop. The infinite loop, because it's like, yeah, bind failed. So, we're done, okay. Now, as cool as this is so we got our 404 here.
[00:15:48]
If we go to root, that works. I can even like follow links cause this takes us, that changes the URL to /blog, which also works cause we have /blog/inject.html. All of that works great. But we are missing an important piece here, which is that I have strategically chosen to make these things have no images or CSS, or JavaScript whatsoever.
[00:16:05]
And the reason that I do that is if let's say, for example, I were taking the front-end master's homepage, which I've actually got right here in a separate tab. This is what our beautiful web server does for the front-end master's homepage. Now we do have one of these images kind of works, but you notice that all the CSS and all the JS and all that stuff.
[00:16:21]
It's just totally busted, and also the image is totally busted. This one's a little bit better. That's kind of cool. Everything's horrendous. Why is everything horrendous? Because all we are doing for our requests, or sorry, our response headers is we're just doing HTP 1.1 200, okay. We are not sending a single response header whatsoever, and the browser uses those headers like content types specifically to figure out like how to interpret this thing.
[00:16:44]
So, if I just send it a bunch of bytes and I say, hey, this is a CSS file, it's not gonna just be like, well that is a dot CSS, I bet it's a CSS file. No, it says like, no, you need to tell me in a header how I'm supposed to interpret these bytes that you've sent me.
[00:16:56]
And right now we're not doing that at all. When it comes to the bare HTML, it's gonna be fine with that. But that's pretty much where it draws the line. So essentially, what we need to do is we need to start actually sending response headers, which is what we're gonna do in the final section of the workshop.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops