RESTinio
zlib.hpp
Go to the documentation of this file.
1 /*
2  restinio
3 */
4 
5 /*!
6  Transformator of data streams using zlib.
7 
8  @since v.0.4.4
9 */
10 
11 #pragma once
12 
13 #include <string>
14 #include <cstring>
15 
16 #include <fmt/format.h>
17 
18 #include <zlib.h>
19 
20 #include <restinio/exception.hpp>
21 #include <restinio/string_view.hpp>
22 #include <restinio/message_builders.hpp>
23 #include <restinio/request_handler.hpp>
24 
25 namespace restinio
26 {
27 
28 namespace transforms
29 {
30 
31 namespace zlib
32 {
33 
34 //! Default reserve buffer size for zlib transformator.
35 //! @since v.0.4.4
37 
38 /** @name Default values for zlib tuning parameters.
39  * @brief Constants are defined with values provided by zlib.
40  *
41  * @since v.0.4.4
42 */
43 ///@{
47 ///@}
48 
49 //
50 // params_t
51 //
52 
53 //! Parameters of performing data transformation with zlib.
54 /*
55  @since v.0.4.4
56 
57  \note There is a special case for compression format: format_t::identity.
58  If this format is set that zlib transformator is transparently copies
59  input to output and so all other params are ignored.
60 */
61 class params_t
62 {
63  public:
64  //! Types of transformation.
65  enum class operation_t
66  {
67  //! Compress the input data.
68  compress,
69  //! Decompress input data.
71  };
72 
73  //! Formats of compressed data.
74  enum class format_t
75  {
76  //! zlib format.
77  deflate,
78  //! gzip format
79  gzip,
80  //! Identity. With semantics descrobed here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding
81  /*
82  Means that no compression will be used and no header/trailer will be applied.
83  */
84  identity
85  };
86 
87  //! Init constructor.
88  /*!
89  It's better to use special functions to cunstruct
90  initial params_t, see:
91  deflate_compress(), deflate_decompress(),
92  gzip_compress(), gzip_decompress(),
93  */
95  //! Operation: compress or decompress.
96  operation_t op,
97  //! Foramt: deflate or gzip.
98  format_t f,
99  //! Compression level.
100  int l = -1 )
101  : m_operation{ op }
102  , m_format{ f }
103  {
104  level( l );
105  }
106 
107  //! Default constructor for identiry transformator
111  {
112  level( -1 );
113  }
114 
115  //! Get operation.
116  operation_t operation() const { return m_operation; }
117 
118  //! Get format.
119  format_t format() const { return m_format; }
120 
121  //! Get compression level.
122  /*!
123  \note Makes sense only for compression operation.
124  */
125  int level() const { return m_level; }
126 
127  //! Set compression level.
128  /*!
129  Must be an integer value in the range of -1 to 9.
130 
131  \note Makes sense only for compression operation.
132  */
133  params_t &
134  level( int level_value ) &
135  {
136  if( level_value < -1 || level_value > 9 )
137  {
138  throw exception_t{
139  fmt::format(
140  "invalid compression level: {}, must be "
141  "an integer value in the range of -1 to 9",
142  level_value ) };
143  }
144 
145  m_level = level_value;
146 
147  return reference_to_self();
148  }
149 
150  //! Set compression level.
151  params_t &&
152  level( int level_value ) &&
153  {
154  return std::move( this->level( level_value ) );
155  }
156 
157  //! Get window_bits.
158  int window_bits() const { return m_window_bits; }
159 
160  //! Set window_bits.
161  /*!
162  Must be an integer value in the range of 8 to MAX_WBITS.
163 
164  \note For descompress operation it is better to use default
165  value of windows beats. See https://zlib.net/manual.html
166  inflateInit2() description for details.
167  */
168  params_t &
169  window_bits( int window_bits_value ) &
170  {
171  // From https://zlib.net/manual.html:
172  // For the current implementation of deflate(),
173  // a windowBits value of 8 (a window size of 256 bytes)
174  // is not supported. As a result, a request for 8 will result in 9
175  // (a 512-byte window). In that case, providing 8 to inflateInit2()
176  // will result in an error when the zlib header with 9 is
177  // checked against the initialization of inflate().
178  // The remedy is to not use 8 with deflateInit2()
179  // with this initialization, or at least in that case use 9
180  // with inflateInit2().
181  // ...
182  // windowBits can also be zero to request that inflate
183  // use the window size in the zlib header of the compressed
184  // stream.
185 
186  if( ( window_bits_value < 8 || window_bits_value > MAX_WBITS ) &&
187  ( 0 != window_bits_value || operation_t::decompress != operation() ) )
188  {
189  throw exception_t{
190  fmt::format(
191  "invalid window_bits: {}, must be "
192  "an integer value in the range of 8 to {} or "
193  "0 for decompress operation",
194  window_bits_value,
195  MAX_WBITS ) };
196  }
197 
198  if( 8 == window_bits_value )
199  window_bits_value = 9;
200 
201  m_window_bits = window_bits_value;
202 
203  return reference_to_self();
204  }
205 
206  //! Set window_bits.
207  params_t &&
208  window_bits( int window_bits_value ) &&
209  {
210  return std::move( this->window_bits( window_bits_value ) );
211  }
212 
213  //! Get compression mem_level.
214  /*!
215  \note Makes sense only for compression operation.
216  */
217  int mem_level() const { return m_mem_level; }
218 
219  //! Set compression mem_level.
220  /*!
221  Must be an integer value in the range of 1 to 9.
222  1 stands for minimum memory usage and
223  9 stands for maximum memory usage.
224  The amount of memory that can be used
225  affects the quality of compression.
226 
227  \note Makes sense only for compression operation.
228  */
229  params_t &
230  mem_level( int mem_level_value ) &
231  {
232  if( mem_level_value < 1 || mem_level_value > MAX_MEM_LEVEL )
233  {
234  throw exception_t{
235  fmt::format(
236  "invalid compression mem_level: {}, must be "
237  "an integer value in the range of 1 to {}",
238  mem_level_value,
239  MAX_MEM_LEVEL ) };
240  }
241 
242  m_mem_level = mem_level_value;
243 
244  return reference_to_self();
245  }
246 
247  //! Set compression mem_level.
248  params_t &&
249  mem_level( int mem_level_value ) &&
250  {
251  return std::move( this->mem_level( mem_level_value ) );
252  }
253 
254  //! Get compression strategy.
255  /*!
256  \note Makes sense only for compression operation.
257  */
258  int strategy() const { return m_strategy; }
259 
260  //! Set compression strategy.
261  /*!
262  Must be an integer value defined by one of
263  zlib constants: Z_FILTERED, Z_HUFFMAN_ONLY,
264  Z_RLE, Z_DEFAULT_STRATEGY.
265 
266  \note Makes sense only for compression operation.
267  */
268  params_t &
269  strategy( int strategy_value ) &
270  {
271  if( Z_DEFAULT_STRATEGY != strategy_value &&
272  Z_FILTERED != strategy_value &&
273  Z_HUFFMAN_ONLY != strategy_value &&
274  Z_RLE != strategy_value )
275  {
276  throw exception_t{
277  fmt::format(
278  "invalid compression strategy: {}, must be "
279  "one of: "
280  "Z_DEFAULT_STRATEGY({}), ",
281  "Z_FILTERED({}), ",
282  "Z_HUFFMAN_ONLY({}), ",
283  "Z_RLE({})",
284  strategy_value,
286  Z_FILTERED,
288  Z_RLE ) };
289  }
290 
291  m_strategy = strategy_value;
292 
293  return reference_to_self();
294  }
295 
296  //! Set compression strategy.
297  params_t &&
298  strategy( int strategy_value ) &&
299  {
300  return std::move( this->strategy( strategy_value ) );
301  }
302 
303  //! Get the size initially reserved for buffer.
304  /*!
305  When using zlib transformator the outout buffer is used.
306  The initial size of such buffer must be defined.
307  zlib_t instance will use this parameter
308  as the initial size of out buffer and as an increment size
309  if out buffer must be enlarged.
310  */
311  std::size_t reserve_buffer_size() const { return m_reserve_buffer_size; }
312 
313  //! Set the size initially reserved for buffer.
314  params_t &
315  reserve_buffer_size( std::size_t size ) &
316  {
317  if( size < 10UL )
318  {
319  throw exception_t{ "too small reserve buffer size" };
320  }
321 
322  m_reserve_buffer_size = size;
323 
324  return reference_to_self();
325  }
326 
327  //! Set the size initially reserved for buffer.
328  params_t &&
329  reserve_buffer_size( std::size_t size ) &&
330  {
331  return std::move( this->reserve_buffer_size( size ) );
332  }
333 
334  private:
335  //! Get the reference to self.
336  params_t & reference_to_self() { return *this; }
337 
338  //! Transformation type.
340 
341  //! Format to be used for compressed data.
343 
344  //! Compression level.
345  /*!
346  An integer value in the range of -1 to 9.
347  */
348  int m_level;
349 
353 
354  //! Size initially reserved for buffer.
356 };
357 
358 /** @name Create parameters for zlib transformators.
359  * @brief A set of function helping to create params_t objects
360  * ommiting some verbose deteils.
361  *
362  * Instead of writing something like this:
363  * \code
364  * params_t params{
365  * restinio::transforms::zlib::params_t::operation_t::compress,
366  * restinio::transforms::zlib::params_t::format_t::gzip,
367  * -1 };
368  * \endcode
369  * It is better to write the following:
370  * \code
371  * params_t params = restinio::transforms::zlib::make_gzip_compress_params();
372  * \endcode
373  *
374  * @since v.0.4.4
375 */
376 ///@{
377 inline params_t
378 make_deflate_compress_params( int compression_level = -1 )
379 {
380  return params_t{
383  compression_level };
384 }
385 
386 inline params_t
388 {
389  return params_t{
392 }
393 
394 inline params_t
395 make_gzip_compress_params( int compression_level = -1 )
396 {
397  return params_t{
400  compression_level };
401 }
402 
403 inline params_t
405 {
406  return params_t{
409 }
410 
411 inline params_t
413 {
414  return params_t{};
415 }
416 ///@}
417 
418 //
419 // zlib_t
420 //
421 
422 //! Zlib transformator.
423 /*!
424  Uses zlib (https://zlib.net) for gzip/deflate compresion/decompression.
425  All higher level functionality that minimizes boilerplate code and makes
426  compression and decompression logic less verbose is based on using zlib_t.
427 
428  Simple usage:
429  \code
430  namespace rtz = restinio::transforms::zlib;
431  rtz::zlib_t z{ rtz::make_gzip_compress_params( compression_level ).level( 9 ) };
432  z.write( input_data );
433  z.complete();
434 
435  auto gziped_data = z.giveaway_output();
436  \endcode
437 
438  Advanced usage:
439  \code
440  namespace rtz = restinio::transforms::zlib;
441  rtz::zlib_t z{ rtz::make_gzip_compress_params( compression_level ).level( 9 ) };
442 
443  std::size_t processed_data = 0;
444  for( const auto d : data_pieces )
445  {
446  z.write( d );
447 
448  // Track how much data is already processed:
449  processed_data += d.size();
450 
451  if( processed_data > 1024 * 1024 )
452  {
453  // If more than 1Mb is processed do flush.
454  z.flush();
455  }
456 
457  if( z.output_size() > 100 * 1024 )
458  {
459  // If more than 100Kb of data is ready, then append it to something.
460  append_output( z.giveaway_output() );
461  }
462  }
463 
464  // Complete the stream and append remeining putput data.
465  z.complete();
466  append_output( z.giveaway_output() );
467  \endcode
468 
469 
470  @since v.0.4.4
471 */
472 class zlib_t
473 {
474  public:
475  zlib_t( const params_t & transformation_params )
477  {
478  if( !is_identity() )
479  {
480  // Setting allocator stuff before initializing
481  // TODO: allocation can be done with user defined allocator.
485 
486  // Track initialization result.
487  int init_result;
488 
489  // Compression.
490  auto current_window_bits = m_params.window_bits();
491 
492  if( params_t::format_t::gzip == m_params.format() )
493  {
494  current_window_bits += 16;
495  }
496 
497  if( params_t::operation_t::compress == m_params.operation() )
498  {
499  // zlib format.
500  init_result =
501  deflateInit2(
502  &m_zlib_stream,
503  m_params.level(),
504  Z_DEFLATED,
505  current_window_bits,
506  m_params.mem_level(),
507  m_params.strategy() );
508  }
509  else
510  {
511  init_result =
512  inflateInit2(
513  &m_zlib_stream,
514  current_window_bits );
515  }
516 
517  if( Z_OK != init_result )
518  {
519  throw exception_t{
520  fmt::format(
521  "Failed to initialize zlib stream: {}, {}",
522  init_result,
523  get_error_msg() ) };
524  }
525 
527 
528  // Reserve initial buffer.
529  inc_buffer();
530  }
531  // else => Nothing to initialize and to reserve.
532  }
533 
534  zlib_t( const zlib_t & ) = delete;
535  zlib_t( zlib_t && ) = delete;
536  zlib_t & operator = ( const zlib_t & ) = delete;
537  zlib_t & operator = ( zlib_t && ) = delete;
538 
540  {
542  {
543  if( params_t::operation_t::compress == m_params.operation() )
544  {
545  deflateEnd( &m_zlib_stream );
546  }
547  else
548  {
549  inflateEnd( &m_zlib_stream );
550  }
551  }
552  }
553 
554  //! Get parameters of current transformation.
555  const params_t & params() const { return m_params; }
556 
557  //! Append input data.
558  /*!
559  Pushes a given data to zlib transform.
560  \a input is the data to be compressed or decompressed.
561  */
562  void
563  write( string_view_t input )
564  {
566 
567  if( is_identity() )
568  {
569  m_out_buffer.append( input.data(), input.size() );
570  m_write_pos = m_out_buffer.size();
571  }
572  else
573  {
574  if( std::numeric_limits< decltype( m_zlib_stream.avail_in ) >::max() < input.size() )
575  {
576  throw exception_t{
577  fmt::format(
578  "input data is too large: {} (max possible: {}), "
579  "try to break large data into pieces",
580  input.size(),
581  std::numeric_limits< decltype( m_zlib_stream.avail_in ) >::max() ) };
582  }
583 
584  if( 0 < input.size() )
585  {
586  m_zlib_stream.next_in =
587  reinterpret_cast< Bytef* >( const_cast< char* >( input.data() ) );
588 
589  m_zlib_stream.avail_in = static_cast< uInt >( input.size() );
590 
591  if( params_t::operation_t::compress == m_params.operation() )
592  {
594  }
595  else
596  {
598  }
599  }
600  }
601  }
602 
603  //! Flush the zlib stream.
604  /*!
605  Flushes underlying zlib stream.
606  All pending output is flushed to the output buffer.
607  */
608  void
610  {
612 
613  if( !is_identity() )
614  {
615  m_zlib_stream.next_in = nullptr;
616  m_zlib_stream.avail_in = static_cast< uInt >( 0 );
617 
618  if( params_t::operation_t::compress == m_params.operation() )
619  {
621  }
622  else
623  {
625  }
626  }
627  }
628 
629  //! Complete the stream.
630  void
632  {
634 
635  if( !is_identity() )
636  {
637  m_zlib_stream.next_in = nullptr;
638  m_zlib_stream.avail_in = static_cast< uInt >( 0 );
639 
640  if( params_t::operation_t::compress == m_params.operation() )
641  {
643  }
644  else
645  {
647  }
648  }
649 
651  }
652 
653  //! Get current accumulated output data
654  /*!
655  On this request a current accumulated output data is reterned.
656  Move semantics is applied. Once current output is fetched
657  zlib_t object resets its internal out buffer.
658 
659  In the following code:
660  \code
661  restinio::transformator::zlib_t z{ restinio::transformator::gzip_compress() };
662 
663  z.write( A );
664  consume_out( z.giveaway_output() ); // (1)
665 
666  z.write( B );
667  z.write( C );
668  consume_out( z.giveaway_output() ); // (2)
669 
670  \endcode
671  At the point (2) `consume_out()` function receives
672  a string that is not an appended version of a string
673  received in point (1).
674  */
675  std::string
677  {
678  std::string result;
679  const auto data_size = m_write_pos;
680  std::swap( result, m_out_buffer );
681  m_write_pos = 0;
682  result.resize( data_size ); // Shrink output data.
683  return result;
684  }
685 
686  //! Get current output size.
687  auto output_size() const { return m_write_pos; }
688 
689  //! Is operation complete?
690  bool is_completed() const { return m_operation_is_complete; }
691 
692  private:
693  bool is_identity() const
694  {
695  return params_t::format_t::identity == m_params.format();
696  }
697 
698  //! Get zlib error message if it exists.
699  const char *
701  {
702  const char * err_msg = "<no zlib error description>";
703  if( m_zlib_stream.msg )
704  err_msg = m_zlib_stream.msg;
705 
706  return err_msg;
707  }
708 
709  //! Checks completion flag and throws if operation is is already completed.
710  void
712  {
713  if( is_completed() )
714  throw exception_t{ "zlib operation is already completed" };
715  }
716 
717  //! Increment internal buffer for receiving output.
718  void
720  {
721  m_out_buffer.resize(
722  m_out_buffer.size() + m_params.reserve_buffer_size() );
723  }
724 
725  //! Prepare out buffer for receiving data.
726  auto
728  {
729  m_zlib_stream.next_out =
730  reinterpret_cast< Bytef* >(
731  const_cast< char* >( m_out_buffer.data() + m_write_pos ) );
732 
733  const auto provided_out_buffer_size =
734  m_out_buffer.size() - m_write_pos;
735  m_zlib_stream.avail_out =
736  static_cast<uInt>( provided_out_buffer_size );
737 
738  return provided_out_buffer_size;
739  }
740 
741  //! Handle incoming data for compression operation.
742  /*
743  Data and its size must be already in
744  `m_zlib_stream.next_in`, `m_zlib_stream.avail_in`.
745  */
746  void
747  write_compress_impl( int flush )
748  {
749  while( true )
750  {
751  const auto provided_out_buffer_size = prepare_out_buffer();
752 
753  int operation_result = deflate( &m_zlib_stream, flush );
754 
755  if( !( Z_OK == operation_result ||
756  Z_BUF_ERROR == operation_result ||
757  ( Z_STREAM_END == operation_result && Z_FINISH == flush ) ) )
758  {
759  const char * err_msg = "<no error desc>";
760  if( m_zlib_stream.msg )
761  err_msg = m_zlib_stream.msg;
762 
763  throw exception_t{
764  fmt::format(
765  "unexpected result of deflate() (zlib): {}, {}",
766  operation_result,
767  err_msg ) };
768  }
769 
770  m_write_pos += provided_out_buffer_size - m_zlib_stream.avail_out;
771 
772  if( 0 == m_zlib_stream.avail_out && Z_STREAM_END != operation_result )
773  {
774  // Looks like not all the output was obtained.
775  // There is a minor chance that it just happened to
776  // occupy exactly the same space that was available,
777  // in that case it would go for a one redundant call to deflate.
778  inc_buffer();
779  continue;
780  }
781 
782  if( 0 == m_zlib_stream.avail_in )
783  {
784  // All the input was consumed.
785  break;
786  }
787  }
788  }
789 
790  //! Handle incoming data for decompression operation.
791  /*
792  Data and its size must be already in
793  `m_zlib_stream.next_in`, `m_zlib_stream.avail_in`.
794  */
795  void
797  {
798  while( true )
799  {
800  const auto provided_out_buffer_size = prepare_out_buffer();
801 
802  int operation_result = inflate( &m_zlib_stream, flush );
803  if( !( Z_OK == operation_result ||
804  Z_BUF_ERROR == operation_result ||
805  Z_STREAM_END == operation_result ) )
806  {
807  throw exception_t{
808  fmt::format(
809  "unexpected result of inflate() (zlib): {}, {}",
810  operation_result,
811  get_error_msg() ) };
812  }
813 
814  m_write_pos += provided_out_buffer_size - m_zlib_stream.avail_out;
815 
816  if( 0 == m_zlib_stream.avail_out && Z_STREAM_END != operation_result )
817  {
818  // Looks like not all the output was obtained.
819  // There is a minor chance that it just happened to
820  // occupy exactly the same space that was available,
821  // in that case it would go for a one redundant call to deflate.
822  inc_buffer();
823  continue;
824  }
825 
826  if( 0 == m_zlib_stream.avail_in )
827  {
828  // All the input was consumed.
829  break;
830  }
831  }
832  }
833 
834  //! Parameters for zlib.
836 
837  //! Flag: was m_zlib_stream initialized properly.
838  /*!
839  If deflateInit2()/inflateInit2() was completed successfully
840  it is needed to call deflateEnd()/inflateEnd().
841  */
843 
844  //! zlib stream.
845  z_stream m_zlib_stream;
846 
847  //! Output buffer.
849  //! Next write pos in out buffer.
851 
852  bool m_operation_is_complete{ false };
853 };
854 
855 /** @name Helper functions for doing zlib transformation with less boilerplate.
856  * @brief A set of handy functions helping to perform zlib transform in one line.
857  *
858  * Instead of writing something like this:
859  * \code
860  * restinio::transforms::zlib::zlib_t z{ restinio::transforms::zlib::gzip_compress() };
861  * z.write( data );
862  * z.complete();
863  * body = z.giveaway_output();
864  * \endcode
865  * It is possible to write the following:
866  * \code
867  * body = restinio::transformators::zlib::gzip_compress( data );
868  * \endcode
869  *
870  * @since v.0.4.4
871 */
872 ///@{
873 
874 //! Do a specified zlib transformation.
875 inline std::string
877 {
878  zlib_t z{ params };
879  z.write( input );
880  z.complete();
881 
882  return z.giveaway_output();
883 }
884 
885 inline std::string
887 {
888  return transform( input, make_deflate_compress_params( compression_level ) );
889 }
890 
891 inline std::string
893 {
894  return transform( input, make_deflate_decompress_params() );
895 }
896 
897 inline std::string
899 {
900  return transform( input, make_gzip_compress_params( compression_level ) );
901 }
902 
903 inline std::string
905 {
906  return transform( input, make_gzip_decompress_params() );
907 }
908 ///@}
909 
910 //
911 // body_appender_t
912 //
913 
914 template < typename Response_Output_Strategy >
916 {
917  body_appender_t() = delete;
918 };
919 
920 namespace impl
921 {
922 
923 //! Check if operation is a copression, and throw if it is not.
925 {
927  {
928  throw exception_t{ "operation is not copress" };
929  }
930 }
931 
932 //! Check if a pointer to zlib transformator is valid.
933 inline void ensure_valid_transforator( zlib_t * ztransformator )
934 {
935  if( nullptr == ztransformator )
936  {
937  throw exception_t{ "invalid body appender" };
938  }
939 }
940 
941 //! Get token for copression format.
943 {
944  std::string result{ "identity" };
945 
946  if( params_t::format_t::deflate == f )
947  {
948  result.assign( "deflate" );
949  }
950  if( params_t::format_t::gzip == f )
951  {
952  result.assign( "gzip" );
953  }
954 
955  return result;
956 }
957 
958 } /* namespace impl */
959 
960 //
961 // body_appender_base_t
962 //
963 
964 //! Base class for body appenders.
965 template < typename Response_Output_Strategy, typename Descendant >
967 {
968  public:
970 
971  body_appender_base_t( const params_t & params, resp_t & resp )
973  , m_resp{ resp }
974  {
977 
981  m_ztransformator->params().format() ) );
982  }
983 
984  body_appender_base_t( const body_appender_base_t & ) = delete;
985  body_appender_base_t & operator = ( const body_appender_base_t & ) = delete;
987 
990  , m_resp{ ba.m_resp }
991  {}
992 
993  virtual ~body_appender_base_t() {}
994 
995  protected:
998 };
999 
1000 //! Base class for body appenders with restinio or user controlled output.
1001 template < typename X_Controlled_Output, typename Descendant >
1003  : public body_appender_base_t< X_Controlled_Output, Descendant >
1004 {
1005  public:
1006  using base_type_t = body_appender_base_t< X_Controlled_Output, Descendant >;
1007 
1008  using base_type_t::base_type_t;
1009 
1010  //! Append a piece of data to response.
1011  Descendant &
1012  append( string_view_t input )
1013  {
1015  this->m_ztransformator->write( input );
1016  return static_cast< Descendant & >( *this );
1017  }
1018 
1019  //! Complete zlib transformation operation.
1020  void
1022  {
1024 
1025  this->m_ztransformator->complete();
1026 
1028  }
1029 };
1030 
1031 /** @name Body appender.
1032  * @brief Helper class for setting the body of response_builder_t<restinio_controlled_output_t>.
1033  *
1034  * Sample usage:
1035  * \code
1036  * namespace rtz = restinio::transforms::zlib;
1037  * auto resp = req->create_response();
1038  *
1039  * resp.append_header( restinio::http_field::server, "RESTinio" )
1040  * .append_header_date_field()
1041  * .append_header( restinio::http_field::content_type, "text/plain; charset=utf-8" );
1042  *
1043  * auto ba = rtz::gzip_body_appender( resp );
1044  * ba.append( some_data );
1045  * // ...
1046  * ba.append( some_more_data );
1047  * ba.complete();
1048  * resp.done();
1049  * \endcode
1050  *
1051  * @since v.0.4.4
1052 */
1053 template <>
1058 {
1059  public:
1060  using base_type_t =
1064 
1065  //! Get the size of transformed body.
1066  auto
1067  size() const
1068  {
1070 
1071  return m_ztransformator->output_size();
1072  }
1073 
1074  using base_type_t::base_type_t;
1075 };
1076 
1077 /** @name Body appender.
1078  * @brief Helper class for setting the body of response_builder_t<user_controlled_output_t>.
1079  *
1080  * Sample usage:
1081  * \code
1082  * namespace rtz = restinio::transforms::zlib;
1083  * auto resp = req->create_response<user_controlled_output_t>();
1084  *
1085  * resp.append_header( restinio::http_field::server, "RESTinio" )
1086  * .append_header_date_field()
1087  * .append_header( restinio::http_field::content_type, "text/plain; charset=utf-8" );
1088  *
1089  * auto ba = rtz::gzip_body_appender( resp );
1090  * ba.append( some_data );
1091  * ba.flush();
1092  * // ...
1093  * ba.append( some_more_data );
1094  * ba.complete();
1095  * resp.done();
1096  * \endcode
1097  *
1098  * @since v.0.4.4
1099 */
1100 template <>
1105 {
1106  public:
1107  using base_type_t =
1111 
1112  using base_type_t::base_type_t;
1113 
1114  auto &
1116  {
1117  impl::ensure_valid_transforator( m_ztransformator.get() );
1118  m_ztransformator->flush();
1119  m_resp
1120  .append_body( m_ztransformator->giveaway_output() )
1121  .flush();
1122 
1123  return *this;
1124  }
1125 };
1126 
1127 
1128 /** @name Body appender.
1129  * @brief Helper class for setting the body of response_builder_t<chunked_output_t>.
1130  *
1131  * Sample usage:
1132  * \code
1133  * namespace rtz = restinio::transforms::zlib;
1134  * auto resp = req->create_response<chunked_output_t>();
1135  *
1136  * resp.append_header( restinio::http_field::server, "RESTinio" )
1137  * .append_header_date_field()
1138  * .append_header( restinio::http_field::content_type, "text/plain; charset=utf-8" );
1139  *
1140  * auto ba = rtz::gzip_body_appender( resp );
1141  * ba.append( some_data );
1142  * ba.append( some_more_data );
1143  * ba.make_chunk(); // Flush copressed data and creates a chunk with it.
1144  * ba.flush(); // Send currently prepared chunks to client
1145  * // ...
1146  * // Copress the data and creates a chunk with it.
1147  * ba.make_chunk( even_more_data );
1148  * ba.flush(); // Send currently prepared chunks to client
1149  * // ...
1150  * ba.append( yet_even_more_data );
1151  * ba.append( last_data );
1152  * ba.complete(); // Creates last chunk, but doesn't send it to client.
1153  * ba.flush(); // Send chunk created by complete() call
1154  * // ...
1155  * resp.done();
1156  * \endcode
1157  *
1158  * @since v.0.4.4
1159 */
1160 template <>
1162  : public body_appender_base_t<
1165 {
1166  public:
1167  using base_type_t =
1171 
1172  using base_type_t::base_type_t;
1173 
1174  //! Append data to be compressed.
1175  /*!
1176  Function only adds data to anderlying zlib stream
1177  and it doesn't affect target response right on here.
1178  */
1179  auto &
1181  {
1183 
1185  return *this;
1186  }
1187 
1188  //! Append data to be compressed and adds current zlib transformator output
1189  //! as a new chunk.
1190  /*!
1191  Adds data and flushes zlib transformator.
1192  Then ready compressed data is taken and used as a new chunk
1193  of target response.
1194  */
1195  auto &
1197  {
1198  append( input ); // m_ztransformator is checked here.
1199 
1200  m_ztransformator->flush(); // Flush already compressed data.
1201 
1202  // Create a chunk with current output.
1204 
1205  return *this;
1206  }
1207 
1208  //! Flushes currently available compressed data with possibly creating new chunk
1209  //! and then flushes target response.
1210  void
1212  {
1214 
1215  if( !m_ztransformator->is_completed() )
1216  {
1219  }
1220 
1221  m_resp.flush();
1222  }
1223 
1224  //! Complete zlib transformation operation.
1225  void
1227  {
1231  }
1232 };
1233 
1234 //! Create body appender with given zlib transformation parameters.
1235 //! @since v.0.4.4
1236 template < typename Response_Output_Strategy >
1240  const params_t & params )
1241 {
1243 }
1244 
1245 //! Create body appender with deflate transformation and a given compression level.
1246 //! @since v.0.4.4
1247 template < typename Response_Output_Strategy >
1251  int compression_level = -1 )
1252 {
1254 }
1255 
1256 //! Create body appender with gzip transformation and a given compression level.
1257 //! @since v.0.4.4
1258 template < typename Response_Output_Strategy >
1262  int compression_level = -1 )
1263 {
1265 }
1266 
1267 //! Create body appender with gzip transformation and a given compression level.
1268 //! @since v.0.4.4
1269 template < typename Response_Output_Strategy >
1273  int = -1 )
1274 {
1276 }
1277 
1278 //! Call a handler over a request body.
1279 /*!
1280  * If body is encoded with either 'deflate' or 'gzip'
1281  * then it is decompressed and the handler is called.
1282  *
1283  * If body is encoded with 'identity' (or not specified)
1284  * the handler is called with original body.
1285  *
1286  * In other cases an exception is thrown.
1287  *
1288  * \tparam Handler a function object capable to handle std::string as an argument.
1289  *
1290  * Sample usage:
1291  * \code
1292  * namespace rtz = restinio::transforms::zlib;
1293  * auto decompressed_echo_handler( restinio::request_handle_t req )
1294  * {
1295  * return
1296  * rtz::handle_body(
1297  * *req,
1298  * [&]( auto body ){
1299  * return
1300  * req->create_response()
1301  * .append_header( restinio::http_field::server, "RESTinio" )
1302  * .append_header_date_field()
1303  * .append_header(
1304  * restinio::http_field::content_type,
1305  * "text/plain; charset=utf-8" );
1306  * .set_body( std::move( body ) )
1307  * .done();
1308  * } );
1309  * }
1310  * \endcode
1311  * @since v.0.4.4
1312 */
1313 template < typename Handler >
1314 decltype(auto)
1315 handle_body( const request_t & req, Handler handler )
1316 {
1317  const auto content_encoding =
1318  req.header().get_field_or( restinio::http_field::content_encoding, "identity" );
1319 
1320  if( caseless_cmp( content_encoding, "deflate" ) )
1321  {
1322  return handler( deflate_decompress( req.body() ) );
1323  }
1324  else if( caseless_cmp( content_encoding, "gzip" ) )
1325  {
1326  return handler( gzip_decompress( req.body() ) );
1327  }
1328  else if( !caseless_cmp( content_encoding, "identity" ) )
1329  {
1330  throw exception_t{
1331  fmt::format( "content-encoding '{}' not supported", content_encoding ) };
1332  }
1333 
1334  return handler( req.body() );
1335 }
1336 
1337 } /* namespace zlib */
1338 
1339 } /* namespace transforms */
1340 
1341 } /* namespace restinio */
int m_level
Compression level.
Definition: zlib.hpp:348
params_t && window_bits(int window_bits_value) &&
Set window_bits.
Definition: zlib.hpp:208
int strategy() const
Get compression strategy.
Definition: zlib.hpp:258
std::string deflate_compress(string_view_t input, int compression_level=-1)
Definition: zlib.hpp:886
#define deflateInit2(strm, level, method, windowBits, memLevel, strategy)
Definition: zlib.h:1797
format_t m_format
Format to be used for compressed data.
Definition: zlib.hpp:342
params_t make_deflate_decompress_params()
Definition: zlib.hpp:387
body_appender_t< Response_Output_Strategy > identity_body_appender(response_builder_t< Response_Output_Strategy > &resp, int=-1)
Create body appender with gzip transformation and a given compression level.
Definition: zlib.hpp:1271
std::string content_encoding_token(params_t::format_t f)
Get token for copression format.
Definition: zlib.hpp:942
void complete()
Complete zlib transformation operation.
Definition: zlib.hpp:1021
#define Z_NO_FLUSH
Definition: zlib.h:168
std::string transform(string_view_t input, const params_t &params)
Do a specified zlib transformation.
Definition: zlib.hpp:876
uInt avail_in
Definition: zlib.h:88
void complete()
Complete zlib transformation operation.
Definition: zlib.hpp:1226
operation_t
Types of transformation.
Definition: zlib.hpp:65
#define MAX_MEM_LEVEL
Definition: zconf.h:260
params_t & window_bits(int window_bits_value) &
Set window_bits.
Definition: zlib.hpp:169
std::unique_ptr< zlib_t > m_ztransformator
Definition: zlib.hpp:996
void write_decompress_impl(int flush)
Handle incoming data for decompression operation.
Definition: zlib.hpp:796
#define Z_FILTERED
Definition: zlib.h:196
void inc_buffer()
Increment internal buffer for receiving output.
Definition: zlib.hpp:719
body_appender_t< Response_Output_Strategy > body_appender(response_builder_t< Response_Output_Strategy > &resp, const params_t &params)
Create body appender with given zlib transformation parameters.
Definition: zlib.hpp:1238
void complete()
Complete the stream.
Definition: zlib.hpp:631
params_t(operation_t op, format_t f, int l=-1)
Init constructor.
Definition: zlib.hpp:94
voidpf opaque
Definition: zlib.h:100
constexpr int default_strategy
Definition: zlib.hpp:46
free_func zfree
Definition: zlib.h:99
params_t & level(int level_value) &
Set compression level.
Definition: zlib.hpp:134
std::size_t m_reserve_buffer_size
Size initially reserved for buffer.
Definition: zlib.hpp:355
body_appender_base_t & operator=(const body_appender_base_t &)=delete
#define inflateInit2(strm, windowBits)
Definition: zlib.h:1800
#define Z_FINISH
Definition: zlib.h:172
body_appender_base_t(body_appender_base_t &&ba)
Definition: zlib.hpp:988
z_stream m_zlib_stream
zlib stream.
Definition: zlib.hpp:845
body_appender_base_t & operator=(body_appender_base_t &&)=delete
operation_t operation() const
Get operation.
Definition: zlib.hpp:116
void write(string_view_t input)
Append input data.
Definition: zlib.hpp:563
decltype(auto) handle_body(const request_t &req, Handler handler)
Call a handler over a request body.
Definition: zlib.hpp:1315
void flush()
Flush the zlib stream.
Definition: zlib.hpp:609
params_t()
Default constructor for identiry transformator.
Definition: zlib.hpp:108
std::size_t m_write_pos
Next write pos in out buffer.
Definition: zlib.hpp:850
#define Z_DEFLATED
Definition: zlib.h:209
auto output_size() const
Get current output size.
Definition: zlib.hpp:687
body_appender_base_t(const body_appender_base_t &)=delete
params_t && level(int level_value) &&
Set compression level.
Definition: zlib.hpp:152
z_const char * msg
Definition: zlib.h:95
const params_t & params() const
Get parameters of current transformation.
Definition: zlib.hpp:555
constexpr std::size_t default_output_reserve_buffer_size
Default reserve buffer size for zlib transformator.
Definition: zlib.hpp:36
params_t && reserve_buffer_size(std::size_t size) &&
Set the size initially reserved for buffer.
Definition: zlib.hpp:329
#define Z_STREAM_END
Definition: zlib.h:178
alloc_func zalloc
Definition: zlib.h:98
std::string deflate_decompress(string_view_t input)
Definition: zlib.hpp:892
std::string m_out_buffer
Output buffer.
Definition: zlib.hpp:848
zlib_t(const zlib_t &)=delete
int window_bits() const
Get window_bits.
Definition: zlib.hpp:158
std::size_t reserve_buffer_size() const
Get the size initially reserved for buffer.
Definition: zlib.hpp:311
Zlib transformator.
Definition: zlib.hpp:472
int level() const
Get compression level.
Definition: zlib.hpp:125
const char * get_error_msg() const
Get zlib error message if it exists.
Definition: zlib.hpp:700
bool m_zlib_stream_initialized
Flag: was m_zlib_stream initialized properly.
Definition: zlib.hpp:842
params_t && mem_level(int mem_level_value) &&
Set compression mem_level.
Definition: zlib.hpp:249
zlib_t(const params_t &transformation_params)
Definition: zlib.hpp:475
Identity. With semantics descrobed here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Ac...
#define Z_HUFFMAN_ONLY
Definition: zlib.h:197
void ensure_is_compression_operation(params_t::operation_t op)
Check if operation is a copression, and throw if it is not.
Definition: zlib.hpp:924
int mem_level() const
Get compression mem_level.
Definition: zlib.hpp:217
params_t & strategy(int strategy_value) &
Set compression strategy.
Definition: zlib.hpp:269
params_t make_identity_params()
Definition: zlib.hpp:412
#define Z_BUF_ERROR
Definition: zlib.h:184
auto prepare_out_buffer()
Prepare out buffer for receiving data.
Definition: zlib.hpp:727
params_t & reserve_buffer_size(std::size_t size) &
Set the size initially reserved for buffer.
Definition: zlib.hpp:315
uInt avail_out
Definition: zlib.h:92
const params_t m_params
Parameters for zlib.
Definition: zlib.hpp:835
z_const Bytef * next_in
Definition: zlib.h:87
#define Z_OK
Definition: zlib.h:177
params_t & mem_level(int mem_level_value) &
Set compression mem_level.
Definition: zlib.hpp:230
#define Z_SYNC_FLUSH
Definition: zlib.h:170
Base class for body appenders with restinio or user controlled output.
Definition: zlib.hpp:1002
zlib_t & operator=(const zlib_t &)=delete
operation_t m_operation
Transformation type.
Definition: zlib.hpp:339
params_t && strategy(int strategy_value) &&
Set compression strategy.
Definition: zlib.hpp:298
format_t format() const
Get format.
Definition: zlib.hpp:119
params_t make_gzip_decompress_params()
Definition: zlib.hpp:404
#define Z_DEFAULT_STRATEGY
Definition: zlib.h:200
#define Z_NULL
Definition: zlib.h:212
std::string gzip_decompress(string_view_t input)
Definition: zlib.hpp:904
body_appender_t< Response_Output_Strategy > gzip_body_appender(response_builder_t< Response_Output_Strategy > &resp, int compression_level=-1)
Create body appender with gzip transformation and a given compression level.
Definition: zlib.hpp:1260
body_appender_t< Response_Output_Strategy > deflate_body_appender(response_builder_t< Response_Output_Strategy > &resp, int compression_level=-1)
Create body appender with deflate transformation and a given compression level.
Definition: zlib.hpp:1249
std::string gzip_compress(string_view_t input, int compression_level=-1)
Definition: zlib.hpp:898
std::string giveaway_output()
Get current accumulated output data.
Definition: zlib.hpp:676
void ensure_operation_in_not_completed() const
Checks completion flag and throws if operation is is already completed.
Definition: zlib.hpp:711
Parameters of performing data transformation with zlib.
Definition: zlib.hpp:61
bool is_completed() const
Is operation complete?
Definition: zlib.hpp:690
format_t
Formats of compressed data.
Definition: zlib.hpp:74
Base class for body appenders.
Definition: zlib.hpp:966
params_t make_gzip_compress_params(int compression_level=-1)
Definition: zlib.hpp:395
zlib_t & operator=(zlib_t &&)=delete
constexpr int default_window_bits
Definition: zlib.hpp:44
void write_compress_impl(int flush)
Handle incoming data for compression operation.
Definition: zlib.hpp:747
#define MAX_WBITS
Definition: zconf.h:270
params_t make_deflate_compress_params(int compression_level=-1)
Definition: zlib.hpp:378
unsigned int uInt
Definition: zconf.h:393
std::enable_if< std::is_same< Parameter_Container, query_string_params_t >::value||std::is_same< Parameter_Container, router::route_params_t >::value, optional_t< Value_Type > >::type opt_value(const Parameter_Container &params, string_view_t key)
Gets the value of a parameter specified by key wrapped in optional_t<Value_Type> if parameter exists ...
Definition: value_or.hpp:64
body_appender_base_t(const params_t &params, resp_t &resp)
Definition: zlib.hpp:971
void ensure_valid_transforator(zlib_t *ztransformator)
Check if a pointer to zlib transformator is valid.
Definition: zlib.hpp:933
params_t & reference_to_self()
Get the reference to self.
Definition: zlib.hpp:336
constexpr int default_mem_level
Definition: zlib.hpp:45
Descendant & append(string_view_t input)
Append a piece of data to response.
Definition: zlib.hpp:1012
#define Z_RLE
Definition: zlib.h:198