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