func Patch(url string, options ...SendOption) (*http.Response, error) {
	return Send("PATCH", url, options...)
}
Patch sends a PATCH http request.
package httputil
import (
	"context"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"time"
	"github.com/cenkalti/backoff"
	"github.com/uber/makisu/lib/log"
)
var retryableCodes = map[int]struct{}{
	http.StatusTooManyRequests:    {},
	http.StatusBadGateway:         {},
	http.StatusServiceUnavailable: {},
	http.StatusGatewayTimeout:     {},
}
type StatusError struct {
	Method       string
	URL          string
	Status       int
	Header       http.Header
	ResponseDump string
}
func NewStatusError(resp *http.Response) StatusError {
	defer resp.Body.Close()
	respBytes, err := ioutil.ReadAll(resp.Body)
	respDump := string(respBytes)
	if err != nil {
		respDump = fmt.Sprintf("failed to dump response: %s", err)
	}
	return StatusError{
		Method:       resp.Request.Method,
		URL:          resp.Request.URL.String(),
		Status:       resp.StatusCode,
		Header:       resp.Header,
		ResponseDump: respDump,
	}
}
func (e StatusError) Error() string {
	if e.ResponseDump == "" {
		return fmt.Sprintf("%s %s %d", e.Method, e.URL, e.Status)
	}
	return fmt.Sprintf("%s %s %d: %s", e.Method, e.URL, e.Status, e.ResponseDump)
}
func IsStatus(err error, status int) bool {
	var e StatusError
	if errors.As(err, &e) {
		return e.Status == status
	}
	return false
}
func IsCreated(err error) bool {
	return IsStatus(err, http.StatusCreated)
}
func IsNotFound(err error) bool {
	return IsStatus(err, http.StatusNotFound)
}
func IsConflict(err error) bool {
	return IsStatus(err, http.StatusConflict)
}
func IsAccepted(err error) bool {
	return IsStatus(err, http.StatusAccepted)
}
func IsForbidden(err error) bool {
	return IsStatus(err, http.StatusForbidden)
}
func isRetryable(code int) bool {
	_, ok := retryableCodes[code]
	return ok
}
func IsRetryable(err error) bool {
	var e StatusError
	if errors.As(err, &e) {
		return isRetryable(e.Status)
	}
	return false
}
type NetworkError struct {
	err error
}
func (e NetworkError) Error() string {
	return fmt.Sprintf("network error: %s", e.err)
}
func IsNetworkError(err error) bool {
	var e NetworkError
	return errors.As(err, &e)
}
type sendOptions struct {
	body          io.Reader
	timeout       time.Duration
	acceptedCodes map[int]bool
	headers       map[string]string
	redirect      func(req *http.Request, via []*http.Request) error
	retry         retryOptions
	transport     http.RoundTripper
	ctx           context.Context
	client *http.Client
	url *url.URL
	httpFallbackDisabled bool
}
type SendOption func(*sendOptions)
func SendNoop() SendOption {
	return func(o *sendOptions) {}
}
func SendBody(body io.Reader) SendOption {
	return func(o *sendOptions) { o.body = body }
}
func SendTimeout(timeout time.Duration) SendOption {
	return func(o *sendOptions) { o.timeout = timeout }
}
func SendHeaders(headers map[string]string) SendOption {
	return func(o *sendOptions) { o.headers = headers }
}
func SendAcceptedCodes(codes ...int) SendOption {
	m := make(map[int]bool)
	for _, c := range codes {
		m[c] = true
	}
	return func(o *sendOptions) { o.acceptedCodes = m }
}
func SendRedirect(redirect func(req *http.Request, via []*http.Request) error) SendOption {
	return func(o *sendOptions) { o.redirect = redirect }
}
func SendClient(client *http.Client) SendOption {
	return func(o *sendOptions) { o.client = client }
}
type retryOptions struct {
	backoff    backoff.BackOff
	extraCodes map[int]bool
}
type RetryOption func(*retryOptions)
func RetryBackoff(b backoff.BackOff) RetryOption {
	return func(o *retryOptions) { o.backoff = b }
}
func RetryCodes(codes ...int) RetryOption {
	return func(o *retryOptions) {
		for _, c := range codes {
			o.extraCodes[c] = true
		}
	}
}
func SendRetry(options ...RetryOption) SendOption {
	b := backoff.NewExponentialBackOff()
	b.InitialInterval = 250 * time.Millisecond
	b.Multiplier = 1  
	b.MaxInterval = 30 * time.Second
	retry := retryOptions{
		backoff:    backoff.WithMaxRetries(b, 3),
		extraCodes: make(map[int]bool),
	}
	for _, o := range options {
		o(&retry)
	}
	return func(o *sendOptions) { o.retry = retry }
}
func DisableHTTPFallback() SendOption {
	return func(o *sendOptions) {
		o.httpFallbackDisabled = true
	}
}
func SendTLS(config *tls.Config) SendOption {
	return func(o *sendOptions) {
		if config == nil {
			return
		}
		o.transport = &http.Transport{TLSClientConfig: config}
		o.url.Scheme = "https"
	}
}
func SendTLSTransport(transport http.RoundTripper) SendOption {
	return func(o *sendOptions) {
		o.transport = transport
		o.url.Scheme = "https"
	}
}
func SendTransport(transport http.RoundTripper) SendOption {
	return func(o *sendOptions) { o.transport = transport }
}
func SendContext(ctx context.Context) SendOption {
	return func(o *sendOptions) { o.ctx = ctx }
}
func Send(method, rawurl string, options ...SendOption) (*http.Response, error) {
	u, err := url.Parse(rawurl)
	if err != nil {
		return nil, fmt.Errorf("parse url: %s", err)
	}
	opts := &sendOptions{
		body:                 nil,
		timeout:              60 * time.Second,
		acceptedCodes:        map[int]bool{http.StatusOK: true},
		headers:              map[string]string{},
		retry:                retryOptions{backoff: &backoff.StopBackOff{}},
		transport:            nil,  
		ctx:                  context.Background(),
		url:                  u,
		httpFallbackDisabled: false,
	}
	for _, o := range options {
		o(opts)
	}
	req, err := newRequest(method, opts)
	if err != nil {
		return nil, err
	}
	client := opts.client
	if client == nil {
		client = &http.Client{
			Timeout:       opts.timeout,
			CheckRedirect: opts.redirect,
			Transport:     opts.transport,
		}
	}
	var resp *http.Response
	for {
		resp, err = client.Do(req)
		if err != nil && shouldFallbackToHTTP(req, resp, opts) && !opts.httpFallbackDisabled {
			log.Warnf("Failed to send https request: %s. Retrying with http...", err)
			originalErr := err
			resp, err = fallbackToHTTP(client, method, opts)
			if err != nil {
				err = fmt.Errorf(
					"failed to fallback from https to http, original https error: %s,\n"+
						"fallback http error: %s", originalErr, err)
			}
		}
		if err != nil || shouldRetry(resp, opts) {
			d := opts.retry.backoff.NextBackOff()
			if d == backoff.Stop {
				break  
			}
			time.Sleep(d)
			continue
		}
		break
	}
	if err != nil {
		return nil, NetworkError{err}
	}
	if !opts.acceptedCodes[resp.StatusCode] {
		return nil, NewStatusError(resp)
	}
	return resp, nil
}
func Get(url string, options ...SendOption) (*http.Response, error) {
	return Send("GET", url, options...)
}
func Head(url string, options ...SendOption) (*http.Response, error) {
	return Send("HEAD", url, options...)
}
func Post(url string, options ...SendOption) (*http.Response, error) {
	return Send("POST", url, options...)
}
func Put(url string, options ...SendOption) (*http.Response, error) {
	return Send("PUT", url, options...)
}