github.com/chwjbn/xclash@v0.2.0/listener/http/proxy.go (about) 1 package http 2 3 import ( 4 "fmt" 5 "github.com/chwjbn/xclash/component/auth" 6 "net" 7 "net/http" 8 "strings" 9 "time" 10 11 "github.com/chwjbn/xclash/adapter/inbound" 12 "github.com/chwjbn/xclash/common/cache" 13 N "github.com/chwjbn/xclash/common/net" 14 C "github.com/chwjbn/xclash/constant" 15 authStore "github.com/chwjbn/xclash/listener/auth" 16 "github.com/chwjbn/xclash/log" 17 ) 18 19 func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache) { 20 21 //http客户端,http时有效 22 var xHttpClient *http.Client=nil 23 defer func() { 24 if xHttpClient!=nil{ 25 xHttpClient.CloseIdleConnections() 26 } 27 }() 28 29 conn := N.NewBufferedConn(c) 30 31 keepAlive := true 32 33 trusted:=false 34 if cache==nil{ 35 trusted=true 36 } 37 38 for keepAlive { 39 40 request, err := ReadRequest(conn.Reader()) 41 if err != nil { 42 break 43 } 44 45 keepAlive = strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive" 46 47 request.RemoteAddr = conn.RemoteAddr().String() 48 49 var authUser *auth.AuthUser 50 var resp *http.Response 51 52 if !trusted{ 53 resp,authUser = authenticate(request, cache) 54 if resp==nil{ 55 trusted=true 56 } 57 } 58 59 if trusted{ 60 61 //HTTPS连接 62 if strings.EqualFold(request.Method,http.MethodConnect){ 63 64 // Manual writing to support CONNECT for http 1.0 (workaround for uplay client) 65 if _, err = fmt.Fprintf(conn, "HTTP/%d.%d %03d %s\r\n\r\n", request.ProtoMajor, request.ProtoMinor, http.StatusOK, "Connection established"); err != nil { 66 break // close connection 67 } 68 69 connCtx:=inbound.NewHTTPS(request, conn) 70 connCtx.SetAuthUser(authUser) 71 72 in <- connCtx 73 74 return // hijack connection 75 76 } 77 78 79 //HTTP连接 80 if xHttpClient==nil{ 81 xHttpClient=newHttpClient(c.RemoteAddr(), in,authUser) 82 } 83 84 host := request.Header.Get("Host") 85 if host != "" { 86 request.Host = host 87 } 88 89 request.RequestURI = "" 90 removeHopByHopHeaders(request.Header) 91 removeExtraHTTPHostPort(request) 92 93 if request.URL.Scheme == "" || request.URL.Host == "" { 94 resp = responseWith(request, http.StatusBadRequest) 95 } else { 96 resp, err = xHttpClient.Do(request) 97 if err != nil { 98 resp = responseWith(request, http.StatusBadGateway) 99 } 100 } 101 102 removeHopByHopHeaders(resp.Header) 103 } 104 105 106 if keepAlive { 107 resp.Header.Set("Proxy-Connection", "keep-alive") 108 resp.Header.Set("Connection", "keep-alive") 109 resp.Header.Set("Keep-Alive", "timeout=4") 110 } 111 112 resp.Close = !keepAlive 113 114 err = resp.Write(conn) 115 if err != nil { 116 break // close connection 117 } 118 119 } 120 121 conn.Close() 122 } 123 124 func authenticate(request *http.Request, cache *cache.Cache) (*http.Response,*auth.AuthUser) { 125 126 var authUser *auth.AuthUser=nil 127 128 authenticator := authStore.Authenticator() 129 if authenticator != nil { 130 credential := parseBasicProxyAuthorization(request) 131 if credential == "" { 132 resp := responseWith(request, http.StatusProxyAuthRequired) 133 resp.Header.Set("Proxy-Authenticate", "Basic") 134 return resp,authUser 135 } 136 137 var authed interface{} 138 if authed = cache.Get(credential); authed == nil { 139 user, pass, err := decodeBasicProxyAuthorization(credential) 140 authed = err == nil && authenticator.Verify(user, pass) 141 cache.Put(credential, authed, time.Minute) 142 143 authUser=&auth.AuthUser{User: user,Pass: pass} 144 } 145 if !authed.(bool) { 146 log.Infoln("Auth failed from %s", request.RemoteAddr) 147 148 return responseWith(request, http.StatusForbidden),authUser 149 } 150 } 151 152 return nil,authUser 153 } 154 155 func responseWith(request *http.Request, statusCode int) *http.Response { 156 return &http.Response{ 157 StatusCode: statusCode, 158 Status: http.StatusText(statusCode), 159 Proto: request.Proto, 160 ProtoMajor: request.ProtoMajor, 161 ProtoMinor: request.ProtoMinor, 162 Header: http.Header{}, 163 } 164 }