You are an expert in writing code in many different languages. Your goal is to perform code completion for the following code, keeping in mind the rest of the code and the file meta data.
    Metadata details:
    filename: request.go
    path of file: ./repos/chainlink/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury/v02
    
    the code of the file until where you have to start completion: package v02

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"time"

	automationTypes "github.com/smartcontractkit/chainlink-automation/pkg/v3/types"

	"github.com/smartcontractkit/chainlink-common/pkg/services"

	"github.com/avast/retry-go/v4"
	"github.com/ethereum/go-ethereum/common/hexutil"

	"github.com/smartcontractkit/chainlink/v2/core/logger"
	"github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/encoding"
	"github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/mercury"
	"github.com/smartcontractkit/chainlink/v2/core/utils"
)

const (
	mercuryPathV02      = "/client?" // only used to access mercury v0.2 server
	retryDelay          = 500 * time.Millisecond
	totalAttempt        = 3
	contentTypeHeader   = "Content-Type"
	authorizationHeader = "Authorization"
	timestampHeader     = "X-Authorization-Timestamp"
	signatureHeader     = "X-Authorization-Signature-SHA256"
)

type MercuryV02Response struct {
	ChainlinkBlob string `json:"chainlinkBlob"`
}

type client struct {
	services.StateMachine
	mercuryConfig mercury.MercuryConfigProvider
	httpClient    mercury.HttpClient
	threadCtrl    utils.ThreadControl
	lggr          logger.Logger
}

func NewClient(mercuryConfig mercury.MercuryConfigProvider, httpClient mercury.HttpClient, threadCtrl utils.ThreadControl, lggr lo
    
func (c *client) DoRequest(ctx context.Context, streamsLookup *mercury.StreamsLookup, upkeepType automationTypes.UpkeepType, pluginRetryKey string) (encoding.PipelineExecutionState, [][]byte, encoding.ErrCode, bool, time.Duration, error) {
	if len(streamsLookup.Feeds) == 0 {
		return encoding.NoPipelineError, nil, encoding.ErrCodeStreamsBadRequest, false, 0 * time.Second, nil
	}
	resultLen := len(streamsLookup.Feeds)
	ch := make(chan mercury.MercuryData, resultLen)
	for i := range streamsLookup.Feeds {
		// TODO (AUTO-7209): limit the number of concurrent requests
		i := i
		c.threadCtrl.GoCtx(ctx, func(ctx context.Context) {
			c.singleFeedRequest(ctx, ch, i, streamsLookup)
		})
	}

	// TODO (AUTO 9090): Understand and fix the use of context.Background() here
	reqTimeoutCtx, cancel := context.WithTimeout(context.Background(), mercury.RequestTimeout)
	defer cancel()

	state := encoding.NoPipelineError
	var reqErr error
	retryable := true
	allFeedsPipelineSuccess := true
	allFeedsReturnedValues := true
	var errCode encoding.ErrCode
	results := make([][]byte, len(streamsLookup.Feeds))
	// in v0.2, when combining results for multiple feed requests
	// if any request resulted in pipeline execution error then use the last execution error as the state
	// if no execution errors, then check if any feed returned an error code, if so use the last error code
	for i := 0; i < resultLen; i++ {
		select {
		case <-reqTimeoutCtx.Done():
			// Request Timed out, return timeout error
			c.lggr.Errorf("at block %s upkeep %s, streams lookup v0.2 timed out", streamsLookup.Time.String(), streamsLookup.UpkeepId.String())
			return encoding.NoPipelineError, nil, encoding.ErrCodeStreamsTimeout, false, 0 * time.Second, nil
		case m := <-ch:
			if m.Error != nil {
				state = m.State
				reqErr = errors.Join(reqErr, m.Error)
				retryable = retryable && m.Retryable
				if m.ErrCode != encoding.ErrCodeNil {
					// Some pipeline errors can get converted to error codes if retries are exhausted
					errCode = m.ErrCode
				}
				allFeedsPipelineSuccess = false
				continue
			}
			if m.ErrCode != encoding.ErrCodeNil {
				errCode = m.ErrCode
				allFeedsReturnedValues = false
				continue
			}
			// Feed request didn't face a pipeline error and didn't return an error code
			results[m.Index] = m.Bytes[0]
		}
	}

	if !allFeedsPipelineSuccess {
		// Some feeds faced a pipeline error during execution
		// If any error was non retryable then just return the state and error
		if !retryable {
			return state, nil, errCode, retryable, 0 * time.Second, reqErr
		}
		// If errors were retryable then calculate retry interval
		retryInterval := mercury.CalculateStreamsRetryConfigFn(upkeepType, pluginRetryKey, c.mercuryConfig)
		if retryInterval != mercury.RetryIntervalTimeout {
			// Return the retyrable state with appropriate retry interval
			return state, nil, errCode, retryable, retryInterval, reqErr
		}

		// Now we have exhausted all our retries. We treat it as not a pipeline error
		// and expose error code to the user
		return encoding.NoPipelineError, nil, errCode, false, 0 * time.Second, nil
	}

	// All feeds faced no pipeline error
	// If any feed request returned an error code, return the error code with empty values, else return the values
	if !allFeedsReturnedValues {
		return encoding.NoPipelineError, nil, errCode, false, 0 * time.Second, nil
	}

	// All success, return the results
	return encoding.NoPipelineError, results, encoding.ErrCodeNil, false, 0 * time.Second, nil
}