packages feed

xstatic 0.1.0 → 0.2.0

raw patch · 3 files changed

+55/−36 lines, 3 filesdep −file-embed

Dependencies removed: file-embed

Files

CHANGELOG.md view
@@ -1,5 +1,12 @@ # Changelog +## 0.2.0++- Add xstaticMiddleware.+- Enable StrictData.+- Remove file-embed dependency, use xstatic-th instead.+- Simplify request file path and etag data type.+ ## 0.1.0  - Initial release
src/XStatic.hs view
@@ -1,73 +1,89 @@ {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE StrictData #-}  module XStatic (     -- * xstatic api     XStaticFile (..),     xstaticApp,+    xstaticMiddleware, -    -- * file-embed re-export-    embedFile,+    -- * helpers+    isGzip, ) where  import Data.Binary.Builder (Builder, fromByteString) import Data.ByteString (ByteString) import Data.ByteString qualified as BS-import Data.ByteString.Char8 (breakEnd, pack)-import Data.ByteString.Char8 qualified as BS8-import Data.FileEmbed (embedFile)+import Data.ByteString.Char8 (pack)+import Data.ByteString.Unsafe qualified as BS (unsafeDrop) import Data.Map.Strict qualified as Map-import Data.Version (Version, showVersion) import Network.HTTP.Types.Header (ResponseHeaders) import Network.HTTP.Types.Status qualified as HTTP import Network.Wai qualified --- | A static file definition+{- | A static file definition.++ Use the @xstatic-th@ or @file-embed@ library to embed a local file.+-} data XStaticFile = XStaticFile-    { name :: ByteString-    -- ^ The expected request filename. The name must not have any slash '/'.-    , content :: ByteString-    -- ^ The file content gzip compressed.-    , contentVersion :: Version-    -- ^ The file version for the etag header.-    , contentType :: ByteString-    -- ^ The content type, e.g. `text/javascript` or `text/css`.+    { xfPath :: ByteString+    -- ^ The expected request path.+    , xfContent :: ByteString+    -- ^ The file content.+    , xfETag :: ByteString+    -- ^ The etag header value.+    , xfType :: ByteString+    -- ^ The content type, e.g. \"application\/javascript\" or \"text\/css\".     }  {- | Create a wai application to serve 'XStaticFile'. - The request are served whenever the basename match, ignoring the parent directory or the query string.+ The \"\/xstatic\/\" request path prefix is ignored. -} xstaticApp :: [XStaticFile] -> Network.Wai.Application-xstaticApp xs = \req resp ->-    let (_, basename) = breakEnd (== '/') (Network.Wai.rawPathInfo req)-     in resp $ case Map.lookup basename files of-            Just (builder, headers) ->-                let body = case Network.Wai.requestMethod req of-                        "HEAD" -> mempty-                        _ -> builder-                 in Network.Wai.responseBuilder HTTP.status200 headers body-            Nothing -> Network.Wai.responseLBS HTTP.status404 mempty mempty+xstaticApp xs = \req resp -> do+    let basePath = Network.Wai.rawPathInfo req+        requestPath+            | "/xstatic/" `BS.isPrefixOf` basePath = BS.unsafeDrop 8 basePath+            | otherwise = basePath++    resp $ case Map.lookup requestPath files of+        Just (builder, headers) ->+            let body = case Network.Wai.requestMethod req of+                    "HEAD" -> mempty+                    _ -> builder+             in Network.Wai.responseBuilder HTTP.status200 headers body+        Nothing -> Network.Wai.responseLBS HTTP.status404 mempty mempty   where     files :: Map.Map ByteString (Builder, ResponseHeaders)     files = Map.fromList $ map toItem xs      toItem :: XStaticFile -> (ByteString, (Builder, ResponseHeaders))     toItem xf =-        ( name xf+        ( xfPath xf         ,-            ( fromByteString $ content xf-            , (addGzipHeader $ content xf)+            ( fromByteString $ xfContent xf+            , (addGzipHeader $ xfContent xf)                 [ ("cache-control", "public, max-age=604800")-                , ("content-length", pack . show . BS.length $ content xf)-                , ("content-type", contentType xf)+                , ("content-length", pack . show . BS.length $ xfContent xf)+                , ("content-type", xfType xf)                 , ("connection", "keep-alive")-                , ("etag", versionToEtag $ contentVersion xf)+                , ("etag", xfETag xf)                 , ("keep-alive", "timeout=5, max=100")                 ]             )         ) +-- | Serve 'XStaticFile' using 'xstaticApp' when the provided application returns a 404.+xstaticMiddleware :: [XStaticFile] -> Network.Wai.Middleware+xstaticMiddleware xs app req resp = app req handleAppResp+  where+    staticApp = xstaticApp xs+    handleAppResp appResp = case HTTP.statusCode (Network.Wai.responseStatus appResp) of+        404 -> staticApp req resp+        _ -> resp appResp+ addGzipHeader :: ByteString -> ResponseHeaders -> ResponseHeaders addGzipHeader fileContent     | isGzip fileContent = (("content-encoding", "gzip") :)@@ -75,6 +91,3 @@  isGzip :: ByteString -> Bool isGzip = BS.isPrefixOf "\x1f\x8b\x08" -- the gzip magic number for deflate--versionToEtag :: Version -> ByteString-versionToEtag = BS8.unwords . BS8.split '.' . pack . showVersion
xstatic.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           xstatic-version:        0.1.0+version:        0.2.0 synopsis:       Low-Fat static file packaging for Haskell projects description:    Low-Fat static file packaging for Haskell projects. category:       JavaScript@@ -36,7 +36,6 @@     , binary     , bytestring     , containers-    , file-embed     , http-types     , wai   default-language: Haskell2010