efinf:blc2016:networks:http

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
efinf:blc2016:networks:http [2016/10/26 20:18] – created Ivo Blöchligerefinf:blc2016:networks:http [2016/12/08 08:19] (current) Ivo Blöchliger
Line 1: Line 1:
 +{{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).
 +<code text>
 +GET /bla.html HTTP/1.1\r\n
 +Host: fginfo.ksbg.ch:42001\r\n
 +Connection: close
 +\r\n
 +\r\n
 +</code>
 +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:
 +<code text>
 +HTTP/1.1 200 OK\r\n
 +Content-Type: text/html\r\n
 +Content-Length: 1354\r\n
 +\r\n
 +.... DATEN ....
 +</code>
 +Oder natürlich
 +<code text>
 +HTTP/1.1 404 Not Found
 +</code>
 +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 ====
 +<code ruby webserver.rb>
 +# 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 = "<!DOCTYPE html><html><body><h1>404 Not found</h1>Sorry, ich bin halt nur ein Webserver... #{pfad} habe ich nicht gefunden...<br><br><h2>Debug Info</h2><pre>#{debug}</pre></body></html>"
 +  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 = "<!DOCTYPE html><html><body><h1>400 Bad request</h1>Sorry, die Anfrage #{req} verstehe ich nicht.<br><br><h2>Debug Info</h2><pre>#{debug}</pre></body></html>"
 +  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 = "<!DOCTYPE html><html><body><h1>Directory #{relpfad}</h1><ul>"
 +  relpfad.gsub!(/\/+$/,"") # Remove trailing slashes
 +  files.each{|f|
 +    f = File.basename(f)
 +    puts relpfad
 +    puts f
 +    msg+="<li><a href=\"#{relpfad + "/" +f}\">#{f}</a></li>\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
 +  }
 +}
 +</code>