2024-07-07 16:08:10 +00:00
|
|
|
{- git-annex command
|
|
|
|
-
|
|
|
|
- Copyright 2024 Joey Hess <id@joeyh.name>
|
|
|
|
-
|
|
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
|
|
|
{-# LANGUAGE DataKinds #-}
|
|
|
|
{-# LANGUAGE TypeOperators #-}
|
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
|
|
|
|
module Command.P2PHttp where
|
|
|
|
|
|
|
|
import Command
|
2024-07-07 16:59:12 +00:00
|
|
|
import P2P.Http
|
2024-07-23 13:12:21 +00:00
|
|
|
import P2P.Http.Url
|
2024-07-09 17:37:55 +00:00
|
|
|
import qualified P2P.Protocol as P2P
|
|
|
|
import Annex.Url
|
2024-07-10 03:44:40 +00:00
|
|
|
import Utility.Env
|
2024-07-10 14:03:26 +00:00
|
|
|
import Utility.MonotonicClock
|
2024-07-07 16:08:10 +00:00
|
|
|
|
convert lockcontent api to http long polling
Websockets would work, but the problem with using them for this is that
each lockcontent call is a separate websocket connection. And that's an
actual TCP connection. One TCP connection per file dropped would be too
expensive. With http long polling, regular http pipelining can be used,
so it will reuse a TCP connection.
Unfortunately, at least with servant, bi-directional streams with long
polling don't result in true bidirectional full duplex communication.
Servant processes the whole client body stream before generating the server
body stream. I think it's entirely possible to do full bi-directional
communication over http, but it would need changes to servant.
And, there's no way for the client to tell if the server successfully
locked the content, since the server will keep processing the client
stream no matter what.:
So, added a new api endpoint, keeplocked. lockcontent will lock the key
for 10 minutes with retention lock, and then a call to keeplocked will
keep it locked for as long as needed. This does mean that there will
need to be a Map of locks by key, and I will probably want to add
some kind of lock identifier that lockcontent returns.
2024-07-08 14:40:38 +00:00
|
|
|
import qualified Network.Wai.Handler.Warp as Warp
|
2024-07-10 03:44:40 +00:00
|
|
|
import Servant
|
2024-07-09 17:37:55 +00:00
|
|
|
import Servant.Client.Streaming
|
|
|
|
import Control.Concurrent.STM
|
2024-07-10 03:44:40 +00:00
|
|
|
import Network.Socket (PortNumber)
|
|
|
|
import qualified Data.Map as M
|
convert lockcontent api to http long polling
Websockets would work, but the problem with using them for this is that
each lockcontent call is a separate websocket connection. And that's an
actual TCP connection. One TCP connection per file dropped would be too
expensive. With http long polling, regular http pipelining can be used,
so it will reuse a TCP connection.
Unfortunately, at least with servant, bi-directional streams with long
polling don't result in true bidirectional full duplex communication.
Servant processes the whole client body stream before generating the server
body stream. I think it's entirely possible to do full bi-directional
communication over http, but it would need changes to servant.
And, there's no way for the client to tell if the server successfully
locked the content, since the server will keep processing the client
stream no matter what.:
So, added a new api endpoint, keeplocked. lockcontent will lock the key
for 10 minutes with retention lock, and then a call to keeplocked will
keep it locked for as long as needed. This does mean that there will
need to be a Map of locks by key, and I will probably want to add
some kind of lock identifier that lockcontent returns.
2024-07-08 14:40:38 +00:00
|
|
|
|
2024-07-07 16:08:10 +00:00
|
|
|
cmd :: Command
|
2024-07-10 16:19:47 +00:00
|
|
|
cmd = withAnnexOptions [jobsOption] $ command "p2phttp" SectionPlumbing
|
2024-07-07 16:08:10 +00:00
|
|
|
"communicate in P2P protocol over http"
|
2024-07-09 21:30:55 +00:00
|
|
|
paramNothing (seek <$$> optParser)
|
|
|
|
|
|
|
|
data Options = Options
|
2024-07-10 03:44:40 +00:00
|
|
|
{ portOption :: Maybe PortNumber
|
2024-07-09 21:30:55 +00:00
|
|
|
, authEnvOption :: Bool
|
|
|
|
, authEnvHttpOption :: Bool
|
2024-07-10 03:44:40 +00:00
|
|
|
, unauthReadOnlyOption :: Bool
|
|
|
|
, unauthAppendOnlyOption :: Bool
|
2024-07-09 21:30:55 +00:00
|
|
|
, wideOpenOption :: Bool
|
|
|
|
}
|
2024-07-07 16:08:10 +00:00
|
|
|
|
2024-07-10 03:44:40 +00:00
|
|
|
optParser :: CmdParamsDesc -> Parser Options
|
|
|
|
optParser _ = Options
|
|
|
|
<$> optional (option auto
|
|
|
|
( long "port" <> metavar paramNumber
|
|
|
|
<> help "specify port to listen on"
|
|
|
|
))
|
|
|
|
<*> switch
|
|
|
|
( long "authenv"
|
|
|
|
<> help "authenticate users from environment (https only)"
|
|
|
|
)
|
|
|
|
<*> switch
|
|
|
|
( long "authenv-http"
|
|
|
|
<> help "authenticate users from environment (including http)"
|
|
|
|
)
|
|
|
|
<*> switch
|
|
|
|
( long "unauth-readonly"
|
|
|
|
<> help "allow unauthenticated users to read the repository"
|
|
|
|
)
|
|
|
|
<*> switch
|
|
|
|
( long "unauth-appendonly"
|
|
|
|
<> help "allow unauthenticated users to read and append to the repository"
|
|
|
|
)
|
|
|
|
<*> switch
|
|
|
|
( long "wideopen"
|
|
|
|
<> help "give unauthenticated users full read+write access"
|
|
|
|
)
|
|
|
|
|
|
|
|
seek :: Options -> CommandSeek
|
2024-07-10 16:19:47 +00:00
|
|
|
seek o = getAnnexWorkerPool $ \workerpool -> do
|
2024-07-10 13:13:01 +00:00
|
|
|
-- XXX remove this
|
|
|
|
when (isNothing (portOption o)) $ do
|
|
|
|
liftIO $ putStrLn "test begins"
|
2024-07-22 23:44:26 +00:00
|
|
|
testLocking
|
2024-07-10 13:13:01 +00:00
|
|
|
giveup "TEST DONE"
|
2024-07-11 18:37:52 +00:00
|
|
|
withLocalP2PConnections workerpool $ \acquireconn -> liftIO $ do
|
2024-07-10 03:44:40 +00:00
|
|
|
authenv <- getAuthEnv
|
2024-07-10 16:19:47 +00:00
|
|
|
st <- mkP2PHttpServerState acquireconn workerpool $
|
2024-07-10 03:44:40 +00:00
|
|
|
mkGetServerMode authenv o
|
|
|
|
Warp.run (fromIntegral port) (p2pHttpApp st)
|
|
|
|
where
|
2024-07-23 13:12:21 +00:00
|
|
|
port = fromMaybe
|
|
|
|
(fromIntegral defaultP2PHttpProtocolPort)
|
|
|
|
(portOption o)
|
2024-07-10 03:44:40 +00:00
|
|
|
|
|
|
|
mkGetServerMode :: M.Map Auth P2P.ServerMode -> Options -> GetServerMode
|
|
|
|
mkGetServerMode _ o _ Nothing
|
|
|
|
| wideOpenOption o = Just P2P.ServeReadWrite
|
|
|
|
| unauthAppendOnlyOption o = Just P2P.ServeAppendOnly
|
|
|
|
| unauthReadOnlyOption o = Just P2P.ServeReadOnly
|
|
|
|
| otherwise = Nothing
|
|
|
|
mkGetServerMode authenv o issecure (Just auth) =
|
|
|
|
case (issecure, authEnvOption o, authEnvHttpOption o) of
|
|
|
|
(Secure, True, _) -> checkauth
|
|
|
|
(NotSecure, _, True) -> checkauth
|
|
|
|
_ -> noauth
|
|
|
|
where
|
|
|
|
checkauth = case M.lookup auth authenv of
|
|
|
|
Just servermode -> Just servermode
|
|
|
|
Nothing -> noauth
|
|
|
|
noauth = mkGetServerMode authenv o issecure Nothing
|
|
|
|
|
|
|
|
getAuthEnv :: IO (M.Map Auth P2P.ServerMode)
|
|
|
|
getAuthEnv = do
|
|
|
|
environ <- getEnvironment
|
|
|
|
let permmap = M.fromList (mapMaybe parseperms environ)
|
|
|
|
return $ M.fromList $
|
|
|
|
map (addperms permmap) $
|
|
|
|
mapMaybe parseusername environ
|
|
|
|
where
|
|
|
|
parseperms (k, v) = case deprefix "GIT_ANNEX_P2PHTTP_PERMISSIONS_" k of
|
|
|
|
Nothing -> Nothing
|
|
|
|
Just username -> case v of
|
|
|
|
"readonly" -> Just
|
|
|
|
(encodeBS username, P2P.ServeReadOnly)
|
|
|
|
"appendonly" -> Just
|
|
|
|
(encodeBS username, P2P.ServeAppendOnly)
|
|
|
|
_ -> Nothing
|
|
|
|
|
|
|
|
parseusername (k, v) = case deprefix "GIT_ANNEX_P2PHTTP_PASSWORD_" k of
|
|
|
|
Nothing -> Nothing
|
|
|
|
Just username -> Just $ Auth (encodeBS username) (encodeBS v)
|
|
|
|
|
|
|
|
deprefix prefix s
|
|
|
|
| prefix `isPrefixOf` s = Just (drop (length prefix) s)
|
|
|
|
| otherwise = Nothing
|
|
|
|
|
|
|
|
addperms permmap auth@(Auth user _) =
|
|
|
|
case M.lookup user permmap of
|
|
|
|
Nothing -> (auth, P2P.ServeReadWrite)
|
|
|
|
Just perms -> (auth, perms)
|
2024-07-09 17:37:55 +00:00
|
|
|
|
2024-07-22 23:44:26 +00:00
|
|
|
testLocking = do
|
|
|
|
mgr <- httpManager <$> getUrlOptions
|
|
|
|
burl <- liftIO $ parseBaseUrl "http://localhost:8080/"
|
|
|
|
let k = B64Key (fromJust $ deserializeKey ("SHA256E-s6--5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03" :: String))
|
|
|
|
res <- liftIO $ clientLockContent (mkClientEnv mgr burl)
|
|
|
|
(P2P.ProtocolVersion 3)
|
|
|
|
k
|
|
|
|
(B64UUID (toUUID ("f11773f0-11e1-45b2-9805-06db16768efe" :: String)))
|
|
|
|
(B64UUID (toUUID ("cu" :: String)))
|
|
|
|
[]
|
|
|
|
Nothing
|
|
|
|
case res of
|
|
|
|
LockResult True (Just lckid) ->
|
|
|
|
liftIO $ clientKeepLocked (mkClientEnv mgr burl)
|
|
|
|
(P2P.ProtocolVersion 3)
|
|
|
|
lckid
|
|
|
|
(B64UUID (toUUID ("f11773f0-11e1-45b2-9805-06db16768efe" :: String)))
|
|
|
|
(B64UUID (toUUID ("cu" :: String)))
|
|
|
|
[]
|
|
|
|
Nothing $ \keeplocked -> do
|
|
|
|
print "running, press enter to drop lock"
|
|
|
|
_ <- getLine
|
|
|
|
atomically $ writeTMVar keeplocked False
|
|
|
|
_ -> liftIO $ print ("lockin failed", res)
|
|
|
|
|
|
|
|
testLockContent = do
|
|
|
|
mgr <- httpManager <$> getUrlOptions
|
|
|
|
burl <- liftIO $ parseBaseUrl "http://localhost:8080/"
|
|
|
|
res <- liftIO $ clientLockContent (mkClientEnv mgr burl)
|
|
|
|
(P2P.ProtocolVersion 3)
|
|
|
|
(B64Key (fromJust $ deserializeKey ("SHA256E-s6--5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03" :: String)))
|
|
|
|
(B64UUID (toUUID ("f11773f0-11e1-45b2-9805-06db16768efe" :: String)))
|
|
|
|
(B64UUID (toUUID ("cu" :: String)))
|
|
|
|
[]
|
|
|
|
Nothing
|
|
|
|
liftIO $ print res
|
|
|
|
|
2024-07-09 17:37:55 +00:00
|
|
|
testKeepLocked = do
|
|
|
|
mgr <- httpManager <$> getUrlOptions
|
|
|
|
burl <- liftIO $ parseBaseUrl "http://localhost:8080/"
|
|
|
|
liftIO $ clientKeepLocked (mkClientEnv mgr burl)
|
|
|
|
(P2P.ProtocolVersion 3)
|
|
|
|
(B64UUID (toUUID ("lck" :: String)))
|
2024-07-22 23:44:26 +00:00
|
|
|
(B64UUID (toUUID ("f11773f0-11e1-45b2-9805-06db16768efe" :: String)))
|
2024-07-09 17:37:55 +00:00
|
|
|
(B64UUID (toUUID ("cu" :: String)))
|
2024-07-22 23:15:52 +00:00
|
|
|
[]
|
|
|
|
Nothing $ \keeplocked -> do
|
2024-07-22 20:56:44 +00:00
|
|
|
print "running, press enter to drop lock"
|
|
|
|
_ <- getLine
|
|
|
|
atomically $ writeTMVar keeplocked False
|
2024-07-09 17:37:55 +00:00
|
|
|
|
2024-07-10 20:06:39 +00:00
|
|
|
testGet = do
|
|
|
|
mgr <- httpManager <$> getUrlOptions
|
|
|
|
burl <- liftIO $ parseBaseUrl "http://localhost:8080/"
|
|
|
|
res <- liftIO $ clientGet (mkClientEnv mgr burl)
|
|
|
|
(P2P.ProtocolVersion 3)
|
2024-07-11 13:55:17 +00:00
|
|
|
(B64Key (fromJust $ deserializeKey ("SHA256E-s1048576000--e3b67ce72aa2571c799d6419e3e36828461ac1c78f8ef300c7f9c8ae671c517f" :: String)))
|
2024-07-10 20:06:39 +00:00
|
|
|
(B64UUID (toUUID ("f11773f0-11e1-45b2-9805-06db16768efe" :: String)))
|
2024-07-11 15:42:32 +00:00
|
|
|
(B64UUID (toUUID ("cu" :: String)))
|
2024-07-10 20:06:39 +00:00
|
|
|
[]
|
|
|
|
Nothing
|
|
|
|
Nothing
|
2024-07-22 20:23:08 +00:00
|
|
|
"outfile"
|
2024-07-10 20:06:39 +00:00
|
|
|
liftIO $ print res
|
|
|
|
|
2024-07-22 14:20:18 +00:00
|
|
|
testPut = do
|
|
|
|
mgr <- httpManager <$> getUrlOptions
|
|
|
|
burl <- liftIO $ parseBaseUrl "http://localhost:8080/"
|
|
|
|
res <- clientPut (mkClientEnv mgr burl)
|
|
|
|
(P2P.ProtocolVersion 3)
|
2024-07-22 16:50:21 +00:00
|
|
|
(B64Key (fromJust $ deserializeKey ("SHA256E-s1048576000--b460ca923520db561d01b99483e9e2fe65ff9dfbdd52c17acba6ac4e874e27d5")))
|
2024-07-22 14:20:18 +00:00
|
|
|
(B64UUID (toUUID ("f11773f0-11e1-45b2-9805-06db16768efe" :: String)))
|
|
|
|
(B64UUID (toUUID ("cu" :: String)))
|
|
|
|
[]
|
|
|
|
Nothing
|
2024-07-22 19:02:08 +00:00
|
|
|
Nothing
|
2024-07-22 14:20:18 +00:00
|
|
|
(AssociatedFile (Just "foo"))
|
2024-07-22 19:02:08 +00:00
|
|
|
"emptyfile"
|
|
|
|
0
|
|
|
|
(liftIO (print "validity check") >> return False)
|
|
|
|
liftIO $ print res
|
|
|
|
|
|
|
|
testPutOffset = do
|
|
|
|
mgr <- httpManager <$> getUrlOptions
|
|
|
|
burl <- liftIO $ parseBaseUrl "http://localhost:8080/"
|
|
|
|
res <- liftIO $ clientPutOffset (mkClientEnv mgr burl)
|
|
|
|
(P2P.ProtocolVersion 3)
|
|
|
|
(B64Key (fromJust $ deserializeKey ("SHA256E-s1048576000--b460ca923520db561d01b99483e9e2fe65ff9dfbdd52c17acba6ac4e874e27d5")))
|
|
|
|
(B64UUID (toUUID ("f11773f0-11e1-45b2-9805-06db16768efe" :: String)))
|
|
|
|
(B64UUID (toUUID ("cu" :: String)))
|
|
|
|
[]
|
|
|
|
Nothing
|
2024-07-22 14:20:18 +00:00
|
|
|
liftIO $ print res
|
|
|
|
|
2024-07-10 13:19:58 +00:00
|
|
|
testRemove = do
|
|
|
|
mgr <- httpManager <$> getUrlOptions
|
|
|
|
burl <- liftIO $ parseBaseUrl "http://localhost:8080/"
|
|
|
|
res <- liftIO $ clientRemove (mkClientEnv mgr burl)
|
|
|
|
(P2P.ProtocolVersion 3)
|
|
|
|
(B64Key (fromJust $ deserializeKey ("WORM-s30-m1720547401--foo" :: String)))
|
|
|
|
(B64UUID (toUUID ("f11773f0-11e1-45b2-9805-06db16768efe" :: String)))
|
2024-07-22 23:44:26 +00:00
|
|
|
(B64UUID (toUUID ("cu" :: String)))
|
2024-07-10 13:19:58 +00:00
|
|
|
[]
|
|
|
|
Nothing
|
|
|
|
liftIO $ print res
|
|
|
|
|
2024-07-10 14:03:26 +00:00
|
|
|
testRemoveBefore = do
|
|
|
|
mgr <- httpManager <$> getUrlOptions
|
|
|
|
burl <- liftIO $ parseBaseUrl "http://localhost:8080/"
|
|
|
|
MonotonicTimestamp t <- liftIO currentMonotonicTimestamp
|
|
|
|
--liftIO $ threadDelaySeconds (Seconds 10)
|
|
|
|
let ts = MonotonicTimestamp (t + 10)
|
|
|
|
liftIO $ print ("running with timestamp", ts)
|
|
|
|
res <- liftIO $ clientRemoveBefore (mkClientEnv mgr burl)
|
|
|
|
(P2P.ProtocolVersion 3)
|
|
|
|
(B64Key (fromJust $ deserializeKey ("WORM-s30-m1720617630--bar" :: String)))
|
|
|
|
(B64UUID (toUUID ("f11773f0-11e1-45b2-9805-06db16768efe" :: String)))
|
2024-07-22 23:44:26 +00:00
|
|
|
(B64UUID (toUUID ("cu" :: String)))
|
2024-07-10 14:03:26 +00:00
|
|
|
[]
|
|
|
|
(Timestamp ts)
|
|
|
|
Nothing
|
|
|
|
liftIO $ print res
|
|
|
|
|
2024-07-10 14:23:10 +00:00
|
|
|
testGetTimestamp = do
|
|
|
|
mgr <- httpManager <$> getUrlOptions
|
|
|
|
burl <- liftIO $ parseBaseUrl "http://localhost:8080/"
|
|
|
|
res <- liftIO $ clientGetTimestamp (mkClientEnv mgr burl)
|
|
|
|
(P2P.ProtocolVersion 3)
|
|
|
|
(B64UUID (toUUID ("f11773f0-11e1-45b2-9805-06db16768efe" :: String)))
|
2024-07-22 23:44:26 +00:00
|
|
|
(B64UUID (toUUID ("cu" :: String)))
|
2024-07-10 14:23:10 +00:00
|
|
|
[]
|
|
|
|
Nothing
|
|
|
|
liftIO $ print res
|
|
|
|
|