route/lib/tunnel/httpproxy.go

152 lines
3.2 KiB
Go
Raw Normal View History

2017-01-20 01:27:14 +00:00
package tunnel
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
2017-01-23 17:07:39 +00:00
"net/http/httputil"
"net/url"
"sync"
2017-01-20 01:27:14 +00:00
"git.xeserv.us/xena/route/lib/tunnel/proto"
2017-01-23 17:07:39 +00:00
"github.com/koding/logging"
2017-01-20 01:27:14 +00:00
)
var (
httpLog = logging.NewLogger("http")
)
// HTTPProxy forwards HTTP traffic.
//
// When tunnel server requests a connection it's proxied to 127.0.0.1:incomingPort
// where incomingPort is control message LocalPort.
// Usually this is tunnel server's public exposed Port.
// This behaviour can be changed by setting LocalAddr or FetchLocalAddr.
// FetchLocalAddr takes precedence over LocalAddr.
//
// When connection to local server cannot be established proxy responds with http error message.
type HTTPProxy struct {
// LocalAddr defines the TCP address of the local server.
// This is optional if you want to specify a single TCP address.
LocalAddr string
// ErrorResp is custom response send to tunnel server when client cannot
// establish connection to local server. If not set a default "no local server"
// response is sent.
ErrorResp *http.Response
// Log is a custom logger that can be used for the proxy.
// If not set a "http" logger is used.
Log logging.Logger
2017-01-23 17:07:39 +00:00
hs *http.Server
rp *httputil.ReverseProxy
2017-01-20 01:27:14 +00:00
}
// Proxy is a ProxyFunc.
func (p *HTTPProxy) Proxy(remote net.Conn, msg *proto.ControlMessage) {
if msg.Protocol != proto.HTTP && msg.Protocol != proto.WS {
panic("Proxy mismatch")
}
var log = p.log()
var port = msg.LocalPort
if port == 0 {
port = 80
}
var localAddr = fmt.Sprintf("127.0.0.1:%d", port)
if p.LocalAddr != "" {
localAddr = p.LocalAddr
2017-01-23 17:07:39 +00:00
}
if p.rp == nil {
2017-01-23 17:07:39 +00:00
su, _ := url.Parse(fmt.Sprintf("http://%s", p.LocalAddr))
p.rp = httputil.NewSingleHostReverseProxy(su)
2017-01-20 01:27:14 +00:00
}
log.Debug("Dialing local server %q", localAddr)
2017-01-23 17:07:39 +00:00
sl := singleListener{
conn: remote,
}
hs := &http.Server{
Handler: p.rp,
}
err := hs.Serve(sl)
2017-01-20 01:27:14 +00:00
if err != nil {
log.Error("Dialing local server %q failed: %s", localAddr, err)
p.sendError(remote)
return
}
}
func (p *HTTPProxy) sendError(remote net.Conn) {
var w = noLocalServer()
if p.ErrorResp != nil {
w = p.ErrorResp
}
buf := new(bytes.Buffer)
w.Write(buf)
if _, err := io.Copy(remote, buf); err != nil {
var log = p.log()
log.Debug("Copy in-mem response error: %s", err)
}
remote.Close()
}
func noLocalServer() *http.Response {
body := bytes.NewBufferString("no local server")
return &http.Response{
Status: http.StatusText(http.StatusServiceUnavailable),
StatusCode: http.StatusServiceUnavailable,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Body: ioutil.NopCloser(body),
ContentLength: int64(body.Len()),
}
}
func (p *HTTPProxy) log() logging.Logger {
if p.Log != nil {
return p.Log
}
return httpLog
}
2017-01-23 17:07:39 +00:00
// A singleListener is a net.Listener that returns a single connection, then
// gives the error io.EOF.
type singleListener struct {
conn net.Conn
once sync.Once
}
func (s singleListener) Accept() (net.Conn, error) {
var c net.Conn
s.once.Do(func() {
c = s.conn
})
if c != nil {
return c, nil
}
return nil, io.EOF
}
func (s singleListener) Close() error {
s.once.Do(func() {
s.conn.Close()
})
return nil
}
func (s singleListener) Addr() net.Addr {
return s.conn.LocalAddr()
}