171 lines
11 KiB
Markdown
171 lines
11 KiB
Markdown
|
||
## for correction
|
||
|
||
## man
|
||
|
||
- **htons, htonl, ntohs, ntohl :** converts the unsigned short or integer argument between host byte order and network byte order
|
||
- **poll :** waits for one of a set of file descriptors to become ready to perform I/O
|
||
- alternatives : select, epoll (epoll_create, epoll_ctl, epoll_wait), kqueue (kqueue, kevent)
|
||
- **socket :** creates an endpoint for communication and returns a file descriptor that refers to that endpoint
|
||
- **listen :** marks a socket as a passive socket, that is, as a socket that will be used to accept incoming connection requests using accept()
|
||
- **accept :** used with connection-based socket types. It extracts the first connection request on the queue of pending connections for the listening socket, creates a new connected socket, and returns a new file descriptor referring to that socket. The newly created socket is not in the listening state. The original socket is unaffected by this call
|
||
- **send :** (~write) used to transmit a message to another socket. May be used only when the socket is in a connected state (so that the intended recipient is known). The only difference between send() and write() is the presence of flags. With a zero flags argument, send() is equivalent to write()
|
||
- **recv :** (~read) used to receive messages from a socket. May be used to receive data on both connectionless and connection-oriented sockets. The only difference between recv() and read() is the presence of flags. With a zero flags argument, recv() is generally equivalent to read()
|
||
- **bind :** associate a socket fd to a local address. When a socket is created with socket(), it exists in a name space (address family) but has no address assigned to it. It is normally necessary to assign a local address using bind() before a socket may receive connections (see accept())
|
||
- **connect :** connects a socket fd to a remote address
|
||
- **inet_addr :** converts the Internet host address cp from IPv4 numbers-and-dots notation into binary data in network byte order. Use of this function is problematic because in case of error it returns -1, wich is a valid address (255.255.255.255). Avoid its use in favor of inet_aton(), inet_pton(), or getaddrinfo()
|
||
- **setsockopt :** manipulate options for a socket fd. Options may exist at multiple protocol levels; they are always present at the uppermost socket level
|
||
- **getsockname :** returns the current address to which a socket fd is bound
|
||
- **fcntl :** manipulate an open fd, by performing some actions, like duplicate it or changing its flags
|
||
|
||
## todo
|
||
|
||
- [ ] read the RFC and do some tests with telnet and NGINX
|
||
- [ ] Your program has to take a configuration file as argument, or use a default path.
|
||
- [ ] You can’t execve another web server.
|
||
- [ ] Your server must never block and the client can be bounced properly if necessary.
|
||
- [ ] It must be non-blocking and use only 1 poll() (or equivalent) for all the I/O operations between the client and the server (listen included).
|
||
- [ ] poll() (or equivalent) must check read and write at the same time.
|
||
- [ ] You must never do a read or a write operation without going through poll() (or equivalent).
|
||
- [ ] Checking the value of errno is strictly forbidden after a read or a write operation.
|
||
- [ ] You don’t need to use poll() (or equivalent) before reading your configuration file. Because you have to use non-blocking file descriptors, it is possible to use read/recv or write/send functions with no poll() (or equivalent), and your server wouldn’t be blocking. But it would consume more system resources. Thus, if you try to read/recv or write/send in any file descriptor without using poll() (or equivalent), your grade will be 0.
|
||
- [ ] You can use every macro and define like FD_SET, FD_CLR, FD_ISSET, FD_ZERO (understanding what and how they do it is very useful).
|
||
- [ ] A request to your server should never hang forever.
|
||
- [ ] Your server must be compatible with the web browser of your choice.
|
||
- [ ] We will consider that NGINX is HTTP 1.1 compliant and may be used to compare headers and answer behaviors.
|
||
- [ ] Your HTTP response status codes must be accurate.
|
||
- [ ] You server must have default error pages if none are provided.
|
||
- [ ] You can’t use fork for something else than CGI (like PHP, or Python, and so forth).
|
||
- [ ] You must be able to serve a fully static website.
|
||
- [ ] Clients must be able to upload files.
|
||
- [ ] You need at least GET, POST, and DELETE methods.
|
||
- [ ] Stress tests your server. It must stay available at all cost.
|
||
- [ ] Your server must be able to listen to multiple ports (see Configuration file)
|
||
- [ ] Your server should never die.
|
||
- [ ] Do not test with only one program.
|
||
- [ ] Write your tests with a more convenient language such as Python or Golang, and so forth. Even in C or C++ if you want to
|
||
- [ ] You must provide some configuration files and default basic files to test and demonstrate every feature works during evaluation.
|
||
|
||
### In the configuration file, you should be able to:
|
||
|
||
- [ ] Choose the port and host of each ’server’.
|
||
- [ ] Setup the server_names or not.
|
||
- [ ] The first server for a host:port will be the default for this host:port (that means it will answer to all the requests that don’t belong to an other server).
|
||
- [ ] Setup default error pages.
|
||
- [ ] Limit client body size.
|
||
- [ ] Setup routes with one or multiple of the following rules/configuration (routes wont be using regexp):
|
||
- [ ] Define a list of accepted HTTP methods for the route.
|
||
- [ ] Define a HTTP redirection.
|
||
- [ ] Define a directory or a file from where the file should be searched (for example, if url /kapouet is rooted to /tmp/www, url /kapouet/pouic/toto/pouet is /tmp/www/pouic/toto/pouet).
|
||
- [ ] Turn on or off directory listing.
|
||
- [ ] Set a default file to answer if the request is a directory.
|
||
- [ ] Execute CGI based on certain file extension (for example .php).
|
||
- [ ] Make the route able to accept uploaded files and configure where they should be saved.
|
||
- [ ] Do you wonder what a CGI is?
|
||
- [ ] Because you won’t call the CGI directly, use the full path as PATH_INFO.
|
||
- [ ] Just remember that, for chunked request, your server needs to unchunked it and the CGI will expect EOF as end of the body.
|
||
- [ ] Same things for the output of the CGI. If no content_length is returned from the CGI, EOF will mark the end of the returned data.
|
||
- [ ] Your program should call the CGI with the file requested as first argument.
|
||
- [ ] The CGI should be run in the correct directory for relative path file access.
|
||
- [ ] Your server should work with one CGI (php-CGI, Python, and so forth).
|
||
|
||
## ressources
|
||
|
||
- [create an http server](https://medium.com/from-the-scratch/http-server-what-do-you-need-to-know-to-build-a-simple-http-server-from-scratch-d1ef8945e4fa)
|
||
- [guide to network programming](https://beej.us/guide/bgnet/)
|
||
- [same, translated in french](http://vidalc.chez.com/lf/socket.html)
|
||
- [bind() vs connect()](https://stackoverflow.com/questions/27014955/socket-connect-vs-bind)
|
||
- [INADDR_ANY for bind](https://stackoverflow.com/questions/16508685/understanding-inaddr-any-for-socket-programming)
|
||
|
||
## code architecture
|
||
|
||
|
||
```
|
||
functions action in this scenario :
|
||
______
|
||
sd = SOCKET() : create a listening socket descriptor
|
||
__________
|
||
SETSOCKOPT(sd) : allow socket descriptor to be reuseable
|
||
_____
|
||
IOCTL(sd) : set listening socket and all incoming socket to be non-blocking
|
||
_____
|
||
FCNTL(sd) : set listening socket and all incoming socket to be non-blocking
|
||
____
|
||
BIND(port) : associate listening socket to a port
|
||
______
|
||
LISTEN(nb_queue) : queue the incoming connections to listening socket
|
||
up to a chosen number
|
||
____
|
||
POLL(fds[]) : wait for event on files descriptors
|
||
______
|
||
SELECT(fds[]) : wait for event on files descriptors
|
||
FD_SET() : add a fd to a set
|
||
FD_CLR() : remove a fd from a set
|
||
FD_ZERO() : clears a set
|
||
FD_ISSET() : test to see if a fd is part of the set
|
||
______
|
||
new_sd = ACCEPT() : extract first connection request in queue of listening socket
|
||
and creates a new socket that is connected
|
||
____
|
||
RECV(new_sd) : read data in socket created by accept()
|
||
____
|
||
SEND(new_sd) : write data in socket created by accept()
|
||
_____
|
||
CLOSE(new_sd) : close open file (here socket) descriptor
|
||
|
||
```
|
||
|
||
compare architectures :
|
||
|
||
```
|
||
POLL SELECT
|
||
______ ______ ______
|
||
lstn_sd = SOCKET() | lstn_sd = SOCKET() | lstn_sd = SOCKET()
|
||
| __________ | __________
|
||
| SETSOCKOPT() | SETSOCKOPT()
|
||
| _____ | _____
|
||
| IOCTL() | IOCTL()
|
||
____ | ____ | ____
|
||
BIND(port) | BIND(port) | BIND(port)
|
||
______ | ______ | ______
|
||
LISTEN(nb_queue) | LISTEN(nb_queue) | LISTEN(nb_queue)
|
||
| |
|
||
| fds[1] = lstn_sd | FD_SET(lstn_fd)
|
||
| |
|
||
| | max_sd = lstn_sd
|
||
| |
|
||
loop | loop | loop
|
||
. ____ | . ____ | . ______
|
||
. POLL() | . POLL(fds[]) | . SELECT(fds[])
|
||
. | . | .
|
||
. | . loop through fds[] | . loop i++ < max_fd
|
||
. | . . | . .
|
||
. | . . POLLIN && lstn_sd ? | . . FD_ISSET(i) & lstn_fd ?
|
||
. | . . loop | . . loop
|
||
. ______ | . . . ______ | . . . ______
|
||
. ACCEPT() | . . . new_sd = ACCEPT() | . . . new_sd = ACCEPT()
|
||
. | . . . | . . .
|
||
. | . . . fds[] += new_sd | . . . FD_SET new_sd in fds[]
|
||
. | . . | . . .
|
||
. | . . | . . . max_sd = new_sd
|
||
. | . . | . .
|
||
. | . . or POLLIN ? | . . or FD_ISSET ?
|
||
. | . . loop | . . loop
|
||
. ____ | . . . ____ | . . . ____
|
||
. RECV() | . . . RECV() | . . . RECV()
|
||
. ____ | . . . ____ | . . . ____
|
||
. SEND() | . . . SEND() | . . . SEND()
|
||
. | |
|
||
. | loop through fds[] | loop through fds[]
|
||
. _____ | . _____ | . _____
|
||
. CLOSE(fds[]) | . CLOSE(fds[]) | . CLOSE(fds[])
|
||
|
||
|
||
first, create the socket, add some options, bind it, and listen to it.
|
||
then, do a loop starting with POLL or SELECT
|
||
if it's incomming connexions, accept and add them all to the list
|
||
if it's accepted connexions, read their datas and write new datas
|
||
|
||
```
|
||
|