22
33//! Test cases for gzip response compression.
44
5+ use bytes:: Bytes ;
56use dropshot:: endpoint;
67use dropshot:: ApiDescription ;
78use dropshot:: HttpError ;
89use dropshot:: HttpResponseOk ;
910use dropshot:: RequestContext ;
11+ use futures:: stream;
1012use http:: { header, Method , StatusCode } ;
13+ use http_body_util:: StreamBody ;
14+ use hyper:: body:: Frame ;
1115use hyper:: { Request , Response } ;
1216use serde:: { Deserialize , Serialize } ;
1317
@@ -100,6 +104,51 @@ struct TinyData {
100104 x : u8 ,
101105}
102106
107+ const STREAMING_TEXT_CHUNK : & str = "{\" message\" :\" streaming chunk\" }\n " ;
108+ const STREAMING_TEXT_CHUNK_COUNT : usize = 32 ;
109+
110+ fn streaming_payload ( ) -> Vec < u8 > {
111+ STREAMING_TEXT_CHUNK . repeat ( STREAMING_TEXT_CHUNK_COUNT ) . into_bytes ( )
112+ }
113+
114+ fn streaming_body_stream (
115+ ) -> impl futures:: Stream < Item = Result < Frame < Bytes > , std:: io:: Error > > + Send {
116+ stream:: iter ( ( 0 ..STREAMING_TEXT_CHUNK_COUNT ) . map ( |_| {
117+ Result :: < Frame < Bytes > , std:: io:: Error > :: Ok ( Frame :: data (
118+ Bytes :: from_static ( STREAMING_TEXT_CHUNK . as_bytes ( ) ) ,
119+ ) )
120+ } ) )
121+ }
122+
123+ #[ endpoint {
124+ method = GET ,
125+ path = "/streaming-missing-content-type" ,
126+ } ]
127+ async fn streaming_without_content_type (
128+ _rqctx : RequestContext < usize > ,
129+ ) -> Result < Response < dropshot:: Body > , HttpError > {
130+ let body = dropshot:: Body :: wrap ( StreamBody :: new ( streaming_body_stream ( ) ) ) ;
131+ Response :: builder ( )
132+ . status ( StatusCode :: OK )
133+ . body ( body)
134+ . map_err ( |e| HttpError :: for_internal_error ( e. to_string ( ) ) )
135+ }
136+
137+ #[ endpoint {
138+ method = GET ,
139+ path = "/streaming-with-content-type" ,
140+ } ]
141+ async fn streaming_with_content_type (
142+ _rqctx : RequestContext < usize > ,
143+ ) -> Result < Response < dropshot:: Body > , HttpError > {
144+ let body = dropshot:: Body :: wrap ( StreamBody :: new ( streaming_body_stream ( ) ) ) ;
145+ Response :: builder ( )
146+ . status ( StatusCode :: OK )
147+ . header ( header:: CONTENT_TYPE , "text/plain" )
148+ . body ( body)
149+ . map_err ( |e| HttpError :: for_internal_error ( e. to_string ( ) ) )
150+ }
151+
103152fn api ( ) -> ApiDescription < usize > {
104153 let mut api = ApiDescription :: new ( ) ;
105154 api. register ( api_large_response) . unwrap ( ) ;
@@ -110,6 +159,8 @@ fn api() -> ApiDescription<usize> {
110159 api. register ( api_xml_suffix_response) . unwrap ( ) ;
111160 api. register ( api_no_content_response) . unwrap ( ) ;
112161 api. register ( api_not_modified_response) . unwrap ( ) ;
162+ api. register ( streaming_without_content_type) . unwrap ( ) ;
163+ api. register ( streaming_with_content_type) . unwrap ( ) ;
113164 api
114165}
115166
@@ -351,14 +402,12 @@ async fn test_no_gzip_without_accept_encoding() {
351402}
352403
353404#[ tokio:: test]
354- async fn test_no_compression_for_streaming_responses ( ) {
355- // Test that streaming responses are not compressed even when client accepts gzip
356- let testctx =
357- test_setup ( "no_compression_streaming" , crate :: streaming:: api ( ) ) ;
405+ async fn test_streaming_without_content_type_skips_compression ( ) {
406+ let testctx = test_setup ( "streaming_missing_content_type" , api ( ) ) ;
358407 let client = & testctx. client_testctx ;
359408
360409 // Make request with Accept-Encoding: gzip header
361- let uri = client. url ( "/streaming" ) ;
410+ let uri = client. url ( "/streaming-missing-content-type " ) ;
362411 let request = hyper:: Request :: builder ( )
363412 . method ( http:: Method :: GET )
364413 . uri ( & uri)
@@ -380,9 +429,40 @@ async fn test_no_compression_for_streaming_responses() {
380429
381430 assert_eq ! ( response. headers( ) . get( header:: CONTENT_ENCODING ) , None ) ;
382431
383- // Consume the body to verify it works ( and to allow teardown to proceed)
432+ // Consume stream and confirm body is the uncompressed payload
384433 let body_bytes = get_response_bytes ( & mut response) . await ;
385- assert ! ( !body_bytes. is_empty( ) , "Streaming response should have content" ) ;
434+ assert_eq ! ( body_bytes, streaming_payload( ) ) ;
435+
436+ testctx. teardown ( ) . await ;
437+ }
438+
439+ #[ tokio:: test]
440+ async fn test_streaming_with_content_type_is_compressed ( ) {
441+ let testctx = test_setup ( "streaming_with_content_type_compressed" , api ( ) ) ;
442+ let client = & testctx. client_testctx ;
443+
444+ let uri = client. url ( "/streaming-with-content-type" ) ;
445+ let request = hyper:: Request :: builder ( )
446+ . method ( http:: Method :: GET )
447+ . uri ( & uri)
448+ . header ( http:: header:: ACCEPT_ENCODING , "gzip" )
449+ . body ( dropshot:: Body :: empty ( ) )
450+ . expect ( "Failed to construct request" ) ;
451+
452+ let mut response = client
453+ . make_request_with_request ( request, http:: StatusCode :: OK )
454+ . await
455+ . expect ( "Streaming request with content type should succeed" ) ;
456+
457+ assert_gzip_encoded ( & response) ;
458+ assert_eq ! (
459+ response. headers( ) . get( header:: CONTENT_TYPE ) ,
460+ Some ( & header:: HeaderValue :: from_static( "text/plain" ) )
461+ ) ;
462+
463+ let compressed_body = get_response_bytes ( & mut response) . await ;
464+ let decompressed = decompress_gzip ( & compressed_body) ;
465+ assert_eq ! ( decompressed, streaming_payload( ) , ) ;
386466
387467 testctx. teardown ( ) . await ;
388468}
0 commit comments