1
- use super :: { glue :: UpgradeBody , h1, h2, upgrade} ;
1
+ use super :: { h1, h2, upgrade} ;
2
2
use futures:: { future, prelude:: * } ;
3
3
use http:: header:: { HeaderValue , TRANSFER_ENCODING } ;
4
- use linkerd_error:: Error ;
4
+ use hyper:: body:: HttpBody ;
5
+ use linkerd_error:: { Error , Result } ;
6
+ use linkerd_http_box:: BoxBody ;
5
7
use linkerd_stack:: layer;
6
8
use std:: {
7
9
future:: Future ,
8
10
pin:: Pin ,
9
11
task:: { Context , Poll } ,
10
12
} ;
13
+ use thiserror:: Error ;
11
14
use tracing:: { debug, trace, warn} ;
12
15
13
16
pub const L5D_ORIG_PROTO : & str = "l5d-orig-proto" ;
@@ -19,14 +22,24 @@ pub struct Upgrade<C, T, B> {
19
22
h2 : h2:: Connection < B > ,
20
23
}
21
24
25
+ #[ derive( Clone , Copy , Debug , Error ) ]
26
+ #[ error( "upgraded connection failed with HTTP/2 reset: {0}" ) ]
27
+ pub struct DowngradedH2Error ( h2:: Reason ) ;
28
+
29
+ #[ pin_project:: pin_project]
30
+ #[ derive( Debug ) ]
31
+ pub struct UpgradeResponseBody {
32
+ inner : hyper:: Body ,
33
+ }
34
+
22
35
/// Downgrades HTTP2 requests that were previousl upgraded to their original
23
36
/// protocol.
24
37
#[ derive( Clone , Debug ) ]
25
38
pub struct Downgrade < S > {
26
39
inner : S ,
27
40
}
28
41
29
- // ==== impl Upgrade == ===
42
+ // === impl Upgrade ===
30
43
31
44
impl < C , T , B > Upgrade < C , T , B > {
32
45
pub ( crate ) fn new ( http1 : h1:: Client < C , T , B > , h2 : h2:: Connection < B > ) -> Self {
@@ -45,23 +58,20 @@ where
45
58
B :: Data : Send ,
46
59
B :: Error : Into < Error > + Send + Sync ,
47
60
{
48
- type Response = http:: Response < UpgradeBody > ;
49
- type Error = hyper:: Error ;
50
- type Future = Pin <
51
- Box <
52
- dyn Future < Output = Result < http:: Response < UpgradeBody > , hyper:: Error > > + Send + ' static ,
53
- > ,
54
- > ;
61
+ type Response = http:: Response < BoxBody > ;
62
+ type Error = Error ;
63
+ type Future = Pin < Box < dyn Future < Output = Result < http:: Response < BoxBody > > > + Send + ' static > > ;
55
64
65
+ #[ inline]
56
66
fn poll_ready ( & mut self , cx : & mut Context < ' _ > ) -> Poll < Result < ( ) , Self :: Error > > {
57
- self . h2 . poll_ready ( cx)
67
+ self . h2 . poll_ready ( cx) . map_err ( downgrade_h2_error )
58
68
}
59
69
60
70
fn call ( & mut self , mut req : http:: Request < B > ) -> Self :: Future {
61
71
debug_assert ! ( req. version( ) != http:: Version :: HTTP_2 ) ;
62
72
if req. extensions ( ) . get :: < upgrade:: Http11Upgrade > ( ) . is_some ( ) {
63
73
debug ! ( "Skipping orig-proto upgrade due to HTTP/1.1 upgrade" ) ;
64
- return self . http1 . request ( req) ;
74
+ return Box :: pin ( self . http1 . request ( req) . map_ok ( |rsp| rsp . map ( BoxBody :: new ) ) ) ;
65
75
}
66
76
67
77
let orig_version = req. version ( ) ;
@@ -89,28 +99,91 @@ where
89
99
90
100
* req. version_mut ( ) = http:: Version :: HTTP_2 ;
91
101
92
- Box :: pin ( self . h2 . call ( req) . map_ok ( move |mut rsp| {
93
- let version = rsp
94
- . headers_mut ( )
95
- . remove ( L5D_ORIG_PROTO )
96
- . and_then ( |orig_proto| {
97
- if orig_proto == "HTTP/1.1" {
98
- Some ( http:: Version :: HTTP_11 )
99
- } else if orig_proto == "HTTP/1.0" {
100
- Some ( http:: Version :: HTTP_10 )
101
- } else {
102
- None
103
- }
104
- } )
105
- . unwrap_or ( orig_version) ;
106
- trace ! ( ?version, "Downgrading response" ) ;
107
- * rsp. version_mut ( ) = version;
108
- rsp. map ( UpgradeBody :: from)
109
- } ) )
102
+ Box :: pin (
103
+ self . h2
104
+ . call ( req)
105
+ . map_err ( downgrade_h2_error)
106
+ . map_ok ( move |mut rsp| {
107
+ let version = rsp
108
+ . headers_mut ( )
109
+ . remove ( L5D_ORIG_PROTO )
110
+ . and_then ( |orig_proto| {
111
+ if orig_proto == "HTTP/1.1" {
112
+ Some ( http:: Version :: HTTP_11 )
113
+ } else if orig_proto == "HTTP/1.0" {
114
+ Some ( http:: Version :: HTTP_10 )
115
+ } else {
116
+ None
117
+ }
118
+ } )
119
+ . unwrap_or ( orig_version) ;
120
+ trace ! ( ?version, "Downgrading response" ) ;
121
+ * rsp. version_mut ( ) = version;
122
+ rsp. map ( |inner| BoxBody :: new ( UpgradeResponseBody { inner } ) )
123
+ } ) ,
124
+ )
125
+ }
126
+ }
127
+
128
+ /// Handles HTTP/2 client errors for HTTP/1.1 requests by wrapping the error type. This
129
+ /// simplifies error handling elsewhere so that HTTP/2 errors can only be encountered when the
130
+ /// original request was HTTP/2.
131
+ fn downgrade_h2_error ( error : hyper:: Error ) -> Error {
132
+ use std:: error:: Error ;
133
+
134
+ let mut cause = error. source ( ) ;
135
+ while let Some ( e) = cause {
136
+ if let Some ( e) = e. downcast_ref :: < h2:: H2Error > ( ) {
137
+ if let Some ( reason) = e. reason ( ) {
138
+ return DowngradedH2Error ( reason) . into ( ) ;
139
+ }
140
+ }
141
+
142
+ cause = error. source ( ) ;
143
+ }
144
+
145
+ error. into ( )
146
+ }
147
+
148
+ // === impl UpgradeResponseBody ===
149
+
150
+ impl Default for UpgradeResponseBody {
151
+ fn default ( ) -> Self {
152
+ UpgradeResponseBody {
153
+ inner : Default :: default ( ) ,
154
+ }
155
+ }
156
+ }
157
+
158
+ impl HttpBody for UpgradeResponseBody {
159
+ type Data = bytes:: Bytes ;
160
+ type Error = Error ;
161
+
162
+ #[ inline]
163
+ fn is_end_stream ( & self ) -> bool {
164
+ self . inner . is_end_stream ( )
165
+ }
166
+
167
+ fn poll_data (
168
+ self : Pin < & mut Self > ,
169
+ cx : & mut Context < ' _ > ,
170
+ ) -> Poll < Option < Result < Self :: Data , Self :: Error > > > {
171
+ Pin :: new ( self . project ( ) . inner )
172
+ . poll_data ( cx)
173
+ . map_err ( downgrade_h2_error)
174
+ }
175
+
176
+ fn poll_trailers (
177
+ self : Pin < & mut Self > ,
178
+ cx : & mut Context < ' _ > ,
179
+ ) -> Poll < Result < Option < http:: HeaderMap > , Self :: Error > > {
180
+ Pin :: new ( self . project ( ) . inner )
181
+ . poll_trailers ( cx)
182
+ . map_err ( downgrade_h2_error)
110
183
}
111
184
}
112
185
113
- // ===== impl Downgrade == ===
186
+ // === impl Downgrade ===
114
187
115
188
impl < S > Downgrade < S > {
116
189
pub fn layer ( ) -> impl layer:: Layer < S , Service = Self > + Copy + Clone {
0 commit comments