22title : Echo, echo, echo
33---
44
5- You already have a [ Hello World server] ( ../ hello-world) ? Excellent! Usually,
5+ You already have a [ Hello World server] ( hello-world.md ) ? Excellent! Usually,
66servers do more than just spit out the same body for every request. To
77exercise several more parts of hyper, this guide will go through
88building an echo server.
@@ -17,142 +17,138 @@ routing. We want to have a route explaining instructions on how to use
1717our server, and another for receiving data. Oh, and we should also
1818handle the case when someone asks for a route we don't know!
1919
20- Before we get started we need to add some new imports:
20+ We're going to be using more of the [ futures] [ future-crate ] crate, so let's add
21+ that as a dependency:
22+
23+ ``` toml
24+ [dependencies ]
25+ hyper = { version = " 0.14" , features = [" full" ] }
26+ tokio = { version = " 1" , features = [" full" ] }
27+ futures = " 0.3"
28+ ```
29+
30+ Then, we need to add some to our imports:
2131
2232``` rust
23- use hyper :: body :: Frame ;
33+ # extern crate hyper;
34+ # extern crate futures;
2435use hyper :: {Method , StatusCode };
25- use http_body_util :: {combinators :: BoxBody , BodyExt };
2636# fn main () {}
2737```
2838
29- Next, we need to make some changes to our ` Service ` function, but as you can see
30- it's still just an async function that takes a ` Request ` and returns a ` Response `
31- future, and you can pass it to your server just like we did for the ` hello ` service.
32-
33- Unlike our ` hello ` service where we didn't care about the request body and
34- we always returned a single chunk of bytes containing our greeting, we're now
35- going to want a bit more freedom in how we shape our response ` Body ` . To achieve
36- this we will change the type of the ` Body ` in our ` Response ` to a boxed trait object.
37- We only care that the response body implements the [ Body] ( https://docs.rs/http-body/1.0.0-rc1/http_body/trait.Body.html ) trait, that its data is ` Bytes ` and its error is a ` hyper::Error ` .
39+ And make some changes to your ` Service ` , such as returning a ` Future ` of a ` Response ` ,
40+ since we may not have one ready immediately:
3841
3942``` rust
40- # use bytes :: Bytes ;
41- # use http_body_util :: {combinators :: BoxBody , BodyExt , Empty , Full };
42- # use hyper :: {Method , Request , Response , StatusCode };
43- async fn echo (
44- req : Request <hyper :: body :: Incoming >,
45- ) -> Result <Response <BoxBody <Bytes , hyper :: Error >>, hyper :: Error > {
43+ # extern crate futures;
44+ # extern crate hyper;
45+ # use futures :: future :: {self , Future };
46+ # use hyper :: {Body , Method , Request , Response , StatusCode };
47+ #
48+ async fn echo (req : Request <Body >) -> Result <Response <Body >, hyper :: Error > {
49+ let mut response = Response :: new (Body :: empty ());
50+
4651 match (req . method (), req . uri (). path ()) {
47- (& Method :: GET , " /" ) => Ok ( Response :: new ( full (
48- " Try POSTing data to /echo" ,
49- ))) ,
52+ (& Method :: GET , " /" ) => {
53+ * response . body_mut () = Body :: from ( " Try POSTing data to /echo" );
54+ } ,
5055 (& Method :: POST , " /echo" ) => {
5156 // we'll be back
5257 },
53-
54- // Return 404 Not Found for other routes.
5558 _ => {
56- let mut not_found = Response :: new (empty ());
57- * not_found . status_mut () = StatusCode :: NOT_FOUND ;
58- Ok (not_found )
59- }
60- }
61- }
62- // We create some utility functions to make Empty and Full bodies
63- // fit our broadened Response body type.
64- fn empty () -> BoxBody <Bytes , hyper :: Error > {
65- Empty :: <Bytes >:: new ()
66- . map_err (| never | match never {})
67- . boxed ()
68- }
69- fn full <T : Into <Bytes >>(chunk : T ) -> BoxBody <Bytes , hyper :: Error > {
70- Full :: new (chunk . into ())
71- . map_err (| never | match never {})
72- . boxed ()
59+ * response . status_mut () = StatusCode :: NOT_FOUND ;
60+ },
61+ };
62+
63+ Ok (response )
7364}
7465# fn main () {}
7566```
7667
77- We built a super simple routing table just by matching on the ` method ` and ` path `
78- of an incoming ` Request ` . If someone requests ` GET / ` , our service will let them
79- know they should try our echo powers out. We also check for ` POST /echo ` , but
80- currently don't do anything about it.
68+ We built a super simple routing table just by matching on the ` method `
69+ and ` path ` of an incoming ` Request ` . If someone requests ` GET / ` , our
70+ service will let them know they should try our echo powers out. We also
71+ are checking for ` POST /echo ` , but currently don't do anything about it.
8172
82- Our third rule catches any other method and path combination, and changes the
83- ` StatusCode ` of the ` Response ` . The default status of a ` Response ` is HTTP’s
84- ` 200 OK ` (` StatusCode::OK ` ), which is correct for the other routes. But the third
85- case will instead send back ` 404 Not Found ` .
73+ Our third rule catches any other method and path combination, and
74+ changes the ` StatusCode ` of the ` Response ` . The default status of a
75+ ` Response ` is HTTP's ` 200 OK ` (` StatusCode::OK ` ), which is correct for
76+ the other routes. But the third case will instead send back `404 Not
77+ Found`.
8678
8779## Body Streams
8880
89- Now let's get that echo in place. An HTTP body is a stream of
90- ` Frames ` , each [ Frame] ( https://docs.rs/http-body/1.0.0-rc1/http_body/struct.Frame.html )
91- containing parts of the ` Body ` data or trailers. So rather than reading the entire ` Body `
92- into a buffer before sending our response, we can stream each frame as it arrives.
93- We'll start with the simplest solution, and then make alterations exercising more complex
94- things you can do with the ` Body ` streams.
81+ Now let's get that echo in place. We'll start with the simplest solution, and
82+ then make alterations exercising more complex things you can do with the
83+ ` Body ` streams.
9584
9685First up, plain echo. Both the ` Request ` and the ` Response ` have body streams,
97- and by default, you can easily pass the ` Body ` of the ` Request ` into a ` Response ` .
86+ and by default, you can easily just pass the ` Body ` of the ` Request ` into the
87+ ` Response ` .
9888
9989``` rust
100- # use bytes :: Bytes ;
101- # use http_body_util :: {combinators :: BoxBody , BodyExt , Empty , Full };
102- # use hyper :: {Method , Request , Response , StatusCode };
103- # async fn echo (
104- # req : Request <hyper :: body :: Incoming >,
105- # ) -> Result <Response <BoxBody <Bytes , hyper :: Error >>, hyper :: Error > {
106- # match (req . method (), req . uri (). path ()) {
107- // Inside the match from before
108- (& Method :: POST , " /echo" ) => Ok (Response :: new (req . into_body (). boxed ())),
109- # _ => unreachable! (),
110- # }
90+ # extern crate hyper;
91+ # use hyper :: {Body , Method , Request , Response };
92+ # fn echo (req : Request <Body >) -> Response <Body > {
93+ # let mut response = Response :: default ();
94+ # match (req . method (), req . uri (). path ()) {
95+ // inside that match from before
96+ (& Method :: POST , " /echo" ) => {
97+ * response . body_mut () = req . into_body ();
98+ },
99+ # _ => unreachable! (),
100+ # }
101+ # response
111102# }
112103# fn main () {}
113104```
114105
115106Running our server now will echo any data we ` POST ` to ` /echo ` . That was easy.
116107What if we wanted to uppercase all the text? We could use a ` map ` on our streams.
117108
118- ## Body mapping
109+ ### Body mapping
110+
111+ We're going to need a couple of extra imports, so let's add those to the top of the file:
112+
113+ ``` rust
114+ # extern crate futures;
115+ # extern crate hyper;
116+ use futures :: TryStreamExt as _;
117+ # fn main () {}
118+ ```
119119
120- Every data ` Frame ` of our body stream is a chunk of bytes, which we can conveniently
121- represent using the ` Bytes ` type from hyper. It can be easily converted into other
120+ A ` Body ` implements the ` Stream ` trait from futures, producing a bunch of
121+ ` Bytes ` s, as data comes in. ` Bytes ` is just a convenient type from hyper
122+ that represents a bunch of bytes. It can be easily converted into other
122123typical containers of bytes.
123124
124- Next, let's add a new ` /echo/uppercase ` route, mapping each byte in the data ` Frame ` s
125- of our request body to uppercase, and returning the stream in our ` Response ` :
125+ Next, let's add a new ` /echo/uppercase ` route mapping the body to uppercase:
126126
127127``` rust
128- # use bytes :: Bytes ;
129- # use http_body_util :: {combinators :: BoxBody , BodyExt , Empty , Full };
130- # use hyper :: body :: Frame ;
131- # use hyper :: {Method , Request , Response , StatusCode };
132- # async fn echo (
133- # req : Request <hyper :: body :: Incoming >,
134- # ) -> Result <Response <BoxBody <Bytes , hyper :: Error >>, hyper :: Error > {
135- # match (req . method (), req . uri (). path ()) {
128+ # extern crate hyper;
129+ # extern crate futures;
130+ # use futures :: TryStreamExt as _;
131+ # use hyper :: {Body , Method , Request , Response };
132+ # fn echo (req : Request <Body >) -> Response <Body > {
133+ # let mut response = Response :: default ();
134+ # match (req . method (), req . uri (). path ()) {
136135// Yet another route inside our match block...
137136(& Method :: POST , " /echo/uppercase" ) => {
138- // Map this body's frame to a different type
139- let frame_stream = req . into_body () . map_frame ( | frame | {
140- let frame = if let Some ( data ) = frame . into_data () {
141- // Convert every byte in every Data frame to uppercase
142- data . iter ()
137+ // This is actually a new `futures::Stream`...
138+ let mapping = req
139+ . into_body ()
140+ . map_ok ( | chunk | {
141+ chunk . iter ()
143142 . map (| byte | byte . to_ascii_uppercase ())
144- . collect :: <Bytes >()
145- } else {
146- Bytes :: new ()
147- };
143+ . collect :: <Vec <u8 >>()
144+ });
148145
149- Frame :: data (frame )
150- });
151-
152- Ok (Response :: new (frame_stream . boxed ()))
146+ // Use `Body::wrap_stream` to convert it to a `Body`...
147+ * response . body_mut () = Body :: wrap_stream (mapping );
153148},
154149# _ => unreachable! (),
155150# }
151+ # response
156152# }
157153# fn main () {}
158154```
@@ -168,40 +164,38 @@ back to us? We can't really stream the data as it comes in, since we need to
168164find the end before we can respond. To do this, we can explore how to easily
169165collect the full body.
170166
171- We want to collect the entire request body and map the result into our ` reverse `
172- function, then return the eventual result. If we import the ` http_body_util::BodyExt `
173- extension trait, we can call the ` collect ` method on our body, which will drive the
174- stream to completion, collecting all the data and trailer frames into a ` Collected ` type.
175- We can easily turn the ` Collected ` body into a single ` Bytes ` by calling its ` into_bytes `
176- method.
167+ In this case, we can't really generate a ` Response ` immediately. Instead, we
168+ must wait for the full request body to be received.
169+
170+ We want to concatenate the request body, and map the result into our ` reverse ` function, and return the eventual result. We can make use of the ` hyper::body::to_bytes ` utility function to make this easy.
177171
178172``` rust
179- # use bytes :: Bytes ;
180- # use http_body_util :: {combinators :: BoxBody , BodyExt , Empty , Full };
181- # use hyper :: {Method , Request , Response , StatusCode };
182- # async fn echo (
183- # req : Request <hyper :: body :: Incoming >,
184- # ) -> Result <Response <BoxBody <Bytes , hyper :: Error >>, hyper :: Error > {
185- # match (req . method (), req . uri (). path ()) {
173+ # extern crate hyper;
174+ # use hyper :: {Body , Method , Request , Response };
175+ # async fn echo (req : Request <Body >) -> Result <Response <Body >, hyper :: Error > {
176+ # let mut response = Response :: default ();
177+ # match (req . method (), req . uri (). path ()) {
186178// Yet another route inside our match block...
187- (& Method :: POST , " /echo/reversed " ) => {
188- // Await the whole body to be collected into a single `Bytes`...
189- let whole_body = req . collect () . await ? . to_bytes () ;
179+ (& Method :: POST , " /echo/reverse " ) => {
180+ // Await the full body to be concatenated into a single `Bytes`...
181+ let full_body = hyper :: body :: to_bytes ( req . into_body ()) . await ? ;
190182
191- // Iterate the whole body in reverse order and collect into a new Vec.
192- let reversed_body = whole_body . iter ()
183+ // Iterate the full body in reverse order and collect into a new Vec.
184+ let reversed = full_body . iter ()
193185 . rev ()
194186 . cloned ()
195187 . collect :: <Vec <u8 >>();
196188
197- Ok ( Response :: new ( full ( reversed_body )))
189+ * response . body_mut () = reversed . into ();
198190},
199191# _ => unreachable! (),
200192# }
193+ # Ok (response )
201194# }
202195# fn main () {}
203196```
204197
205198You can see a compiling [ example here] [ example ] .
206199
207200[ example] : {{ site.examples_url }}/echo.rs
201+ [ future-crate ] : https://github.com/rust-lang-nursery/futures-rs
0 commit comments