src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

accept_encoding_gzip.go (5040B)


      1 package acceptencoding
      2 
      3 import (
      4 	"compress/gzip"
      5 	"context"
      6 	"fmt"
      7 	"io"
      8 
      9 	"github.com/aws/smithy-go"
     10 	"github.com/aws/smithy-go/middleware"
     11 	smithyhttp "github.com/aws/smithy-go/transport/http"
     12 )
     13 
     14 const acceptEncodingHeaderKey = "Accept-Encoding"
     15 const contentEncodingHeaderKey = "Content-Encoding"
     16 
     17 // AddAcceptEncodingGzipOptions provides the options for the
     18 // AddAcceptEncodingGzip middleware setup.
     19 type AddAcceptEncodingGzipOptions struct {
     20 	Enable bool
     21 }
     22 
     23 // AddAcceptEncodingGzip explicitly adds handling for accept-encoding GZIP
     24 // middleware to the operation stack. This allows checksums to be correctly
     25 // computed without disabling GZIP support.
     26 func AddAcceptEncodingGzip(stack *middleware.Stack, options AddAcceptEncodingGzipOptions) error {
     27 	if options.Enable {
     28 		if err := stack.Finalize.Add(&EnableGzip{}, middleware.Before); err != nil {
     29 			return err
     30 		}
     31 		if err := stack.Deserialize.Insert(&DecompressGzip{}, "OperationDeserializer", middleware.After); err != nil {
     32 			return err
     33 		}
     34 		return nil
     35 	}
     36 
     37 	return stack.Finalize.Add(&DisableGzip{}, middleware.Before)
     38 }
     39 
     40 // DisableGzip provides the middleware that will
     41 // disable the underlying http client automatically enabling for gzip
     42 // decompress content-encoding support.
     43 type DisableGzip struct{}
     44 
     45 // ID returns the id for the middleware.
     46 func (*DisableGzip) ID() string {
     47 	return "DisableAcceptEncodingGzip"
     48 }
     49 
     50 // HandleFinalize implements the FinalizeMiddleware interface.
     51 func (*DisableGzip) HandleFinalize(
     52 	ctx context.Context, input middleware.FinalizeInput, next middleware.FinalizeHandler,
     53 ) (
     54 	output middleware.FinalizeOutput, metadata middleware.Metadata, err error,
     55 ) {
     56 	req, ok := input.Request.(*smithyhttp.Request)
     57 	if !ok {
     58 		return output, metadata, &smithy.SerializationError{
     59 			Err: fmt.Errorf("unknown request type %T", input.Request),
     60 		}
     61 	}
     62 
     63 	// Explicitly enable gzip support, this will prevent the http client from
     64 	// auto extracting the zipped content.
     65 	req.Header.Set(acceptEncodingHeaderKey, "identity")
     66 
     67 	return next.HandleFinalize(ctx, input)
     68 }
     69 
     70 // EnableGzip provides a middleware to enable support for
     71 // gzip responses, with manual decompression. This prevents the underlying HTTP
     72 // client from performing the gzip decompression automatically.
     73 type EnableGzip struct{}
     74 
     75 // ID returns the id for the middleware.
     76 func (*EnableGzip) ID() string {
     77 	return "AcceptEncodingGzip"
     78 }
     79 
     80 // HandleFinalize implements the FinalizeMiddleware interface.
     81 func (*EnableGzip) HandleFinalize(
     82 	ctx context.Context, input middleware.FinalizeInput, next middleware.FinalizeHandler,
     83 ) (
     84 	output middleware.FinalizeOutput, metadata middleware.Metadata, err error,
     85 ) {
     86 	req, ok := input.Request.(*smithyhttp.Request)
     87 	if !ok {
     88 		return output, metadata, &smithy.SerializationError{
     89 			Err: fmt.Errorf("unknown request type %T", input.Request),
     90 		}
     91 	}
     92 
     93 	// Explicitly enable gzip support, this will prevent the http client from
     94 	// auto extracting the zipped content.
     95 	req.Header.Set(acceptEncodingHeaderKey, "gzip")
     96 
     97 	return next.HandleFinalize(ctx, input)
     98 }
     99 
    100 // DecompressGzip provides the middleware for decompressing a gzip
    101 // response from the service.
    102 type DecompressGzip struct{}
    103 
    104 // ID returns the id for the middleware.
    105 func (*DecompressGzip) ID() string {
    106 	return "DecompressGzip"
    107 }
    108 
    109 // HandleDeserialize implements the DeserializeMiddlware interface.
    110 func (*DecompressGzip) HandleDeserialize(
    111 	ctx context.Context, input middleware.DeserializeInput, next middleware.DeserializeHandler,
    112 ) (
    113 	output middleware.DeserializeOutput, metadata middleware.Metadata, err error,
    114 ) {
    115 	output, metadata, err = next.HandleDeserialize(ctx, input)
    116 	if err != nil {
    117 		return output, metadata, err
    118 	}
    119 
    120 	resp, ok := output.RawResponse.(*smithyhttp.Response)
    121 	if !ok {
    122 		return output, metadata, &smithy.DeserializationError{
    123 			Err: fmt.Errorf("unknown response type %T", output.RawResponse),
    124 		}
    125 	}
    126 	if v := resp.Header.Get(contentEncodingHeaderKey); v != "gzip" {
    127 		return output, metadata, err
    128 	}
    129 
    130 	// Clear content length since it will no longer be valid once the response
    131 	// body is decompressed.
    132 	resp.Header.Del("Content-Length")
    133 	resp.ContentLength = -1
    134 
    135 	resp.Body = wrapGzipReader(resp.Body)
    136 
    137 	return output, metadata, err
    138 }
    139 
    140 type gzipReader struct {
    141 	reader io.ReadCloser
    142 	gzip   *gzip.Reader
    143 }
    144 
    145 func wrapGzipReader(reader io.ReadCloser) *gzipReader {
    146 	return &gzipReader{
    147 		reader: reader,
    148 	}
    149 }
    150 
    151 // Read wraps the gzip reader around the underlying io.Reader to extract the
    152 // response bytes on the fly.
    153 func (g *gzipReader) Read(b []byte) (n int, err error) {
    154 	if g.gzip == nil {
    155 		g.gzip, err = gzip.NewReader(g.reader)
    156 		if err != nil {
    157 			g.gzip = nil // ensure uninitialized gzip value isn't used in close.
    158 			return 0, fmt.Errorf("failed to decompress gzip response, %w", err)
    159 		}
    160 	}
    161 
    162 	return g.gzip.Read(b)
    163 }
    164 
    165 func (g *gzipReader) Close() error {
    166 	if g.gzip == nil {
    167 		return nil
    168 	}
    169 
    170 	if err := g.gzip.Close(); err != nil {
    171 		g.reader.Close()
    172 		return fmt.Errorf("failed to decompress gzip response, %w", err)
    173 	}
    174 
    175 	return g.reader.Close()
    176 }