web: --serve-api disables server-side UI; startup logging cleanups
This commit is contained in:
		
							parent
							
								
									ff481e88c0
								
							
						
					
					
						commit
						c3da82043b
					
				| @ -23,7 +23,7 @@ import Hledger.Web.Handler.UploadR | |||||||
| import Hledger.Web.Handler.JournalR | import Hledger.Web.Handler.JournalR | ||||||
| import Hledger.Web.Handler.RegisterR | import Hledger.Web.Handler.RegisterR | ||||||
| import Hledger.Web.Import | import Hledger.Web.Import | ||||||
| import Hledger.Web.WebOptions (WebOpts(serve_)) | import Hledger.Web.WebOptions (WebOpts(serve_,serve_api_)) | ||||||
| 
 | 
 | ||||||
| -- This line actually creates our YesodDispatch instance. It is the second half | -- This line actually creates our YesodDispatch instance. It is the second half | ||||||
| -- of the call to mkYesodData which occurs in Foundation.hs. Please see the | -- of the call to mkYesodData which occurs in Foundation.hs. Please see the | ||||||
| @ -41,7 +41,7 @@ makeApplication opts' j' conf' = do | |||||||
|     logWare <$> toWaiApp foundation |     logWare <$> toWaiApp foundation | ||||||
|   where |   where | ||||||
|     logWare | development  = logStdoutDev |     logWare | development  = logStdoutDev | ||||||
|             | serve_ opts' = logStdout |             | serve_ opts' || serve_api_ opts' = logStdout | ||||||
|             | otherwise    = id |             | otherwise    = id | ||||||
| 
 | 
 | ||||||
| makeFoundation :: AppConfig DefaultEnv Extra -> WebOpts -> IO App | makeFoundation :: AppConfig DefaultEnv Extra -> WebOpts -> IO App | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ | |||||||
| 
 | 
 | ||||||
| module Hledger.Web.Foundation where | module Hledger.Web.Foundation where | ||||||
| 
 | 
 | ||||||
| import Control.Monad (join) | import Control.Monad (join, when) | ||||||
| import qualified Data.ByteString.Char8 as BC | import qualified Data.ByteString.Char8 as BC | ||||||
| import Data.Traversable (for) | import Data.Traversable (for) | ||||||
| import Data.IORef (IORef, readIORef, writeIORef) | import Data.IORef (IORef, readIORef, writeIORef) | ||||||
| @ -96,7 +96,9 @@ type Form x = Html -> MForm (HandlerT App IO) (FormResult x, Widget) | |||||||
| instance Yesod App where | instance Yesod App where | ||||||
|   approot = ApprootMaster $ appRoot . settings |   approot = ApprootMaster $ appRoot . settings | ||||||
| 
 | 
 | ||||||
|   makeSessionBackend _ = Just <$> defaultClientSessionBackend 120 ".hledger-web_client_session_key.aes" |   makeSessionBackend _ = | ||||||
|  |     let sessionexpirysecs = 120 | ||||||
|  |     in  Just <$> defaultClientSessionBackend sessionexpirysecs ".hledger-web_client_session_key.aes" | ||||||
| 
 | 
 | ||||||
|   defaultLayout widget = do |   defaultLayout widget = do | ||||||
|     master <- getYesod |     master <- getYesod | ||||||
| @ -204,6 +206,11 @@ getViewData = do | |||||||
|         Right c -> pure [c] |         Right c -> pure [c] | ||||||
|   return VD {opts, today, j, q, m, qopts, caps} |   return VD {opts, today, j, q, m, qopts, caps} | ||||||
| 
 | 
 | ||||||
|  | checkServerSideUiEnabled :: Handler () | ||||||
|  | checkServerSideUiEnabled = do | ||||||
|  |   VD{opts=WebOpts{serve_api_}} <- getViewData | ||||||
|  |   when serve_api_ $ permissionDenied "server-side UI is disabled due to --serve-api" | ||||||
|  | 
 | ||||||
| -- | Find out if the sidebar should be visible. Show it, unless there is a | -- | Find out if the sidebar should be visible. Show it, unless there is a | ||||||
| -- showsidebar cookie set to "0", or a ?sidebar=0 query parameter. | -- showsidebar cookie set to "0", or a ?sidebar=0 query parameter. | ||||||
| shouldShowSidebar :: Handler Bool | shouldShowSidebar :: Handler Bool | ||||||
|  | |||||||
| @ -24,10 +24,13 @@ import Hledger.Web.WebOptions (WebOpts(..)) | |||||||
| import Hledger.Web.Widget.AddForm (addForm) | import Hledger.Web.Widget.AddForm (addForm) | ||||||
| 
 | 
 | ||||||
| getAddR :: Handler () | getAddR :: Handler () | ||||||
| getAddR = postAddR | getAddR = do | ||||||
|  |   checkServerSideUiEnabled | ||||||
|  |   postAddR | ||||||
| 
 | 
 | ||||||
| postAddR :: Handler () | postAddR :: Handler () | ||||||
| postAddR = do | postAddR = do | ||||||
|  |   checkServerSideUiEnabled | ||||||
|   VD{caps, j, today} <- getViewData |   VD{caps, j, today} <- getViewData | ||||||
|   when (CapAdd `notElem` caps) (permissionDenied "Missing the 'add' capability") |   when (CapAdd `notElem` caps) (permissionDenied "Missing the 'add' capability") | ||||||
| 
 | 
 | ||||||
| @ -58,6 +61,7 @@ postAddR = do | |||||||
| -- The web form handler above should probably use PUT as well. | -- The web form handler above should probably use PUT as well. | ||||||
| putAddR :: Handler RepJson | putAddR :: Handler RepJson | ||||||
| putAddR = do | putAddR = do | ||||||
|  |   checkServerSideUiEnabled | ||||||
|   VD{caps, j, opts} <- getViewData |   VD{caps, j, opts} <- getViewData | ||||||
|   when (CapAdd `notElem` caps) (permissionDenied "Missing the 'add' capability") |   when (CapAdd `notElem` caps) (permissionDenied "Missing the 'add' capability") | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -23,10 +23,13 @@ editForm f txt = | |||||||
|     fs = FieldSettings "text" mzero mzero mzero [("class", "form-control"), ("rows", "25")] |     fs = FieldSettings "text" mzero mzero mzero [("class", "form-control"), ("rows", "25")] | ||||||
| 
 | 
 | ||||||
| getEditR :: FilePath -> Handler () | getEditR :: FilePath -> Handler () | ||||||
| getEditR = postEditR | getEditR f = do | ||||||
|  |   checkServerSideUiEnabled | ||||||
|  |   postEditR f | ||||||
| 
 | 
 | ||||||
| postEditR :: FilePath -> Handler () | postEditR :: FilePath -> Handler () | ||||||
| postEditR f = do | postEditR f = do | ||||||
|  |   checkServerSideUiEnabled | ||||||
|   VD {caps, j} <- getViewData |   VD {caps, j} <- getViewData | ||||||
|   when (CapManage `notElem` caps) (permissionDenied "Missing the 'manage' capability") |   when (CapManage `notElem` caps) (permissionDenied "Missing the 'manage' capability") | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -17,6 +17,7 @@ import Hledger.Web.Widget.Common (accountQuery, mixedAmountAsHtml) | |||||||
| -- | The formatted journal view, with sidebar. | -- | The formatted journal view, with sidebar. | ||||||
| getJournalR :: Handler Html | getJournalR :: Handler Html | ||||||
| getJournalR = do | getJournalR = do | ||||||
|  |   checkServerSideUiEnabled | ||||||
|   VD{caps, j, m, opts, qopts, today} <- getViewData |   VD{caps, j, m, opts, qopts, today} <- getViewData | ||||||
|   when (CapView `notElem` caps) (permissionDenied "Missing the 'view' capability") |   when (CapView `notElem` caps) (permissionDenied "Missing the 'view' capability") | ||||||
|   let title = case inAccount qopts of |   let title = case inAccount qopts of | ||||||
|  | |||||||
| @ -30,10 +30,13 @@ import Hledger.Web.Import | |||||||
| import Hledger.Web.Widget.Common (journalFile404) | import Hledger.Web.Widget.Common (journalFile404) | ||||||
| 
 | 
 | ||||||
| getRootR :: Handler Html | getRootR :: Handler Html | ||||||
| getRootR = redirect JournalR | getRootR = do | ||||||
|  |   checkServerSideUiEnabled | ||||||
|  |   redirect JournalR | ||||||
| 
 | 
 | ||||||
| getManageR :: Handler Html | getManageR :: Handler Html | ||||||
| getManageR = do | getManageR = do | ||||||
|  |   checkServerSideUiEnabled | ||||||
|   VD{caps, j} <- getViewData |   VD{caps, j} <- getViewData | ||||||
|   when (CapManage `notElem` caps) (permissionDenied "Missing the 'manage' capability") |   when (CapManage `notElem` caps) (permissionDenied "Missing the 'manage' capability") | ||||||
|   defaultLayout $ do |   defaultLayout $ do | ||||||
| @ -42,6 +45,7 @@ getManageR = do | |||||||
| 
 | 
 | ||||||
| getDownloadR :: FilePath -> Handler TypedContent | getDownloadR :: FilePath -> Handler TypedContent | ||||||
| getDownloadR f = do | getDownloadR f = do | ||||||
|  |   checkServerSideUiEnabled | ||||||
|   VD{caps, j} <- getViewData |   VD{caps, j} <- getViewData | ||||||
|   when (CapManage `notElem` caps) (permissionDenied "Missing the 'manage' capability") |   when (CapManage `notElem` caps) (permissionDenied "Missing the 'manage' capability") | ||||||
|   (f', txt) <- journalFile404 f j |   (f', txt) <- journalFile404 f j | ||||||
|  | |||||||
| @ -22,6 +22,7 @@ import Hledger.Web.Widget.Common (accountQuery, mixedAmountAsHtml) | |||||||
| -- | The main journal/account register view, with accounts sidebar. | -- | The main journal/account register view, with accounts sidebar. | ||||||
| getRegisterR :: Handler Html | getRegisterR :: Handler Html | ||||||
| getRegisterR = do | getRegisterR = do | ||||||
|  |   checkServerSideUiEnabled | ||||||
|   VD{caps, j, m, opts, qopts, today} <- getViewData |   VD{caps, j, m, opts, qopts, today} <- getViewData | ||||||
|   when (CapView `notElem` caps) (permissionDenied "Missing the 'view' capability") |   when (CapView `notElem` caps) (permissionDenied "Missing the 'view' capability") | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -27,10 +27,13 @@ uploadForm f = | |||||||
|     fs = FieldSettings "file" Nothing (Just "file") (Just "file") [] |     fs = FieldSettings "file" Nothing (Just "file") (Just "file") [] | ||||||
| 
 | 
 | ||||||
| getUploadR :: FilePath -> Handler () | getUploadR :: FilePath -> Handler () | ||||||
| getUploadR = postUploadR | getUploadR f = do | ||||||
|  |   checkServerSideUiEnabled | ||||||
|  |   postUploadR f | ||||||
| 
 | 
 | ||||||
| postUploadR :: FilePath -> Handler () | postUploadR :: FilePath -> Handler () | ||||||
| postUploadR f = do | postUploadR f = do | ||||||
|  |   checkServerSideUiEnabled | ||||||
|   VD {caps, j} <- getViewData |   VD {caps, j} <- getViewData | ||||||
|   when (CapManage `notElem` caps) (permissionDenied "Missing the 'manage' capability") |   when (CapManage `notElem` caps) (permissionDenied "Missing the 'manage' capability") | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -69,16 +69,18 @@ web opts j = do | |||||||
|                            } |                            } | ||||||
|   app <- makeApplication opts j' appconfig |   app <- makeApplication opts j' appconfig | ||||||
|   -- XXX would like to allow a host name not just an IP address here |   -- XXX would like to allow a host name not just an IP address here | ||||||
|   _ <- printf "Starting web app on IP address %s port %d with base url %s\n" h p u |   _ <- printf "Serving web %s on %s:%d with base url %s\n" | ||||||
|   if serve_ opts |          (if serve_api_ opts then "API" else "UI and API" :: String) h p u | ||||||
|  |   if serve_ opts || serve_api_ opts | ||||||
|     then do |     then do | ||||||
|       putStrLn "Press ctrl-c to quit" |       putStrLn "Press ctrl-c to quit" | ||||||
|       hFlush stdout |       hFlush stdout | ||||||
|       let warpsettings = setHost (fromString h) (setPort p defaultSettings) |       let warpsettings = setHost (fromString h) (setPort p defaultSettings) | ||||||
|       Network.Wai.Handler.Warp.runSettings warpsettings app |       Network.Wai.Handler.Warp.runSettings warpsettings app | ||||||
|     else do |     else do | ||||||
|       putStrLn "Starting web browser..." |       putStrLn "This server will exit after 2m with no browser windows open (or press ctrl-c)" | ||||||
|       putStrLn "Web app will auto-exit after a few minutes with no browsers (or press ctrl-c)" |       putStrLn "Opening web browser..." | ||||||
|       hFlush stdout |       hFlush stdout | ||||||
|  |       -- exits after 2m of inactivity (hardcoded) | ||||||
|       Network.Wai.Handler.Launch.runHostPortUrl h p "" app |       Network.Wai.Handler.Launch.runHostPortUrl h p "" app | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -31,6 +31,10 @@ webflags = | |||||||
|       ["serve", "server"] |       ["serve", "server"] | ||||||
|       (setboolopt "serve") |       (setboolopt "serve") | ||||||
|       "serve and log requests, don't browse or auto-exit" |       "serve and log requests, don't browse or auto-exit" | ||||||
|  |   , flagNone | ||||||
|  |       ["serve-api"] | ||||||
|  |       (setboolopt "serve-api") | ||||||
|  |       "like --serve, but serve only the JSON web API, without the server-side web UI" | ||||||
|   , flagReq |   , flagReq | ||||||
|       ["host"] |       ["host"] | ||||||
|       (\s opts -> Right $ setopt "host" s opts) |       (\s opts -> Right $ setopt "host" s opts) | ||||||
| @ -89,6 +93,7 @@ webmode = | |||||||
| -- hledger-web options, used in hledger-web and above | -- hledger-web options, used in hledger-web and above | ||||||
| data WebOpts = WebOpts | data WebOpts = WebOpts | ||||||
|   { serve_ :: Bool |   { serve_ :: Bool | ||||||
|  |   , serve_api_ :: Bool | ||||||
|   , host_ :: String |   , host_ :: String | ||||||
|   , port_ :: Int |   , port_ :: Int | ||||||
|   , base_url_ :: String |   , base_url_ :: String | ||||||
| @ -99,7 +104,7 @@ data WebOpts = WebOpts | |||||||
|   } deriving (Show) |   } deriving (Show) | ||||||
| 
 | 
 | ||||||
| defwebopts :: WebOpts | defwebopts :: WebOpts | ||||||
| defwebopts = WebOpts def def def def def [CapView, CapAdd] Nothing def | defwebopts = WebOpts def def def def def def [CapView, CapAdd] Nothing def | ||||||
| 
 | 
 | ||||||
| instance Default WebOpts where def = defwebopts | instance Default WebOpts where def = defwebopts | ||||||
| 
 | 
 | ||||||
| @ -120,6 +125,7 @@ rawOptsToWebOpts rawopts = | |||||||
|     return |     return | ||||||
|       defwebopts |       defwebopts | ||||||
|       { serve_ = boolopt "serve" rawopts |       { serve_ = boolopt "serve" rawopts | ||||||
|  |       , serve_api_ = boolopt "serve-api" rawopts | ||||||
|       , host_ = h |       , host_ = h | ||||||
|       , port_ = p |       , port_ = p | ||||||
|       , base_url_ = b |       , base_url_ = b | ||||||
|  | |||||||
| @ -64,6 +64,9 @@ as shown in the synopsis above. | |||||||
| `--serve` | `--serve` | ||||||
| : serve and log requests, don't browse or auto-exit | : serve and log requests, don't browse or auto-exit | ||||||
| 
 | 
 | ||||||
|  | `--serve-api` | ||||||
|  | : like --serve, but serve only the JSON web API, without the server-side web UI | ||||||
|  | 
 | ||||||
| `--host=IPADDR` | `--host=IPADDR` | ||||||
| : listen on this IP address (default: 127.0.0.1) | : listen on this IP address (default: 127.0.0.1) | ||||||
| 
 | 
 | ||||||
| @ -108,6 +111,8 @@ browser window, and will exit after two minutes of inactivity (no | |||||||
| requests and no browser windows viewing it). | requests and no browser windows viewing it). | ||||||
| With `--serve`, it just runs the web app without exiting, and logs | With `--serve`, it just runs the web app without exiting, and logs | ||||||
| requests to the console. | requests to the console. | ||||||
|  | With `--serve-api`, only the JSON web api (see below) is served,  | ||||||
|  | with the usual HTML server-side web UI disabled. | ||||||
| 
 | 
 | ||||||
| By default the server listens on IP address 127.0.0.1, accessible only to local requests. | By default the server listens on IP address 127.0.0.1, accessible only to local requests. | ||||||
| You can use `--host` to change this, eg `--host 0.0.0.0` to listen on all configured addresses. | You can use `--host` to change this, eg `--host 0.0.0.0` to listen on all configured addresses. | ||||||
| @ -217,6 +222,9 @@ And here's how to test adding that with curl: | |||||||
| $ curl -s http://127.0.0.1:5000/add -X PUT -H 'Content-Type: application/json' --data-binary @txn.pretty.json; echo | $ curl -s http://127.0.0.1:5000/add -X PUT -H 'Content-Type: application/json' --data-binary @txn.pretty.json; echo | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | By default, both the server-side HTML UI and the JSON API are served. | ||||||
|  | Running with `--serve-api` disables the former, useful if you only want to serve the API. | ||||||
|  | 
 | ||||||
| _man_({{ | _man_({{ | ||||||
| 
 | 
 | ||||||
| # ENVIRONMENT | # ENVIRONMENT | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user