very dirty commit

This commit is contained in:
Aymeric GUERACAGUE 2021-04-06 19:56:37 +02:00
parent ed90eb566e
commit 5a3f081804
Signed by: Superkooka
GPG Key ID: F78F2B172E894865
19 changed files with 759 additions and 108 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/*.log

View File

@ -1,11 +1,92 @@
vagrant up # Web Crystal
WebCrystal est un serveur HTTP réalisé à l'occasion du défi de [NaN](https://discord.gg/zcWp9sC).
Ce projet m'a permis d'apprendre Crystal, le code présente donc pas mal de bad-practice, de chose mal géré...
## Consigne
> ### Implémentation d'un serveur HTTP
>
> Défi sur une semaine, à rendre au plus tard le lundi 5 avril à 6h à votre ambassadeur préféré. Ce défi est considéré plus difficile que d'ordinaire, vous avez une semaine pour le résoudre.
>
> #### Le serveur
>
> Le serveur tourne en HTTP/1.0 derrière le port 8080 et est compatible avec les principaux navigateurs. Le serveur comprend les requêtes HEAD, GET, POST, PUT et DELETE. Le serveur expose une application "compteur" à l'url http://compteur.notaname.fr/, les autres url doivent renvoyer une page d'erreur au format HTML avec un code 404. Le serveur doit être capable de répondre à plusieurs utilisateurs à la fois. Le serveur peut être accessible à l'extérieur de votre réseau domestique. Le serveur peut comprendre TLS, dans quel cas il écoute sur le port 8443.
>
> Parce que le but de ce défi est d'implémenter un serveur web, il est bien entendu interdit d'utiliser un quelconque framework. Plus précisément, vous n'avez pas le droit d'utiliser une quelconque bibliothèque HTTP et vous vous contentez des bibliothèques réseaux et systèmes de votre langage. Le choix de votre stack système est libre, vous pouvez faire du multithreading, de l'asynchrone, etc.
>
> #### L'application
>
> L'application permet de manipuler des compteurs. Le serveur démarre avec deux compteurs : "carotte" et "etoile". Le compteur "carotte" est normal et est initialisé à 10. Un compteur "etoile" est le seul compteur spécial, il n'est pas possible de le modifier et il renvoie la somme de tous les autres compteurs. La valeur d'un compteur ne peut que croître.
>L'application compteur admet 5 routes (en plus de ``HEAD``):
>
> * ``GET /`` : retourne la liste de tous les compteurs.
> * ``GET /<compteur>`` : retourne l'état du compteur <compteur>.
> * ``POST /`` : crée un nouveau compteur et le renvoie.
> * ``PUT /<compteur>`` : met à jour la valeur du compteur <compteur>.
> * ``DELETE /<compteur>`` : supprime le compteur <compteur>.
>
> Il est possible de communiquer en ``x-www-form-urlencoded`` et en ``JSON`` avec l'application (requête). L'application est capable de répondre au format ``HTML``, au format ``JSON`` ou à défaut au format texte (réponse). Interessez vous aux headers ``Accept`` et ``Content-Type``.
>
> Pour simuler une application plus compliquée et mettre à mal votre implémentation réseau, chaque route simule des calculs et des requêtes à un service externe. Vous simulez les calculs en faisant tourner une boucle dans le vide à raison de 0.2 secondes. Vous simulez une requête externe en mettant votre processus en sleep pendant 0.3 secondes au milieu.
>
> Exemple en Python, après des tests vous avez trouvé qu'itérer sur 1 millard d'éléments prend 200 millisecondes.
```python
counters = {'carotte': 10}
def get(counter):
for i in range(1_000_000_000): pass
time.sleep(0.3) # remplacez par asyncio.sleep(0.3) si vous faites de l'async
return b"{'name': 'carotte', 'value': %d}" % counters[counter]
```
> #### Considérations techniques
>
> Pour faciliter l'implémentation, vous pouvez limiter la taille de la commande et des headers à maximum 1024 caractères par ligne. La RFC (de ce qu'on sait) ne précise rien à ce propos et la plupart des serveurs permettent 8k ou 16k caractères par ligne dans les headers.
>
> #### Ressources
>
> Voici une liste de ressource que nous vous conseillons de lire. Nous ajouterons à cette liste toutes les ressources que vous nous conseillerez.
>
>* https://fr.wikipedia.org/wiki/Hypertext_Transfer_Protocol
>* https://www.pierre-giraud.com/http-reseau-securite-cours/
>* http://www.kegel.com/c10k.html
>* https://www.w3.org/Protocols/HTTP/1.0/spec.html
## Lancer le serveur
Pour lancer le projet simplement vous pouvez utiliser vagrant, ou alors installer crystal
```vagrant up
vagrant ssh vagrant ssh
cd /vagrant cd /vagrant
clear && crystal run src/main.cr clear && crystal run src/main.cr
```
vagrant halt Pour simuler un test de charge
```
vegeta attack -targets targets.txt -rate=20 -duration=30s vegeta attack -targets targets.txt -rate=20 -duration=30s
vegeta attack -targets=targets.txt -name=300qps -rate=300 -duration=25s > results.300qps.bin;cat results.300qps.bin | vegeta plot > plot.300qps.html vegeta attack -targets=targets.txt -name=300qps -rate=300 -duration=25s > results.300qps.bin;cat results.300qps.bin | vegeta plot > plot.300qps.html
```
## Pour compiler le serveur
```
crystal build ./src/main.cr
mv ./src/main ./web
```
## TODO
* Ajouter le logger
* Ecrire des test
* Ecrire une CI, capable de crée les releases...
* Utiliser un analyser static comme [ameba](https://github.com/crystal-ameba/ameba).
* CGI
* Configuration (json --> DSL)
* TLS

2
Vagrantfile vendored
View File

@ -7,6 +7,8 @@ Vagrant.configure("2") do |config|
s.inline = <<-SHELL s.inline = <<-SHELL
curl -fsSL https://crystal-lang.org/install.sh | sudo bash curl -fsSL https://crystal-lang.org/install.sh | sudo bash
sudo apt install php-fpm -y
curl -LO https://github.com/tsenart/vegeta/releases/download/v12.7.0/vegeta-12.7.0-linux-amd64.tar.gz curl -LO https://github.com/tsenart/vegeta/releases/download/v12.7.0/vegeta-12.7.0-linux-amd64.tar.gz
tar -zxvf vegeta-12.7.0-linux-amd64.tar.gz tar -zxvf vegeta-12.7.0-linux-amd64.tar.gz
sudo mv ./vegeta /usr/bin/vegeta sudo mv ./vegeta /usr/bin/vegeta

18
config.json Normal file
View File

@ -0,0 +1,18 @@
{
"notaname.home": {
"handler": "static_serve",
"document_root": "/var/www/notaname/home"
},
"compteur.notaname.home": {
"handler": "internal.compter"
},
"compteur.cheat.notaname.home": {
"handler": "proxy_pass",
"host": "127.0.0.1",
"port": 8080
}
}

16
main.js Normal file
View File

@ -0,0 +1,16 @@
var http = require("http");
var serv = http.createServer(
function (req, res) {
console.log("ping !");
res.writeHead(200, { "Content-Type": "text/plain" });
res.write("Hello world !");
res.end();
}
);
serv.listen(8080);
console.log("Server running at http://localhost:8080");

View File

@ -0,0 +1,135 @@
require "../method"
class CompterHandler < Handler
@@compters : Hash(String, Int32) = {"carotte" => 40, "etoile" => 40}
def handle(request : Request) : Response
if request.method == Method::GET && !(regexified_uri = /^\/$/.match(request.uri)).nil?
return show_all
elsif request.method == Method::POST && !(regexified_uri = /^\/$/.match(request.uri)).nil?
if request.is_form_body || request.is_json_body
body = request.get_body
else
return Response.new "HTTP", "1.0", HTTPStatus::BAD_REQUEST
end
return create body
elsif request.method == Method::GET && !(regexified_uri = /^\/(?'compter_name'[a-zA-Z0-9]+)$/.match(request.uri)).nil?
return show regexified_uri["compter_name"]
elsif request.method == Method::PUT && !(regexified_uri = /^\/(?'compter_name'[a-zA-Z0-9]+)$/.match(request.uri)).nil?
if request.is_form_body || request.is_json_body
body = request.get_body
else
return Response.new "HTTP", "1.0", HTTPStatus::BAD_REQUEST
end
return increment regexified_uri["compter_name"], body
elsif request.method == Method::DELETE && !(regexified_uri = /^\/(?'compter_name'[a-zA-Z0-9]+)$/.match(request.uri)).nil?
return delete regexified_uri["compter_name"]
end
Response.new "HTTP", "1.0", HTTPStatus::NOT_FOUND
end
def show_all : Response
response = Response.new "HTTP", "1.0", HTTPStatus::OK
response.body = @@compters.to_json
response
end
def show(compter : String)
if !@@compters[compter]?
return Response.new "HTTP", "1.0", HTTPStatus::NOT_FOUND
end
response = Response.new "HTTP", "1.0", HTTPStatus::OK
response.body = "{\"" + compter + "\":" + "\"" + @@compters[compter].to_s + "\"}"
response
end
def create(body)
if body.nil?
raise "WTF"
end
compter_name = body["name"]?
# Broken, I dont know why ?
# if !compter_name || !compter_name.is_a?(String)
# return Response.new "HTTP", "1.0", HTTPStatus::BAD_REQUEST
# end
if @@compters[compter_name.to_s]?
return Response.new "HTTP", "1.0", HTTPStatus::CONFLICT
end
compter_value = body["value"]?
if !compter_value
compter_value = 0
end
# Can be broken with json body
@@compters[compter_name.to_s] = compter_value.to_s.to_i
calcul_etoile
show compter_name.to_s
end
def increment(compter : String, body)
if !@@compters[compter]?
return Response.new "HTTP", "1.0", HTTPStatus::NOT_FOUND
end
if body.nil?
raise "WTF"
end
compter_value = body["value"]?
if !compter_value || !compter_value.is_a?(String)
return Response.new "HTTP", "1.0", HTTPStatus::BAD_REQUEST
end
if compter == "etoile"
return Response.new "HTTP", "1.0", HTTPStatus::FORBIDDEN
end
if @@compters[compter] > compter_value.to_i
return Response.new "HTTP", "1.0", HTTPStatus::NOT_ACCEPTABLE
end
@@compters[compter] = compter_value.to_i
calcul_etoile
show compter
end
def delete(compter : String)
if @@compters[compter]?.nil?
return Response.new "HTTP", "1.0", HTTPStatus::NOT_FOUND
end
if compter == "etoile"
return Response.new "HTTP", "1.0", HTTPStatus::FORBIDDEN
end
@@compters.delete(compter)
calcul_etoile
Response.new "HTTP", "1.0", HTTPStatus::NO_CONTENT
end
def calcul_etoile()
@@compters["etoile"] = 0
@@compters.each do |compter|
if compter[0] == "etoile"
next
end
@@compters["etoile"] += @@compters[compter[0]]
end
end
end

View File

@ -0,0 +1,126 @@
require "./handler"
# Translation of https://github.com/adoy/PHP-FastCGI-Client/blob/master/src/Adoy/FastCGI/Client.php in crystal
# Handles communication with a FastCGI application
FAST_CGI_VERSION_1 = 1
FAST_CGI_BEGIN_REQUEST = 1
FAST_CGI_ABORT_REQUEST = 2
FAST_CGI_END_REQUEST = 3
FAST_CGI_PARAMS = 4
FAST_CGI_STDIN = 5
FAST_CGI_STDOUT = 6
FAST_CGI_STDERR = 7
FAST_CGI_DATA = 8
FAST_CGI_GET_VALUES = 9
FAST_CGI_GET_VALUES_RESULT = 10
FAST_CGI_UNKNOWN_TYPE = 11
FAST_CGI_MAXTYPE = UNKNOWN_TYPE
FAST_CGI_RESPONDER = 1
FAST_CGI_AUTHORIZER = 2
FAST_CGI_FILTER = 3
FAST_CGI_REQUEST_COMPLETE = 0
FAST_CGI_CANT_MPX_CONN = 1
FAST_CGI_OVERLOADED = 2
FAST_CGI_UNKNOWN_ROLE = 3
FAST_CGI_MAX_CONNS = "MAX_CONNS"
FAST_CGI_MAX_REQS = "MAX_REQS"
FAST_CGI_MPXS_CONNS = "MPXS_CONNS"
FAST_CGI_HEADER_LEN = 8
FAST_CGI_MAX_LENGTH = 0xffff
class FastCGIHandler < Handler
# Broken handler
id = rand((1 << 16) - 1)
keep_alive = 1
fast_cgi_request = build_packet(FAST_CGI_BEGIN_REQUEST, "#{0.chr}#{FAST_CGI_RESPONDER.chr}#{keep_alive.chr}#{0.chr}#{0.chr}#{0.chr}#{0.chr}#{0.chr}", id)
fast_cgi_request_environnement = ""
environnement = Hash(String, String).new
environnement["GATEWAY_INTERFACE"] = "FastCGI/1.0"
environnement["DOCUMENT_ROOT"] = "/vagrant"
environnement["DOCUMENT_URI"] = "/vagrant/index.php"
environnement["PATH_INFO"] = "/vagrant/index.php"
environnement["REQUEST_URI"] = "/index.php"
environnement["REQUEST_METHOD"] = "GET"
environnement["SCRIPT_FILENAME"] = "index.php"
environnement["SERVER_SOFTWARE"] = "php/fcgiclient"
environnement["REMOTE_ADDR"] = "127.0.0.1"
environnement["REMOTE_PORT"] = "9985"
environnement["SERVER_ADDR"] = "127.0.0.1"
environnement["SERVER_PORT"] = "8080"
environnement["SERVER_NAME"] = "mag-tured"
environnement["SERVER_PROTOCOL"] = "HTTP/1.0"
environnement.each do |key, value|
fast_cgi_request_environnement += build_key_value_pair(key, value, id)
end
if !fast_cgi_request_environnement.empty?
fast_cgi_request += build_packet(FAST_CGI_PARAMS, fast_cgi_request_environnement, id)
end
fast_cgi_request += build_packet(FAST_CGI_PARAMS, "", id)
if !request.body.empty?
stdin = request.body
until stdin.bytesize < FAST_CGI_MAX_LENGTH
chunk = stdin[0, FAST_CGI_MAX_LENGTH]
fast_cgi_request += build_packet(FAST_CGI_STDIN, chunk, id)
stdin = stdin.byte_slice(0, FAST_CGI_MAX_LENGTH)
end
fast_cgi_request += build_packet(FAST_CGI_STDIN, stdin, id)
end
fast_cgi_request += build_packet(FAST_CGI_STDIN, "", id)
UNIXSocket.open("/run/php/php7.2-fpm.sock") do |fpm_socket|
fpm_socket << fast_cgi_request
message = fpm_socket.gets
end
def build_packet(type : Int32, content : String, id : Int32)
content_length = content.bytesize
return String.build do |io|
io << FAST_CGI_VERSION_1.chr
io << type.chr
io << ((id >> 8) & 0xFF).chr
io << (id & 0xFF).chr
io << ((content_length >> 8) & 0xFF).chr
io << (content_length & 0xFF).chr
io << 0.chr
io << 0.chr
io << content
end
end
def build_key_value_pair(key : String, value : String, id : Int32)
key_lenght = key.bytesize
value_lenght = value.bytesize
if 128 <= key_lenght
pair = "#{key_lenght.chr}"
else
pair = "#{((key_lenght >> 24) | 0x80).chr}#{((key_lenght >> 16) & 0xFF).chr}#{((key_lenght >> 8) & 0xFF)}#{(key_lenght & 0xFF).chr}"
end
if 128 <= value_lenght
pair = "#{value_lenght.chr}"
else
pair = "#{((value_lenght >> 24) | 0x80).chr}#{((value_lenght >> 16) & 0xFF).chr}#{((value_lenght >> 8) & 0xFF)}#{(value_lenght & 0xFF).chr}"
end
return "#{pair}#{key}#{value}"
end
end

View File

@ -0,0 +1,9 @@
require "json"
require "../request"
abstract class Handler
def initialize(@configuration : JSON::Any)
end
abstract def handle(request : Request) : Response
end

View File

@ -0,0 +1,15 @@
require "./handler"
require "../response"
class ProxyHandler < Handler
def handle(request : Request) : Response
puts "ping"
host = @configuration["host"].to_s
port = @configuration["port"].to_s.to_i
return TCPSocket.open host, port do |tcp_socket|
tcp_socket << request.to_s
return Response.new tcp_socket.receive[0]
end
end
end

View File

@ -0,0 +1,28 @@
require "./handler"
class StaticHandler < Handler
def handle(request : Request) : Response
file = @configuration["document_root"].to_s + request.uri
if request.uri.ends_with? "/"
file = @configuration["document_root"].to_s + request.uri + "index.html"
end
if !File.exists?(file)
response = Response.new "HTTP", "1.0", HTTPStatus::NOT_FOUND
response.body = "404"
return response
end
if !File.info(file).permissions.owner_read?
response = Response.new "HTTP", "1.0", HTTPStatus::UNAUTHORIZED
response.body = "403"
return response
end
response = Response.new "HTTP", "1.0", HTTPStatus::OK
response.body = File.read(file)
return response
end
end

3
src/http/helper.cr Normal file
View File

@ -0,0 +1,3 @@
class Helper
end

142
src/http/message.cr Normal file
View File

@ -0,0 +1,142 @@
require "./message"
require "./method"
require "uri"
require "json"
class Message
@@status_line_regex = /(?'method'[A-Z]+) (?'uri'[^ ]+) (?'protocol'(?'protocol_name'[^ ]+)\/(?'protocol_version'[0-9]+.[0-9]+))/
@@header_line_regex = /^(?'name'[^:]+): (?'values'.+)/
@protocol_name = "HTTP"
@protocol_version = "1.0"
@method : Method = Method::NON_STANDARD
@uri : String = "/"
@headers : Hash(String, String) = {} of String => String
@body : String = ""
def parse_message(request : String)
protocol_name = Nil
protocol_version = Nil
method = Method::NON_STANDARD
uri = "/"
headers = {} of String => String
body = ""
current_header = ""
is_body = false
is_status_line = true
request.chars.each do |char|
if is_body
body += char
next
end
current_header += char
if current_header == "\r\n"
is_body = true
next
end
if current_header.ends_with?("\r\n") # End of header
if !is_status_line
regexified_header = @@header_line_regex.match(current_header)
if regexified_header.nil?
raise "Header regex is broken :aie:"
end
headers[regexified_header["name"]] = regexified_header["values"].gsub({"\r": "", "\n": ""})
else
is_status_line = false
regexified_status_line = @@status_line_regex.match(current_header)
if regexified_status_line.nil?
raise "Status line regex is broken :aie:"
end
method = Method.parse?(regexified_status_line["method"])
uri = regexified_status_line["uri"]
protocol_name = regexified_status_line["protocol_name"]
protocol_version = regexified_status_line["protocol_version"]
end
current_header = ""
end
end
return {
"protocol_name" => protocol_name,
"protocol_version" => protocol_version,
"method" => method,
"uri" => uri,
"headers" => headers,
"body" => body
}
end
def parse_form_body(body : String)
form_body = Hash(String, String).new()
key = ""
value = ""
is_key = true
body.chars.each do |char|
if char == '='
is_key = false
next
end
if char == '&'
form_body[key] = value[0, value.size - 1]
key = ""
value = ""
next
end
if is_key
key += char
else
value += char
end
end
form_body[key] = value
return form_body
end
def is_form_body
return "application/x-www-form-urlencoded" == @headers["Content-Type"]?
end
def is_json_body
return "application/json" == @headers["Content-Type"]?
end
def get_body
if is_json_body
return Hash(String, JSON::Any).from_json @body
elsif is_form_body
return parse_form_body @body
end
end
def header(header : String)
return @headers[header]?
end
def headers
return @headers
end
def body : String
return @body
end
def method : Method
return @method
end
def uri: String
return @uri
end
end

View File

@ -1,99 +1,31 @@
require "./message"
require "./method" require "./method"
require "uri" require "uri"
require "json"
class Request class Request < Message
@method : Method = Method::NON_STANDARD @plain_request : String = ""
@protocol_name : String | Nil
@protocol_version : String | Nil
@headers : Hash(String, String) = Hash(String, String).new()
@body : String = ""
@@status_line_regex = /(?'method'[A-Z]+) (?'uri'[^ ]+) (?'protocol'(?'protocol_name'[^ ]+)\/(?'protocol_version'[0-9]+.[0-9]+))/
@@header_line_regex = /^(?'name'[^:]+): (?'values'.+)/
def initialize(socket) def initialize(socket)
if socket.nil? if socket.nil?
raise "Oooops" raise "Oooops"
end end
plain_request = socket.receive[0] @plain_request = socket.receive[0]
req = parse_message(@plain_request)
current_header = "" @protocol_name = req["protocol_name"].as(String)
is_body = false @protocol_version = req["protocol_version"].as(String)
is_status_line = true @method = req["method"].as(Method)
@uri = req["uri"].as(String)
@headers = req["headers"].as(Hash(String, String))
@body = req["body"].as(String)
plain_request.chars.each do |char|
if is_body
@body += char
next
end end
current_header += char def to_s : String
return @plain_request
if current_header == "\r\n"
is_body = true
next
end
if current_header.ends_with?("\r\n") # End of header
if !is_status_line
regexified_header = @@header_line_regex.match(current_header)
if regexified_header.nil?
raise "Header regex is broken :aie:"
end
@headers[regexified_header["name"]] = regexified_header["values"].gsub({"\r": "", "\n": ""})
else
is_status_line = false
regexified_status_line = @@status_line_regex.match(current_header)
if regexified_status_line.nil?
raise "Status line regex is broken :aie:"
end
method = Method.parse?(regexified_status_line["method"])
if method.nil?
@method = Method::NON_STANDARD
else
@method = method
end
protocol_name = regexified_status_line["protocol_name"]
if protocol_name.nil?
raise "Status Line must have a valid protocol name"
else
@protocol_name = protocol_name
end
protocol_version = regexified_status_line["protocol_version"]
if protocol_version.nil?
raise "Status Line must have a valid protocol version"
else
@protocol_version = protocol_version
end
end
current_header = ""
end
end
if @protocol_name.nil?
raise "Request must have a protocol name in status line"
end
if @protocol_version.nil?
raise "Request must have a protocol version in status line"
end
puts "Method: " + @method.to_s
puts "Protocol name: " + @protocol_name.to_s
puts "Protocol version: " + @protocol_version.to_s
puts "Headers: " + @headers.to_s
puts "Body: "
puts @body
puts "\r\n"
end
def method : Method
return @method
end end
end end

View File

@ -1,21 +1,49 @@
class Response require "./message"
def initialize() require "./status"
@headers = [
"HTTP/1.1 200 OK" class Response < Message
]
@status : HTTPStatus = HTTPStatus::OK
def initialize (response : String)
req = parse_message(response)
@protocol_name = req["protocol_name"].as(String)
@protocol_version = req["protocol_version"].as(String)
@method = req["method"].as(Method)
@uri = req["uri"].as(String)
@headers = req["headers"].as(Hash(String, String))
@body = req["body"].as(String)
end end
def to_s() def initialize(protocol_name : String, protocol_version : String, status : HTTPStatus)
body = File.read("/vagrant/static/hellow.html") # TODO: Send true HTTP Status name
@headers << "Content-Length: " + (body.bytesize + 1).to_s() # +1 for CRLF ? # @status_line = protocol_name + "/" + protocol_version + " " + status.value.to_s + " " + status.to_s + "\r\n"
request = ""
@headers.each() do |header|
request += header + "\r\n"
end end
def addHeader(name : String, value : String)
end
def body
@body = body
end
def body=(@body : String)
end
def to_s
@headers["Content-Length"] = @body.bytesize.to_s
request = @protocol_name + "/" + @protocol_version + " " + @status.value.to_s + " " + @status.to_s + "\r\n"
@headers.each do |name, value|
request += name + ": " + value + "\r\n"
end
request += "\r\n" request += "\r\n"
request += @body
request += body
return request return request
end end

75
src/http/status.cr Normal file
View File

@ -0,0 +1,75 @@
enum HTTPStatus
# 1xx Informational
CONTINUE = 100
SWITCHING_PROTOCOLS = 101
PROCESSING = 102
EARLY_HINT = 103
# 2xx Success
OK = 200
CREATED = 201
ACCEPTED = 202
NON_AUTHORITATIVE_INFORMATION = 203
NO_CONTENT = 204
RESET_CONTENT = 205
PARTIAL_CONTENT = 206
MULTI_STATUS = 207
ALREADY_REPORTED = 208
IM_USED = 226
# 3xx Redirection
MULTIPLE_CHOICES = 300
MOVED_PERMANENTLY = 301
FOUND = 302
SEE_OTHER = 303
NOT_MODIFIED = 304
USE_PROXY = 305
UNUSED = 306
TEMPORARY_REDIRECT = 307
PERMANENT_REDIRECT = 308
# 4xx Client Error
BAD_REQUEST = 400
UNAUTHORIZED = 401
PAYEMENT_REQUIRED = 402
FORBIDDEN = 403
NOT_FOUND = 404
METHOD_NOT_ALLOWED = 405
NOT_ACCEPTABLE = 406
PROXY_AUTHENTICATION_REQUIRED = 407
REQUEST_TIMEOUT = 408
CONFLICT = 409
GONE = 410
LENGTH_REQUIRED = 411
PRECONDITION_FAILED = 412
PAYLOAD_TOO_LARGE = 413
URI_TOO_LONG = 414
UNSUPPORTED_MEDIA_TYPE = 415
RANGE_NOT_SATISFIABLE = 416
EXPECTATION_FAILED = 417
IM_A_TEAPOT = 418
MISDIRECTED_REQUEST = 421
UNPROCESSABLE_ENTITY = 422
LOCKED = 423
FAILED_DEPENDENCY = 424
TOO_EARLY = 425
UPGRADE_REQUIRED = 426
PRECONDITION_REQUIRED = 428
TOO_MANY_REQUESTS = 429
REQUEST_HEADER_FILEDS_TOO_LARGE = 431
UNAVAILABLE_FOR_LEGA_REASONS = 451
# 5xx Server Error
INTERNAL_SERVER_ERROR = 500
NOT_IMPLEMENTED = 501
BAD_GATEWAY = 502
SERVICE_UNAVAILABLE = 503
GATEWAY_TIMEOUT = 504
HTTP_VERSION_NOT_SUPPORTED = 505
VARIANT_ALSO_NEGOTIATES = 506
INSUFFICIENT_STORIAGE = 507
LOOP_DETECTED = 508
BANDWIDTH_LIMIT_EXEEDED = 509
NOT_EXTENDED = 510
NETWORK_AUTHENTIFICATION_REQUIRED = 511
end

View File

@ -1,19 +1,46 @@
require "socket" require "socket"
require "option_parser" require "option_parser"
require "env"
require "json"
require "./http/request" require "./http/request"
require "./http/response" require "./http/response"
require "./http/handler/static_handler"
require "./http/handler/proxy_handler"
require "./http/handler/compter_handler"
def handle_request(client) def handle_request(client)
request = Request.new(client) request = Request.new(client)
response = Response.new() puts request
configuration_file = File.open("/home/kooka/config.json") do |file|
file.gets_to_end
end
client.puts response.to_s() configuration = Hash(String, JSON::Any).from_json configuration_file
configuration.each do |key, value|
if request.header("Host") != key
next
end
client.close case value["handler"]
when "proxy_pass"
handler = ProxyHandler.new value
when "static_serve"
handler = StaticHandler.new value
when "internal.compter"
handler = CompterHandler.new value
else
client << Response.new "HTTP", "1.0", HTTPStatus::BAD_GATEWAY
break
end
client << handler.handle(request).to_s
break
end
end end
tcp_port = 8000 tcp_port = 8090
hostname = "127.0.0.1" hostname = "127.0.0.1"
OptionParser.parse do |parser| OptionParser.parse do |parser|

4
web/cgi/index.php Normal file
View File

@ -0,0 +1,4 @@
<?php
file_get_contents('http://127.0.0.1:8080');
$file = fopen('/vagrant/boomer.txt', 'w') or die("PHP t'es naze");
var_dump(getenv());

10
web/compteur/main.js Normal file
View File

@ -0,0 +1,10 @@
const http = require('http');
const requestListener = function (req, res) {
console.log(req.method.toUpperCase())
res.writeHead(200);
res.end('Hello, World!');
}
const server = http.createServer(requestListener);
server.listen(8080);

View File

@ -4,10 +4,9 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wesh</title> <title>Document</title>
</head> </head>
<body> <body>
<h1>Hellow World !</h1> <p>Salut</p>
<p>Cimer Nuker pour la découverte 😉</p>
</body> </body>
</html> </html>