github.com/codingeasygo/util@v0.0.0-20231206062002-1ce2f004b7d9/proxy/http/http.go (about)

     1  package http
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"io"
     8  	"net"
     9  	"net/http"
    10  	"strings"
    11  	"sync"
    12  
    13  	"github.com/codingeasygo/util/xio"
    14  )
    15  
    16  //Server is http proxy server
    17  type Server struct {
    18  	BufferSize int
    19  	listners   map[net.Listener]string
    20  	waiter     sync.WaitGroup
    21  	Dialer     xio.PiperDialer
    22  	Agent      string
    23  }
    24  
    25  //NewServer will return new server
    26  func NewServer() (proxy *Server) {
    27  	proxy = &Server{
    28  		BufferSize: 32 * 1024,
    29  		listners:   map[net.Listener]string{},
    30  		waiter:     sync.WaitGroup{},
    31  		Dialer:     xio.PiperDialerF(xio.DialNetPiper),
    32  		Agent:      "EasyGo/v1.0.0",
    33  	}
    34  	return
    35  }
    36  
    37  //Run will listen tcp on address and accept to ProcConn
    38  func (s *Server) loopAccept(l net.Listener) (err error) {
    39  	var conn net.Conn
    40  	for {
    41  		conn, err = l.Accept()
    42  		if err != nil {
    43  			break
    44  		}
    45  		go s.ProcConn(conn)
    46  	}
    47  	s.waiter.Done()
    48  	return
    49  }
    50  
    51  //Run will listen tcp on address and sync accept to ProcConn
    52  func (s *Server) Run(addr string) (err error) {
    53  	listener, err := net.Listen("tcp", addr)
    54  	if err == nil {
    55  		s.listners[listener] = addr
    56  		InfoLog("Server listen http proxy on %v", addr)
    57  		s.waiter.Add(1)
    58  		err = s.loopAccept(listener)
    59  	}
    60  	return
    61  }
    62  
    63  //Start will listen tcp on address and async accept to ProcConn
    64  func (s *Server) Start(addr string) (listener net.Listener, err error) {
    65  	listener, err = net.Listen("tcp", addr)
    66  	if err == nil {
    67  		s.listners[listener] = addr
    68  		InfoLog("Server listen http proxy on %v", addr)
    69  		s.waiter.Add(1)
    70  		go s.loopAccept(listener)
    71  	}
    72  	return
    73  }
    74  
    75  //Stop will stop listener and wait loop stop
    76  func (s *Server) Stop() (err error) {
    77  	for listener, addr := range s.listners {
    78  		err = listener.Close()
    79  		delete(s.listners, listener)
    80  		InfoLog("Server http proxy listener on %v is stopped by %v", addr, err)
    81  	}
    82  	s.waiter.Wait()
    83  	return
    84  }
    85  
    86  //ProcConn will processs net connect as http proxy
    87  func (s *Server) ProcConn(conn io.ReadWriteCloser) (err error) {
    88  	// DebugLog("Server proxy http connection on %v from %v", xio.LocalAddr(conn), xio.RemoteAddr(conn))
    89  	defer func() {
    90  		if err != xio.ErrAsyncRunning {
    91  			DebugLog("Server http proxy connection on %v from %v is done with %v", xio.LocalAddr(conn), xio.RemoteAddr(conn), err)
    92  			conn.Close()
    93  		}
    94  	}()
    95  	reader := bufio.NewReader(conn)
    96  	req, err := http.ReadRequest(reader)
    97  	if err != nil {
    98  		return
    99  	}
   100  	resp := &http.Response{
   101  		ProtoMajor: 1,
   102  		ProtoMinor: 1,
   103  		Header:     http.Header{},
   104  	}
   105  	resp.Header.Add("Proxy-Agent", s.Agent)
   106  	// if req.Method != http.MethodConnect && len(req.Header.Get("Proxy-Connection")) < 1 {
   107  	// 	DebugLog("Server sending proxy server info on %v to %v", xio.LocalAddr(conn), xio.RemoteAddr(conn))
   108  	// 	if req.Method == http.MethodHead {
   109  	// 		resp.StatusCode = http.StatusOK
   110  	// 		resp.Write(conn)
   111  	// 	} else {
   112  	// 		resp.StatusCode = http.StatusInternalServerError
   113  	// 		resp.Body = xio.NewCombinedReadWriteCloser(bytes.NewBufferString("not supported"), nil, nil)
   114  	// 		resp.Write(conn)
   115  	// 		WarnLog("Server http proxy received not supported connect by method:%v,url:%v,header:%v", req.Method, req.URL, converter.JSON(req.Header))
   116  	// 	}
   117  	// 	return
   118  	// }
   119  	req.Header.Del("Proxy-Authorization")
   120  	req.Header.Del("Proxy-Connection")
   121  	var raw xio.Piper
   122  	var uri string
   123  	if req.Method == "CONNECT" {
   124  		uri = "tcp://" + req.RequestURI
   125  		DebugLog("Server http proxy start dial to %v on %v from %v", uri, xio.LocalAddr(conn), xio.RemoteAddr(conn))
   126  		raw, err = s.Dialer.DialPiper(uri, s.BufferSize)
   127  		if err != nil {
   128  			resp.StatusCode = http.StatusInternalServerError
   129  			resp.Body = xio.NewCombinedReadWriteCloser(bytes.NewBufferString(err.Error()), nil, nil)
   130  			resp.Write(conn)
   131  			InfoLog("Server http proxy dial to %v on %v fail with %v", uri, xio.RemoteAddr(conn), err)
   132  			return
   133  		}
   134  		resp.StatusCode = http.StatusOK
   135  		resp.Status = "Connection established"
   136  		resp.Write(conn)
   137  	} else {
   138  		host := req.Host
   139  		if _, port, _ := net.SplitHostPort(host); port == "" {
   140  			host += ":80"
   141  		}
   142  		uri = "tcp://" + host
   143  		DebugLog("Server http proxy start dial to %v on %v from %v", uri, xio.LocalAddr(conn), xio.RemoteAddr(conn))
   144  		raw, err = s.Dialer.DialPiper(uri, s.BufferSize)
   145  		if err != nil {
   146  			resp.StatusCode = http.StatusInternalServerError
   147  			resp.Body = xio.NewCombinedReadWriteCloser(bytes.NewBufferString(err.Error()), nil, nil)
   148  			resp.Write(conn)
   149  			InfoLog("Server http proxy dial to %v on %v fail with %v", uri, xio.RemoteAddr(conn), err)
   150  			return
   151  		}
   152  		buffer := bytes.NewBuffer(nil)
   153  		req.Write(buffer)
   154  		prefix := xio.NewPrefixReadWriteCloser(conn)
   155  		prefix.Prefix = buffer.Bytes()
   156  		conn = prefix
   157  	}
   158  	err = raw.PipeConn(conn, uri)
   159  	if err != xio.ErrAsyncRunning {
   160  		raw.Close()
   161  	}
   162  	return
   163  }
   164  
   165  //Dial will dial uri by proxy server
   166  func Dial(proxy, uri string) (conn net.Conn, err error) {
   167  	proxy = strings.TrimPrefix(proxy, "http://")
   168  	proxy = strings.TrimSuffix(proxy, "/")
   169  	conn, err = net.Dial("tcp", proxy)
   170  	if err != nil {
   171  		return
   172  	}
   173  	address := uri
   174  	if !strings.HasPrefix(address, "http://") {
   175  		address = "http://" + address
   176  	}
   177  	req, err := http.NewRequest("CONNECT", address, nil)
   178  	if err != nil {
   179  		return
   180  	}
   181  	req.Write(conn)
   182  	reader := bufio.NewReader(conn)
   183  	resp, err := http.ReadResponse(reader, req)
   184  	if resp.StatusCode != http.StatusOK {
   185  		err = fmt.Errorf("proxy response %v", resp.StatusCode)
   186  	}
   187  	return
   188  }