Skip to content

Lts 12.14 #38

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Nov 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Use Alpine Linux as base image
FROM alpine:3.6
FROM alpine:3.8

# Install libpq and gmp dependencies (dynamic libraries required by the project)
RUN apk update && apk add libpq gmp libffi
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.build
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM alpine:3.6
FROM alpine:3.8

RUN echo "@testing http://nl.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories
RUN apk -U add shadow@testing
Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,17 @@ If you have any problems processing any Postgres related library on a Mac, try i

After the build you should be able to run the server using `~/.local/bin/postgres-websockets` (you can add `~/.local/bin` to your PATH variable):

To run the example bellow you will need a PostgreSQL server running on port 5432 of your localhost. You can also change the database connection string editting the `sample.conf` file.
To run the example bellow you will need a PostgreSQL server running on port 5432 of your localhost.
```bash
~/.local/bin/postgres-websockets sample.conf
PGWS_DB_URI="postgres://localhost:5432/postgres" PGWS_JWT_SECRET="auwhfdnskjhewfi34uwehdlaehsfkuaeiskjnfduierhfsiweskjcnzeiluwhskdewishdnpwe" ~/.local/bin/postgres-websockets
postgres-websockets <version> / Connects websockets to PostgreSQL asynchronous notifications.
Listening on port 3000
```

You can also use the provided [sample-env](./sample-env) file to export the needed variables:
```bash
source sample-env && ~/.local/bin/postgres-websockets
```
After running the above command, open your browser on http://localhost:3000 to see an example of usage.

The sample config file provided in the [sample.conf](https://github.com/diogob/postgres-websockets/tree/master/sample.conf) file comes with a jwt secret just for testing and is used in the sample client.
Expand Down
111 changes: 13 additions & 98 deletions app/Config.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,10 @@ module Config ( prettyVersion
)
where

import System.IO.Error (IOError)
import Control.Applicative
import qualified Data.Configurator as C
import qualified Data.Configurator.Parser as C
import qualified Data.Configurator.Types as C
import Data.Monoid
import Data.Scientific (floatingOrInteger)
import Data.Text (intercalate, lines)
import Data.Text.Encoding (encodeUtf8)
import Env
import Data.Text (intercalate)
import Data.Version (versionBranch)
import Options.Applicative hiding (str)
import Paths_postgres_websockets (version)
import System.IO (hPrint)
import Text.Heredoc
import Text.PrettyPrint.ANSI.Leijen hiding ((<>), (<$>))
import qualified Text.PrettyPrint.ANSI.Leijen as L
import Protolude hiding (intercalate, (<>))

-- | Config file settings for the server
Expand All @@ -54,91 +42,18 @@ data AppConfig = AppConfig {
prettyVersion :: Text
prettyVersion = intercalate "." $ map show $ versionBranch version

-- | Function to read and parse options from the command line
-- | Function to read and parse options from the environment
readOptions :: IO AppConfig
readOptions = do
-- First read the config file path from command line
cfgPath <- customExecParser parserPrefs opts
-- Now read the actual config file
conf <- catch
(C.readConfig =<< C.load [C.Required cfgPath])
configNotfoundHint

let (mAppConf, errs) = flip C.runParserM conf $ do
-- db ----------------
cDbUri <- C.key "db-uri"
cPool <- fromMaybe 10 . join . fmap coerceInt <$> C.key "db-pool"
-- server ------------
cPath <- C.key "server-root"
cHost <- fromMaybe "*4" . mfilter (/= "") <$> C.key "server-host"
cPort <- fromMaybe 3000 . join . fmap coerceInt <$> C.key "server-port"
cAuditC <- C.key "audit-channel"
cChannel <- case cAuditC of
Just c -> fromMaybe c . mfilter (/= "") <$> C.key "listen-channel"
Nothing -> C.key "listen-channel"
-- jwt ---------------
cJwtSec <- C.key "jwt-secret"
cJwtB64 <- fromMaybe False <$> C.key "secret-is-base64"

return $ AppConfig cDbUri cPath cHost cPort cChannel (encodeUtf8 cJwtSec) cJwtB64 cPool

case mAppConf of
Nothing -> do
forM_ errs $ hPrint stderr
exitFailure
Just appConf ->
return appConf

where
coerceInt :: (Read i, Integral i) => C.Value -> Maybe i
coerceInt (C.Number x) = rightToMaybe $ floatingOrInteger x
coerceInt (C.String x) = readMaybe $ toS x
coerceInt _ = Nothing

opts = info (helper <*> pathParser) $
fullDesc
<> progDesc (
"postgres-websockets "
<> toS prettyVersion
<> " / Connects websockets to PostgreSQL asynchronous notifications."
)
<> footerDoc (Just $
text "Example Config File:"
L.<> nest 2 (hardline L.<> exampleCfg)
)

parserPrefs = prefs showHelpOnError

configNotfoundHint :: IOError -> IO a
configNotfoundHint e = die $ "Cannot open config file:\n\t" <> show e

missingKeyHint :: C.KeyError -> IO a
missingKeyHint (C.KeyError n) =
die $
"Required config parameter \"" <> n <> "\" is missing or of wrong type.\n"

exampleCfg :: Doc
exampleCfg = vsep . map (text . toS) . lines $
[str|db-uri = "postgres://user:pass@localhost:5432/dbname"
|db-pool = 10
|
|server-root = "./client-example"
|server-host = "*4"
|server-port = 3000
|listen-channel = "postgres-websockets-listener"
|
|## choose a secret to enable JWT auth
|## (use "@filename" to load from separate file)
|# jwt-secret = "foo"
|# secret-is-base64 = false
|]


pathParser :: Parser FilePath
pathParser =
strArgument $
metavar "FILENAME" <>
help "Path to configuration file"
readOptions =
Env.parse (header "You need to configure some environment variables to start the service.") $
AppConfig <$> var (str <=< nonempty) "PGWS_DB_URI" (help "String to connect to PostgreSQL")
<*> var str "PGWS_ROOT_PATH" (def "./" <> helpDef show <> help "Root path to serve static files")
<*> var str "PGWS_HOST" (def "*4" <> helpDef show <> help "Address the server will listen for websocket connections")
<*> var auto "PGWS_PORT" (def 3000 <> helpDef show <> help "Port the server will listen for websocket connections")
<*> var str "PGWS_LISTEN_CHANNEL" (def "postgres-websockets-listener" <> helpDef show <> help "Master channel used in the database to send or read messages in any notification channel")
<*> var str "PGWS_JWT_SECRET" (help "Secret used to sign JWT tokens used to open communications channels")
<*> var auto "PGWS_JWT_SECRET_BASE64" (def False <> helpDef show <> help "Indicate whether the JWT secret should be decoded from a base64 encoded string")
<*> var auto "PGWS_POOL_SIZE" (def 10 <> helpDef show <> help "How many connection to the database should be used by the connection pool")

data PgVersion = PgVersion {
pgvNum :: Int32
Expand Down
12 changes: 8 additions & 4 deletions app/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import qualified Data.ByteString.Base64 as B64
import Data.String (IsString (..))
import Data.Text (pack, replace, strip, stripPrefix)
import Data.Text.Encoding (encodeUtf8, decodeUtf8)
import qualified Hasql.Query as H
import qualified Hasql.Statement as H
import qualified Hasql.Session as H
import qualified Hasql.Decoders as HD
import qualified Hasql.Encoders as HE
Expand All @@ -27,19 +27,23 @@ import System.IO (BufferMode (..),

isServerVersionSupported :: H.Session Bool
isServerVersionSupported = do
ver <- H.query () pgVersion
ver <- H.statement () pgVersion
return $ ver >= pgvNum minimumPgVersion
where
pgVersion =
H.statement "SELECT current_setting('server_version_num')::integer"
HE.unit (HD.singleRow $ HD.value HD.int4) False
H.Statement "SELECT current_setting('server_version_num')::integer"
HE.unit (HD.singleRow $ HD.column HD.int4) False

main :: IO ()
main = do
hSetBuffering stdout LineBuffering
hSetBuffering stdin LineBuffering
hSetBuffering stderr NoBuffering

putStrLn $ ("postgres-websockets " :: Text)
<> prettyVersion
<> " / Connects websockets to PostgreSQL asynchronous notifications."

conf <- loadSecretFile =<< readOptions
let host = configHost conf
port = configPort conf
Expand Down
4 changes: 2 additions & 2 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ dependencies:
- "~/.stack"
- ".stack-work"
pre:
- curl -L https://github.com/commercialhaskell/stack/releases/download/v1.1.2/stack-1.1.2-linux-x86_64.tar.gz | tar zx -C /tmp
- sudo mv /tmp/stack-1.1.2-linux-x86_64/stack /usr/bin
- curl -L https://github.com/commercialhaskell/stack/releases/download/v1.9.1/stack-1.9.1-linux-x86_64.tar.gz | tar zx -C /tmp
- sudo mv /tmp/stack-1.9.1-linux-x86_64/stack /usr/bin
override:
- stack setup
- rm -fr $(stack path --dist-dir) $(stack path --local-install-root)
Expand Down
11 changes: 6 additions & 5 deletions postgres-websockets.cabal
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: postgres-websockets
version: 0.4.2.1
version: 0.5.0.0
synopsis: Middleware to map LISTEN/NOTIFY messages to Websockets
description: Please see README.md
homepage: https://github.com/diogob/postgres-websockets#readme
Expand All @@ -22,10 +22,10 @@ library
, PostgresWebsockets.HasqlBroadcast
, PostgresWebsockets.Claims
build-depends: base >= 4.7 && < 5
, hasql-pool >= 0.4 && < 0.5
, hasql-pool >= 0.4 && < 0.6
, text >= 1.2 && < 2
, wai >= 3.2 && < 4
, websockets >= 0.9 && < 0.11
, websockets >= 0.9 && < 0.13
, wai-websockets >= 3.0 && < 4
, http-types >= 0.9
, bytestring >= 0.10
Expand Down Expand Up @@ -53,6 +53,7 @@ executable postgres-websockets
hs-source-dirs: app
main-is: Main.hs
other-modules: Config
, Paths_postgres_websockets
ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N
build-depends: base >= 4.7 && < 5
, transformers >= 0.4 && < 0.6
Expand All @@ -63,16 +64,16 @@ executable postgres-websockets
, protolude >= 0.2
, base64-bytestring
, bytestring
, configurator-ng >= 0.0.0.1
, configurator
, optparse-applicative
, scientific >= 0.3.5.0
, text
, time
, wai
, wai-extra
, wai-app-static
, heredoc
, ansi-wl-pprint
, envparse
default-language: Haskell2010
default-extensions: OverloadedStrings, NoImplicitPrelude, QuasiQuotes

Expand Down
18 changes: 9 additions & 9 deletions sample.conf → sample-env
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
## PostgreSQL URI where the server will connect to issue NOTIFY and LISTEN commands
db-uri = "postgres://localhost:5432/postgres"
export PGWS_DB_URI="postgres://localhost:5432/postgres"

## Size of connection pool used to issue notify commands (LISTEN commands are always issued on the same connection that is not part of the pool).
db-pool = 10
export PGWS_POOL_SIZE=10

## server-root can be used to serve some static files for convenience when testing.
server-root = "./client-example"
## Root path can be used to serve some static files for convenience when testing.
export PGWS_ROOT_PATH="./client-example"

## Sends a copy of every message received from websocket clients to the channel specified bellow as an aditional NOTIFY command.
listen-channel = "postgres-websockets-listener"
export PGWS_LISTEN_CHANNEL="postgres-websockets-listener"

## Host and port on which the websockets server (and the static files server) will be listening.
server-host = "*4"
server-port = 3000
export PGWS_HOST="*4"
export PGWS_PORT=3000

## choose a secret to enable JWT auth
## (use "@filename" to load from separate file)
jwt-secret = "auwhfdnskjhewfi34uwehdlaehsfkuaeiskjnfduierhfsiweskjcnzeiluwhskdewishdnpwe"
secret-is-base64 = false
export PGWS_JWT_SECRET="auwhfdnskjhewfi34uwehdlaehsfkuaeiskjnfduierhfsiweskjcnzeiluwhskdewishdnpwe"
export PGWS_JWT_SECRET_BASE64=False
4 changes: 2 additions & 2 deletions src/PostgresWebsockets/Claims.hs
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ claims2map = val2map . toJSON
hs256jwk :: ByteString -> JWK
hs256jwk key =
fromKeyMaterial km
& jwkUse .~ Just Sig
& jwkAlg .~ (Just $ JWSAlg HS256)
& jwkUse ?~ Sig
& jwkAlg ?~ JWSAlg HS256
where
km = OctKeyMaterial (OctKeyParameters (JOSE.Types.Base64Octets key))

Expand Down
12 changes: 6 additions & 6 deletions src/PostgresWebsockets/Database.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ module PostgresWebsockets.Database

import Protolude hiding (replace)
import Hasql.Pool (Pool, UsageError, use)
import Hasql.Session (sql, run, query)
import Hasql.Session (sql, run, statement)
import qualified Hasql.Session as S
import Hasql.Query (statement)
import qualified Hasql.Statement as HST
import Hasql.Connection (Connection, withLibPQConnection)
import qualified Hasql.Decoders as HD
import qualified Hasql.Encoders as HE
Expand Down Expand Up @@ -45,19 +45,19 @@ toPgIdentifier x = PgIdentifier $ "\"" <> strictlyReplaceQuotes (trimNullChars x
-- | Given a Hasql Pool, a channel and a message sends a notify command to the database
notifyPool :: Pool -> ByteString -> ByteString -> IO (Either Error ())
notifyPool pool channel mesg =
mapError <$> use pool (query (toS channel, toS mesg) callStatement)
mapError <$> use pool (statement (toS channel, toS mesg) callStatement)
where
mapError :: Either UsageError () -> Either Error ()
mapError = mapLeft (NotifyError . show)
callStatement = statement ("SELECT pg_notify" <> "($1, $2)") encoder HD.unit False
encoder = contramap fst (HE.value HE.text) <> contramap snd (HE.value HE.text)
callStatement = HST.Statement ("SELECT pg_notify" <> "($1, $2)") encoder HD.unit False
encoder = contramap fst (HE.param HE.text) <> contramap snd (HE.param HE.text)

-- | Given a Hasql Connection, a channel and a message sends a notify command to the database
notify :: Connection -> PgIdentifier -> ByteString -> IO (Either Error ())
notify con channel mesg =
mapError <$> run (sql ("NOTIFY " <> fromPgIdentifier channel <> ", '" <> mesg <> "'")) con
where
mapError :: Either S.Error () -> Either Error ()
mapError :: Either S.QueryError () -> Either Error ()
mapError = mapLeft (NotifyError . show)

-- | Given a Hasql Connection and a channel sends a listen command to the database
Expand Down
2 changes: 1 addition & 1 deletion src/PostgresWebsockets/HasqlBroadcast.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module PostgresWebsockets.HasqlBroadcast
, relayMessagesForever
) where

import Protolude
import Protolude hiding (putErrLn)

import Hasql.Connection
import Data.Aeson (decode, Value(..))
Expand Down
7 changes: 3 additions & 4 deletions stack.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
resolver: lts-9.12
resolver: lts-12.14
extra-deps:
- protolude-0.2
- configurator-ng-0.0.0.1
- critbit-0.2.0.0
- envparse-0.4.1@sha256:989902e6368532548f61de1fa245ad2b39176cddd8743b20071af519a709ce30

ghc-options:
postgres-websockets: -O2 -Wall -fwarn-identities -fno-warn-redundant-constraints