1
- use std:: {
2
- fmt:: { self , Debug , Display , Formatter } ,
3
- future:: Future ,
4
- mem,
5
- pin:: Pin ,
6
- str:: FromStr ,
7
- task:: { Context , Poll } ,
8
- time:: Duration ,
9
- } ;
10
-
11
1
use futures:: { ready, Stream } ;
12
2
use hyper:: {
13
3
body:: { Bytes , HttpBody } ,
@@ -19,28 +9,63 @@ use hyper::{
19
9
use hyper_rustls:: HttpsConnector as RustlsConnector ;
20
10
use log:: { debug, info, trace, warn} ;
21
11
use pin_project:: pin_project;
12
+ use std:: {
13
+ boxed,
14
+ fmt:: { self , Debug , Display , Formatter } ,
15
+ future:: Future ,
16
+ mem,
17
+ pin:: Pin ,
18
+ str:: FromStr ,
19
+ task:: { Context , Poll } ,
20
+ time:: Duration ,
21
+ } ;
22
22
use tokio:: time:: Sleep ;
23
23
24
24
use super :: config:: ReconnectOptions ;
25
25
use super :: decode:: Decoded ;
26
26
use super :: error:: { Error , Result } ;
27
27
28
+ use crate :: Event ;
28
29
pub use hyper:: client:: HttpConnector ;
30
+
29
31
#[ cfg( feature = "rustls" ) ]
30
32
pub type HttpsConnector = RustlsConnector < HttpConnector > ;
31
33
34
+ /// Represents a [`Pin`]'d [`Send`] + [`Sync`] stream, returned by [`Client`]'s stream method.
35
+ pub type BoxStream < T > = Pin < boxed:: Box < dyn Stream < Item = T > + Send + Sync > > ;
36
+
37
+ /// Client is the Server-Sent-Events interface.
38
+ /// This trait is sealed and cannot be implemented for types outside this crate.
39
+ pub trait Client : Send + Sync + private:: Sealed {
40
+ /// Returns a stream of [`Event`]s.
41
+ fn stream ( & self ) -> BoxStream < Result < Event > > ;
42
+ }
43
+
32
44
/*
33
45
* TODO remove debug output
34
46
* TODO specify list of stati to not retry (e.g. 204)
35
47
*/
36
48
49
+ /// ClientBuilder provides a series of builder methods to easily construct a [`Client`].
37
50
pub struct ClientBuilder {
38
51
url : Uri ,
39
52
headers : HeaderMap ,
40
53
reconnect_opts : ReconnectOptions ,
41
54
}
42
55
43
56
impl ClientBuilder {
57
+ /// Create a builder for a given URL.
58
+ pub fn for_url ( url : & str ) -> Result < ClientBuilder > {
59
+ let url = url
60
+ . parse ( )
61
+ . map_err ( |e| Error :: InvalidParameter ( Box :: new ( e) ) ) ?;
62
+ Ok ( ClientBuilder {
63
+ url,
64
+ headers : HeaderMap :: new ( ) ,
65
+ reconnect_opts : ReconnectOptions :: default ( ) ,
66
+ } )
67
+ }
68
+
44
69
/// Set a HTTP header on the SSE request.
45
70
pub fn header ( mut self , name : & str , value : & str ) -> Result < ClientBuilder > {
46
71
let name =
@@ -62,11 +87,12 @@ impl ClientBuilder {
62
87
self
63
88
}
64
89
65
- pub fn build_with_conn < C > ( self , conn : C ) -> Client < C >
90
+ /// Build with a specific client connector.
91
+ pub fn build_with_conn < C > ( self , conn : C ) -> impl Client
66
92
where
67
- C : Connect + Clone ,
93
+ C : Connect + Clone + Send + Sync + ' static ,
68
94
{
69
- Client {
95
+ ClientImpl {
70
96
http : hyper:: Client :: builder ( ) . build ( conn) ,
71
97
request_props : RequestProps {
72
98
url : self . url ,
@@ -76,18 +102,24 @@ impl ClientBuilder {
76
102
}
77
103
}
78
104
79
- pub fn build_http ( self ) -> Client < HttpConnector > {
105
+ /// Build with an HTTP client connector.
106
+ pub fn build_http ( self ) -> impl Client {
80
107
self . build_with_conn ( HttpConnector :: new ( ) )
81
108
}
82
109
83
110
#[ cfg( feature = "rustls" ) ]
84
- pub fn build ( self ) -> Client < HttpsConnector > {
111
+ /// Build with an HTTPS client connector, using the OS root certificate store.
112
+ pub fn build ( self ) -> impl Client {
85
113
let conn = HttpsConnector :: with_native_roots ( ) ;
86
114
self . build_with_conn ( conn)
87
115
}
88
116
89
- pub fn build_with_http_client < C > ( self , http : hyper:: Client < C > ) -> Client < C > {
90
- Client {
117
+ /// Build with the given [`hyper::client::Client`].
118
+ pub fn build_with_http_client < C > ( self , http : hyper:: Client < C > ) -> impl Client
119
+ where
120
+ C : Connect + Clone + Send + Sync + ' static ,
121
+ {
122
+ ClientImpl {
91
123
http,
92
124
request_props : RequestProps {
93
125
url : self . url ,
@@ -105,62 +137,33 @@ struct RequestProps {
105
137
reconnect_opts : ReconnectOptions ,
106
138
}
107
139
108
- /// Client that connects to a server using the Server-Sent Events protocol
140
+ /// A client implementation that connects to a server using the Server-Sent Events protocol
109
141
/// and consumes the event stream indefinitely.
110
- pub struct Client < C > {
142
+ /// Can be parameterized with different hyper Connectors, such as HTTP or HTTPS.
143
+ struct ClientImpl < C > {
111
144
http : hyper:: Client < C > ,
112
145
request_props : RequestProps ,
113
146
}
114
147
115
- impl Client < ( ) > {
116
- /// Construct a new `Client` (via a [`ClientBuilder`]). This will not
117
- /// perform any network activity until [`.stream()`] is called.
118
- ///
119
- /// [`ClientBuilder`]: struct.ClientBuilder.html
120
- /// [`.stream()`]: #method.stream
121
- pub fn for_url ( url : & str ) -> Result < ClientBuilder > {
122
- let url = url
123
- . parse ( )
124
- . map_err ( |e| Error :: InvalidParameter ( Box :: new ( e) ) ) ?;
125
- Ok ( ClientBuilder {
126
- url,
127
- headers : HeaderMap :: new ( ) ,
128
- reconnect_opts : ReconnectOptions :: default ( ) ,
129
- } )
130
- }
131
- }
132
-
133
- pub type EventStream < C > = Decoded < ReconnectingRequest < C > > ;
134
-
135
- impl < C > Client < C > {
148
+ impl < C > Client for ClientImpl < C >
149
+ where
150
+ C : Connect + Clone + Send + Sync + ' static ,
151
+ {
136
152
/// Connect to the server and begin consuming the stream. Produces a
137
153
/// [`Stream`] of [`Event`](crate::Event)s wrapped in [`Result`].
138
154
///
139
155
/// Do not use the stream after it returned an error!
140
156
///
141
157
/// After the first successful connection, the stream will
142
158
/// reconnect for retryable errors.
143
- pub fn stream ( & self ) -> EventStream < C >
144
- where
145
- C : Connect + Clone + Send + Sync + ' static ,
146
- {
147
- Decoded :: new ( ReconnectingRequest :: new (
159
+ fn stream ( & self ) -> BoxStream < Result < Event > > {
160
+ Box :: pin ( Decoded :: new ( ReconnectingRequest :: new (
148
161
self . http . clone ( ) ,
149
162
self . request_props . clone ( ) ,
150
- ) )
163
+ ) ) )
151
164
}
152
165
}
153
166
154
- #[ must_use = "streams do nothing unless polled" ]
155
- #[ pin_project]
156
- pub struct ReconnectingRequest < C > {
157
- http : hyper:: Client < C > ,
158
- props : RequestProps ,
159
- #[ pin]
160
- state : State ,
161
- next_reconnect_delay : Duration ,
162
- }
163
-
164
167
#[ allow( clippy:: large_enum_variant) ] // false positive
165
168
#[ pin_project( project = StateProj ) ]
166
169
enum State {
@@ -192,6 +195,16 @@ impl Debug for State {
192
195
}
193
196
}
194
197
198
+ #[ must_use = "streams do nothing unless polled" ]
199
+ #[ pin_project]
200
+ pub struct ReconnectingRequest < C > {
201
+ http : hyper:: Client < C > ,
202
+ props : RequestProps ,
203
+ #[ pin]
204
+ state : State ,
205
+ next_reconnect_delay : Duration ,
206
+ }
207
+
195
208
impl < C > ReconnectingRequest < C > {
196
209
fn new ( http : hyper:: Client < C > , props : RequestProps ) -> ReconnectingRequest < C > {
197
210
let reconnect_delay = props. reconnect_opts . delay ;
@@ -202,9 +215,7 @@ impl<C> ReconnectingRequest<C> {
202
215
next_reconnect_delay : reconnect_delay,
203
216
}
204
217
}
205
- }
206
218
207
- impl < C > ReconnectingRequest < C > {
208
219
fn send_request ( & self ) -> Result < ResponseFuture >
209
220
where
210
221
C : Connect + Clone + Send + Sync + ' static ,
@@ -235,24 +246,6 @@ impl<C> ReconnectingRequest<C> {
235
246
}
236
247
}
237
248
238
- fn delay ( dur : Duration , description : & str ) -> Sleep {
239
- info ! ( "Waiting {:?} before {}" , dur, description) ;
240
- tokio:: time:: sleep ( dur)
241
- }
242
-
243
- #[ derive( Debug ) ]
244
- struct StatusError {
245
- status : StatusCode ,
246
- }
247
-
248
- impl Display for StatusError {
249
- fn fmt ( & self , f : & mut Formatter < ' _ > ) -> fmt:: Result {
250
- write ! ( f, "Invalid status code: {}" , self . status)
251
- }
252
- }
253
-
254
- impl std:: error:: Error for StatusError { }
255
-
256
249
impl < C > Stream for ReconnectingRequest < C >
257
250
where
258
251
C : Connect + Clone + Send + Sync + ' static ,
@@ -343,3 +336,28 @@ where
343
336
}
344
337
}
345
338
}
339
+
340
+ fn delay ( dur : Duration , description : & str ) -> Sleep {
341
+ info ! ( "Waiting {:?} before {}" , dur, description) ;
342
+ tokio:: time:: sleep ( dur)
343
+ }
344
+
345
+ #[ derive( Debug ) ]
346
+ struct StatusError {
347
+ status : StatusCode ,
348
+ }
349
+
350
+ impl Display for StatusError {
351
+ fn fmt ( & self , f : & mut Formatter < ' _ > ) -> fmt:: Result {
352
+ write ! ( f, "Invalid status code: {}" , self . status)
353
+ }
354
+ }
355
+
356
+ impl std:: error:: Error for StatusError { }
357
+
358
+ mod private {
359
+ use crate :: client:: ClientImpl ;
360
+
361
+ pub trait Sealed { }
362
+ impl < C > Sealed for ClientImpl < C > { }
363
+ }
0 commit comments