2016-05-13 06:51:48 +03:00
|
|
|
// Copyright 2015 The etcd Authors
|
2015-01-25 06:19:16 +03:00
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
2014-10-18 02:41:22 +04:00
|
|
|
|
2016-04-14 07:09:08 +03:00
|
|
|
package httpproxy
|
2014-09-11 03:38:52 +04:00
|
|
|
|
|
|
|
import (
|
2015-06-01 21:45:23 +03:00
|
|
|
"bytes"
|
2017-02-02 16:19:02 +03:00
|
|
|
"context"
|
2014-11-11 02:56:42 +03:00
|
|
|
"fmt"
|
2014-09-11 03:38:52 +04:00
|
|
|
"io"
|
2015-06-01 21:45:23 +03:00
|
|
|
"io/ioutil"
|
2014-09-11 03:38:52 +04:00
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"strings"
|
2015-06-17 11:15:00 +03:00
|
|
|
"sync/atomic"
|
2015-06-17 16:32:13 +03:00
|
|
|
"time"
|
2015-08-24 20:51:17 +03:00
|
|
|
|
2016-03-23 03:10:28 +03:00
|
|
|
"github.com/coreos/pkg/capnslog"
|
2019-04-27 00:17:16 +03:00
|
|
|
"go.etcd.io/etcd/v3/etcdserver/api/v2http/httptypes"
|
2014-09-11 03:38:52 +04:00
|
|
|
)
|
|
|
|
|
2015-09-25 18:06:26 +03:00
|
|
|
var (
|
2018-08-29 03:13:25 +03:00
|
|
|
plog = capnslog.NewPackageLogger("go.etcd.io/etcd", "proxy/httpproxy")
|
2015-09-25 18:06:26 +03:00
|
|
|
|
|
|
|
// Hop-by-hop headers. These are removed when sent to the backend.
|
|
|
|
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
|
|
|
|
// This list of headers borrowed from stdlib httputil.ReverseProxy
|
|
|
|
singleHopHeaders = []string{
|
|
|
|
"Connection",
|
|
|
|
"Keep-Alive",
|
|
|
|
"Proxy-Authenticate",
|
|
|
|
"Proxy-Authorization",
|
|
|
|
"Te", // canonicalized version of "TE"
|
|
|
|
"Trailers",
|
|
|
|
"Transfer-Encoding",
|
|
|
|
"Upgrade",
|
|
|
|
}
|
|
|
|
)
|
2014-09-11 03:38:52 +04:00
|
|
|
|
|
|
|
func removeSingleHopHeaders(hdrs *http.Header) {
|
|
|
|
for _, h := range singleHopHeaders {
|
|
|
|
hdrs.Del(h)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type reverseProxy struct {
|
|
|
|
director *director
|
|
|
|
transport http.RoundTripper
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *reverseProxy) ServeHTTP(rw http.ResponseWriter, clientreq *http.Request) {
|
2016-04-07 22:20:03 +03:00
|
|
|
reportIncomingRequest(clientreq)
|
2014-09-11 03:38:52 +04:00
|
|
|
proxyreq := new(http.Request)
|
|
|
|
*proxyreq = *clientreq
|
2015-06-17 16:32:13 +03:00
|
|
|
startTime := time.Now()
|
2014-09-11 03:38:52 +04:00
|
|
|
|
2015-06-01 21:45:23 +03:00
|
|
|
var (
|
|
|
|
proxybody []byte
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
|
|
|
if clientreq.Body != nil {
|
|
|
|
proxybody, err = ioutil.ReadAll(clientreq.Body)
|
|
|
|
if err != nil {
|
2016-05-24 01:36:11 +03:00
|
|
|
msg := fmt.Sprintf("failed to read request body: %v", err)
|
|
|
|
plog.Println(msg)
|
|
|
|
e := httptypes.NewHTTPError(http.StatusInternalServerError, "httpproxy: "+msg)
|
2015-09-25 18:06:26 +03:00
|
|
|
if we := e.WriteTo(rw); we != nil {
|
|
|
|
plog.Debugf("error writing HTTPError (%v) to %s", we, clientreq.RemoteAddr)
|
|
|
|
}
|
2015-06-01 21:45:23 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-11 03:38:52 +04:00
|
|
|
// deep-copy the headers, as these will be modified below
|
|
|
|
proxyreq.Header = make(http.Header)
|
|
|
|
copyHeader(proxyreq.Header, clientreq.Header)
|
|
|
|
|
|
|
|
normalizeRequest(proxyreq)
|
|
|
|
removeSingleHopHeaders(&proxyreq.Header)
|
|
|
|
maybeSetForwardedFor(proxyreq)
|
|
|
|
|
|
|
|
endpoints := p.director.endpoints()
|
|
|
|
if len(endpoints) == 0 {
|
2016-05-24 01:36:11 +03:00
|
|
|
msg := "zero endpoints currently available"
|
2015-06-17 16:32:13 +03:00
|
|
|
reportRequestDropped(clientreq, zeroEndpoints)
|
|
|
|
|
2014-11-11 03:37:15 +03:00
|
|
|
// TODO: limit the rate of the error logging.
|
2016-05-24 01:36:11 +03:00
|
|
|
plog.Println(msg)
|
|
|
|
e := httptypes.NewHTTPError(http.StatusServiceUnavailable, "httpproxy: "+msg)
|
2015-09-25 18:06:26 +03:00
|
|
|
if we := e.WriteTo(rw); we != nil {
|
|
|
|
plog.Debugf("error writing HTTPError (%v) to %s", we, clientreq.RemoteAddr)
|
|
|
|
}
|
2014-09-11 03:38:52 +04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-06-17 11:15:00 +03:00
|
|
|
var requestClosed int32
|
2015-04-28 23:59:02 +03:00
|
|
|
completeCh := make(chan bool, 1)
|
|
|
|
closeNotifier, ok := rw.(http.CloseNotifier)
|
2017-02-02 16:19:02 +03:00
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
proxyreq = proxyreq.WithContext(ctx)
|
|
|
|
defer cancel()
|
2015-04-28 23:59:02 +03:00
|
|
|
if ok {
|
2016-05-04 19:32:26 +03:00
|
|
|
closeCh := closeNotifier.CloseNotify()
|
2015-04-28 23:59:02 +03:00
|
|
|
go func() {
|
|
|
|
select {
|
2016-05-04 19:32:26 +03:00
|
|
|
case <-closeCh:
|
2015-06-19 09:45:45 +03:00
|
|
|
atomic.StoreInt32(&requestClosed, 1)
|
2016-05-24 01:36:11 +03:00
|
|
|
plog.Printf("client %v closed request prematurely", clientreq.RemoteAddr)
|
2018-02-18 21:27:05 +03:00
|
|
|
cancel()
|
2015-04-28 23:59:02 +03:00
|
|
|
case <-completeCh:
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
completeCh <- true
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2014-09-11 03:38:52 +04:00
|
|
|
var res *http.Response
|
|
|
|
|
|
|
|
for _, ep := range endpoints {
|
2015-06-01 21:45:23 +03:00
|
|
|
if proxybody != nil {
|
|
|
|
proxyreq.Body = ioutil.NopCloser(bytes.NewBuffer(proxybody))
|
|
|
|
}
|
2014-09-11 03:38:52 +04:00
|
|
|
redirectRequest(proxyreq, ep.URL)
|
|
|
|
|
|
|
|
res, err = p.transport.RoundTrip(proxyreq)
|
2015-06-17 11:15:00 +03:00
|
|
|
if atomic.LoadInt32(&requestClosed) == 1 {
|
2015-06-16 21:39:42 +03:00
|
|
|
return
|
|
|
|
}
|
2014-09-11 03:38:52 +04:00
|
|
|
if err != nil {
|
2015-06-17 16:32:13 +03:00
|
|
|
reportRequestDropped(clientreq, failedSendingRequest)
|
2016-05-24 01:36:11 +03:00
|
|
|
plog.Printf("failed to direct request to %s: %v", ep.URL.String(), err)
|
2014-09-11 03:38:52 +04:00
|
|
|
ep.Failed()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if res == nil {
|
2014-11-11 03:37:15 +03:00
|
|
|
// TODO: limit the rate of the error logging.
|
2016-05-24 01:36:11 +03:00
|
|
|
msg := fmt.Sprintf("unable to get response from %d endpoint(s)", len(endpoints))
|
2015-06-17 16:32:13 +03:00
|
|
|
reportRequestDropped(clientreq, failedGettingResponse)
|
2016-05-24 01:36:11 +03:00
|
|
|
plog.Println(msg)
|
|
|
|
e := httptypes.NewHTTPError(http.StatusBadGateway, "httpproxy: "+msg)
|
2015-09-25 18:06:26 +03:00
|
|
|
if we := e.WriteTo(rw); we != nil {
|
|
|
|
plog.Debugf("error writing HTTPError (%v) to %s", we, clientreq.RemoteAddr)
|
|
|
|
}
|
2014-09-11 03:38:52 +04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
defer res.Body.Close()
|
2015-06-17 16:32:13 +03:00
|
|
|
reportRequestHandled(clientreq, res, startTime)
|
2014-09-11 03:38:52 +04:00
|
|
|
removeSingleHopHeaders(&res.Header)
|
|
|
|
copyHeader(rw.Header(), res.Header)
|
|
|
|
|
|
|
|
rw.WriteHeader(res.StatusCode)
|
|
|
|
io.Copy(rw, res.Body)
|
|
|
|
}
|
|
|
|
|
|
|
|
func copyHeader(dst, src http.Header) {
|
|
|
|
for k, vv := range src {
|
|
|
|
for _, v := range vv {
|
|
|
|
dst.Add(k, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func redirectRequest(req *http.Request, loc url.URL) {
|
|
|
|
req.URL.Scheme = loc.Scheme
|
|
|
|
req.URL.Host = loc.Host
|
|
|
|
}
|
|
|
|
|
|
|
|
func normalizeRequest(req *http.Request) {
|
|
|
|
req.Proto = "HTTP/1.1"
|
|
|
|
req.ProtoMajor = 1
|
|
|
|
req.ProtoMinor = 1
|
|
|
|
req.Close = false
|
|
|
|
}
|
|
|
|
|
|
|
|
func maybeSetForwardedFor(req *http.Request) {
|
|
|
|
clientIP, _, err := net.SplitHostPort(req.RemoteAddr)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we aren't the first proxy retain prior
|
|
|
|
// X-Forwarded-For information as a comma+space
|
|
|
|
// separated list and fold multiple headers into one.
|
|
|
|
if prior, ok := req.Header["X-Forwarded-For"]; ok {
|
|
|
|
clientIP = strings.Join(prior, ", ") + ", " + clientIP
|
|
|
|
}
|
|
|
|
req.Header.Set("X-Forwarded-For", clientIP)
|
|
|
|
}
|