feat(resources) add http-project-guide

This commit is contained in:
moe
2020-06-08 22:32:20 +01:00
parent 49b2b7eabe
commit d3df50b340
15 changed files with 789 additions and 0 deletions
@@ -0,0 +1,42 @@
---
authors:
- "Kibb#4205"
created_at: 2020/06/08
title: "Chapter 1: Getting setup"
---
## Getting setup.
One of the first steps to any project is having a nice clear workspace to get started.
We can do the same with programming projects by creating an empty directory for our project to be developed in.
You should pick a suitable name for this project, you
can pick a word or phrase yourself or use a [project name generator](http://codenames.herokuapp.com/).
Try to make sure that there is no space in your project name as this will cause issues later.
If you are using a build tool that sets up the environment for you now would be a good time to run it.
Feel free to add the Hello World of your language to test that everything is setup correctly.
One of the key steps in this project will be to track progress and changes as you develop.
For this we are going to be using `git` if you do not know git,
don't worry there will be links to [tutorials](https://www.atlassian.com/git/tutorials)
and [guides](https://rogerdudler.github.io/git-guide/).
Setup the git repository and push it to a remote host like Github or Gitlab or BitBucket.
Consider adding a `.gitignore` file to stop from committing unimportant files.
Here is a [guide](https://help.github.com/en/github/using-git/ignoring-files)
## Goals
- Pick a name for this project.
- Create a new directory on your system.
- Create a Hello World.
- Initialize a git repository in this directory.
- Add any files you need.
- Push the repository to a public host.
## Bonus goals
- Create a `.gitignore` file and add that to the repository.
@@ -0,0 +1,66 @@
---
authors:
- "Kibb#4205"
created_at: 2020/06/08
title: "Chapter 2: So what are we building?"
---
## Chapter 2: So what are we building?
The actual final end goal is going to remain a mystery for the time being, but what I can tell you is that we are going
to be building a server!
What is a server? Its a program that accepts incoming connections, reads and writes to that connection and closing
it eventually.
You will hear the term socket used a lot in this project. Sockets are a way of connecting two devices on a network together.
One socket listens, the other socket connects.
The main design of sockets in the unix world is [Berkeley Sockets](https://en.wikipedia.org/wiki/Berkeley_sockets) and
on windows its [Winsock](https://www.wikiwand.com/en/Winsock).
This project will mostly be using berkeley sockets but if you want to use windows APIs feel free.
In general there is a flow for sockets. On the client you follow a flow like this;
```txt
+---------+
| Connect |
+---+-----+
|
+---v--+
+---> Send |
| +---+--+
| |
| +---v--+
+---+ Recv |
+---+--+
|
+---v---+
| Close |
+-------+
```
First the client connects. Then the client can send and receive (recv) data until the connection is finally closed.
On the server the flow is slightly more involved.
```txt
+------+ +--------+ +--------+ +------+ +------+ +-------+
| Bind +---> Listen +---> Accept +---> Recv +---> Send +---> Close |
+------+ +---^----+ +---+----+ +--^---+ +--+---+ +-------+
| | | |
+------------+ +----------+
```
When a server starts up and creates a socket, it binds that socket to an interface and a port.
The server will then set the socket to listening for connections,
when a connection comes in it needs to accept that connection.
Once a connection has been accepted it can receive and send to the socket the same as the client and eventually close it.
This overview is brushing over a few details and you are encouraged to do your own research
## Goals
- Read up on sockets.
- Find the documentation on your languages bindings for sockets.
@@ -0,0 +1,89 @@
---
authors:
- "Kibb#4205"
created_at: 2020/06/08
title: "Chapter 3: Creating a socket"
---
## Chapter 3: Creating a socket.
Finally some actual code. Lets get started!
The goal of this chapter is to create a socket and have it listen for connections. This will lay the ground work for future
chapters in the project
The first step is to create a socket in your language.
Different languages will expect you to set it up in different ways, you should check the documentation for your given
network bindings, something like this.
```js
var socket = new Socket();
```
Once we have that socket we can then bind it to an interface.
The interface is what IP network / device it is going to listen on, the port is the port it will bind to.
Whenever you see a connection like `127.0.0.1:6789` the IP address comes first, then `:` then the port number.
In the pseudo code this would be the required code to bind the socket. You can pick any port number for this, I chose `6789`
```js
socket.bind("127.0.0.1", 6789);
```
Now that the socket is bound, we can put it into listening mode. Listening mode implies that people will be connecting
to this socket. The parameter is *usually* the backlog of connections you want to allow.
```js
socket.listen(1);
```
Once we have set the socket up in listen mode we can finally accept an incoming connection.
```js
var conn = socket.accept();
```
This will accept a connection and hold it.
You will need to make sure your program doesn't exit prematurely so you might want to have it ask for some standard input or wait on a condition.
```js
var socket = new Socket();
socket.bind("127.0.0.1", 6789);
socket.listen(1);
var conn = socket.accept();
stdin.read(); // to stop the program just exiting.
```
If you are using windows you can use [Putty](https://www.ssh.com/ssh/putty/putty-manuals/0.68/Chapter3.html#using-rawprot) to create a raw connection.
On linux you can use `netcat` or `nc` to make network connections,
you can see if your server is listening properly by running a command like this
```sh
nc -v -v localhost <port>
```
where `<port>` is the port number you picked earlier on.
If it works you should see some output like
```sh
localhost [127.0.0.1] 6789 (radg) open
```
If something has gone wrong then you will get a different message.
When you have completed the goals for this chapter you will have created a socket, bound it, listened for connections and accepted a connection.
You should try experimenting with connecting twice and seeing what happens. This could be something to do with that backlog referenced earlier.
## Goals
- Create a socket.
- Bind it to an interface and port.
- Listen for a single connection.
## Bonus goals
- Allow for a backlog of connections.
@@ -0,0 +1,56 @@
---
authors:
- "Kibb#4205"
created_at: 2020/06/08
title: "Chapter 4: Writing to the socket"
---
## Chapter 4: Writing to the socket.
We have a server, but it doesn't really do anything. That isn't very useful really.
In this chapter we will take it a step further and actually write a message to the connection.
Given our pseudo code from the last chapter,
```js
var socket = new Socket();
socket.bind("127.0.0.1", 6789);
socket.listen(1);
var conn = socket.accept();
```
We now want to be able to write something to this connection.
Most network bindings will have a `send` or a `write` method. We can call this to send data to the client.
```js
conn.send("Hello Joe!\n");
conn.close();
```
When testing this with netcat we should see that when someone connects it prints out `Hello Joe!` in the terminal,
then closes the connection.
For the time being we are going to stick to hard coded responses, but you can change that message to whatever you want.
You can always send a message to a client that is *not* closed.
```js
conn.send("hi ");
conn.send("my name ");
conn.send("is ");
conn.send("slim shady\n");
conn.close();
```
This code will send `hi my name is slim shady` to anyone that connects.
Notice it doesn't have to be all in one single send method
## Goals
- Write a hello message to a client.
## Bonus goals
- Send a message to the client every X seconds.
@@ -0,0 +1,39 @@
---
authors:
- "Kibb#4205"
created_at: 2020/06/08
title: "Chapter 4: Reading from the socket"
---
## Chapter 4: Reading from the socket.
Its a good start, we have a server that can accept connections. It will write a message to the client.
But we aren't reading anything from the client. This will all change now.
Most network bindings will have a `recv` method. This method allows you to receive some bytes from the client.
There might be higher level abstractions like `recv_line` or `recv_all` or something like that but we are gonna focus
on the simplest version `recv`. The basic version of recv takes a number, and tries to read that many bytes from the
connected client. It will read as many as it can then return what it read.
Given our code from previous chapters.
```js
var client = socket.accept();
client.send("Welcome!\n");
var reply = client.recv(4096);
client.send("You said: \n");
client.send(reply);
client.close();
```
Note that you can also test your server with netcat, use netcat as a client and whatever you write in the terminal will be sent to the server.
## Goals
- Read a message from the client.
- Write that message back to the client.
## Bonus goals
- Find a method that allows you to read lines OR parse the bytes into lines yourself.
- Accept multiple lines.
@@ -0,0 +1,51 @@
---
authors:
- "Kibb#4205"
created_at: 2020/06/08
title: "Chapter 6: What is a protocol?"
---
## Chapter 6: What is a protocol?
How do we humans communicate? Through a mutually agreed convention of a language and conventions.
Similarly, How do we make clients and servers talk to each other?
They need some sort of agreed way to ask for things and respond with replies or errors. This mutually agreed set of rules is commonly known as a protocol.
In this chapter, we are going to create a really simple protocol for getting and storing strings, a *dictionary*.
With our hypothetical protocol your server will store the definition to a series of words.
You can do this nice and simply with a map like data structure or something more complex, your choice.
In a client-server architecture, genrally a client initiates communication by making a request to the server. In our case, the client will make a request to *get* a definition from the dictionary server.
To *get* a definition, the client will send this:
```txt
GET someword
```
All lines in this protocol will be terminated with `\n`
Your server will then look that word up and reply with the value stored against it.
The server will need to reply with a sensible error message if the word does not exist.
For now the reply will be either
```txt
ANSWER the description
```
or
```txt
ERROR can't find someword
```
We have created a simple reading protocol. This protocol can be extended by adding new requests of the form:
```txt
VERB args go here
```
I encourage you to mess about and add other verb based commands.
## Goals
- Read `GET word` from the client
- Reply `ANSWER definition` to the client
- Give errors on undefined words. `ERROR undefined`
## Bonus goals
- Allow clients to `SET word definition*` at runtime. Where definition might be multiple words ending in `\n`
- Add other commands like `CLEAR` to clear all definitions or `ALL` to get all words currently defined.
@@ -0,0 +1,38 @@
---
authors:
- "Kibb#4205"
created_at: 2020/06/08
title: "Chapter 7: Testing"
---
## Chapter 7: Testing.
Im assuming most of the testing has been done by hand up to this point but this isn't really
scalable as a long term solution. This is where unit testing comes in, all projects should have tests that cover
at least the bare minimum of cases that are expected. This project is no different.
Pretty much all languages have some sort of unit test functionality, to make connections and assert things.
We will need to write a bit of code to be able to facilitate the unit testing. You will now need to create a
tcp client that can connect and you can send and receive lines via, this will help with testing the project as
a whole.
```js
fun test_get_definition() {
var client = connect("127.0.0.1", 5678);
client.send("GET word\n");
var line = client.recv(4096);
assert(line == "ANSWER something interesting here\n");
}
```
The idea is to try and test all the functionality of your program and use these tests to identify what is going wrong.
Coverage is a reasonably good metric for establishing if your code is well tested. Coverage is identifying what lines
are covered by unit tests and what lines are not.
## Goals
- Unit test any verbs defined in the previous chapter.
## Bonus goals
- Reach a 90% test coverage.
@@ -0,0 +1,37 @@
---
authors:
- "Kibb#4205"
created_at: 2020/06/08
title: "Chapter 8: Multiple connections at once"
---
## Chapter 8: Multiple connections at once.
Up until now we have only had one server, one client.
If you try to run the server from earlier and connect to it twice you will get one of two things happen. Either the client
fails to connect because the server is busy and refuses the connection, or the connection will hang waiting in the backlog
to be accepted.
Now we are going to move to support multiple connections at the same time.
There are a number of ways to do this; threads, processes, actors, async/await, promises, callbacks, select, iocp. The list can
go on for hours. Each language will have different bindings and ways of doing these things, you will need to do some research as to
what the best option is for you.
Both epoll and select are common in most languages a few other suggestions include.
- Threads in C/C++/Rust
- NIO / Threads in Java.
- Asyncio / Threads in Python.
- Goroutines in Go.
- Callbacks / Handlers in Javascript
- Actors in Elixir/Erlang
## Goals
- Support multiple connections at once.
## Bonus goals
- See how many connections you can have concurrently using something like [wrk](https://github.com/wg/wrk)
@@ -0,0 +1,27 @@
---
authors:
- "Kibb#4205"
created_at: 2020/06/08
title: "Chapter 9: Documentation and clean code"
---
## Chapter 9: Documentation and clean code.
Wew this is the chapter nobody wants to talk about.
A good project is one that is readable and understandable. To be readable code should have some sort of consistency
and formatting, this helps with navigation and generally makes working on the code easier. The second is understandable,
this comes in two flavours, concise code with clear meaning and documented code with clear explanation.
Most languages offer some sort of docstring processing, or have support for external tools like doxygen.
You should take advantage of this and document your code so far, try to explain and design choices and tell the reader
what each method does BUT do not go over the top. Nobody wants 500 lines of documentation explaining that `x = 1`.
## Goals
- Document and comment your code
## Bonus goals
- Write a readme.md in your repo
@@ -0,0 +1,61 @@
---
authors:
- "Kibb#4205"
created_at: 2020/06/08
title: "Chapter 10: Methods and resources"
---
## Chapter 10: Methods and resources.
If you haven't guessed by now, we are writing a HTTP server. Not against the entire specification (though you can if you want)
just against a limited subset that *should* work with a browser if you have done it correctly.
HTTP is a line delimited protocol. The idea being that a client will request content from a server and the server will
deliver it.
Each time one side delivers a line to the other it should be delimited by `\r` and `\n` which is carriage ret and line feed.
The start of a HTTP request is as follows.
```txt
[method] [resource] [http-version]\r\n
```
### Methods
The methods are `OPTIONS` or `GET` or `HEAD` or `POST` or `PUT` or `DELETE` or `TRACE` or `CONNECT`
Some of these you might have experienced before, others are a bit more unusual.
`GET` is the simplest with "GET me this resource". Get is also considered idempotent which means the act of doing it again
should have identical results, cause *no* side effects, it can also be cached.
`PUT` is "PUT something at this resource". This method is also considered idempotent and is usually used to create or update a resource.
`HEAD` is identical to `GET` but doesn't return the resource, only the headers (more on headers and responses in the next chapter) and start of the request. It is also idempotent.
`POST` post is explicitly to create new entries at the located resource. It should not be cached and should not be considered idempotent.
`DELETE` deletes the resource given. It should be repeatable and is considered idempotent.
`OPTIONS` is kind of a query to see what is allowed on a given resource, it should reply with what methods are supported and any other information.
`TRACE` and `CONNECT` we are going to ignore these two as they are not commonly used anymore and wont be of much use to us.
### Resource
A resource is just something identified by a URL. An example of a resource could be `/index.html` or `/api/person`
These are both resources, its down to the server to decide what these resources mean. In a web application they might
correspond to a controller, or as a file on disk.
### Version
The version tells the server what http version the client is using and can support, and the server replies with the same.
## Goals
- Process an incoming request (you can use curl, wget, your browser to send requests)
- Read the first line and break it into a method, a resource and the version and then close the connection.
## Bonus goals
- Log when a request is invalid (unknown method)
@@ -0,0 +1,52 @@
---
authors:
- "Kibb#4205"
created_at: 2020/06/08
title: "Chapter 11: Headers and bodies"
---
## Chapter 11: Headers and bodies.
We are almost there, we can almost complete an entire request. But before we can finish a request, we need to
read the last few parts of an entire request.
We have already read the `GET /resource HTTP/1.0\r\n` line parsed.
The next line following this will be a header line.
Headers are just key value pairs that repeat until you read an empty `\r\n` line.
You split on the `:` and treat the left hand side as the key, and the right hand side as the value.
This is an example request from curl.
```txt
GET / HTTP/1.1
Host: localhost:9995
User-Agent: curl/7.67.0
Accept: */*
```
This breaks down into the following information.
* Method: GET
* Resource: /
* Headers
* Host: localhost:9995
* User-Agent: curl/7.67.0
* Accept: */*
That's all the information contained in this request.
There are hundreds of different headers and browsers / clients will often define their own.
For a list of common headers look [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers)
Some client requests will send a body. This will be included after the header block.
For now this is not important.
## Goals
- Read an entire request including all the headers.
## Bonus goals
- Handle malformed headers.
@@ -0,0 +1,119 @@
---
authors:
- "Kibb#4205"
created_at: 2020/06/08
title: "Chapter 11: Replying"
---
## Chapter 11: Replying.
Servers have a number of responses they can make to a request. These range from "OK" to "Not Found" a list can be found [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status)
The ones we will focus on in this chapter is
- 200 - OK
- 400 - Bad Request
- 404 - Not Found
Lets work through an example
```txt
GET /test.txt HTTP/1.1
Host: localhost:9995
User-Agent: curl/7.67.0
Accept: */*
```
Lets start by replying to the request given above.
For this we need to have a directory for our web hosting files to live in. Lets call this `./www`.
Create the directory and add a test file in it called `test.txt` this file should contain some text of your choice.
Now the request that came in asked for the `test.txt` file we are hosting.
So to answer the request we must first check we have that resource, in this case we do.
So we are going to reply with the most common HTTP response. OK.
The first line of the response looks like this. We include the version we support, the status number, and the human readable
description of that status number.
```txt
HTTP/1.0 200 OK
```
Once we have sent that line we can send any headers we want to send.
There are no mandatory headers but there are some suggested ones we will cover later.
Then we send an empty `\r\n` to indicate we are done sending the headers. Now we can send the body.
We read the `test.txt` file in and we write that as the body.
Once we are done sending we can simply close the connection to indicate we are done sending the body.
The entire response might look something like this
```txt
HTTP/1.0 200 OK
Etiam bibendum sapien ut est posuere pretium. Vestibulum a justo at sapien pharetra sagittis in eget lacus.
Sed ullamcorper quam sed nulla molestie interdum. Vestibulum hendrerit, est vel tristique
luctus, urna nibh pulvinar ligula, vel scelerisque nisi orci ac mauris. Nam tempor, orci nec rutrum sodales,
nulla diam imperdiet enim, id pellentesque leo nibh et urna. Vivamus non tortor dapibus, efficitur ex sed,
blandit odio. Vivamus volutpat, nunc sed pulvinar pellentesque, ipsum ante vestibulum sem, vitae malesuada
dui odio ut erat.
```
Boom. Done.
There are other replies we might send though, for example lets say someone requests `missing.txt` and we don't have that.
We need to inform the client it doesn't exist. We do this by sending a 404. Its mostly the same as the previous but we change
the number and message to something different
```txt
HTTP/1.0 404 Not Found
Sorry we don't have that file!
```
The final response is a generic response when the client sends you something that doesn't make sense.
Imagine the client sent
```txt
GasdasET /test.txt
!!!!!!
WHAT/1.1
```
That doesn't look like a valid request so we should tell them that by sending a Bad Request response.
```txt
HTTP/1.0 400 Bad Request
Im sorry I just don't understand.
```
We can look at once more example this time with headers in the response.
Lets say we reply and we also want to provide information about the http server that was used. We might send
```txt
HTTP/1.0 200 OK
Server: my-awesome-server
This is my content here.
```
This is an example of the `Server` header, there are other common headers as listed in the link at the top.
## Goals
- Read the file asked for by the request and reply with the contents
- Add other resources and maybe support directories like `/subdirectory/file.txt`
- Reply with a 404 if you can't find the file requested.
## Bonus goals
- Implement the `Content-Length` header
- Implement the `Server` header
- Implement the `Content-Type` header
@@ -0,0 +1,39 @@
---
authors:
- "Kibb#4205"
created_at: 2020/06/08
title: "Chapter 12: Config"
---
## Chapter 12: Config.
For the time being we have been hard coding all the various values like the port and the web directory,
its about time we made this configurable.
Lets create a config file that we can set the port and the host in. Lets say this config file is called `config`
and can contain any format you want. YAML, JSON, INI, Plain text; whatever suits you and your language.
For these examples we will use YAML but they are all similar
```yaml
host: localhost
port: 9985
web_directory: ./www
```
Lets update the program to read the values from config and use those.
The whole point of making it configurable is the person deploying this in the future doesn't want to edit code or
change values in your program to get it to work to their usecase. So by providing a way to configure your application
it makes it more portable and usable in different situations.
This should be quite an easy chapter but its a reasonably important one.
## Goals
- Replace any hard coded values with values set in a config file.
## Bonus goals
- Provide sane defaults if the values are missing
- Add your own config values.
@@ -0,0 +1,43 @@
---
authors:
- "Kibb#4205"
created_at: 2020/06/08
title: "Chapter 14: Security"
---
## Chapter 14: Security.
Unless you have spotted it (if you have then well done!) there is already a security risk in our HTTP server.
Lets look at that now.
```txt
GET /../config.yaml HTTP/1.0
```
This request is a relative path. So your server accepts the path. Goes up one directory from the `www` directory.
Reads your *config* and sends it to the attacker.
This is real bad. Imagine if your config had database passwords in it?
Imagine if someone requested `../../../../etc/passwd` and your server replied with it!
This is where security comes in.
You need to find a way to isolate the `www` directory we created so nobody can read stuff outside of it.
- You could use some sort of filesystem sandbox like chroot / jails
- You could manually work out if `..` will take you above the `www` directory.
- You could make the http server run as a different user and only give that user permission to read `www` directory.
This is just one of many possible attacks against your web server.
You should look through guides for your language of choice and find ways to harden your application.
## Goals
- Solve the ability to read files outside of your `www` directory.
## Bonus goals
- Audit your code for other issues.
@@ -0,0 +1,30 @@
---
authors:
- "Kibb#4205"
created_at: 2020/06/08
title: HTTP Project Guide
---
## Intro
Welcome to the TPH first guided project!
The goal of this event is to help guide you through the steps of building a self contained project.
Any questions about the project, goals, or queries can be handled in the event channel.
You can use any language that the server currently has a channel for.
This project will expect you to have basic understanding of the language you are using and have a few extra tools
installed like; `git`, `netcat` (linux), `putty` (windows)
This project will guide you in the direction of what you need to do but it leaves individual learning up to you.
Feel free to ask for help, search topics online, look for tutorials for any of the concepts presented.
The idea is to guide you along but not give you all the answers. There will be pseudo code in some of the chapters in
this project its down to you to achieve the functionality in your language of choice.
The pseudo code isn't representative of a final project and is there to convey specific ideas you need to achieve
## Rules
- Put in the work yourself. Don't copy and paste, the only person you are cheating is yourself.
- If you are helping don't spoon-feed. Its about the journey not the destination.