github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/util/proxy/upgradeaware.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package proxy
    18  
    19  import (
    20  	"bufio"
    21  	"bytes"
    22  	"fmt"
    23  	"io"
    24  	"io/ioutil"
    25  	"log"
    26  	"net"
    27  	"net/http"
    28  	"net/http/httputil"
    29  	"net/url"
    30  	"os"
    31  	"strings"
    32  	"time"
    33  
    34  	"github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/api/errors"
    35  	"github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/util/httpstream"
    36  	utilnet "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/util/net"
    37  	utilruntime "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/util/runtime"
    38  
    39  	"github.com/mxk/go-flowrate/flowrate"
    40  	"k8s.io/klog/v2"
    41  )
    42  
    43  // UpgradeRequestRoundTripper provides an additional method to decorate a request
    44  // with any authentication or other protocol level information prior to performing
    45  // an upgrade on the server. Any response will be handled by the intercepting
    46  // proxy.
    47  type UpgradeRequestRoundTripper interface {
    48  	http.RoundTripper
    49  	// WrapRequest takes a valid HTTP request and returns a suitably altered version
    50  	// of request with any HTTP level values required to complete the request half of
    51  	// an upgrade on the server. It does not get a chance to see the response and
    52  	// should bypass any request side logic that expects to see the response.
    53  	WrapRequest(*http.Request) (*http.Request, error)
    54  }
    55  
    56  // UpgradeAwareHandler is a handler for proxy requests that may require an upgrade
    57  type UpgradeAwareHandler struct {
    58  	// UpgradeRequired will reject non-upgrade connections if true.
    59  	UpgradeRequired bool
    60  	// Location is the location of the upstream proxy. It is used as the location to Dial on the upstream server
    61  	// for upgrade requests unless UseRequestLocationOnUpgrade is true.
    62  	Location *url.URL
    63  	// AppendLocationPath determines if the original path of the Location should be appended to the upstream proxy request path
    64  	AppendLocationPath bool
    65  	// Transport provides an optional round tripper to use to proxy. If nil, the default proxy transport is used
    66  	Transport http.RoundTripper
    67  	// UpgradeTransport, if specified, will be used as the backend transport when upgrade requests are provided.
    68  	// This allows clients to disable HTTP/2.
    69  	UpgradeTransport UpgradeRequestRoundTripper
    70  	// WrapTransport indicates whether the provided Transport should be wrapped with default proxy transport behavior (URL rewriting, X-Forwarded-* header setting)
    71  	WrapTransport bool
    72  	// UseRequestLocation will use the incoming request URL when talking to the backend server.
    73  	UseRequestLocation bool
    74  	// UseLocationHost overrides the HTTP host header in requests to the backend server to use the Host from Location.
    75  	// This will override the req.Host field of a request, while UseRequestLocation will override the req.URL field
    76  	// of a request. The req.URL.Host specifies the server to connect to, while the req.Host field
    77  	// specifies the Host header value to send in the HTTP request. If this is false, the incoming req.Host header will
    78  	// just be forwarded to the backend server.
    79  	UseLocationHost bool
    80  	// FlushInterval controls how often the standard HTTP proxy will flush content from the upstream.
    81  	FlushInterval time.Duration
    82  	// MaxBytesPerSec controls the maximum rate for an upstream connection. No rate is imposed if the value is zero.
    83  	MaxBytesPerSec int64
    84  	// Responder is passed errors that occur while setting up proxying.
    85  	Responder ErrorResponder
    86  	// Reject to forward redirect response
    87  	RejectForwardingRedirects bool
    88  }
    89  
    90  const defaultFlushInterval = 200 * time.Millisecond
    91  
    92  // ErrorResponder abstracts error reporting to the proxy handler to remove the need to hardcode a particular
    93  // error format.
    94  type ErrorResponder interface {
    95  	Error(w http.ResponseWriter, req *http.Request, err error)
    96  }
    97  
    98  // SimpleErrorResponder is the legacy implementation of ErrorResponder for callers that only
    99  // service a single request/response per proxy.
   100  type SimpleErrorResponder interface {
   101  	Error(err error)
   102  }
   103  
   104  func NewErrorResponder(r SimpleErrorResponder) ErrorResponder {
   105  	return simpleResponder{r}
   106  }
   107  
   108  type simpleResponder struct {
   109  	responder SimpleErrorResponder
   110  }
   111  
   112  func (r simpleResponder) Error(w http.ResponseWriter, req *http.Request, err error) {
   113  	r.responder.Error(err)
   114  }
   115  
   116  // upgradeRequestRoundTripper implements proxy.UpgradeRequestRoundTripper.
   117  type upgradeRequestRoundTripper struct {
   118  	http.RoundTripper
   119  	upgrader http.RoundTripper
   120  }
   121  
   122  var (
   123  	_ UpgradeRequestRoundTripper  = &upgradeRequestRoundTripper{}
   124  	_ utilnet.RoundTripperWrapper = &upgradeRequestRoundTripper{}
   125  )
   126  
   127  // WrappedRoundTripper returns the round tripper that a caller would use.
   128  func (rt *upgradeRequestRoundTripper) WrappedRoundTripper() http.RoundTripper {
   129  	return rt.RoundTripper
   130  }
   131  
   132  // WriteToRequest calls the nested upgrader and then copies the returned request
   133  // fields onto the passed request.
   134  func (rt *upgradeRequestRoundTripper) WrapRequest(req *http.Request) (*http.Request, error) {
   135  	resp, err := rt.upgrader.RoundTrip(req)
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  	return resp.Request, nil
   140  }
   141  
   142  // onewayRoundTripper captures the provided request - which is assumed to have
   143  // been modified by other round trippers - and then returns a fake response.
   144  type onewayRoundTripper struct{}
   145  
   146  // RoundTrip returns a simple 200 OK response that captures the provided request.
   147  func (onewayRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   148  	return &http.Response{
   149  		Status:     "200 OK",
   150  		StatusCode: http.StatusOK,
   151  		Body:       ioutil.NopCloser(&bytes.Buffer{}),
   152  		Request:    req,
   153  	}, nil
   154  }
   155  
   156  // MirrorRequest is a round tripper that can be called to get back the calling request as
   157  // the core round tripper in a chain.
   158  var MirrorRequest http.RoundTripper = onewayRoundTripper{}
   159  
   160  // NewUpgradeRequestRoundTripper takes two round trippers - one for the underlying TCP connection, and
   161  // one that is able to write headers to an HTTP request. The request rt is used to set the request headers
   162  // and that is written to the underlying connection rt.
   163  func NewUpgradeRequestRoundTripper(connection, request http.RoundTripper) UpgradeRequestRoundTripper {
   164  	return &upgradeRequestRoundTripper{
   165  		RoundTripper: connection,
   166  		upgrader:     request,
   167  	}
   168  }
   169  
   170  // normalizeLocation returns the result of parsing the full URL, with scheme set to http if missing
   171  func normalizeLocation(location *url.URL) *url.URL {
   172  	normalized, _ := url.Parse(location.String())
   173  	if len(normalized.Scheme) == 0 {
   174  		normalized.Scheme = "http"
   175  	}
   176  	return normalized
   177  }
   178  
   179  // NewUpgradeAwareHandler creates a new proxy handler with a default flush interval. Responder is required for returning
   180  // errors to the caller.
   181  func NewUpgradeAwareHandler(location *url.URL, transport http.RoundTripper, wrapTransport, upgradeRequired bool, responder ErrorResponder) *UpgradeAwareHandler {
   182  	return &UpgradeAwareHandler{
   183  		Location:        normalizeLocation(location),
   184  		Transport:       transport,
   185  		WrapTransport:   wrapTransport,
   186  		UpgradeRequired: upgradeRequired,
   187  		FlushInterval:   defaultFlushInterval,
   188  		Responder:       responder,
   189  	}
   190  }
   191  
   192  func proxyRedirectsforRootPath(path string, w http.ResponseWriter, req *http.Request) bool {
   193  	redirect := false
   194  	method := req.Method
   195  
   196  	// From pkg/genericapiserver/endpoints/handlers/proxy.go#ServeHTTP:
   197  	// Redirect requests with an empty path to a location that ends with a '/'
   198  	// This is essentially a hack for https://issue.k8s.io/4958.
   199  	// Note: Keep this code after tryUpgrade to not break that flow.
   200  	if len(path) == 0 && (method == http.MethodGet || method == http.MethodHead) {
   201  		var queryPart string
   202  		if len(req.URL.RawQuery) > 0 {
   203  			queryPart = "?" + req.URL.RawQuery
   204  		}
   205  		w.Header().Set("Location", req.URL.Path+"/"+queryPart)
   206  		w.WriteHeader(http.StatusMovedPermanently)
   207  		redirect = true
   208  	}
   209  	return redirect
   210  }
   211  
   212  // ServeHTTP handles the proxy request
   213  func (h *UpgradeAwareHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
   214  	if h.tryUpgrade(w, req) {
   215  		return
   216  	}
   217  	if h.UpgradeRequired {
   218  		h.Responder.Error(w, req, errors.NewBadRequest("Upgrade request required"))
   219  		return
   220  	}
   221  
   222  	loc := *h.Location
   223  	loc.RawQuery = req.URL.RawQuery
   224  
   225  	// If original request URL ended in '/', append a '/' at the end of the
   226  	// of the proxy URL
   227  	if !strings.HasSuffix(loc.Path, "/") && strings.HasSuffix(req.URL.Path, "/") {
   228  		loc.Path += "/"
   229  	}
   230  
   231  	proxyRedirect := proxyRedirectsforRootPath(loc.Path, w, req)
   232  	if proxyRedirect {
   233  		return
   234  	}
   235  
   236  	if h.Transport == nil || h.WrapTransport {
   237  		h.Transport = h.defaultProxyTransport(req.URL, h.Transport)
   238  	}
   239  
   240  	// WithContext creates a shallow clone of the request with the same context.
   241  	newReq := req.WithContext(req.Context())
   242  	newReq.Header = utilnet.CloneHeader(req.Header)
   243  	if !h.UseRequestLocation {
   244  		newReq.URL = &loc
   245  	}
   246  	if h.UseLocationHost {
   247  		// exchanging req.Host with the backend location is necessary for backends that act on the HTTP host header (e.g. API gateways),
   248  		// because req.Host has preference over req.URL.Host in filling this header field
   249  		newReq.Host = h.Location.Host
   250  	}
   251  
   252  	// create the target location to use for the reverse proxy
   253  	reverseProxyLocation := &url.URL{Scheme: h.Location.Scheme, Host: h.Location.Host}
   254  	if h.AppendLocationPath {
   255  		reverseProxyLocation.Path = h.Location.Path
   256  	}
   257  
   258  	proxy := httputil.NewSingleHostReverseProxy(reverseProxyLocation)
   259  	proxy.Transport = h.Transport
   260  	proxy.FlushInterval = h.FlushInterval
   261  	proxy.ErrorLog = log.New(noSuppressPanicError{}, "", log.LstdFlags)
   262  	if h.RejectForwardingRedirects {
   263  		oldModifyResponse := proxy.ModifyResponse
   264  		proxy.ModifyResponse = func(response *http.Response) error {
   265  			code := response.StatusCode
   266  			if code >= 300 && code <= 399 && len(response.Header.Get("Location")) > 0 {
   267  				// close the original response
   268  				response.Body.Close()
   269  				msg := "the backend attempted to redirect this request, which is not permitted"
   270  				// replace the response
   271  				*response = http.Response{
   272  					StatusCode:    http.StatusBadGateway,
   273  					Status:        fmt.Sprintf("%d %s", response.StatusCode, http.StatusText(response.StatusCode)),
   274  					Body:          io.NopCloser(strings.NewReader(msg)),
   275  					ContentLength: int64(len(msg)),
   276  				}
   277  			} else {
   278  				if oldModifyResponse != nil {
   279  					if err := oldModifyResponse(response); err != nil {
   280  						return err
   281  					}
   282  				}
   283  			}
   284  			return nil
   285  		}
   286  	}
   287  	if h.Responder != nil {
   288  		// if an optional error interceptor/responder was provided wire it
   289  		// the custom responder might be used for providing a unified error reporting
   290  		// or supporting retry mechanisms by not sending non-fatal errors to the clients
   291  		proxy.ErrorHandler = h.Responder.Error
   292  	}
   293  	proxy.ServeHTTP(w, newReq)
   294  }
   295  
   296  type noSuppressPanicError struct{}
   297  
   298  func (noSuppressPanicError) Write(p []byte) (n int, err error) {
   299  	// skip "suppressing panic for copyResponse error in test; copy error" error message
   300  	// that ends up in CI tests on each kube-apiserver termination as noise and
   301  	// everybody thinks this is fatal.
   302  	if strings.Contains(string(p), "suppressing panic") {
   303  		return len(p), nil
   304  	}
   305  	return os.Stderr.Write(p)
   306  }
   307  
   308  // tryUpgrade returns true if the request was handled.
   309  func (h *UpgradeAwareHandler) tryUpgrade(w http.ResponseWriter, req *http.Request) bool {
   310  	if !httpstream.IsUpgradeRequest(req) {
   311  		klog.V(6).Infof("Request was not an upgrade")
   312  		return false
   313  	}
   314  
   315  	var (
   316  		backendConn net.Conn
   317  		rawResponse []byte
   318  		err         error
   319  	)
   320  
   321  	location := *h.Location
   322  	if h.UseRequestLocation {
   323  		location = *req.URL
   324  		location.Scheme = h.Location.Scheme
   325  		location.Host = h.Location.Host
   326  		if h.AppendLocationPath {
   327  			location.Path = singleJoiningSlash(h.Location.Path, location.Path)
   328  		}
   329  	}
   330  
   331  	clone := utilnet.CloneRequest(req)
   332  	// Only append X-Forwarded-For in the upgrade path, since httputil.NewSingleHostReverseProxy
   333  	// handles this in the non-upgrade path.
   334  	utilnet.AppendForwardedForHeader(clone)
   335  	klog.V(6).Infof("Connecting to backend proxy (direct dial) %s\n  Headers: %v", &location, clone.Header)
   336  	if h.UseLocationHost {
   337  		clone.Host = h.Location.Host
   338  	}
   339  	clone.URL = &location
   340  	backendConn, err = h.DialForUpgrade(clone)
   341  	if err != nil {
   342  		klog.V(6).Infof("Proxy connection error: %v", err)
   343  		h.Responder.Error(w, req, err)
   344  		return true
   345  	}
   346  	defer backendConn.Close()
   347  
   348  	// determine the http response code from the backend by reading from rawResponse+backendConn
   349  	backendHTTPResponse, headerBytes, err := getResponse(io.MultiReader(bytes.NewReader(rawResponse), backendConn))
   350  	if err != nil {
   351  		klog.V(6).Infof("Proxy connection error: %v", err)
   352  		h.Responder.Error(w, req, err)
   353  		return true
   354  	}
   355  	if len(headerBytes) > len(rawResponse) {
   356  		// we read beyond the bytes stored in rawResponse, update rawResponse to the full set of bytes read from the backend
   357  		rawResponse = headerBytes
   358  	}
   359  
   360  	// If the backend did not upgrade the request, return an error to the client. If the response was
   361  	// an error, the error is forwarded directly after the connection is hijacked. Otherwise, just
   362  	// return a generic error here.
   363  	if backendHTTPResponse.StatusCode != http.StatusSwitchingProtocols && backendHTTPResponse.StatusCode < 400 {
   364  		err := fmt.Errorf("invalid upgrade response: status code %d", backendHTTPResponse.StatusCode)
   365  		klog.Errorf("Proxy upgrade error: %v", err)
   366  		h.Responder.Error(w, req, err)
   367  		return true
   368  	}
   369  
   370  	// Once the connection is hijacked, the ErrorResponder will no longer work, so
   371  	// hijacking should be the last step in the upgrade.
   372  	requestHijacker, ok := w.(http.Hijacker)
   373  	if !ok {
   374  		klog.V(6).Infof("Unable to hijack response writer: %T", w)
   375  		h.Responder.Error(w, req, fmt.Errorf("request connection cannot be hijacked: %T", w))
   376  		return true
   377  	}
   378  	requestHijackedConn, _, err := requestHijacker.Hijack()
   379  	if err != nil {
   380  		klog.V(6).Infof("Unable to hijack response: %v", err)
   381  		h.Responder.Error(w, req, fmt.Errorf("error hijacking connection: %v", err))
   382  		return true
   383  	}
   384  	defer requestHijackedConn.Close()
   385  
   386  	if backendHTTPResponse.StatusCode != http.StatusSwitchingProtocols {
   387  		// If the backend did not upgrade the request, echo the response from the backend to the client and return, closing the connection.
   388  		klog.V(6).Infof("Proxy upgrade error, status code %d", backendHTTPResponse.StatusCode)
   389  		// set read/write deadlines
   390  		deadline := time.Now().Add(10 * time.Second)
   391  		backendConn.SetReadDeadline(deadline)
   392  		requestHijackedConn.SetWriteDeadline(deadline)
   393  		// write the response to the client
   394  		err := backendHTTPResponse.Write(requestHijackedConn)
   395  		if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
   396  			klog.Errorf("Error proxying data from backend to client: %v", err)
   397  		}
   398  		// Indicate we handled the request
   399  		return true
   400  	}
   401  
   402  	// Forward raw response bytes back to client.
   403  	if len(rawResponse) > 0 {
   404  		klog.V(6).Infof("Writing %d bytes to hijacked connection", len(rawResponse))
   405  		if _, err = requestHijackedConn.Write(rawResponse); err != nil {
   406  			utilruntime.HandleError(fmt.Errorf("Error proxying response from backend to client: %v", err))
   407  		}
   408  	}
   409  
   410  	// Proxy the connection. This is bidirectional, so we need a goroutine
   411  	// to copy in each direction. Once one side of the connection exits, we
   412  	// exit the function which performs cleanup and in the process closes
   413  	// the other half of the connection in the defer.
   414  	writerComplete := make(chan struct{})
   415  	readerComplete := make(chan struct{})
   416  
   417  	go func() {
   418  		var writer io.WriteCloser
   419  		if h.MaxBytesPerSec > 0 {
   420  			writer = flowrate.NewWriter(backendConn, h.MaxBytesPerSec)
   421  		} else {
   422  			writer = backendConn
   423  		}
   424  		_, err := io.Copy(writer, requestHijackedConn)
   425  		if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
   426  			klog.Errorf("Error proxying data from client to backend: %v", err)
   427  		}
   428  		close(writerComplete)
   429  	}()
   430  
   431  	go func() {
   432  		var reader io.ReadCloser
   433  		if h.MaxBytesPerSec > 0 {
   434  			reader = flowrate.NewReader(backendConn, h.MaxBytesPerSec)
   435  		} else {
   436  			reader = backendConn
   437  		}
   438  		_, err := io.Copy(requestHijackedConn, reader)
   439  		if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
   440  			klog.Errorf("Error proxying data from backend to client: %v", err)
   441  		}
   442  		close(readerComplete)
   443  	}()
   444  
   445  	// Wait for one half the connection to exit. Once it does the defer will
   446  	// clean up the other half of the connection.
   447  	select {
   448  	case <-writerComplete:
   449  	case <-readerComplete:
   450  	}
   451  	klog.V(6).Infof("Disconnecting from backend proxy %s\n  Headers: %v", &location, clone.Header)
   452  
   453  	return true
   454  }
   455  
   456  // FIXME: Taken from net/http/httputil/reverseproxy.go as singleJoiningSlash is not exported to be re-used.
   457  // See-also: https://github.com/golang/go/issues/44290
   458  func singleJoiningSlash(a, b string) string {
   459  	aslash := strings.HasSuffix(a, "/")
   460  	bslash := strings.HasPrefix(b, "/")
   461  	switch {
   462  	case aslash && bslash:
   463  		return a + b[1:]
   464  	case !aslash && !bslash:
   465  		return a + "/" + b
   466  	}
   467  	return a + b
   468  }
   469  
   470  func (h *UpgradeAwareHandler) DialForUpgrade(req *http.Request) (net.Conn, error) {
   471  	if h.UpgradeTransport == nil {
   472  		return dial(req, h.Transport)
   473  	}
   474  	updatedReq, err := h.UpgradeTransport.WrapRequest(req)
   475  	if err != nil {
   476  		return nil, err
   477  	}
   478  	return dial(updatedReq, h.UpgradeTransport)
   479  }
   480  
   481  // getResponseCode reads a http response from the given reader, returns the response,
   482  // the bytes read from the reader, and any error encountered
   483  func getResponse(r io.Reader) (*http.Response, []byte, error) {
   484  	rawResponse := bytes.NewBuffer(make([]byte, 0, 256))
   485  	// Save the bytes read while reading the response headers into the rawResponse buffer
   486  	resp, err := http.ReadResponse(bufio.NewReader(io.TeeReader(r, rawResponse)), nil)
   487  	if err != nil {
   488  		return nil, nil, err
   489  	}
   490  	// return the http response and the raw bytes consumed from the reader in the process
   491  	return resp, rawResponse.Bytes(), nil
   492  }
   493  
   494  // dial dials the backend at req.URL and writes req to it.
   495  func dial(req *http.Request, transport http.RoundTripper) (net.Conn, error) {
   496  	conn, err := dialURL(req.Context(), req.URL, transport)
   497  	if err != nil {
   498  		return nil, fmt.Errorf("error dialing backend: %v", err)
   499  	}
   500  
   501  	if err = req.Write(conn); err != nil {
   502  		conn.Close()
   503  		return nil, fmt.Errorf("error sending request: %v", err)
   504  	}
   505  
   506  	return conn, err
   507  }
   508  
   509  func (h *UpgradeAwareHandler) defaultProxyTransport(url *url.URL, internalTransport http.RoundTripper) http.RoundTripper {
   510  	scheme := url.Scheme
   511  	host := url.Host
   512  	suffix := h.Location.Path
   513  	if strings.HasSuffix(url.Path, "/") && !strings.HasSuffix(suffix, "/") {
   514  		suffix += "/"
   515  	}
   516  	pathPrepend := strings.TrimSuffix(url.Path, suffix)
   517  	rewritingTransport := &Transport{
   518  		Scheme:       scheme,
   519  		Host:         host,
   520  		PathPrepend:  pathPrepend,
   521  		RoundTripper: internalTransport,
   522  	}
   523  	return &corsRemovingTransport{
   524  		RoundTripper: rewritingTransport,
   525  	}
   526  }
   527  
   528  // corsRemovingTransport is a wrapper for an internal transport. It removes CORS headers
   529  // from the internal response.
   530  // Implements pkg/util/net.RoundTripperWrapper
   531  type corsRemovingTransport struct {
   532  	http.RoundTripper
   533  }
   534  
   535  var _ = utilnet.RoundTripperWrapper(&corsRemovingTransport{})
   536  
   537  func (rt *corsRemovingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
   538  	resp, err := rt.RoundTripper.RoundTrip(req)
   539  	if err != nil {
   540  		return nil, err
   541  	}
   542  	removeCORSHeaders(resp)
   543  	return resp, nil
   544  }
   545  
   546  func (rt *corsRemovingTransport) WrappedRoundTripper() http.RoundTripper {
   547  	return rt.RoundTripper
   548  }
   549  
   550  // removeCORSHeaders strip CORS headers sent from the backend
   551  // This should be called on all responses before returning
   552  func removeCORSHeaders(resp *http.Response) {
   553  	resp.Header.Del("Access-Control-Allow-Credentials")
   554  	resp.Header.Del("Access-Control-Allow-Headers")
   555  	resp.Header.Del("Access-Control-Allow-Methods")
   556  	resp.Header.Del("Access-Control-Allow-Origin")
   557  }