Skip to content

Commit 8129eb6

Browse files
committed
Add HTTP2 support to webpush
1 parent 8d95f1b commit 8129eb6

4 files changed

Lines changed: 63 additions & 19 deletions

File tree

simplexmq.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ library
305305
, containers ==0.6.*
306306
, crypton ==0.34.*
307307
, crypton-x509 ==1.7.*
308+
, crypton-x509-system ==1.6.*
308309
, crypton-x509-store ==1.6.*
309310
, crypton-x509-validation ==1.6.*
310311
, cryptostore ==0.3.*

src/Simplex/Messaging/Notifications/Server/Env.hs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,21 @@ import qualified Simplex.Messaging.Crypto as C
3030
import Simplex.Messaging.Notifications.Protocol
3131
import Simplex.Messaging.Notifications.Server.Push
3232
import Simplex.Messaging.Notifications.Server.Push.APNS
33-
import Simplex.Messaging.Notifications.Server.Push.WebPush (WebPushClient (..), WebPushConfig, wpPushProviderClient)
33+
import Simplex.Messaging.Notifications.Server.Push.WebPush (WebPushClient (..), WebPushConfig, wpPushProviderClientH1, wpPushProviderClientH2, wpHTTP2Client)
3434
import Simplex.Messaging.Notifications.Server.Stats
3535
import Simplex.Messaging.Notifications.Server.Store (newNtfSTMStore)
3636
import Simplex.Messaging.Notifications.Server.Store.Postgres
3737
import Simplex.Messaging.Notifications.Server.Store.Types
3838
import Simplex.Messaging.Notifications.Server.StoreLog (readWriteNtfSTMStore)
3939
import Simplex.Messaging.Notifications.Transport (NTFVersion, VersionRangeNTF)
40-
import Simplex.Messaging.Protocol (BasicAuth, CorrId, Party (..), SMPServer, SParty (..), Transmission)
40+
import Simplex.Messaging.Protocol (BasicAuth, CorrId, Party (..), SMPServer, SParty (..), Transmission, SrvLoc (..))
4141
import Simplex.Messaging.Server.Env.STM (StartOptions (..))
4242
import Simplex.Messaging.Server.Expiration
4343
import Simplex.Messaging.Server.QueueStore.Postgres.Config (PostgresStoreCfg (..))
4444
import Simplex.Messaging.Server.StoreLog (closeStoreLog)
4545
import Simplex.Messaging.Session
4646
import Simplex.Messaging.TMap (TMap)
47+
import Simplex.Messaging.Util (tshow)
4748
import qualified Simplex.Messaging.TMap as TM
4849
import Simplex.Messaging.Transport (ASrvTransport, SMPServiceRole (..), ServiceCredentials (..), THandleParams, TransportPeer (..))
4950
import Simplex.Messaging.Transport.Server (AddHTTP, ServerCredentials, TransportServerConfig, loadFingerprint, loadServerCredential)
@@ -180,14 +181,19 @@ newAPNSPushClient NtfPushServer {apnsConfig, pushClients} pp = do
180181
Just host -> apnsPushProviderClient <$> createAPNSPushClient host apnsConfig
181182

182183
newWPPushClient :: NtfPushServer -> WPProvider -> IO PushProviderClient
183-
newWPPushClient NtfPushServer {wpConfig, pushClients} pp = do
184+
newWPPushClient NtfPushServer {wpConfig, pushClients} (WPP (WPSrvLoc (SrvLoc h p))) = do
184185
logDebug "New WP Client requested"
185186
-- We use one http manager per push server (which may be used by different clients)
186187
manager <- wpHTTPManager
187188
cache <- newIORef Nothing
188189
random <- C.newRandom
189190
let client = WebPushClient {wpConfig, cache, manager, random}
190-
pure $ wpPushProviderClient client
191+
r <- wpHTTP2Client h p
192+
case r of
193+
Right client -> pure $ wpPushProviderClientH2 client
194+
Left e -> do
195+
logError $ "Error connecting to H2 WP: " <> tshow e
196+
wpPushProviderClientH1 client
191197

192198
wpHTTPManager :: IO Manager
193199
wpHTTPManager =

src/Simplex/Messaging/Notifications/Server/Push/WebPush.hs

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,17 @@ import Data.Time.Clock.System (getSystemTime, systemSeconds)
3838
import Network.HTTP.Client
3939
import qualified Network.HTTP.Types as N
4040
import qualified Simplex.Messaging.Crypto as C
41-
import Simplex.Messaging.Notifications.Protocol (DeviceToken (..), NtfRegCode (..), WPAuth (..), WPKey (..), WPP256dh (..), WPTokenParams (..), encodePNMessages, wpAud, wpRequest)
41+
import Simplex.Messaging.Notifications.Protocol (DeviceToken (..), NtfRegCode (..), WPAuth (..), WPKey (..), WPP256dh (..), WPTokenParams (..), WPProvider (..), encodePNMessages, wpAud, wpRequest)
4242
import Simplex.Messaging.Notifications.Server.Push
4343
import Simplex.Messaging.Notifications.Server.Store.Types
4444
import Simplex.Messaging.Util (liftError', safeDecodeUtf8, tshow)
45+
import Simplex.Messaging.Transport.HTTP2.Client (HTTP2Client, getHTTP2Client, defaultHTTP2ClientConfig, HTTP2ClientError, sendRequest)
46+
import Network.Socket (ServiceName, HostName)
47+
import System.X509.Unix
48+
import qualified Network.HTTP2.Client as H
49+
import Data.ByteString.Builder (lazyByteString)
50+
import Simplex.Messaging.Encoding.String (StrEncoding(..))
51+
import Data.Bifunctor (first)
4552
import UnliftIO.STM
4653

4754
-- | Vapid
@@ -61,7 +68,6 @@ mkVapid key = VapidKey {key, fp}
6168
data WebPushClient = WebPushClient
6269
{ wpConfig :: WebPushConfig,
6370
cache :: IORef (Maybe WPCache),
64-
manager :: Manager,
6571
random :: TVar ChaChaDRG
6672
}
6773

@@ -132,26 +138,56 @@ mkVapidHeader VapidKey {key, fp} uriAuthority expire = do
132138
signedToken <- signedJWTTokenRaw key jwt
133139
pure $ "vapid t=" <> signedToken <> ",k=" <> fp
134140

135-
wpPushProviderClient :: WebPushClient -> PushProviderClient
136-
wpPushProviderClient _ NtfTknRec {token = APNSDeviceToken _ _} _ = throwE PPInvalidPusher
137-
wpPushProviderClient c@WebPushClient {wpConfig, cache, manager} tkn@NtfTknRec {token = token@(WPDeviceToken pp params)} pn = do
141+
wpHTTP2Client :: HostName -> ServiceName -> IO (Either HTTP2ClientError HTTP2Client)
142+
wpHTTP2Client h p = do
143+
caStore <- Just <$> getSystemCertificateStore
144+
let config = defaultHTTP2ClientConfig
145+
getHTTP2Client h p caStore config nop
146+
where
147+
nop = pure ()
148+
149+
wpHeaders :: B.ByteString -> [N.Header]
150+
wpHeaders vapidH = [
151+
-- Why http2-client doesn't accept TTL AND Urgency?
152+
-- Keeping Urgency for now, the TTL should be around 30 days by default on the push servers
153+
-- ("TTL", "2592000"), -- 30 days
154+
("Urgency", "high"),
155+
("Content-Encoding", "aes128gcm"),
156+
("Authorization", vapidH)
157+
-- TODO: topic for pings and interval
158+
]
159+
160+
wpHTTP2Req :: B.ByteString -> [(N.HeaderName, B.ByteString)] -> LB.ByteString -> H.Request
161+
wpHTTP2Req path headers s = H.requestBuilder N.methodPost path headers (lazyByteString s)
162+
163+
wpPushProviderClientH2 :: WebPushClient -> HTTP2Client -> PushProviderClient
164+
wpPushProviderClientH2 _ _ NtfTknRec {token = APNSDeviceToken _ _} _ = throwE PPInvalidPusher
165+
wpPushProviderClientH2 c@WebPushClient {wpConfig, cache} http2 tkn@NtfTknRec {token = (WPDeviceToken pp@(WPP p) params)} pn = do
166+
-- TODO [webpush] this function should accept type that is restricted to WP token (so, possibly WPProvider and WPTokenParams)
167+
-- parsing will happen in DeviceToken parser, so it won't fail here
168+
encBody <- body
169+
vapidH <- liftError' toPPWPError $ try $ getVapidHeader (vapidKey wpConfig) cache $ wpAud pp
170+
let req = wpHTTP2Req (wpPath params) (wpHeaders vapidH) $ LB.fromStrict encBody
171+
logDebug $ "HTTP/2 Request to " <> tshow (strEncode p)
172+
void $ liftHTTPS2 $ sendRequest http2 req Nothing
173+
where
174+
body :: ExceptT PushProviderError IO B.ByteString
175+
body = withExceptT PPCryptoError $ wpEncrypt c tkn params pn
176+
liftHTTPS2 a = ExceptT $ first PPConnection <$> a
177+
178+
wpPushProviderClientH1 :: WebPushClient -> Manager -> PushProviderClient
179+
wpPushProviderClientH1 _ _ NtfTknRec {token = APNSDeviceToken _ _} _ = throwE PPInvalidPusher
180+
wpPushProviderClientH1 c@WebPushClient {wpConfig, cache} manager tkn@NtfTknRec {token = token@(WPDeviceToken pp params)} pn = do
138181
-- TODO [webpush] this function should accept type that is restricted to WP token (so, possibly WPProvider and WPTokenParams)
139182
-- parsing will happen in DeviceToken parser, so it won't fail here
140183
r <- wpRequest token
141184
vapidH <- liftError' toPPWPError $ try $ getVapidHeader (vapidKey wpConfig) cache $ wpAud pp
142185
logDebug $ "Web Push request to " <> tshow (host r)
143186
encBody <- withExceptT PPCryptoError $ wpEncrypt c tkn params pn
144-
let requestHeaders =
145-
[ ("TTL", "2592000"), -- 30 days
146-
("Urgency", "high"),
147-
("Content-Encoding", "aes128gcm"),
148-
("Authorization", vapidH)
149-
-- TODO: topic for pings and interval
150-
]
151-
req =
187+
let req =
152188
r
153189
{ method = "POST",
154-
requestHeaders,
190+
requestHeaders = wpHeaders vapidH,
155191
requestBody = RequestBodyBS encBody,
156192
redirectCount = 0
157193
}

tests/NtfServerTests.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,8 @@ testWPNotificationSubscription (ATransport t, msType) createQueue =
205205
PushMockRequest {notification = WPNotification {authorization, encoding, ttl, urgency, body}} <-
206206
getMockNotification wp tkn
207207
encoding `shouldBe` Just "aes128gcm"
208-
ttl `shouldBe` Just "2592000"
208+
-- We can't pass TTL and Urgency ATM
209+
-- ttl `shouldBe` Just "2592000"
209210
urgency `shouldBe` Just "high"
210211
-- TODO: uncomment when vapid is merged
211212
-- authorization `shouldContainBS` "vapid t="

0 commit comments

Comments
 (0)