github.com/aavshr/aws-sdk-go@v1.41.3/aws/request/request.go (about)

     1  package request
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"net/url"
     9  	"reflect"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/aavshr/aws-sdk-go/aws"
    14  	"github.com/aavshr/aws-sdk-go/aws/awserr"
    15  	"github.com/aavshr/aws-sdk-go/aws/client/metadata"
    16  	"github.com/aavshr/aws-sdk-go/internal/sdkio"
    17  )
    18  
    19  const (
    20  	// ErrCodeSerialization is the serialization error code that is received
    21  	// during protocol unmarshaling.
    22  	ErrCodeSerialization = "SerializationError"
    23  
    24  	// ErrCodeRead is an error that is returned during HTTP reads.
    25  	ErrCodeRead = "ReadError"
    26  
    27  	// ErrCodeResponseTimeout is the connection timeout error that is received
    28  	// during body reads.
    29  	ErrCodeResponseTimeout = "ResponseTimeout"
    30  
    31  	// ErrCodeInvalidPresignExpire is returned when the expire time provided to
    32  	// presign is invalid
    33  	ErrCodeInvalidPresignExpire = "InvalidPresignExpireError"
    34  
    35  	// CanceledErrorCode is the error code that will be returned by an
    36  	// API request that was canceled. Requests given a aws.Context may
    37  	// return this error when canceled.
    38  	CanceledErrorCode = "RequestCanceled"
    39  
    40  	// ErrCodeRequestError is an error preventing the SDK from continuing to
    41  	// process the request.
    42  	ErrCodeRequestError = "RequestError"
    43  )
    44  
    45  // A Request is the service request to be made.
    46  type Request struct {
    47  	Config     aws.Config
    48  	ClientInfo metadata.ClientInfo
    49  	Handlers   Handlers
    50  
    51  	Retryer
    52  	AttemptTime            time.Time
    53  	Time                   time.Time
    54  	Operation              *Operation
    55  	HTTPRequest            *http.Request
    56  	HTTPResponse           *http.Response
    57  	Body                   io.ReadSeeker
    58  	streamingBody          io.ReadCloser
    59  	BodyStart              int64 // offset from beginning of Body that the request body starts
    60  	Params                 interface{}
    61  	Error                  error
    62  	Data                   interface{}
    63  	RequestID              string
    64  	RetryCount             int
    65  	Retryable              *bool
    66  	RetryDelay             time.Duration
    67  	NotHoist               bool
    68  	SignedHeaderVals       http.Header
    69  	LastSignedAt           time.Time
    70  	DisableFollowRedirects bool
    71  
    72  	// Additional API error codes that should be retried. IsErrorRetryable
    73  	// will consider these codes in addition to its built in cases.
    74  	RetryErrorCodes []string
    75  
    76  	// Additional API error codes that should be retried with throttle backoff
    77  	// delay. IsErrorThrottle will consider these codes in addition to its
    78  	// built in cases.
    79  	ThrottleErrorCodes []string
    80  
    81  	// A value greater than 0 instructs the request to be signed as Presigned URL
    82  	// You should not set this field directly. Instead use Request's
    83  	// Presign or PresignRequest methods.
    84  	ExpireTime time.Duration
    85  
    86  	context aws.Context
    87  
    88  	built bool
    89  
    90  	// Need to persist an intermediate body between the input Body and HTTP
    91  	// request body because the HTTP Client's transport can maintain a reference
    92  	// to the HTTP request's body after the client has returned. This value is
    93  	// safe to use concurrently and wrap the input Body for each HTTP request.
    94  	safeBody *offsetReader
    95  }
    96  
    97  // An Operation is the service API operation to be made.
    98  type Operation struct {
    99  	Name       string
   100  	HTTPMethod string
   101  	HTTPPath   string
   102  	*Paginator
   103  
   104  	BeforePresignFn func(r *Request) error
   105  }
   106  
   107  // New returns a new Request pointer for the service API operation and
   108  // parameters.
   109  //
   110  // A Retryer should be provided to direct how the request is retried. If
   111  // Retryer is nil, a default no retry value will be used. You can use
   112  // NoOpRetryer in the Client package to disable retry behavior directly.
   113  //
   114  // Params is any value of input parameters to be the request payload.
   115  // Data is pointer value to an object which the request's response
   116  // payload will be deserialized to.
   117  func New(cfg aws.Config, clientInfo metadata.ClientInfo, handlers Handlers,
   118  	retryer Retryer, operation *Operation, params interface{}, data interface{}) *Request {
   119  
   120  	if retryer == nil {
   121  		retryer = noOpRetryer{}
   122  	}
   123  
   124  	method := operation.HTTPMethod
   125  	if method == "" {
   126  		method = "POST"
   127  	}
   128  
   129  	httpReq, _ := http.NewRequest(method, "", nil)
   130  
   131  	var err error
   132  	httpReq.URL, err = url.Parse(clientInfo.Endpoint)
   133  	if err != nil {
   134  		httpReq.URL = &url.URL{}
   135  		err = awserr.New("InvalidEndpointURL", "invalid endpoint uri", err)
   136  	}
   137  
   138  	if len(operation.HTTPPath) != 0 {
   139  		opHTTPPath := operation.HTTPPath
   140  		var opQueryString string
   141  		if idx := strings.Index(opHTTPPath, "?"); idx >= 0 {
   142  			opQueryString = opHTTPPath[idx+1:]
   143  			opHTTPPath = opHTTPPath[:idx]
   144  		}
   145  
   146  		if strings.HasSuffix(httpReq.URL.Path, "/") && strings.HasPrefix(opHTTPPath, "/") {
   147  			opHTTPPath = opHTTPPath[1:]
   148  		}
   149  		httpReq.URL.Path += opHTTPPath
   150  		httpReq.URL.RawQuery = opQueryString
   151  	}
   152  
   153  	r := &Request{
   154  		Config:     cfg,
   155  		ClientInfo: clientInfo,
   156  		Handlers:   handlers.Copy(),
   157  
   158  		Retryer:     retryer,
   159  		Time:        time.Now(),
   160  		ExpireTime:  0,
   161  		Operation:   operation,
   162  		HTTPRequest: httpReq,
   163  		Body:        nil,
   164  		Params:      params,
   165  		Error:       err,
   166  		Data:        data,
   167  	}
   168  	r.SetBufferBody([]byte{})
   169  
   170  	return r
   171  }
   172  
   173  // A Option is a functional option that can augment or modify a request when
   174  // using a WithContext API operation method.
   175  type Option func(*Request)
   176  
   177  // WithGetResponseHeader builds a request Option which will retrieve a single
   178  // header value from the HTTP Response. If there are multiple values for the
   179  // header key use WithGetResponseHeaders instead to access the http.Header
   180  // map directly. The passed in val pointer must be non-nil.
   181  //
   182  // This Option can be used multiple times with a single API operation.
   183  //
   184  //    var id2, versionID string
   185  //    svc.PutObjectWithContext(ctx, params,
   186  //        request.WithGetResponseHeader("x-amz-id-2", &id2),
   187  //        request.WithGetResponseHeader("x-amz-version-id", &versionID),
   188  //    )
   189  func WithGetResponseHeader(key string, val *string) Option {
   190  	return func(r *Request) {
   191  		r.Handlers.Complete.PushBack(func(req *Request) {
   192  			*val = req.HTTPResponse.Header.Get(key)
   193  		})
   194  	}
   195  }
   196  
   197  // WithGetResponseHeaders builds a request Option which will retrieve the
   198  // headers from the HTTP response and assign them to the passed in headers
   199  // variable. The passed in headers pointer must be non-nil.
   200  //
   201  //    var headers http.Header
   202  //    svc.PutObjectWithContext(ctx, params, request.WithGetResponseHeaders(&headers))
   203  func WithGetResponseHeaders(headers *http.Header) Option {
   204  	return func(r *Request) {
   205  		r.Handlers.Complete.PushBack(func(req *Request) {
   206  			*headers = req.HTTPResponse.Header
   207  		})
   208  	}
   209  }
   210  
   211  // WithLogLevel is a request option that will set the request to use a specific
   212  // log level when the request is made.
   213  //
   214  //     svc.PutObjectWithContext(ctx, params, request.WithLogLevel(aws.LogDebugWithHTTPBody)
   215  func WithLogLevel(l aws.LogLevelType) Option {
   216  	return func(r *Request) {
   217  		r.Config.LogLevel = aws.LogLevel(l)
   218  	}
   219  }
   220  
   221  // ApplyOptions will apply each option to the request calling them in the order
   222  // the were provided.
   223  func (r *Request) ApplyOptions(opts ...Option) {
   224  	for _, opt := range opts {
   225  		opt(r)
   226  	}
   227  }
   228  
   229  // Context will always returns a non-nil context. If Request does not have a
   230  // context aws.BackgroundContext will be returned.
   231  func (r *Request) Context() aws.Context {
   232  	if r.context != nil {
   233  		return r.context
   234  	}
   235  	return aws.BackgroundContext()
   236  }
   237  
   238  // SetContext adds a Context to the current request that can be used to cancel
   239  // a in-flight request. The Context value must not be nil, or this method will
   240  // panic.
   241  //
   242  // Unlike http.Request.WithContext, SetContext does not return a copy of the
   243  // Request. It is not safe to use use a single Request value for multiple
   244  // requests. A new Request should be created for each API operation request.
   245  //
   246  // Go 1.6 and below:
   247  // The http.Request's Cancel field will be set to the Done() value of
   248  // the context. This will overwrite the Cancel field's value.
   249  //
   250  // Go 1.7 and above:
   251  // The http.Request.WithContext will be used to set the context on the underlying
   252  // http.Request. This will create a shallow copy of the http.Request. The SDK
   253  // may create sub contexts in the future for nested requests such as retries.
   254  func (r *Request) SetContext(ctx aws.Context) {
   255  	if ctx == nil {
   256  		panic("context cannot be nil")
   257  	}
   258  	setRequestContext(r, ctx)
   259  }
   260  
   261  // WillRetry returns if the request's can be retried.
   262  func (r *Request) WillRetry() bool {
   263  	if !aws.IsReaderSeekable(r.Body) && r.HTTPRequest.Body != NoBody {
   264  		return false
   265  	}
   266  	return r.Error != nil && aws.BoolValue(r.Retryable) && r.RetryCount < r.MaxRetries()
   267  }
   268  
   269  func fmtAttemptCount(retryCount, maxRetries int) string {
   270  	return fmt.Sprintf("attempt %v/%v", retryCount, maxRetries)
   271  }
   272  
   273  // ParamsFilled returns if the request's parameters have been populated
   274  // and the parameters are valid. False is returned if no parameters are
   275  // provided or invalid.
   276  func (r *Request) ParamsFilled() bool {
   277  	return r.Params != nil && reflect.ValueOf(r.Params).Elem().IsValid()
   278  }
   279  
   280  // DataFilled returns true if the request's data for response deserialization
   281  // target has been set and is a valid. False is returned if data is not
   282  // set, or is invalid.
   283  func (r *Request) DataFilled() bool {
   284  	return r.Data != nil && reflect.ValueOf(r.Data).Elem().IsValid()
   285  }
   286  
   287  // SetBufferBody will set the request's body bytes that will be sent to
   288  // the service API.
   289  func (r *Request) SetBufferBody(buf []byte) {
   290  	r.SetReaderBody(bytes.NewReader(buf))
   291  }
   292  
   293  // SetStringBody sets the body of the request to be backed by a string.
   294  func (r *Request) SetStringBody(s string) {
   295  	r.SetReaderBody(strings.NewReader(s))
   296  }
   297  
   298  // SetReaderBody will set the request's body reader.
   299  func (r *Request) SetReaderBody(reader io.ReadSeeker) {
   300  	r.Body = reader
   301  
   302  	if aws.IsReaderSeekable(reader) {
   303  		var err error
   304  		// Get the Bodies current offset so retries will start from the same
   305  		// initial position.
   306  		r.BodyStart, err = reader.Seek(0, sdkio.SeekCurrent)
   307  		if err != nil {
   308  			r.Error = awserr.New(ErrCodeSerialization,
   309  				"failed to determine start of request body", err)
   310  			return
   311  		}
   312  	}
   313  	r.ResetBody()
   314  }
   315  
   316  // SetStreamingBody set the reader to be used for the request that will stream
   317  // bytes to the server. Request's Body must not be set to any reader.
   318  func (r *Request) SetStreamingBody(reader io.ReadCloser) {
   319  	r.streamingBody = reader
   320  	r.SetReaderBody(aws.ReadSeekCloser(reader))
   321  }
   322  
   323  // Presign returns the request's signed URL. Error will be returned
   324  // if the signing fails. The expire parameter is only used for presigned Amazon
   325  // S3 API requests. All other AWS services will use a fixed expiration
   326  // time of 15 minutes.
   327  //
   328  // It is invalid to create a presigned URL with a expire duration 0 or less. An
   329  // error is returned if expire duration is 0 or less.
   330  func (r *Request) Presign(expire time.Duration) (string, error) {
   331  	r = r.copy()
   332  
   333  	// Presign requires all headers be hoisted. There is no way to retrieve
   334  	// the signed headers not hoisted without this. Making the presigned URL
   335  	// useless.
   336  	r.NotHoist = false
   337  
   338  	u, _, err := getPresignedURL(r, expire)
   339  	return u, err
   340  }
   341  
   342  // PresignRequest behaves just like presign, with the addition of returning a
   343  // set of headers that were signed. The expire parameter is only used for
   344  // presigned Amazon S3 API requests. All other AWS services will use a fixed
   345  // expiration time of 15 minutes.
   346  //
   347  // It is invalid to create a presigned URL with a expire duration 0 or less. An
   348  // error is returned if expire duration is 0 or less.
   349  //
   350  // Returns the URL string for the API operation with signature in the query string,
   351  // and the HTTP headers that were included in the signature. These headers must
   352  // be included in any HTTP request made with the presigned URL.
   353  //
   354  // To prevent hoisting any headers to the query string set NotHoist to true on
   355  // this Request value prior to calling PresignRequest.
   356  func (r *Request) PresignRequest(expire time.Duration) (string, http.Header, error) {
   357  	r = r.copy()
   358  	return getPresignedURL(r, expire)
   359  }
   360  
   361  // IsPresigned returns true if the request represents a presigned API url.
   362  func (r *Request) IsPresigned() bool {
   363  	return r.ExpireTime != 0
   364  }
   365  
   366  func getPresignedURL(r *Request, expire time.Duration) (string, http.Header, error) {
   367  	if expire <= 0 {
   368  		return "", nil, awserr.New(
   369  			ErrCodeInvalidPresignExpire,
   370  			"presigned URL requires an expire duration greater than 0",
   371  			nil,
   372  		)
   373  	}
   374  
   375  	r.ExpireTime = expire
   376  
   377  	if r.Operation.BeforePresignFn != nil {
   378  		if err := r.Operation.BeforePresignFn(r); err != nil {
   379  			return "", nil, err
   380  		}
   381  	}
   382  
   383  	if err := r.Sign(); err != nil {
   384  		return "", nil, err
   385  	}
   386  
   387  	return r.HTTPRequest.URL.String(), r.SignedHeaderVals, nil
   388  }
   389  
   390  const (
   391  	notRetrying = "not retrying"
   392  )
   393  
   394  func debugLogReqError(r *Request, stage, retryStr string, err error) {
   395  	if !r.Config.LogLevel.Matches(aws.LogDebugWithRequestErrors) {
   396  		return
   397  	}
   398  
   399  	r.Config.Logger.Log(fmt.Sprintf("DEBUG: %s %s/%s failed, %s, error %v",
   400  		stage, r.ClientInfo.ServiceName, r.Operation.Name, retryStr, err))
   401  }
   402  
   403  // Build will build the request's object so it can be signed and sent
   404  // to the service. Build will also validate all the request's parameters.
   405  // Any additional build Handlers set on this request will be run
   406  // in the order they were set.
   407  //
   408  // The request will only be built once. Multiple calls to build will have
   409  // no effect.
   410  //
   411  // If any Validate or Build errors occur the build will stop and the error
   412  // which occurred will be returned.
   413  func (r *Request) Build() error {
   414  	if !r.built {
   415  		r.Handlers.Validate.Run(r)
   416  		if r.Error != nil {
   417  			debugLogReqError(r, "Validate Request", notRetrying, r.Error)
   418  			return r.Error
   419  		}
   420  		r.Handlers.Build.Run(r)
   421  		if r.Error != nil {
   422  			debugLogReqError(r, "Build Request", notRetrying, r.Error)
   423  			return r.Error
   424  		}
   425  		r.built = true
   426  	}
   427  
   428  	return r.Error
   429  }
   430  
   431  // Sign will sign the request, returning error if errors are encountered.
   432  //
   433  // Sign will build the request prior to signing. All Sign Handlers will
   434  // be executed in the order they were set.
   435  func (r *Request) Sign() error {
   436  	r.Build()
   437  	if r.Error != nil {
   438  		debugLogReqError(r, "Build Request", notRetrying, r.Error)
   439  		return r.Error
   440  	}
   441  
   442  	SanitizeHostForHeader(r.HTTPRequest)
   443  
   444  	r.Handlers.Sign.Run(r)
   445  	return r.Error
   446  }
   447  
   448  func (r *Request) getNextRequestBody() (body io.ReadCloser, err error) {
   449  	if r.streamingBody != nil {
   450  		return r.streamingBody, nil
   451  	}
   452  
   453  	if r.safeBody != nil {
   454  		r.safeBody.Close()
   455  	}
   456  
   457  	r.safeBody, err = newOffsetReader(r.Body, r.BodyStart)
   458  	if err != nil {
   459  		return nil, awserr.New(ErrCodeSerialization,
   460  			"failed to get next request body reader", err)
   461  	}
   462  
   463  	// Go 1.8 tightened and clarified the rules code needs to use when building
   464  	// requests with the http package. Go 1.8 removed the automatic detection
   465  	// of if the Request.Body was empty, or actually had bytes in it. The SDK
   466  	// always sets the Request.Body even if it is empty and should not actually
   467  	// be sent. This is incorrect.
   468  	//
   469  	// Go 1.8 did add a http.NoBody value that the SDK can use to tell the http
   470  	// client that the request really should be sent without a body. The
   471  	// Request.Body cannot be set to nil, which is preferable, because the
   472  	// field is exported and could introduce nil pointer dereferences for users
   473  	// of the SDK if they used that field.
   474  	//
   475  	// Related golang/go#18257
   476  	l, err := aws.SeekerLen(r.Body)
   477  	if err != nil {
   478  		return nil, awserr.New(ErrCodeSerialization,
   479  			"failed to compute request body size", err)
   480  	}
   481  
   482  	if l == 0 {
   483  		body = NoBody
   484  	} else if l > 0 {
   485  		body = r.safeBody
   486  	} else {
   487  		// Hack to prevent sending bodies for methods where the body
   488  		// should be ignored by the server. Sending bodies on these
   489  		// methods without an associated ContentLength will cause the
   490  		// request to socket timeout because the server does not handle
   491  		// Transfer-Encoding: chunked bodies for these methods.
   492  		//
   493  		// This would only happen if a aws.ReaderSeekerCloser was used with
   494  		// a io.Reader that was not also an io.Seeker, or did not implement
   495  		// Len() method.
   496  		switch r.Operation.HTTPMethod {
   497  		case "GET", "HEAD", "DELETE":
   498  			body = NoBody
   499  		default:
   500  			body = r.safeBody
   501  		}
   502  	}
   503  
   504  	return body, nil
   505  }
   506  
   507  // GetBody will return an io.ReadSeeker of the Request's underlying
   508  // input body with a concurrency safe wrapper.
   509  func (r *Request) GetBody() io.ReadSeeker {
   510  	return r.safeBody
   511  }
   512  
   513  // Send will send the request, returning error if errors are encountered.
   514  //
   515  // Send will sign the request prior to sending. All Send Handlers will
   516  // be executed in the order they were set.
   517  //
   518  // Canceling a request is non-deterministic. If a request has been canceled,
   519  // then the transport will choose, randomly, one of the state channels during
   520  // reads or getting the connection.
   521  //
   522  // readLoop() and getConn(req *Request, cm connectMethod)
   523  // https://github.com/golang/go/blob/master/src/net/http/transport.go
   524  //
   525  // Send will not close the request.Request's body.
   526  func (r *Request) Send() error {
   527  	defer func() {
   528  		// Regardless of success or failure of the request trigger the Complete
   529  		// request handlers.
   530  		r.Handlers.Complete.Run(r)
   531  	}()
   532  
   533  	if err := r.Error; err != nil {
   534  		return err
   535  	}
   536  
   537  	for {
   538  		r.Error = nil
   539  		r.AttemptTime = time.Now()
   540  
   541  		if err := r.Sign(); err != nil {
   542  			debugLogReqError(r, "Sign Request", notRetrying, err)
   543  			return err
   544  		}
   545  
   546  		if err := r.sendRequest(); err == nil {
   547  			return nil
   548  		}
   549  		r.Handlers.Retry.Run(r)
   550  		r.Handlers.AfterRetry.Run(r)
   551  
   552  		if r.Error != nil || !aws.BoolValue(r.Retryable) {
   553  			return r.Error
   554  		}
   555  
   556  		if err := r.prepareRetry(); err != nil {
   557  			r.Error = err
   558  			return err
   559  		}
   560  	}
   561  }
   562  
   563  func (r *Request) prepareRetry() error {
   564  	if r.Config.LogLevel.Matches(aws.LogDebugWithRequestRetries) {
   565  		r.Config.Logger.Log(fmt.Sprintf("DEBUG: Retrying Request %s/%s, attempt %d",
   566  			r.ClientInfo.ServiceName, r.Operation.Name, r.RetryCount))
   567  	}
   568  
   569  	// The previous http.Request will have a reference to the r.Body
   570  	// and the HTTP Client's Transport may still be reading from
   571  	// the request's body even though the Client's Do returned.
   572  	r.HTTPRequest = copyHTTPRequest(r.HTTPRequest, nil)
   573  	r.ResetBody()
   574  	if err := r.Error; err != nil {
   575  		return awserr.New(ErrCodeSerialization,
   576  			"failed to prepare body for retry", err)
   577  
   578  	}
   579  
   580  	// Closing response body to ensure that no response body is leaked
   581  	// between retry attempts.
   582  	if r.HTTPResponse != nil && r.HTTPResponse.Body != nil {
   583  		r.HTTPResponse.Body.Close()
   584  	}
   585  
   586  	return nil
   587  }
   588  
   589  func (r *Request) sendRequest() (sendErr error) {
   590  	defer r.Handlers.CompleteAttempt.Run(r)
   591  
   592  	r.Retryable = nil
   593  	r.Handlers.Send.Run(r)
   594  	if r.Error != nil {
   595  		debugLogReqError(r, "Send Request",
   596  			fmtAttemptCount(r.RetryCount, r.MaxRetries()),
   597  			r.Error)
   598  		return r.Error
   599  	}
   600  
   601  	r.Handlers.UnmarshalMeta.Run(r)
   602  	r.Handlers.ValidateResponse.Run(r)
   603  	if r.Error != nil {
   604  		r.Handlers.UnmarshalError.Run(r)
   605  		debugLogReqError(r, "Validate Response",
   606  			fmtAttemptCount(r.RetryCount, r.MaxRetries()),
   607  			r.Error)
   608  		return r.Error
   609  	}
   610  
   611  	r.Handlers.Unmarshal.Run(r)
   612  	if r.Error != nil {
   613  		debugLogReqError(r, "Unmarshal Response",
   614  			fmtAttemptCount(r.RetryCount, r.MaxRetries()),
   615  			r.Error)
   616  		return r.Error
   617  	}
   618  
   619  	return nil
   620  }
   621  
   622  // copy will copy a request which will allow for local manipulation of the
   623  // request.
   624  func (r *Request) copy() *Request {
   625  	req := &Request{}
   626  	*req = *r
   627  	req.Handlers = r.Handlers.Copy()
   628  	op := *r.Operation
   629  	req.Operation = &op
   630  	return req
   631  }
   632  
   633  // AddToUserAgent adds the string to the end of the request's current user agent.
   634  func AddToUserAgent(r *Request, s string) {
   635  	curUA := r.HTTPRequest.Header.Get("User-Agent")
   636  	if len(curUA) > 0 {
   637  		s = curUA + " " + s
   638  	}
   639  	r.HTTPRequest.Header.Set("User-Agent", s)
   640  }
   641  
   642  // SanitizeHostForHeader removes default port from host and updates request.Host
   643  func SanitizeHostForHeader(r *http.Request) {
   644  	host := getHost(r)
   645  	port := portOnly(host)
   646  	if port != "" && isDefaultPort(r.URL.Scheme, port) {
   647  		r.Host = stripPort(host)
   648  	}
   649  }
   650  
   651  // Returns host from request
   652  func getHost(r *http.Request) string {
   653  	if r.Host != "" {
   654  		return r.Host
   655  	}
   656  
   657  	if r.URL == nil {
   658  		return ""
   659  	}
   660  
   661  	return r.URL.Host
   662  }
   663  
   664  // Hostname returns u.Host, without any port number.
   665  //
   666  // If Host is an IPv6 literal with a port number, Hostname returns the
   667  // IPv6 literal without the square brackets. IPv6 literals may include
   668  // a zone identifier.
   669  //
   670  // Copied from the Go 1.8 standard library (net/url)
   671  func stripPort(hostport string) string {
   672  	colon := strings.IndexByte(hostport, ':')
   673  	if colon == -1 {
   674  		return hostport
   675  	}
   676  	if i := strings.IndexByte(hostport, ']'); i != -1 {
   677  		return strings.TrimPrefix(hostport[:i], "[")
   678  	}
   679  	return hostport[:colon]
   680  }
   681  
   682  // Port returns the port part of u.Host, without the leading colon.
   683  // If u.Host doesn't contain a port, Port returns an empty string.
   684  //
   685  // Copied from the Go 1.8 standard library (net/url)
   686  func portOnly(hostport string) string {
   687  	colon := strings.IndexByte(hostport, ':')
   688  	if colon == -1 {
   689  		return ""
   690  	}
   691  	if i := strings.Index(hostport, "]:"); i != -1 {
   692  		return hostport[i+len("]:"):]
   693  	}
   694  	if strings.Contains(hostport, "]") {
   695  		return ""
   696  	}
   697  	return hostport[colon+len(":"):]
   698  }
   699  
   700  // Returns true if the specified URI is using the standard port
   701  // (i.e. port 80 for HTTP URIs or 443 for HTTPS URIs)
   702  func isDefaultPort(scheme, port string) bool {
   703  	if port == "" {
   704  		return true
   705  	}
   706  
   707  	lowerCaseScheme := strings.ToLower(scheme)
   708  	if (lowerCaseScheme == "http" && port == "80") || (lowerCaseScheme == "https" && port == "443") {
   709  		return true
   710  	}
   711  
   712  	return false
   713  }