Skip to content

Commit

Permalink
service/cloudwatch: Add helper for GZIP request payload (#4425)
Browse files Browse the repository at this point in the history
Adds a new helper, `WithGzipRequest` to compress the request payload
before it is sent. May not be supported by all API operations. See
service's API Reference docs for details about operations that are
supported.
  • Loading branch information
jasdel committed Jun 3, 2022
1 parent 58af6f1 commit 9207008
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 1 deletion.
3 changes: 2 additions & 1 deletion CHANGELOG_PENDING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
### SDK Features

### SDK Enhancements

* `service/cloudwatch`: Add helper to send request payload as GZIP content encoding
* Adds a new helper, `WithGzipRequest` to the `cloudwatch` package. The helper will configure the payload to be sent as `content-encoding: gzip`. It is supported by operations like `PutMetricData`. See the service's API Reference documentation for other operations supported.
### SDK Bugs
59 changes: 59 additions & 0 deletions internal/encoding/gzip/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package gzip

import (
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"strconv"

"github.com/aws/aws-sdk-go/aws/request"
)

// NewGzipRequestHandler provides a named request handler that compresses the
// request payload. Add this to enable GZIP compression for a client.
//
// Known to work with Amazon CloudWatch's PutMetricData operation.
// https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_PutMetricData.html
func NewGzipRequestHandler() request.NamedHandler {
return request.NamedHandler{
Name: "GzipRequestHandler",
Fn: gzipRequestHandler,
}
}

func gzipRequestHandler(req *request.Request) {
compressedBytes, err := compress(req.Body)
if err != nil {
req.Error = fmt.Errorf("failed to compress request payload, %v", err)
return
}

req.HTTPRequest.Header.Set("Content-Encoding", "gzip")
req.HTTPRequest.Header.Set("Content-Length", strconv.Itoa(len(compressedBytes)))

req.SetBufferBody(compressedBytes)
}

func compress(input io.Reader) ([]byte, error) {
var b bytes.Buffer
w, err := gzip.NewWriterLevel(&b, gzip.BestCompression)
if err != nil {
return nil, fmt.Errorf("failed to create gzip writer, %v", err)
}

inBytes, err := ioutil.ReadAll(input)
if err != nil {
return nil, fmt.Errorf("failed read payload to compress, %v", err)
}

if _, err = w.Write(inBytes); err != nil {
return nil, fmt.Errorf("failed to write payload to be compressed, %v", err)
}
if err = w.Close(); err != nil {
return nil, fmt.Errorf("failed to flush payload being compressed, %v", err)
}

return b.Bytes(), nil
}
50 changes: 50 additions & 0 deletions internal/encoding/gzip/handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package gzip

import (
"bytes"
"io/ioutil"
"net/http"
"strconv"
"strings"
"testing"

"github.com/aws/aws-sdk-go/aws/request"
)

func TestGzipRequestHandler(t *testing.T) {
handler := NewGzipRequestHandler()

req := &request.Request{}
uncompressed := "asdfasdfasdf"
req.Body = strings.NewReader(uncompressed)
httpReq, err := http.NewRequest("POST", "http://localhost", strings.NewReader(uncompressed))
if err != nil {
panic(err)
}
req.HTTPRequest = httpReq

expectCompressed, err := compress(strings.NewReader(uncompressed))
if err != nil {
t.Fatalf("expect no error, got %v", err)
}

handler.Fn(req)
if req.Error != nil {
t.Fatalf("expect no error, got %v", req.Error)
}

if e, a := "gzip", req.HTTPRequest.Header.Get("Content-Encoding"); e != a {
t.Errorf("expect %v content-encoding, got %v", e, a)
}
if e, a := strconv.Itoa(len(expectCompressed)), req.HTTPRequest.Header.Get("Content-Length"); e != a {
t.Errorf("expect %v content-length, got %v", e, a)
}

actualCompressed, err := ioutil.ReadAll(req.Body)
if err != nil {
t.Fatalf("ReadAll request body failed, %v", err)
}
if !bytes.Equal(expectCompressed, actualCompressed) {
t.Errorf("expect new body to equal expectCompressed")
}
}
19 changes: 19 additions & 0 deletions service/cloudwatch/customizations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package cloudwatch

import (
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/internal/encoding/gzip"
)

// WithGzipRequest is a request.Option that adds a request handler to the Build
// stage of the operation's pipeline that will content-encoding GZIP the
// request payload before sending it to the API. This will buffer the request
// payload in memory, GZIP it, and reassign the GZIP'ed payload as the new
// request payload.
//
// GZIP may not be supported by all API operations. See API's documentation for
// the operation your using to see if GZIP request payload is supported.
// https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_PutMetricData.html
func WithGzipRequest(r *request.Request) {
r.Handlers.Build.PushBackNamed(gzip.NewGzipRequestHandler())
}
26 changes: 26 additions & 0 deletions service/cloudwatch/customizations_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//go:build go1.7
// +build go1.7

package cloudwatch_test

import (
"context"

"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/awstesting/unit"
"github.com/aws/aws-sdk-go/service/cloudwatch"
)

func ExampleCloudWatch_PutMetricDataWithContext_withGzipRequest() {
client := cloudwatch.New(sess)

// The WithContext form of the operation methods accept request options.
// The WithGzipRequest option will gzip the request payload before it is
// sent.
result, err := client.PutMetricDataWithContext(context.TODO(), params, cloudwatch.WithGzipRequest)

_, _ = result, err
}

var params *cloudwatch.PutMetricDataInput
var sess *session.Session = unit.Session

0 comments on commit 9207008

Please sign in to comment.