efinf:blc2016:networks:http

Hypertext Transfer Protocol: Transfer von Webseiten und Zugemüse (auch einfach Dateien).

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
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
  }
}
  • efinf/blc2016/networks/http.txt
  • Last modified: 2016/12/08 09:19
  • by Ivo Blöchliger