on master help sign in
added obelisk-asset as a submodule
commit7d6d91f32b8e805c124cb40a1600f2f8a63f9333 authorhsloan <ishaqsloan@gmail.com> dateTue 22 Aug 2017, 16:48 (9 years ago) parente23ebf6
6 files changed+4−351
.gitmodules+3 obelisk-asset+1 obelisk-asset/README.md−1 obelisk-asset/obelisk-asset.cabal−33 obelisk-asset/src/Obelisk/Asset/Accept.hs−212 obelisk-asset/src/Obelisk/Asset/Serve.hs−105
.gitmodulesnew file+3
@@ -0,0 +1,3 @@
1+[submodule "obelisk-asset"]
2+ path = obelisk-asset
3+ url = git@github.com:obsidiansystems/obelisk-asset.git
obelisk-assetnew file+1

Submodule changed.

obelisk-asset/README.mddeleted−1
@@ -1 +0,0 @@
1−# obelisk-asset
obelisk-asset/obelisk-asset.cabaldeleted−33
@@ -1,33 +0,0 @@
1−Name: obelisk-asset
2−Version: 0.1
3−Synopsis: Project Synopsis Here
4−Description: Project Description Here
5−License: AllRightsReserved
6−Author: Author
7−Maintainer: maintainer@example.com
8−Stability: Experimental
9−Category: Web
10−Build-type: Simple
11−Cabal-version: >=1.2
12−
13−library
14− hs-source-dirs: src
15−
16− build-depends:
17− base
18− , attoparsec
19− , bytestring
20− , containers
21− , directory
22− , filepath
23− , snap
24− , snap-core
25− , text
26− , transformers
27− , unix
28− exposed-modules:
29− Obelisk.Asset.Accept
30− Obelisk.Asset.Serve
31−
32− other-extensions: TemplateHaskell
33− ghc-options: -threaded -Wall -Werror -fno-warn-unused-do-bind -fwarn-tabs -funbox-strict-fields -O2 -fprof-auto-calls -rtsopts
obelisk-asset/src/Obelisk/Asset/Accept.hsdeleted−212
@@ -1,212 +0,0 @@
1−{-# LANGUAGE CPP, OverloadedStrings, BangPatterns, ScopedTypeVariables, GeneralizedNewtypeDeriving #-}
2−module Obelisk.Asset.Accept where
3−
4−#ifdef __GLASGOW_HASKELL__
5−#if __GLASGOW_HASKELL__ < 710
6−import Prelude hiding (takeWhile, sequence)
7−import Control.Monad hiding (sequence)
8−import Data.Traversable (sequence)
9−#else
10−import Prelude hiding (takeWhile)
11−import Control.Monad
12−#endif
13−#else
14−import Prelude hiding (takeWhile)
15−import Control.Monad
16−#endif
17−
18−import Control.Applicative
19−import qualified Data.ByteString as BS
20−import Data.Attoparsec.ByteString as A
21−import Data.Attoparsec.ByteString.Char8 as AC8
22−import Data.ByteString (ByteString)
23−import Data.Maybe
24−import Data.Word (Word8)
25−import Data.Monoid
26−import Data.Fixed
27−import Data.Map (Map)
28−import qualified Data.Map as Map
29−import Data.Either
30−import Control.Arrow
31−import Data.List
32−import Data.String
33−import Data.Proxy
34−import Data.Ord
35−
36−newtype Encoding = Encoding { unEncoding :: ByteString } deriving (Show, Read, Eq, Ord, IsString)
37−type QValueResolution = E3 -- This resolution is given by the standard
38−newtype QValue = QValue { unQValue :: Fixed QValueResolution } deriving (Show, Read, Eq, Ord)
39−
40−-- | When no Accept-encoding header is present, prefer identity, then gzip or compress, then anything else available
41−--
42−-- See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
43−missingAcceptableEncodings :: AcceptableEncodings
44−missingAcceptableEncodings = AcceptableEncodings
45− { _acceptableEncodings_defaultQValue = QValue 0.001
46− , _acceptableEncodings_byEncoding = Map.fromList
47− [ (Encoding "identity", QValue 1)
48− , (Encoding "gzip", QValue 0.5)
49− , (Encoding "compress", QValue 0.5)
50− ]
51− }
52−
53−data AcceptableEncodings
54− = AcceptableEncodings { _acceptableEncodings_defaultQValue :: QValue
55− , _acceptableEncodings_byEncoding :: Map Encoding QValue
56− }
57− deriving (Show, Read, Eq, Ord)
58−
59−chooseEncoding :: [Encoding] -> AcceptableEncodings -> Maybe Encoding
60−chooseEncoding es ae = fmap snd $ listToMaybe $ sort $ catMaybes $ zipWith f es [(1::Int)..]
61− where f e n = case encodingQValue e ae of
62− QValue 0 -> Nothing
63− q -> Just ((Down q, n), e) -- Choose by quality first (in descending order), then by the server's preference order
64−
65−encodingQValue :: Encoding -> AcceptableEncodings -> QValue
66−encodingQValue e ae = Map.findWithDefault (_acceptableEncodings_defaultQValue ae) e $ _acceptableEncodings_byEncoding ae
67−
68−acceptEncodingBody :: Parser AcceptableEncodings
69−acceptEncodingBody = do
70− l <- hashRule (Just 1) Nothing $ do
71− c <- (Nothing <$ literal "*") <|> (Just <$> contentCoding)
72− mq <- optional $ do
73− literal ";"
74− literal "q"
75− literal "="
76− qvalue
77− let q = fromMaybe (QValue 1) mq
78− return (c, q)
79− let (stars, specificEncodings) = partitionEithers $ flip map l $ \(c, q) -> case c of
80− Nothing -> Left q
81− Just n -> Right (n, q)
82− starQValue <- case stars of
83− [] -> return Nothing
84− [q] -> return $ Just q
85− _ -> fail "acceptEncodingBody: multiple * values provided"
86− byEncodingProvided <- sequence $ Map.fromListWithKey (\k _ _ -> fail $ "acceptEncodingBody: encoding " <> show k <> " repeated multiple times") $ map (second return) specificEncodings
87− let defaultIdentityQValue = fromMaybe (QValue 1) starQValue -- identity has a default qvalue of 1 unless * is given a different qvalue explicitly
88− defaultQValue = fromMaybe (QValue 0) starQValue
89− byEncoding = Map.filter (/= defaultQValue) -- Canonicalize: qvalues equal to the default are redundant
90− . Map.alter (Just . fromMaybe defaultIdentityQValue) "identity" -- Add implicit "identity" encoding, unless it has been explicitly added
91− $ byEncodingProvided
92− return $ AcceptableEncodings defaultQValue byEncoding
93−
94−-- | See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.9
95−qvalue :: Parser QValue
96−qvalue = do
97− skipMany lws
98− q0 <|> q1
99− where q0 = do
100− char '0'
101− decimals <- option [] $ do
102− char '.'
103− starRule (Just 0) (Just numAllowedDigits) digit
104− return $ QValue $ MkFixed $ fromIntegral $ (read ('0' : decimals) :: Int) * 10 ^ (numAllowedDigits - length decimals)
105− q1 = do
106− char '1'
107− option () $ do
108− char '.'
109− starRule (Just 0) (Just numAllowedDigits) $ char '0'
110− return ()
111− return $ QValue 1
112− numAllowedDigits :: Int
113− numAllowedDigits = fromIntegral $ resolution (Proxy :: Proxy QValueResolution)
114−
115−contentCoding :: Parser Encoding
116−contentCoding = Encoding <$> token
117−
118−token :: Parser ByteString
119−token = skipMany lws >> A.takeWhile1 isTokenChar
120−
121−literal :: ByteString -> Parser ()
122−literal s = void $ skipMany lws >> string s
123−
124−isTokenChar :: Word8 -> Bool
125−isTokenChar c = isChar c && not (isCtl c || isSeparator c)
126−
127−isChar :: Word8 -> Bool
128−isChar c = c <= 127
129−
130−isCtl :: Word8 -> Bool
131−isCtl c = c <= 31 || c == 127
132−
133−isSeparator :: Word8 -> Bool
134−isSeparator c = BS.elem c $ spVal `BS.cons` htVal `BS.cons` "()<>@,;:\\\"/[]?={}"
135−
136−-- | A version of 'liftM2' that is strict in the result of its first
137−-- action.
138−liftM2' :: (Monad m) => (a -> b -> c) -> m a -> m b -> m c
139−liftM2' f a b = do
140− !x <- a
141− y <- b
142− return (f x y)
143−{-# INLINE liftM2' #-}
144−
145−-- | See http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.1
146−starRule :: Maybe Int -> Maybe Int -> Parser a -> Parser [a]
147−starRule minNum maxNum element = do
148− let numMandatory = fromMaybe 0 minNum
149− mandatoryVals <- replicateM numMandatory element
150− optionalVals <- case maxNum of
151− Nothing -> many' element
152− Just n -> do
153− let countUpTo 0 _ = return []
154− countUpTo m a = liftM2' (:) a (countUpTo (pred m) a) <|> return []
155− countUpTo (n - numMandatory) element
156− return $ mandatoryVals ++ optionalVals
157−
158−-- | See http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.1
159−-- The spec is a bit ambiguous on whether extra commas and whitespace are permitted before and after the elements; this implementation permits them
160−hashRule :: forall a. Maybe Int -> Maybe Int -> Parser a -> Parser [a]
161−hashRule minNum maxNum element = do
162− let numMandatory = fromMaybe 0 minNum
163− sep :: Parser ()
164− sep = do
165− skipMany1 $ do
166− skipMany lws
167− char ','
168− return ()
169− processMandatory :: Bool -> Int -> Parser [a]
170− processMandatory isInitial 0 = processOptional isInitial $ fmap (subtract numMandatory) maxNum
171− processMandatory isInitial n = do
172− if isInitial then void $ optional sep else sep
173− liftM2' (:) (skipMany lws >> element) $ processMandatory False $ pred n
174− processOptional :: Bool -> Maybe Int -> Parser [a]
175− processOptional _ (Just 0) = return []
176− processOptional isInitial n = (<|> return []) $ do
177− if isInitial then void $ optional sep else sep
178− liftM2' (:) (skipMany lws >> element) $ processOptional False $ fmap pred n
179− result <- processMandatory True numMandatory
180− _ <- optional sep
181− skipMany lws
182− return result
183−
184−-- | Linear whitespace
185−-- See http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2
186−lws :: Parser ()
187−lws = do
188− option () $ cr >> lf
189− starRule (Just 1) Nothing $ sp <|> ht
190− return ()
191−
192−-- | See http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2
193−cr :: Parser ()
194−cr = void $ word8 13
195−
196−-- | See http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2
197−lf :: Parser ()
198−lf = void $ word8 10
199−
200−-- | See http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2
201−sp :: Parser ()
202−sp = void $ word8 spVal
203−
204−spVal :: Word8
205−spVal = 32
206−
207−-- | See http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2
208−ht :: Parser ()
209−ht = void $ word8 htVal
210−
211−htVal :: Word8
212−htVal = 9
obelisk-asset/src/Obelisk/Asset/Serve.hsdeleted−105
@@ -1,105 +0,0 @@
1−{-# LANGUAGE CPP, OverloadedStrings, TemplateHaskell #-}
2−module Obelisk.Asset.Serve where
3−
4−import Obelisk.Asset.Accept
5−
6−import Snap
7−import Snap.Util.FileServe
8−
9−import Control.Applicative
10−import Control.Exception (try, throwIO, catchJust)
11−import Control.Monad
12−import Control.Monad.IO.Class
13−import Data.Attoparsec.ByteString (parseOnly, endOfInput)
14−import Data.ByteString (ByteString)
15−import qualified Data.ByteString as BS
16−import Data.List
17−import Data.Monoid
18−import qualified Data.Text as T
19−import Data.Text.Encoding
20−import System.Directory
21−import System.FilePath
22−import System.IO.Error
23−import System.Posix (getFileStatus, fileSize)
24−
25−cachePermanently :: MonadSnap m => m ()
26−cachePermanently = do
27− modifyResponse $ setHeader "Cache-Control" "public, max-age=315360000"
28− modifyResponse $ setHeader "Expires" "Tue, 01 Feb 2050 00:00:00 GMT" --TODO: This should be set to "approximately one year from the time the response is sent"
29−
30−doNotCache :: MonadSnap m => m ()
31−doNotCache = do
32− modifyResponse $ setHeader "Cache-Control" "no-cache, no-store, must-revalidate"
33− modifyResponse $ setHeader "Expires" "0"
34−
35−serveAssets :: MonadSnap m => FilePath -> FilePath -> m ()
36−serveAssets = serveAssets' True
37−
38−serveAssetsInPlace :: MonadSnap m => FilePath -> FilePath -> m ()
39−serveAssetsInPlace = serveAssets' False
40−
41−serveAssets' :: MonadSnap m => Bool -> FilePath -> FilePath -> m ()
42−serveAssets' doRedirect base fallback = do
43− pRaw <- getSafePath
44− let go p = do
45− assetType <- liftIO $ try $ BS.readFile $ base </> p </> "type"
46− case assetType of
47− Right "immutable" -> do
48− conditionalOnModification <- getsRequest $ getHeader "If-Modified-Since"
49− case conditionalOnModification of
50− Nothing -> do
51− encodedFiles <- liftM (filter (`notElem` [".", ".."])) $ liftIO $ getDirectoryContents $ base </> p </> "encodings"
52− availableEncodings <- liftM (map snd . sort) $ forM encodedFiles $ \f -> do
53− stat <- liftIO $ getFileStatus $ base </> p </> "encodings" </> f
54− return (fileSize stat, Encoding $ encodeUtf8 $ T.pack f)
55− acceptEncodingRaw <- getsRequest $ getHeader "Accept-Encoding"
56− ae <- case acceptEncodingRaw of
57− Nothing -> return missingAcceptableEncodings
58− Just aer -> case parseOnly (acceptEncodingBody <* endOfInput) aer of
59− Right ae -> return ae
60− Left err -> error err
61− Just (Encoding e) <- return $ chooseEncoding availableEncodings ae
62− modifyResponse $ setHeader "Content-Encoding" e . setHeader "Vary" "Accept-Encoding"
63− if doRedirect then cachePermanently else doNotCache --TODO: Use Etags when not redirecting
64− let finalFilename = base </> p </> "encodings" </> T.unpack (decodeUtf8 e)
65− stat <- liftIO $ getFileStatus finalFilename
66− modifyResponse $ setHeader "Last-Modified" "Thu, 1 Jan 1970 00:00:00 GMT"
67− modifyResponse $ setResponseCode 200 . setContentLength (fromIntegral $ fileSize stat) . setContentType (fileType defaultMimeTypes p)
68− sendFile finalFilename
69− Just _ -> cachePermanently >> modifyResponse (setResponseCode 304)
70− Right "redirect" -> do
71− mtarget <- liftIO $ getAssetTarget $ base </> p
72− case mtarget of
73− Just target -> if doRedirect
74− then do
75− doNotCache
76− redirect target
77− else go $ takeDirectory p </> T.unpack (decodeUtf8 target)
78− Nothing -> serveFile $ fallback </> p
79− Right unknown -> error $ T.unpack ("serveAssets': Unknown asset " <> decodeUtf8 unknown)
80− Left err | isDoesNotExistError err -> (doNotCache >> serveFileIfExists (fallback </> p)) <|> do
81− let (dirname, filename) = splitFileName p
82− unhashedFilename = drop 1 $ dropWhile (/= '-') filename
83− if null unhashedFilename then pass else do
84− doNotCache
85− serveFileIfExists $ fallback </> dirname </> unhashedFilename
86− | otherwise -> liftIO $ throwIO err
87− go $ if "/" `isSuffixOf` pRaw || pRaw == "" then pRaw <> "index.html" else pRaw
88−
89−serveFileIfExists :: MonadSnap m => FilePath -> m ()
90−serveFileIfExists f = do
91− exists <- liftIO $ doesFileExist f
92− if exists then serveFile f else pass
93−
94−serveFileIfExistsAs :: MonadSnap m => ByteString -> FilePath -> m ()
95−serveFileIfExistsAs mimeType f = do
96− exists <- liftIO $ doesFileExist f
97− if exists then serveFileAs mimeType f else pass
98−
99−getAssetTarget :: FilePath -> IO (Maybe ByteString)
100−getAssetTarget p = catchJust (\e -> if isDoesNotExistErrorType (ioeGetErrorType e) then Just () else Nothing) (liftM Just $ BS.readFile $ p </> "target") (\_ -> return Nothing)
101−
102−getAssetPath :: FilePath -> FilePath -> IO (Maybe FilePath)
103−getAssetPath base p = do
104− target <- getAssetTarget $ base </> p
105− return $ fmap ((takeDirectory p </>) . T.unpack . decodeUtf8) target