Requests        von Clients, und lässt die Requests 
   durch separate        Threads bearbeiten.   Request
   in  fünf      Schritten:  Lesen, Parsen, Response erstellen, Response 
          an Client senden, wieder Lesen bei Keep Aliveglobal_mvar).acceptConnections :: Config -> Socket -> IO ()acceptConnections conf sock = do { (handle, remote) <- accept
        sock    ;                  
                               forkIO
        (                 
                           
     catchAllIO    ( talk conf handle remote                  
                                
                            'finally'
        hClose handle )                  
                               
                       ( \e -> logError
    e  )                 
                            
              )                 
                         acceptConnections
        conf sockacceptConnections kriegt als Argumente eine Server Konfiguration conf
        und ein auf Verbindung wartendes Socket sock.
        Es wartet auf neue ankommende Requests über
 das Socket.       Wenn ein Request ankommt,
 wird ein neuer Thread mit forkIO       erzeugt, der diesen Request
 dann bearbeitet, und die    Schleife   wartet wieder auf weitere Verbindungen.
 Der neu erzeugte Thread    kommuniziert   anschliessend mit dem Client über
 den Aufruf von talk,       das eine Funktion zur Kommunikation
 in HTTP ist. finally       ermöglicht eine streng
sequenzierte Ausführung von Aktionen,    unabhängig   von Exceptions.
                
finally :: IO a -> IO b -> IO afinally funktioniert wie in Java: Zuerst wird das erste Argument dann
das zweite Argument ausgeführt, auch wenn das erste Argument eine Exception
        auslöst, und gibt den Wert vom ersten Argument zurück (oder
    wirft    die ausgelöste Exception wieder). Damit wird in der Schleife
    gewährleistet,    dass das Socket ordnungsgemäß geschlossen
    wird auch wenn irgendwelche    Fehler oder Bugs im Code auftreten.
                
catchAllIO :: IO a -> (Exception -> IO a) -> IO acatchAllIO führt zuerst das erste Argument aus. Wenn dabei eine Exception ausgelöst wird, führt es das zweite Argument (den Exception Handler) aus, andernfalls liefert es des Ergebnis zurück. In der obigen Schleife wird catchAllIO zum Auffangen von allen Fehlern benutzt, die dann in die Error Log Datei geschrieben werden.
Requests
       besteht aus folgenden Schritten:Request aus dem Socket 
   lesen.      Request parsen.          Response erstellen.Response an Client senden.Socket       zurückkehren (zurück zum Schritt
1).Das Lesen aus dem Socket erfolgt durch getRequest:
              
getRequest :: Handle -> IO [String]getRequest gibt eine Liste von String
       als return zurück.
              Beispiel für einen Request:
              
GET /index.html HTTP/1.1Host: www.haskell.orgDate: Wed May 31 11:08:40 GMT 2000Das Kommando des Requests ist in diesem Fall GET, index.html
       ist der Name des angeforderten Objektes und 1.1
die    Version    des benutzten HTTP Protokolls. In den weiteren Zeilen (genannt
   headers),   werden weitere Informationen gegeben, die meistens optional
 sind.  Wenn der   Server einen Header nicht versteht, soll er dieses ignorieren.
              
Danach kommt das Parsen des Requests in eine Request
       Struktur:
              
data Request = Request { reqCmd
           :: RequestCmd,                 
              reqURI     :: ReqURI,                 
              reqHTTPVer :: HTTPVersion,                 
              reqHeaders :: [RequestHeader]  }parseRequest :: Config -> [String] -> Either Response RequestDas Parsen kann hierbei zu einer Response führen,
       die einen Mißerfolg darstellt und wahrscheinlich eine "Bad Request" Response,
       die auch spezifischere Informationen enthalten kann, ist.
              Wenn das Parsen erfolgreich war, wird eine Response 
     generiert:
              
data Response = Response { respCode     :: Int,                 
                respHeaders  :: [String],                 
                respCoding   :: [TransferCoding],                 
                respBody     :: ResponseBody,                 
                respSendBody :: Bool  }data ResponseBody = NoBody                 
       | FileBody Integer{-size-} FilePath                 
       | HereItIs StringgenResponse :: Config -> Request -> IO Response genResponse überprüft den Request
       auf Validität (Korrektheit) und erzeugt eine passende Response.
       Bei einem GET Request erhält
   man    eine Response mit einem FileBody.
   Ein    "invalid request" (ungültiger Request) resultiert
      in einer Fehlermeldung, die aus einer automatisch generierten HTML
besteht,       die den Fehler in dem HereItIs Body näher
beschreibt.       Falls eine Response aus einer ganzen
Datei besteht,   wird    die Datei nicht als String in respBody
      enthalten sein, sondern nur den Pfad der Datei in respBody
      enthalten. Damit kann die Datei auf eine effizientere Art übertragen
      werden, wie durch Konvertierung zum String und zur
 Datei     zurück.
              
Anschließend, muss nur noch die Response zum Client
       gesendet werden:
              
sendResponse :: Config -> Handle -> Response -> IO ()Zusammengefasst, sieht die resultierende talk Funktion
       in etwa folgendermaßen aus:
              
talk :: Config -> Handle -> HostAddress -> IO ()talk conf handle haddr = do req <- getRequest handle                 
               case parseRequest r of                 
                  Left resp -> do sendResponse
      conf handle resp                 
                          
             return ()                 
                  Right req -> do resp <-
    genResponse   conf req                 
                          
             sendResponse conf handle resp                 
                          
             logAccess req resp haddr                 
                          
             if (isKeepAlive req)                 
                          
               then talk conf handle haddr                 
                          
               else return ()Hierbei fehlt nur noch der Code zum Error-Logging und Timeout, was in
den folgenden Abschnitten behandelt wird. Und schließlich, resultiert logAccess
       in einem Eintrag in die Log-Datei, was auch nachfolgend behandelt
wird.
  
                                    
Timeout-Mechanismus
                                     Timeout ist bei einem Server notwendig,
    wenn   z.B. eine Verbindung zum Client abgebrochen ist oder der Client
 eine   außergewöhnlich   lange Antwortzeit hat.
                                 Mit dem Timeout können Verbindungen 
zu  solchen     Clients abgebrochen werden und die entsprechenden Ressourcen
 wieder freigegeben     werden.
             Hier eine generische Implementierung von einem timeout: 
                          
             
timeout :: Int       -- timeout in seconds        -> IO a      -- 
      action to run        -> IO b      -- 
      action to run on timeout        -> IO a(timeout t a b) lässt zuerst a  laufen, 
      bis es entweder sich beendet oder t Sekunden vergangen 
     sind. Wenn sich a innerhalb t Sekunden 
 selbst    beendet, wird das Ergebnis von a zurück 
gegeben,  andernfalls wird a terminiert und b 
ausgeführt.  Falls a eine Exception wirft, wird diese 
durch timeout  weitergeleitet. Aufgrund keiner anderen Seiteneffekten,
kann timeout  an beliebigen Stellen im Code verwendet werden.
            Aufgrund asynchroner Excpetions, kann jedoch Aktion a 
     jederzeit terminiert werden, weshalb es erforderlich ist es exception 
    safe zu machen, d.h. alle variablen Datentypen dürfen in keinem 
   inkonsistenten Zustand gelassen werden oder irgendwelche Ressourcen ausgelassen 
   werden. In diesem Zusammenhang, ist es sogar notwendig den geasamten Code 
   exception safe zu machen, da stack overflow bzw. heap overflow als
   asynchrone Exceptions ausgelöst werden. Um Code exception safe
   zu machen, gibt es in Haskell folgende zwei Funktionen:
            
block   :: IO a -> IO aunblock :: IO a -> IO a(block a) führt a aus, wobei asynchrone 
     Exceptions abgeblockt werden. Erst durch den Aufruf (unblock 
   a)  kann der Thread wieder von anderen Threads terminiert werden. 
  Auch diese beiden Funktionen können an beliebigen Stellen im Code verwendet
   werde 
            Beispiel:
            
block ( do a <- takeMVar m           (unblock (...))             'catchAllIO'                (\e ->
      do putMVar m a; throw e)           putMVar m a   )In diesem Beispiel soll gezeigt werden, dass ein Lock in Form von MVar 
     m  auch dann sicher freigesetzt werden kann, wenn eine Exception 
     geworfen wurde.
            Eine andere Methode einen Code exception safe zu machen, 
 ist   die   Verwendung von der Kombination finally und bracket. 
     Damit kann man eine locking Sequenz einfacher schreiben:
            
bracket   :: IO a -> (a -> IO b) -> (a -> IO
      c) -> IO cbracket (takeMVar m) (putMVar m) (...)        
       -- vereinfachte Schreibweise einer locking Sequenz
Requests inklusive einiger Informationen 
    zu den versendeten Responses aufgelistet werden.
Das Format eines Log-Eintrags, d.h. welche Datenfelder enthalten  sind, 
   kann konfiguriert werden und kann auch andere Datenfelder zu Request  und
   Response enthalten.Aufgrund einiger standardisierten Log-Eintrag Formaten 
  von verbreiteten Servern und Programmen, die Log-Dateien auswerten und dazu
  Reporte erstellen, wird in dem Haskell Web Server ein kompatibles Format
 der Log-Datei erstellt:
    
logAccess :: Request          -> Response          -> HostAddress          -> TimeDiff          -> IO () Ein Thread das ein Request bearbeitet erzeugt einen Log-Eintrag, indem
  es logAccess mit den Argumenten Request, Response, Client Adresse und der
  Dauer der Bearbeitung (Zeit zwischen Erhalt des Requests und Beendigung
der  Response) aufruft.
    Das Schreiben des Eintrags in die Log-Datei wird jedoch von einem separatem
  Thread durchgeführt. Die Log-Einträge werden dabei über
einen   globalen Channel von einem Thread zum Log-Thread übergeben.
logAccess   fügt den Log-Eintrag in den Channel ein und der Log-Thread
holt sich   (und entfernt) den Log-Eintrag aus dem Channel und schreibt es
in die Log-Datei.
    Ein separater Log-Thread hat den Vorteil, dass das System geringer belastet
  wird, weil die Request-Threads sich gleich nach Abgabe des Eintrags in
den   Channel beenden können und somit nicht weiter mit Schreiben in
die Log-Datei  belastet werden. Zusätzlich, kann der Log-Thread mehrere
Einträge  zusammenfassen und als einen einzelnen Block in die Log-Datei
schreiben.
    Der Log-Thread kann darüberhinaus sich selbst wieder neu starten 
(restart),  wenn eine Exception ausgelöst wird. Bei einem Restart versucht 
der Thread  die Log-Datei wieder neu zu öffnen (re-open) um dann mit 
dem Schreiben  des nächsten Log-Eintrags fortzufahren.
    Dieser Restart-Mechanismus wird ausserdem von dem main-Thread (Endlosschleife)
  genutzt (mißbraucht), wenn ein Request zum auslesen der Log-Datei
empfangen  wurde.
    
Das Error-Logging funktioniert nach dem gleichen Prinzip, d.h. es gibt
  auch hier einen separaten Error-Log-Thread der in die Error-Log-Datei die
  Einträge schreibt und sich selbst Restarten kann, falls er eine Exception
  empfängt (wobei auch der Restart mit Exception in die Datei reingeschrieben
  wird). Und auch hier werden die Error-Log-Einträge über einen
globalen  Channel übergeben.
    Die Error-Log-Einträge werden von den Exception Handlern erzeugt 
und  in den Channel eingefügt, wenn bei einem Request-Thread eine Exception
  ausgelöst wurde.
MVar sehen:                  
                global_mvar :: MVar Stringglobal_mvar = unsafePerformIO newEmptyMVarunsafePerformIO ist tatsächlich unsicher, weil 
das Programm sich bei Ersetzung einer global_mvar durch 
ihren Wert (unsafePerformIO newEmptyMVar) anders verhält. 
Um das zu verhindern, muss jegliche Optimierung von Compiler umgangen werden. 
Beim GHC muss man folgendes (irgendwo im Code) hinzufügen: {-# 
NoInline global_mvar #-}    MVars statt IORefs zu benutzen.  
 ...  [
                  Informatik und Master-Seminar SS2003 ]  ...  [ HWS
  Gesamtübersicht              ]  ...  [ Web Server
  Implementierung  ]     ...     [ Zusammenfassung
]   ...