{-# LANGUAGE ScopedTypeVariables #-} -- | A library for programming custom proxy servers. module Network.HTTP.Proxy.Server (proxyMain ,Settings (..) ,Cache (..) ,Default(..)) where import Network.HTTP hiding (port) import Network.HTTP.Server hiding (Response, Request) import Network.HTTP.Server.Logger import Data.Default.Class import Network.HostName -- | Proxy entry-point. Spawns a new proxy server. proxyMain :: forall s. HStream s => Settings s -> IO () proxyMain settings = do hname <- case hostname settings of Nothing -> getHostName Just hostn -> return hostn let config = defaultConfig {srvPort = fromInteger $ portnum settings ,srvHost = hname ,srvLog = mylogger} putStrLn "Proxy server started on port 3128\n" serverWith config (proxyHandler settings) mylogger = stdLogger proxyHandler :: HStream s => Settings s -> Handler s proxyHandler settings _ _ request = -- check that the request is authorized isAuthorized settings request >>= \authorized -> if authorized then processRequest settings request else errorProxyUnauthorized -- |Processes the request; this is the main proxy procedure processRequest :: HStream s => Settings s -> Request s -> IO (Response s) processRequest settings request = do -- modify the request modRequest <- requestModifier settings request -- check the cache mCachedResponse <- queryCache (cache settings) modRequest case mCachedResponse of -- found in cache: return Just response -> return response -- not found: fetch it from a remote server, invoke the -- 'responseModifier' hook, record in cache and return Nothing -> do response <- fetch request modResponse <- responseModifier settings request response recordInCache (cache settings) request modResponse return modResponse fetch :: HStream s => Request s -> IO (Response s) fetch request = do result <- simpleHTTP request case result of Left err -> do putStrLn ("Connection error: " ++ show err) errorInternalServerError Right rsp -> return rsp -- | Proxy server settings data Settings s = Settings {requestModifier :: Request s -> IO (Request s) -- ^ A function for modifying requests. Will be called for -- each request received; the modified request will be -- forwarded to the target server. Defaults to an identity -- function. ,responseModifier :: Request s -> Response s -> IO (Response s) -- ^ A function for modifying responses. Will be called -- for each response received; the modified response will -- be forwarded to the client. Defaults to an identity -- function. ,cache :: Cache s -- ^ The cache. Use 'def' for no cache. ,isAuthorized :: Request s -> IO Bool -- ^ Authorization function. Allows denying certain -- requests. Defaults to allowing all requests ,logger :: String -> IO () -- ^ A logging function. The default is no logging. ,portnum :: Integer -- ^ Proxy server port number; default is 3128 ,hostname :: Maybe String -- ^ The server host name. Defaults to the result of -- 'getHostName' } instance Default (Settings s) where def = Settings {requestModifier = return ,responseModifier = \_ -> return ,cache = def ,isAuthorized = return . const True ,logger = \_ -> return () ,portnum = 3128 ,hostname = Nothing} -- | The cache. data Cache s = Cache {queryCache :: Request s -> IO (Maybe (Response s)) -- ^ Retreive the response to a request from the -- cache. ,recordInCache :: Request s -> Response s -> IO () -- ^ Record the response to a request in the -- cache. } instance Default (Cache s) where def = Cache {queryCache = return . const Nothing ,recordInCache = \_ -> return . const ()} -- |A generic 500 response errorInternalServerError :: HStream s => IO (Response s) errorInternalServerError = return $ err_response InternalServerError -- |A generic 407 response. TODO: RFC 2068 requres to send -- Proxy-Authenticate header with this response code errorProxyUnauthorized :: HStream s => IO (Response s) errorProxyUnauthorized = return $ err_response ProxyAuthenticationRequired -- |A generic 400 response errorBadRequest :: HStream s => IO (Response s) errorBadRequest = return $ err_response BadRequest