Skip to content

Commit d4625a3

Browse files
voidusvapourismo
andauthored
Client-side support for server-sent events (SSE) (#1811)
Co-authored-by: Ole Krüger <[email protected]>
1 parent 9cda0cf commit d4625a3

File tree

10 files changed

+556
-1
lines changed

10 files changed

+556
-1
lines changed

changelog.d/1317-server-sent-events

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
synopsis: Server-sent events (SSE) for client-side
2+
prs: #1317
3+
issues: #1317
4+
5+
description: {
6+
7+
Implement Server-sent events (SSE) for the Servant client using a new
8+
combinator "ServerSentEvents". The raw event messages, accumulated events and
9+
JSON-processed events can be exposed.
10+
11+
}

servant-client-core/servant-client-core.cabal

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ library
9292
Servant.Client.Core.Response
9393
Servant.Client.Core.MultiVerb.ResponseUnrender
9494
Servant.Client.Core.RunClient
95+
Servant.Client.Core.ServerSentEvents
9596
Servant.Client.Free
9697
Servant.Client.Generic
9798

@@ -102,6 +103,7 @@ library
102103
--
103104
-- note: mtl lower bound is so low because of GHC-7.8
104105
build-depends:
106+
, attoparsec >= 0.13.2.2 && < 0.15
105107
, base >= 4.16.4.0 && < 4.22
106108
, bytestring >=0.11 && <0.13
107109
, constraints >=0.2 && <0.15
@@ -138,11 +140,15 @@ test-suite spec
138140
other-modules:
139141
Servant.Client.Core.Internal.BaseUrlSpec
140142
Servant.Client.Core.RequestSpec
143+
Servant.Client.Core.ServerSentEventsSpec
141144

142145
-- Dependencies inherited from the library. No need to specify bounds.
143146
build-depends:
144147
, base
145148
, base-compat
149+
, bytestring
150+
, transformers
151+
, servant
146152
, servant-client-core
147153

148154
-- Additional dependencies

servant-client-core/src/Servant/Client/Core/HasClient.hs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ import Servant.API.Generic
7171
(GenericMode(..), ToServant, ToServantApi
7272
, GenericServant, toServant, fromServant)
7373
import Servant.API.ContentTypes
74-
(contentTypes, AllMime (allMime), AllMimeUnrender (allMimeUnrender))
74+
(contentTypes, AllMime (allMime), AllMimeUnrender (allMimeUnrender), EventStream)
7575
import Servant.API.QueryString (ToDeepQuery(..), generateDeepParam)
7676
import Servant.API.Status
7777
(statusFromNat)
@@ -81,6 +81,10 @@ import Servant.API.Modifiers
8181
import Servant.API.TypeErrors
8282
import Servant.API.UVerb
8383
(HasStatus, HasStatuses (Statuses, statuses), UVerb, Union, Unique, inject, statusOf, foldMapUnion, matchUnion)
84+
import Servant.API.ServerSentEvents
85+
(EventKind (JsonEvent, RawEvent), ServerSentEvents')
86+
import Servant.API.Stream
87+
(NoFraming)
8488

8589
import Servant.Client.Core.Auth
8690
import Servant.Client.Core.BasicAuth
@@ -90,6 +94,7 @@ import Servant.Client.Core.Response
9094
import Servant.Client.Core.MultiVerb.ResponseUnrender
9195
import qualified Servant.Client.Core.Response as Response
9296
import Servant.Client.Core.RunClient
97+
import Servant.Client.Core.ServerSentEvents
9398
import Servant.API.MultiVerb
9499
import qualified Network.HTTP.Media as M
95100
import Data.Typeable
@@ -451,6 +456,63 @@ instance {-# OVERLAPPING #-}
451456
, requestMethod = reflectMethod (Proxy :: Proxy method)
452457
}
453458

459+
type SseClientDelegate method status =
460+
Stream method status NoFraming EventStream
461+
462+
instance
463+
( RunClient m
464+
, HasClient m (SseClientDelegate method status (EventMessageStreamT IO))
465+
)
466+
=> HasClient m (ServerSentEvents' method status 'RawEvent EventMessage) where
467+
type Client m (ServerSentEvents' method status 'RawEvent EventMessage) =
468+
Client m (SseClientDelegate method status (EventMessageStreamT IO))
469+
470+
hoistClientMonad p _ =
471+
hoistClientMonad
472+
p
473+
(Proxy :: Proxy (SseClientDelegate method status (EventMessageStreamT IO)))
474+
475+
clientWithRoute p _ =
476+
clientWithRoute
477+
p
478+
(Proxy :: Proxy (SseClientDelegate method status (EventMessageStreamT IO)))
479+
480+
instance
481+
( RunClient m
482+
, HasClient m (SseClientDelegate method status (EventStreamT IO))
483+
)
484+
=> HasClient m (ServerSentEvents' method status 'RawEvent (Event a)) where
485+
type Client m (ServerSentEvents' method status 'RawEvent (Event a)) =
486+
Client m (SseClientDelegate method status (EventStreamT IO))
487+
488+
hoistClientMonad p _ =
489+
hoistClientMonad
490+
p
491+
(Proxy :: Proxy (SseClientDelegate method status (EventStreamT IO)))
492+
493+
clientWithRoute p _ =
494+
clientWithRoute
495+
p
496+
(Proxy :: Proxy (SseClientDelegate method status (EventStreamT IO)))
497+
498+
instance
499+
( RunClient m
500+
, HasClient m (SseClientDelegate method status (JsonEventStreamT IO a))
501+
)
502+
=> HasClient m (ServerSentEvents' method status 'JsonEvent a) where
503+
type Client m (ServerSentEvents' method status 'JsonEvent a) =
504+
Client m (SseClientDelegate method status (JsonEventStreamT IO a))
505+
506+
hoistClientMonad p _ =
507+
hoistClientMonad
508+
p
509+
(Proxy :: Proxy (SseClientDelegate method status (JsonEventStreamT IO a)))
510+
511+
clientWithRoute p _ =
512+
clientWithRoute
513+
p
514+
(Proxy :: Proxy (SseClientDelegate method status (JsonEventStreamT IO a)))
515+
454516
-- | If you use a 'Header' in one of your endpoints in your API,
455517
-- the corresponding querying function will automatically take
456518
-- an additional argument of the type specified by your 'Header',

0 commit comments

Comments
 (0)