Static web server with a bit of error handling I am looking for a review on this code, which is a simple static web server, with a small bit of error handling. It's feeling a bit "messy" / "imperative" at the moment, so advice would be welcome.

import Network.Socket hiding (send, sendTo, recv, recvFrom)
import Network.Socket.ByteString
import qualified Data.ByteString.Char8 as C
import qualified Data.ByteString as B
import Data.List
import Control.Concurrent
import Control.Exception (SomeException, try)
import qualified Data.Map.Strict as Map
import System.FilePath.Posix
import System.Directory (doesFileExist)
import Text.Printf

port = 8080
incomingBufferSize = 16384
mimeTypes = Map.fromList [
    (".htm", "text/html"),
    (".html", "text/html"),
    (".js", "application/javascript"),
    (".css", "text/css"),
    (".png", "image/png"),
    (".jpg", "image/jpeg"),
    (".jpeg", "image/jpeg")
  ]
defaultMime = "application/octet-stream"
headerOkText = "HTTP/1.1 200 OK\r\nContent-Type: %s\r\n\r\n"
header404 = "HTTP/1.1 404\r\n\r\n"
header500 = "HTTP/1.1 500\r\n\r\n"

main = do
  sock &lt;- socket AF_INET Stream 0
  setSocketOption sock ReuseAddr 1
  bind sock $ SockAddrInet port iNADDR_ANY
  listen sock sOMAXCONN
  mainLoop sock

mainLoop :: Socket -&gt; IO ()
mainLoop sock = do
  (conn, _) &lt;- accept sock
  forkIO $ handle conn
  mainLoop sock

handle :: Socket -&gt; IO ()
handle conn = do
  incoming &lt;- recv conn incomingBufferSize
  let unsafeLocation = extractLocation incoming
  if isSafeLocation unsafeLocation then
    do
      fileContents &lt;- try $ response unsafeLocation
      send conn $ contentsOr500 fileContents
  else
    send conn $ C.pack header404
  close conn
  where
    extractLocation = C.unpack . C.tail . head . tail . C.split ' '
    isSafeLocation location = not $ ".." `isInfixOf` location

contentsOr500 :: Either SomeException B.ByteString -&gt; B.ByteString
contentsOr500 (Left _) = C.pack header500
contentsOr500 (Right contents) = contents

response :: String -&gt; IO (B.ByteString)
response requestedLocation = do
  exists &lt;- doesFileExist requestedLocation
  if exists then
    do
      file &lt;- B.readFile requestedLocation
      return $ fullResponse file $ takeExtension requestedLocation
    else
      return $ C.pack header404

fullResponse :: B.ByteString -&gt; String -&gt; B.ByteString
fullResponse contents extension = C.pack headerWithMime `B.append` contents
  where
    headerWithMime = printf headerOkText $ mimeForExtension extension
    mimeForExtension = flip (Map.findWithDefault defaultMime) mimeTypes
My initial reaction to this code is that it would be clearer if you bring some functions outside main = do.

One of the great things about Haskell is it's explicit type system and safety; your code doesn't leverage this as there are no function type declarations.