Skip to content

Commit

Permalink
Optimize GZIP Compression (prebid#3411)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexBVolcy authored Jan 25, 2024
1 parent 73b895e commit 0c28657
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 14 deletions.
58 changes: 44 additions & 14 deletions exchange/bidder.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"net/http/httptrace"
"regexp"
"strings"
"sync"
"time"

"github.com/golang/glog"
Expand Down Expand Up @@ -523,12 +524,12 @@ func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.Reques
func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.RequestData, logger util.LogMsg, bidderRequestStartTime time.Time, tmaxAdjustments *TmaxAdjustmentsPreprocessed) *httpCallInfo {
var requestBody []byte

switch strings.ToUpper(bidder.config.EndpointCompression) {
case Gzip:
requestBody = compressToGZIP(req.Body)
req.Headers.Set("Content-Encoding", "gzip")
default:
requestBody = req.Body
requestBody, err := getRequestBody(req, bidder.config.EndpointCompression)
if err != nil {
return &httpCallInfo{
request: req,
err: err,
}
}
httpReq, err := http.NewRequest(req.Method, req.Uri, bytes.NewBuffer(requestBody))
if err != nil {
Expand Down Expand Up @@ -716,14 +717,6 @@ func prepareStoredResponse(impId string, bidResp json.RawMessage) *httpCallInfo
return respData
}

func compressToGZIP(requestBody []byte) []byte {
var b bytes.Buffer
w := gzip.NewWriter(&b)
w.Write([]byte(requestBody))
w.Close()
return b.Bytes()
}

func getBidTypeForAdjustments(bidType openrtb_ext.BidType, impID string, imp []openrtb2.Imp) string {
if bidType == openrtb_ext.BidTypeVideo {
for _, imp := range imp {
Expand Down Expand Up @@ -751,3 +744,40 @@ func hasShorterDurationThanTmax(ctx bidderTmaxContext, tmaxAdjustments TmaxAdjus
}
return false
}

func getRequestBody(req *adapters.RequestData, endpointCompression string) ([]byte, error) {
var requestBody []byte

switch strings.ToUpper(endpointCompression) {
case Gzip:
// Compress to GZIP
var b bytes.Buffer

w := gzipWriterPool.Get().(*gzip.Writer)
defer gzipWriterPool.Put(w)

w.Reset(&b)
_, err := w.Write(req.Body)
if err != nil {
return nil, err
}
err = w.Close()
if err != nil {
return nil, err
}
requestBody = b.Bytes()

// Set Header
req.Headers.Set("Content-Encoding", "gzip")

return requestBody, nil
default:
return req.Body, nil
}
}

var gzipWriterPool = sync.Pool{
New: func() interface{} {
return gzip.NewWriter(nil)
},
}
75 changes: 75 additions & 0 deletions exchange/bidder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package exchange

import (
"bytes"
"compress/gzip"
"context"
"crypto/tls"
"encoding/json"
Expand Down Expand Up @@ -3347,3 +3348,77 @@ func TestDoRequestImplWithTmaxTimeout(t *testing.T) {
test.assertFn(httpCallInfo.err)
}
}

func TestGetRequestBody(t *testing.T) {
tests := []struct {
name string
endpointCompression string
givenReqBody []byte
}{
{
name: "No-Compression",
endpointCompression: "",
givenReqBody: []byte("test body"),
},
{
name: "GZIP-Compression",
endpointCompression: "GZIP",
givenReqBody: []byte("test body"),
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := &adapters.RequestData{Body: test.givenReqBody, Headers: http.Header{}}
requestBody, err := getRequestBody(req, test.endpointCompression)
assert.NoError(t, err)

if test.endpointCompression == "GZIP" {
assert.Equal(t, "gzip", req.Headers.Get("Content-Encoding"))

decompressedReqBody, err := decompressGzip(requestBody)
assert.NoError(t, err)
assert.Equal(t, test.givenReqBody, decompressedReqBody)
} else {
assert.Equal(t, test.givenReqBody, requestBody)
}
})
}
}

func decompressGzip(input []byte) ([]byte, error) {
r, err := gzip.NewReader(bytes.NewReader(input))
if err != nil {
return nil, err
}
defer r.Close()

decompressed, err := io.ReadAll(r)
if err != nil {
return nil, err
}

return decompressed, nil
}

func BenchmarkCompressToGZIPOptimized(b *testing.B) {
// Setup the mock server
respBody := "{\"bid\":false}"
respStatus := 200
server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
defer server.Close()

// Prepare the request data
req := &adapters.RequestData{
Method: "POST",
Uri: server.URL,
Body: []byte("{\"key\":\"val\"}"),
Headers: http.Header{},
}

// Run the benchmark
b.ResetTimer()
for i := 0; i < b.N; i++ {
getRequestBody(req, "GZIP")
}
}

0 comments on commit 0c28657

Please sign in to comment.