{{backlinks>.}}
===== HTTP =====
Hypertext Transfer Protocol: Transfer von Webseiten und Zugemüse (auch einfach Dateien).
==== Aufgabe ====
Programmieren Sie einen minimalen Webserver, der folgende Anforderungen erfüllt:
* Es gibt einen Ordner **html**, indem Dateien (und Unterordner) liegen, die der Webserver ausliefern kann.
* Prüfen Sie den angeforderten Pfad. Insbesondere darf es nicht möglich sein, auf Dateien ausserhalb des Verzeichnisses **html** zuzugreifen.
* Schreiben Sie eine minimale html-Datei, die auch ein Bild enthält und testen Sie ihren Webserver damit.
=== Hilfestellungen ===
== HTTP Protokoll ==
Minimale HTTP-Anfrage (Achtung, verwendet "\r\n" für einen Zeilenumbruch (wie DOS), nicht nur einfach "\n" (wie Unix).
GET /bla.html HTTP/1.1\r\n
Host: fginfo.ksbg.ch:42001\r\n
Connection: close
\r\n
\r\n
Die erste Zeile enthält Methode (GET), Pfad /bla.html und Protokollversion.
Die zweite Zeile enthält den Servernamen und evtl. den Port
Die dritte Zeile ist optional
Zwei leere Zeilen schliessen die Anfrage ab.
Die Minimale Antwort sieht wie folgt aus:
HTTP/1.1 200 OK\r\n
Content-Type: text/html\r\n
Content-Length: 1354\r\n
\r\n
.... DATEN ....
Oder natürlich
HTTP/1.1 404 Not Found
Als Content-Type kommt z.B. auch image/png oder imag/jpg in Frage.
== Ruby File Access ==
Siehe auch https://ruby-doc.org/core-2.2.0/File.html
* File.exists?(pfad) Überprüft, ob die Datei existiert.
* File.read(pfad) Liefert den Inhalt einer Datei als String zurück
* File.absolute_path(pfad) Liefert den absoluten Pfad zurück (z.B. /home/hansli/server/html/index.html)
Um mit Verzeichnissen zu arbeiten, siehe auch https://ruby-doc.org/core-2.2.0/File.html
* Dir.pwd liefert den aktuellen Pfad
* Dir.glob("html/*") liefert ein Array mit allen Dateien im Verzeichnis html
==== Lösungsvorschlag ====
# coding: utf-8
require 'socket'
# Damit Ruby auch bei Fehlern im Thread abbricht mit Fehlermeldung
Thread.abort_on_exception=true
# 2 Methoden für Fehlermeldungen
def notfound(client, pfad, debug="")
msg = "404 Not found
Sorry, ich bin halt nur ein Webserver... #{pfad} habe ich nicht gefunden...
Debug Info
#{debug}"
client.puts "HTTP/1.1 404 Not Found\r
Content-Type: text/html\r
Content-Length: #{msg.size}\r\n\r\n"
client.puts msg
puts "Got a 404"
end
def badRequest(client, req, debug="")
msg = "400 Bad request
Sorry, die Anfrage #{req} verstehe ich nicht.
Debug Info
#{debug}"
client.puts "HTTP/1.1 400 Bad request\r
Content-Type: text/html\r
Content-Length: #{msg.size}\r\n\r\n"
client.puts msg
puts "Got a 400"
end
# Methode für Dirlisting
def dirlist(client,basepfad, abspfad, relpfad)
relpfad = "/" if (relpfad=="")
puts "Dirlist für #{abspfad}, #{relpfad}"
files = Dir.glob(abspfad+"/*") # Alle Dateien im Verzeichnis
relpfad.gsub(/\/+$/,"") # Remove trailing slash
msg = "Directory #{relpfad}
"
relpfad.gsub!(/\/+$/,"") # Remove trailing slashes
files.each{|f|
f = File.basename(f)
puts relpfad
puts f
msg+="- #{f}
\n"
}
client.puts "HTTP/1.1 200 OK\r
Content-Type: text/html\r
Content-Length: #{msg.size}\r\n\r\n"
client.puts msg
puts "Listed #{abspfad}"
end
# Server auf port 420XX laufen lassen
server = TCPServer.open(42001)
# Endlos-Schleife
loop {
# Auf Verbindung warten.
# Wenn verbunden, einen Thread damit beschäftigen
# und gleich auf nächste Verbindung warten
Thread.start(server.accept) { |client|
# Request einlesen, zeilenweise im Array all speichern
all = []
while ((req=client.gets.chomp)!="")
puts req
all.push(req)
end
p all
# Erste Zeile analysieren
pfad = all[0].scan(/^GET ([^ ]+)/)
# Wenn kein Match, also bad request (nicht http-protokoll)
p pfad
if (pfad.size==0)
puts "400!"
badRequest(client, all[0], all.join("\n"))
else
puts
# Dem Request den ordner 'html' vorne anfügen
pfad = "html"+pfad[0][0]
# Daraus den absoluten Pfad erstellen
pfad = File.absolute_path(pfad)
# Überprüfen, ob auch im richtigen verzeichnis
# Aktueller Pfad plus html
richtig = File.absolute_path(Dir.pwd+"/html")
p pfad
p richtig
if pfad.start_with?(richtig) && File.exists?(pfad)
if (File.directory?(pfad))
dirlist(client,richtig, pfad, pfad.gsub(richtig,""))
else
puts "Got a 200"
# Typ bestimmen: Paare aus RegEx und MIME-Type (/i macht die regex case-insensitive)
types = [[/html?$/i,"text/html"],
[/jpe?g$/i,'image/jpg'],
[/png$/i,'image/png'],
[/gif/i, 'image/gif'],
[/txt/i, 'text/plain']]
type = "text/html" # Default, wenn nichts anderes gefunden
types.each{|t|
if pfad =~ t[0]
type=t[1]
end
}
daten = File.read(pfad)
client.puts "HTTP/1.1 200 OK\r
Content-Type: #{type}\r
Content-Length: #{daten.size}\r\n\r\n"
client.print daten
end
else
puts "Not found"
notfound(client, pfad, all.join("\n"))
end
end
client.close
}
}