Accept range headers
TODO: response headers and proper codes
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
-- {{{ Imports
|
||||||
|
|
||||||
module Main where
|
module Main where
|
||||||
|
|
||||||
import Control.Applicative
|
import Control.Applicative
|
||||||
@@ -28,6 +30,12 @@ import qualified Data.Text as T
|
|||||||
import Text.Regex.TDFA ((=~))
|
import Text.Regex.TDFA ((=~))
|
||||||
import Text.Read (readMaybe)
|
import Text.Read (readMaybe)
|
||||||
|
|
||||||
|
import Data.Ranged.Ranges (emptyRange)
|
||||||
|
|
||||||
|
import Debug.Trace
|
||||||
|
|
||||||
|
-- }}}
|
||||||
|
|
||||||
data AppConfig = AppConfig {
|
data AppConfig = AppConfig {
|
||||||
configDbUri :: String
|
configDbUri :: String
|
||||||
, configPort :: Int }
|
, configPort :: Int }
|
||||||
@@ -49,26 +57,33 @@ main = do
|
|||||||
where
|
where
|
||||||
describe = progDesc "create a REST API to an existing Postgres database"
|
describe = progDesc "create a REST API to an existing Postgres database"
|
||||||
|
|
||||||
|
traceThis :: (Show a) => a -> a
|
||||||
|
traceThis x = trace (show x) x
|
||||||
|
|
||||||
app :: AppConfig -> Application
|
app :: AppConfig -> Application
|
||||||
app config req respond = do
|
app config req respond = do
|
||||||
r <- try $
|
r <- try $
|
||||||
case path of
|
case path of
|
||||||
[] -> responseLBS status200 [json] <$> (printTables ver =<< conn)
|
[] -> responseLBS status200 [json] <$> (printTables ver =<< conn)
|
||||||
[table] -> responseLBS status200 [json] <$>
|
[table] -> if range == Just emptyRange
|
||||||
|
then return $ responseLBS status416 [] "HTTP Range error"
|
||||||
|
else responseLBS status200 [json] <$>
|
||||||
( if verb == methodOptions
|
( if verb == methodOptions
|
||||||
then printColumns ver table =<< conn
|
then printColumns ver table =<< conn
|
||||||
else selectWhere (T.pack $ show ver) table qq =<< conn )
|
else
|
||||||
|
selectWhere (T.pack $ show ver) table qq range =<< conn )
|
||||||
_ -> return $ responseLBS status404 [] ""
|
_ -> return $ responseLBS status404 [] ""
|
||||||
|
|
||||||
respond $ either sqlErrorHandler id r
|
respond $ either sqlErrorHandler id r
|
||||||
|
|
||||||
where
|
where
|
||||||
path = pathInfo req
|
path = pathInfo req
|
||||||
verb = requestMethod req
|
verb = requestMethod req
|
||||||
json = (hContentType, "application/json")
|
json = (hContentType, "application/json")
|
||||||
conn = connectPostgreSQL $ configDbUri config
|
conn = connectPostgreSQL $ configDbUri config
|
||||||
qq = queryString req
|
qq = queryString req
|
||||||
ver = fromMaybe 1 $ requestedVersion (requestHeaders req)
|
ver = fromMaybe 1 $ requestedVersion (requestHeaders req)
|
||||||
|
range = requestedRange (requestHeaders req)
|
||||||
|
|
||||||
requestedVersion :: RequestHeaders -> Maybe Int
|
requestedVersion :: RequestHeaders -> Maybe Int
|
||||||
requestedVersion hdrs =
|
requestedVersion hdrs =
|
||||||
|
|||||||
+10
-5
@@ -7,6 +7,7 @@ import Data.Maybe (fromMaybe)
|
|||||||
import Data.List (intercalate)
|
import Data.List (intercalate)
|
||||||
import Data.Monoid ((<>))
|
import Data.Monoid ((<>))
|
||||||
|
|
||||||
|
import qualified RangeQuery as R
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Data.ByteString.Lazy as BL
|
import qualified Data.ByteString.Lazy as BL
|
||||||
import qualified Data.ByteString.Char8 as BS
|
import qualified Data.ByteString.Char8 as BS
|
||||||
@@ -16,20 +17,24 @@ import Database.HDBC.PostgreSQL
|
|||||||
|
|
||||||
import Network.HTTP.Types.URI
|
import Network.HTTP.Types.URI
|
||||||
|
|
||||||
selectWhere :: T.Text -> T.Text -> Query -> Connection -> IO BL.ByteString
|
selectWhere :: T.Text -> T.Text -> Query -> Maybe R.NonnegRange -> Connection -> IO BL.ByteString
|
||||||
selectWhere ver table qq conn = do
|
selectWhere ver table qq range conn = do
|
||||||
s <- selectSql
|
s <- selectSql
|
||||||
w <- whereClause conn qq
|
w <- whereClause conn qq
|
||||||
r <- quickQuery conn (BS.unpack $ s <> w) []
|
r <- quickQuery conn (BS.unpack $ s <> w) []
|
||||||
|
|
||||||
return $ case r of
|
return $ case r of
|
||||||
[[json]] -> fromSql json
|
[[json]] -> fromSql json
|
||||||
_ -> "" :: BL.ByteString
|
_ -> "" :: BL.ByteString
|
||||||
|
|
||||||
where
|
where
|
||||||
|
limit = fromMaybe "ALL" $ show <$> (R.limit =<< range)
|
||||||
|
offset = fromMaybe 0 (R.offset <$> range)
|
||||||
selectSql = pgFormat conn
|
selectSql = pgFormat conn
|
||||||
"select array_to_json(array_agg(row_to_json(t)))\
|
"select array_to_json(array_agg(row_to_json(t)))\
|
||||||
\ from (select * from %I.%I) t"
|
\ from (select * from %I.%I LIMIT %s OFFSET %s) t"
|
||||||
[toSql ver, toSql table]
|
[toSql ver, toSql table, toSql limit, toSql offset]
|
||||||
|
|
||||||
|
|
||||||
whereClause :: Connection -> Query -> IO BS.ByteString
|
whereClause :: Connection -> Query -> IO BS.ByteString
|
||||||
whereClause _ [] = return ""
|
whereClause _ [] = return ""
|
||||||
@@ -41,7 +46,7 @@ whereClause conn qs =
|
|||||||
clause = BS.intercalate " and " <$> preds
|
clause = BS.intercalate " and " <$> preds
|
||||||
|
|
||||||
preds :: IO [BS.ByteString]
|
preds :: IO [BS.ByteString]
|
||||||
preds = sequence $ map (wherePred conn) qs
|
preds = mapM (wherePred conn) qs
|
||||||
|
|
||||||
|
|
||||||
wherePred :: Connection -> QueryItem -> IO BS.ByteString
|
wherePred :: Connection -> QueryItem -> IO BS.ByteString
|
||||||
|
|||||||
+18
-4
@@ -14,15 +14,17 @@ import Text.Read (readMaybe)
|
|||||||
|
|
||||||
import Data.Maybe (fromMaybe, listToMaybe)
|
import Data.Maybe (fromMaybe, listToMaybe)
|
||||||
|
|
||||||
rangeGeq :: Int -> Range Int
|
type NonnegRange = Range Int
|
||||||
|
|
||||||
|
rangeGeq :: Int -> NonnegRange
|
||||||
rangeGeq n =
|
rangeGeq n =
|
||||||
Range (BoundaryBelow n) BoundaryAboveAll
|
Range (BoundaryBelow n) BoundaryAboveAll
|
||||||
|
|
||||||
rangeLeq :: Int -> Range Int
|
rangeLeq :: Int -> NonnegRange
|
||||||
rangeLeq n =
|
rangeLeq n =
|
||||||
Range BoundaryBelowAll (BoundaryAbove n)
|
Range BoundaryBelowAll (BoundaryAbove n)
|
||||||
|
|
||||||
parseRange :: String -> Maybe(Range Int)
|
parseRange :: String -> Maybe NonnegRange
|
||||||
parseRange range = do
|
parseRange range = do
|
||||||
let rangeRegex = "^([0-9]+)-([0-9]*)$" :: String
|
let rangeRegex = "^([0-9]+)-([0-9]*)$" :: String
|
||||||
|
|
||||||
@@ -34,5 +36,17 @@ parseRange range = do
|
|||||||
|
|
||||||
return $ rangeIntersection lower upper
|
return $ rangeIntersection lower upper
|
||||||
|
|
||||||
requestedRange :: RequestHeaders -> Maybe(Range Int)
|
requestedRange :: RequestHeaders -> Maybe NonnegRange
|
||||||
requestedRange hdrs = parseRange =<< BS.unpack <$> lookup hRange hdrs
|
requestedRange hdrs = parseRange =<< BS.unpack <$> lookup hRange hdrs
|
||||||
|
|
||||||
|
limit :: NonnegRange -> Maybe Int
|
||||||
|
limit range =
|
||||||
|
case [rangeLower range, rangeUpper range]
|
||||||
|
of [BoundaryBelow from, BoundaryAbove to] -> Just (1 + to - from)
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
|
offset :: NonnegRange -> Int
|
||||||
|
offset range =
|
||||||
|
case rangeLower range
|
||||||
|
of BoundaryBelow from -> from
|
||||||
|
_ -> 0 -- should never happen
|
||||||
|
|||||||
Reference in New Issue
Block a user