1
1
pub mod http {
2
2
use std:: collections:: HashMap ;
3
3
use std:: env;
4
+ use std:: fmt;
4
5
use std:: io:: { self , BufRead , BufReader , Read , Write } ;
5
6
use std:: net:: TcpStream ;
6
7
use std:: sync:: Arc ;
@@ -10,12 +11,6 @@ pub mod http {
10
11
use rustls:: { ClientConfig , ClientSession , StreamOwned } ;
11
12
use webpki:: DNSNameRef ;
12
13
13
- const UNREACHABLE : & str = "Unreachable" ;
14
- const MALFORMED_URL : & str = "Malformed URL" ;
15
- const CONNECTION_ERROR : & str = "Connection error" ;
16
- const MALFORMED_RESPONSE : & str = "Malformed response" ;
17
- const UNSUPPORTED_ENCODING : & str = "Unsupported encoding" ;
18
-
19
14
enum Stream {
20
15
Tcp ( TcpStream ) ,
21
16
Tls ( StreamOwned < ClientSession , TcpStream > ) ,
@@ -105,46 +100,77 @@ pub mod http {
105
100
}
106
101
}
107
102
108
- pub fn request ( url : & str ) -> Result < ( HashMap < String , String > , Vec < u8 > ) , String > {
103
+ #[ derive( Debug ) ]
104
+ pub enum RequestError {
105
+ Unreachable ,
106
+ MalformedUrl ,
107
+ UnknownScheme ( String ) ,
108
+ ConnectionError ,
109
+ StatusError ( String , String ) ,
110
+ MalformedResponse ,
111
+ UnsupportedEncoding ,
112
+ }
113
+
114
+ impl fmt:: Display for RequestError {
115
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
116
+ match self {
117
+ RequestError :: Unreachable => f. write_str ( "Unreachable" ) ,
118
+ RequestError :: MalformedUrl => f. write_str ( "Malformed URL" ) ,
119
+ RequestError :: UnknownScheme ( scheme) => {
120
+ write ! ( f, "Unknown scheme: {}" , scheme)
121
+ }
122
+ RequestError :: ConnectionError => f. write_str ( "Connection error" ) ,
123
+ RequestError :: StatusError ( status, reason) => {
124
+ write ! ( f, "Status error: {} {}" , status, reason)
125
+ }
126
+ RequestError :: MalformedResponse => f. write_str ( "Malformed response" ) ,
127
+ RequestError :: UnsupportedEncoding => f. write_str ( "Unsupported encoding" ) ,
128
+ }
129
+ }
130
+ }
131
+
132
+ impl std:: error:: Error for RequestError { }
133
+
134
+ pub fn request ( url : & str ) -> Result < ( HashMap < String , String > , Vec < u8 > ) , RequestError > {
109
135
// 1. Parse scheme
110
136
let ( scheme, url) = split2 ( url, ":" ) . unwrap_or ( ( "https" , url) ) ;
111
137
let default_port = match scheme {
112
138
"http" => 80 ,
113
139
"https" => 443 ,
114
140
"data" => {
115
141
// Exercise data scheme
116
- let ( content_type, body) = split2 ( url, "," ) . ok_or ( MALFORMED_URL ) ?;
142
+ let ( content_type, body) = split2 ( url, "," ) . ok_or ( RequestError :: MalformedUrl ) ?;
117
143
let mut headers = HashMap :: new ( ) ;
118
144
headers. insert ( "content-type" . to_owned ( ) , content_type. to_owned ( ) ) ;
119
145
return Ok ( ( headers, body. as_bytes ( ) . to_vec ( ) ) ) ;
120
146
}
121
- _ => panic ! ( "Unknown scheme {}" , scheme ) ,
147
+ _ => return Err ( RequestError :: UnknownScheme ( scheme. to_string ( ) ) ) ,
122
148
} ;
123
149
let url = url. strip_prefix ( "//" ) . unwrap_or ( url) ;
124
150
125
151
// 2. Parse host
126
- let ( host, path) = split2 ( url, "/" ) . ok_or ( MALFORMED_URL ) ?;
152
+ let ( host, path) = split2 ( url, "/" ) . ok_or ( RequestError :: MalformedUrl ) ?;
127
153
let path = format ! ( "/{}" , path) ;
128
154
129
155
// 3. Parse port
130
156
let ( host, port) = if host. contains ( ':' ) {
131
- let ( host, port) = split2 ( host, ":" ) . ok_or ( UNREACHABLE ) ?;
132
- let port = port. parse ( ) . map_err ( |_| MALFORMED_URL ) ?;
157
+ let ( host, port) = split2 ( host, ":" ) . ok_or ( RequestError :: Unreachable ) ?;
158
+ let port = port. parse ( ) . or ( Err ( RequestError :: MalformedUrl ) ) ?;
133
159
( host, port)
134
160
} else {
135
161
( host, default_port)
136
162
} ;
137
163
138
164
// 4. Connect
139
- let stream = TcpStream :: connect ( ( host, port) ) . map_err ( |_| CONNECTION_ERROR ) ?;
165
+ let stream = TcpStream :: connect ( ( host, port) ) . or ( Err ( RequestError :: ConnectionError ) ) ?;
140
166
let mut stream = if scheme != "https" {
141
167
Stream :: Tcp ( stream)
142
168
} else {
143
169
let mut config = ClientConfig :: new ( ) ;
144
170
config
145
171
. root_store
146
172
. add_server_trust_anchors ( & webpki_roots:: TLS_SERVER_ROOTS ) ;
147
- let host = DNSNameRef :: try_from_ascii_str ( host) . map_err ( |_| MALFORMED_URL ) ?;
173
+ let host = DNSNameRef :: try_from_ascii_str ( host) . or ( Err ( RequestError :: MalformedUrl ) ) ?;
148
174
let client = ClientSession :: new ( & Arc :: new ( config) , host) ;
149
175
let stream = StreamOwned :: new ( client, stream) ;
150
176
Stream :: Tls ( stream)
@@ -163,7 +189,7 @@ pub mod http {
163
189
host,
164
190
env:: consts:: OS
165
191
)
166
- . map_err ( |_| CONNECTION_ERROR ) ?;
192
+ . or ( Err ( RequestError :: ConnectionError ) ) ?;
167
193
168
194
// 6. Receive response
169
195
let mut reader = BufReader :: new ( stream) ;
@@ -172,16 +198,21 @@ pub mod http {
172
198
let mut line = String :: new ( ) ;
173
199
reader
174
200
. read_line ( & mut line)
175
- . map_err ( |_| MALFORMED_RESPONSE ) ?;
201
+ . or ( Err ( RequestError :: MalformedResponse ) ) ?;
176
202
177
203
// 8. Parse status line
178
- let ( _version, status) = split2 ( & line, " " ) . ok_or ( MALFORMED_RESPONSE ) ?;
179
- let ( status, explanation) = split2 ( status, " " ) . ok_or ( MALFORMED_RESPONSE ) ?;
204
+ let ( _version, status) = split2 ( & line, " " ) . ok_or ( RequestError :: MalformedResponse ) ?;
205
+ let ( status, explanation) = split2 ( status, " " ) . ok_or ( RequestError :: MalformedResponse ) ?;
180
206
181
207
// 9. Check status
182
208
match status {
183
209
"200" | "301" | "302" => ( ) ,
184
- _ => panic ! ( "{}: {}" , status, explanation) ,
210
+ _ => {
211
+ return Err ( RequestError :: StatusError (
212
+ status. to_string ( ) ,
213
+ explanation. to_string ( ) ,
214
+ ) )
215
+ }
185
216
} ;
186
217
187
218
// 10. Parse headers
@@ -190,11 +221,11 @@ pub mod http {
190
221
line. clear ( ) ;
191
222
reader
192
223
. read_line ( & mut line)
193
- . map_err ( |_| MALFORMED_RESPONSE ) ?;
224
+ . or ( Err ( RequestError :: MalformedResponse ) ) ?;
194
225
if line == "\r \n " {
195
226
break ;
196
227
}
197
- let ( header, value) = split2 ( & line, ":" ) . ok_or ( MALFORMED_RESPONSE ) ?;
228
+ let ( header, value) = split2 ( & line, ":" ) . ok_or ( RequestError :: MalformedResponse ) ?;
198
229
let header = header. to_ascii_lowercase ( ) ;
199
230
let value = value. trim ( ) ;
200
231
headers. insert ( header, value. to_string ( ) ) ;
@@ -205,7 +236,9 @@ pub mod http {
205
236
}
206
237
207
238
let content_encoding: ContentEncoding = match headers. get ( "content-encoding" ) {
208
- Some ( encoding) => encoding. parse ( ) . map_err ( |_| UNSUPPORTED_ENCODING ) ?,
239
+ Some ( encoding) => encoding
240
+ . parse ( )
241
+ . or ( Err ( RequestError :: UnsupportedEncoding ) ) ?,
209
242
None => ContentEncoding :: Identity ,
210
243
} ;
211
244
@@ -220,15 +253,15 @@ pub mod http {
220
253
let mut line = String :: new ( ) ;
221
254
reader
222
255
. read_line ( & mut line)
223
- . map_err ( |_| MALFORMED_RESPONSE ) ?;
256
+ . or ( Err ( RequestError :: MalformedResponse ) ) ?;
224
257
let n_bytes = u64:: from_str_radix ( line. trim_end ( ) , 16 ) . unwrap_or ( 0 ) ;
225
258
if n_bytes == 0 {
226
259
break ;
227
260
}
228
261
let mut chunk = vec ! [ 0u8 ; n_bytes as usize ] ;
229
262
reader
230
263
. read_exact ( & mut chunk)
231
- . map_err ( |_| MALFORMED_RESPONSE ) ?;
264
+ . or ( Err ( RequestError :: MalformedResponse ) ) ?;
232
265
reader. read_exact ( & mut vec ! [ 0u8 ; 2 ] ) . unwrap ( ) ;
233
266
unchunked. write_all ( & chunk) . unwrap ( ) ;
234
267
}
@@ -243,8 +276,7 @@ pub mod http {
243
276
let mut body = Vec :: new ( ) ;
244
277
reader
245
278
. read_to_end ( & mut body)
246
- . map_err ( |_| MALFORMED_RESPONSE )
247
- . unwrap ( ) ;
279
+ . or ( Err ( RequestError :: MalformedResponse ) ) ?;
248
280
body
249
281
} ;
250
282
0 commit comments