github.com/alejandroesc/spdy@v0.0.0-20200317064415-01a02f0eb389/proxy.go (about)

     1  // Copyright 2014 Jamie Hall. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package spdy
     6  
     7  import (
     8  	"crypto/tls"
     9  	"fmt"
    10  	"net"
    11  	"net/http"
    12  	"net/http/httputil"
    13  	"net/url"
    14  
    15  	"github.com/SlyMarbo/spdy/common"
    16  )
    17  
    18  // Connect is used to perform connection
    19  // reversal where the client (who is normally
    20  // behind a NAT of some kind) connects to a server
    21  // on the internet. The connection is then reversed
    22  // so that the 'server' sends requests to the 'client'.
    23  // See ConnectAndServe() for a blocking version of this
    24  func Connect(addr string, config *tls.Config, srv *http.Server) (Conn, error) {
    25  	if config == nil {
    26  		config = new(tls.Config)
    27  	}
    28  	if srv == nil {
    29  		srv = &http.Server{Handler: http.DefaultServeMux}
    30  	}
    31  	AddSPDY(srv)
    32  
    33  	u, err := url.Parse(addr)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  
    38  	var conn net.Conn
    39  
    40  	conn, err = tls.Dial("tcp", u.Host, config)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  
    45  	req, err := http.NewRequest("GET", addr, nil)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  
    50  	client := httputil.NewClientConn(conn, nil)
    51  	err = client.Write(req)
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	res, err := client.Read(req)
    57  	if err != nil && err != httputil.ErrPersistEOF {
    58  		fmt.Println(res)
    59  		return nil, err
    60  	}
    61  
    62  	if res.StatusCode != http.StatusOK {
    63  		log.Printf("Proxy responded with status code %d\n", res.StatusCode)
    64  		return nil, common.ErrConnectFail
    65  	}
    66  
    67  	conn, _ = client.Hijack()
    68  
    69  	server, err := NewServerConn(conn, srv, 3, 1)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	return server, nil
    75  }
    76  
    77  // ConnectAndServe is used to perform connection
    78  // reversal. (See Connect() for more details.)
    79  //
    80  // This works very similarly to ListenAndServeTLS,
    81  // except that addr and config are used to connect
    82  // to the client. If srv is nil, a new http.Server
    83  // is used, with http.DefaultServeMux as the handler.
    84  func ConnectAndServe(addr string, config *tls.Config, srv *http.Server) error {
    85  	server, err := Connect(addr, config, srv)
    86  	if err != nil {
    87  		return err
    88  	}
    89  
    90  	return server.Run()
    91  }
    92  
    93  type ProxyConnHandler interface {
    94  	ProxyConnHandle(Conn)
    95  }
    96  
    97  type ProxyConnHandlerFunc func(Conn)
    98  
    99  func (p ProxyConnHandlerFunc) ProxyConnHandle(c Conn) {
   100  	p(c)
   101  }
   102  
   103  type proxyHandler struct {
   104  	ProxyConnHandler
   105  }
   106  
   107  func (p proxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   108  	r.Body.Close()
   109  
   110  	conn, _, err := w.(http.Hijacker).Hijack()
   111  	if err != nil {
   112  		log.Println("Failed to hijack connection in ProxyConnections.", err)
   113  		return
   114  	}
   115  
   116  	defer conn.Close()
   117  
   118  	if _, ok := conn.(*tls.Conn); !ok {
   119  		log.Println("Recieved a non-TLS connection in ProxyConnections.")
   120  		return
   121  	}
   122  
   123  	// Send the connection accepted response.
   124  	res := new(http.Response)
   125  	res.Status = "200 Connection Established"
   126  	res.StatusCode = http.StatusOK
   127  	res.Proto = "HTTP/1.1"
   128  	res.ProtoMajor = 1
   129  	res.ProtoMinor = 1
   130  	if err = res.Write(conn); err != nil {
   131  		log.Println("Failed to send connection established message in ProxyConnections.", err)
   132  		return
   133  	}
   134  
   135  	client, err := NewClientConn(conn, nil, 3, 1)
   136  	if err != nil {
   137  		log.Println("Error creating SPDY connection in ProxyConnections.", err)
   138  		return
   139  	}
   140  
   141  	go client.Run()
   142  
   143  	// Call user code.
   144  	p.ProxyConnHandle(client)
   145  
   146  	client.Close()
   147  }
   148  
   149  // ProxyConnections is used with ConnectAndServe in connection-
   150  // reversing proxies. This returns an http.Handler which will call
   151  // handler each time a client connects. The call is treated as
   152  // an event loop and the connection may be terminated if the call
   153  // returns. The returned Handler should then be used in a normal
   154  // HTTP server, like the following:
   155  //
   156  //   package main
   157  //
   158  //   import (
   159  //     "net/http"
   160  //
   161  //     "github.com/SlyMarbo/spdy"
   162  //   )
   163  //
   164  //   func handleProxy(conn spdy.Conn) {
   165  //     // make requests...
   166  //   }
   167  //
   168  //   func main() {
   169  //     handler := spdy.ProxyConnHandlerFunc(handleProxy)
   170  //     http.Handle("/", spdy.ProxyConnections(handler))
   171  //     http.ListenAndServeTLS(":80", "cert.pem", "key.pem", nil)
   172  //   }
   173  //
   174  // Use Conn.Request to make requests to the client and Conn.Conn
   175  // to access the underlying connection for further details like
   176  // the client's address.
   177  func ProxyConnections(handler ProxyConnHandler) http.Handler {
   178  	return proxyHandler{handler}
   179  }