~alcinnz/hurl

ref: 53ee542a2785d96abf627d34d5462c639a5948b9 hurl/src/Network/URI/Cache.hs -rw-r--r-- 2.7 KiB
53ee542a — Adrian Cochrane Implement key-value storage for caching (TODO limit disk usage) 3 years ago
                                                                                
a4ffbfc2 Adrian Cochrane
53ee542a Adrian Cochrane
a4ffbfc2 Adrian Cochrane
53ee542a Adrian Cochrane
a4ffbfc2 Adrian Cochrane
53ee542a Adrian Cochrane
a4ffbfc2 Adrian Cochrane
53ee542a Adrian Cochrane
a4ffbfc2 Adrian Cochrane
53ee542a Adrian Cochrane
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
{-# LANGUAGE OverloadedStrings #-}
module Network.URI.Cache(shouldCacheHTTP) where -- , storeCacheHTTP, writeCacheHTTP)
import Network.HTTP.Client
import Network.HTTP.Types.Status
import Network.HTTP.Types.Header

-- For escaping filepaths, since I already have this dependency
import Network.URI (escapeURIString, isUnescapedInURIComponent)

import Data.ByteString as Strict
import Data.ByteString.Char8 as C
import Data.ByteString.Lazy as Lazy
import System.IO as IO
import System.FilePath

import Data.Maybe (mapMaybe, listToMaybe, isJust, isNothing)
import Data.Char (isSpace)
import Data.List as L
import Control.Monad (forM)

strip = C.dropWhile isSpace -- FIXME Upgrade bytestring dependency for a real strip function.

httpCacheDirective :: Response b -> Strict.ByteString -> Maybe Strict.ByteString
httpCacheDirective response key | Just header <- lookup hCacheControl $ responseHeaders response =
        let directives = Prelude.map strip $ C.split ',' header
        in if key `Prelude.elem` directives
            then Just ""
            else listToMaybe $ mapMaybe (C.stripPrefix $ C.snoc key '=') directives
    | otherwise = Nothing

shouldCacheHTTP :: Response b -> Bool
-- IETF RFC7234 Section 3
shouldCacheHTTP response = -- Assume GET
    statusCode (responseStatus response) `Prelude.elem` [200, 201, 404] && -- Supported response code
        isNothing (httpCacheDirective response "no-store") && -- Honor no-store
        True && -- This is a private cache, don't check for Cache-Control: private
        (isJust (lookup hExpires $ responseHeaders response) || -- Support Expires: header
        isJust (httpCacheDirective response "max-age") ||
        isJust (httpCacheDirective response "public")) -- Override directive


------
--- Key-value storage
------

read :: FilePath -> String -> IO ([(String, String)], Lazy.ByteString)
write :: FilePath -> String -> ([(String, String)], Lazy.ByteString) -> IO ()
openKey :: FilePath -> String -> (Handle -> IO r) -> IO r

openKey dir key =
    IO.withFile (dir </> escapeURIString isUnescapedInURIComponent key) ReadMode

read dir key = openKey dir key parseHeaders
  where
    parseHeaders h = do
        line <- IO.hGetLine h
        case L.break isSpace $ strip' line of
            ("", "") -> do
                body <- Lazy.hGetContents h
                return ([], body)
            (key, value) -> do
                (headers, body) <- parseHeaders h
                return ((key, strip' value):headers, body)
    strip' = L.dropWhile isSpace . L.dropWhileEnd isSpace

write dir key (headers, body) = openKey dir key $ \h -> do
    forM headers $ \(key, value) -> do
        IO.hPutStrLn h (key++' ':value)
    IO.hPutStrLn h ""
    Lazy.hPut h body