diff --git a/README.md b/README.md index 374db72..70e792b 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,13 @@ 白嫖云函数,构建自己的代理服务 +基于https://github.com/Sakurasan/scf-proxy修改 + +## 更新 +1. 增加http/2协议转发\ +需关闭ALPN,例如:Firefox about:config security.ssl.enable_alpn=false +2. 支持浏览器代理 +3. 原有的命令行方式改为yaml配置文件 ## 快速食用🍰 ``` sh build.sh diff --git a/build.sh b/build.sh index 83ac694..acf2a94 100644 --- a/build.sh +++ b/build.sh @@ -1,7 +1,6 @@ - #!/bin/bash work_path=$(cd `dirname $0`/../; pwd) echo $work_path -GOOS=linux GOARCH=amd64 go build -o main cmd/main.go +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -a -ldflags '-s -w' -gcflags="all=-trimpath=${PWD}" -asmflags="all=-trimpath=${PWD}" -o main cmd/main.go zip main.zip main \ No newline at end of file diff --git a/cmd/client.go b/cmd/client.go index cbc75af..0e41191 100644 --- a/cmd/client.go +++ b/cmd/client.go @@ -2,19 +2,20 @@ package main import ( "crypto/tls" - "flag" - "fmt" - "log" + "github.com/chroblert/jlog" + "golang.org/x/net/http2" + "net" "net/http" "net/http/httputil" "scf-proxy/pkg/mitm" "scf-proxy/pkg/scf" + "scf-proxy/pkg/viper" "sync" "time" ) const ( - HTTP_ADDR = "127.0.0.1:8080" + //HTTP_ADDR = "127.0.0.1:8080" SESSIONS_TO_CACHE = 10000 ) @@ -24,14 +25,14 @@ var ( ) func init() { - flag.StringVar(&clientPort, "p", "8080", "scf-proxy 客户端端口") - flag.StringVar(&scf.ScfApiProxyUrl, "scfurl", "", "scf-proxy 服务端地址") - flag.Parse() - if scf.ScfApiProxyUrl == "" { - panic("scf-proxy 服务端地址为空") - } - fmt.Println(scf.ScfApiProxyUrl) - + _ = jlog.SetLogFullPath("logs/client.log", 0755, 0755) + jlog.SetStoreToFile(true) + jlog.SetMaxSizePerLogFile("10MB") + jlog.SetMaxStoreDays(30) + jlog.SetLogCount(30) + jlog.IsIniCreateNewLog(true) + jlog.SetVerbose(true) + scf.ScfApiProxyUrl, clientPort = viper.YamlConfig() } func main() { @@ -39,8 +40,10 @@ func main() { runHTTPServer() // Uncomment the below line to keep the server running exampleWg.Wait() + //defer jlog.Flush() // Output: + } func runHTTPServer() { @@ -66,20 +69,36 @@ func runHTTPServer() { rp := &httputil.ReverseProxy{ Director: func(req *http.Request) { - log.Printf("Processing request to: %s", req.URL) + jlog.Infof("Processing request to: %s", req.URL) }, - Transport: &http.Transport{ + //Transport: &http.Transport{ + // //AllowHTTP: true, + // TLSClientConfig: &tls.Config{ + // // Use a TLS session cache to minimize TLS connection establishment + // // Requires Go 1.3+ + // ClientSessionCache: tls.NewLRUClientSessionCache(SESSIONS_TO_CACHE), + // }, + // //DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) { + // // return net.Dial(network, addr) + // //}, + //}, + Transport: &http2.Transport{ + AllowHTTP: true, TLSClientConfig: &tls.Config{ // Use a TLS session cache to minimize TLS connection establishment // Requires Go 1.3+ ClientSessionCache: tls.NewLRUClientSessionCache(SESSIONS_TO_CACHE), }, + DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) { + return net.Dial(netw, addr) + }, }, + FlushInterval: -1, } handler, err := mitm.Wrap(rp, cryptoConfig) if err != nil { - log.Fatalf("Unable to wrap reverse proxy: %s", err) + jlog.Fatalf("Unable to wrap reverse proxy: %s", err) } server := &http.Server{ @@ -88,14 +107,15 @@ func runHTTPServer() { ReadTimeout: 1 * time.Minute, WriteTimeout: 1 * time.Minute, } - + _ = http2.ConfigureServer(server, nil) go func() { - log.Printf("About to start HTTP proxy at :%s", clientPort) + jlog.Infof("About to start HTTP proxy at :%s\n", clientPort) if err := server.ListenAndServe(); err != nil { - log.Fatalf("Unable to start HTTP proxy: %s", err) + jlog.Fatalf("Unable to start HTTP proxy: %s", err) } exampleWg.Done() }() return + //} } diff --git a/cmd/main.go b/cmd/main.go index 375f424..9dd92c8 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -5,13 +5,11 @@ import ( "context" "encoding/json" "fmt" - - "scf-proxy/pkg/scf" - "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus" "github.com/tencentyun/scf-go-lib/cloudfunction" "github.com/tencentyun/scf-go-lib/events" + "scf-proxy/pkg/scf" ) func main() { diff --git a/go.mod b/go.mod index a650ed0..c6dfc08 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,15 @@ module scf-proxy go 1.15 require ( + github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect + github.com/chroblert/jlog v0.0.8 + github.com/coreos/etcd v3.3.10+incompatible // indirect + github.com/coreos/go-etcd v2.0.0+incompatible // indirect github.com/sirupsen/logrus v1.8.1 + github.com/spf13/viper v1.12.0 github.com/tencentyun/scf-go-lib v0.0.0-20200624065115-ba679e2ec9c9 + github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 // indirect + github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 // indirect + golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 + golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect ) diff --git a/pkg/mitm/mitm.go b/pkg/mitm/mitm.go index ccaa8fb..d4439f2 100644 --- a/pkg/mitm/mitm.go +++ b/pkg/mitm/mitm.go @@ -3,13 +3,15 @@ package mitm import ( "crypto/tls" "fmt" + "github.com/chroblert/jlog" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" "io" - "log" "net/http" - "net/http/httputil" "scf-proxy/pkg/scf" "strings" "sync" + "time" ) const ( @@ -30,9 +32,14 @@ type HandlerWrapper struct { } func Wrap(handler http.Handler, cryptoConf *CryptoConfig) (*HandlerWrapper, error) { + h2s := &http2.Server{ + IdleTimeout: time.Second * 60, + } wrapper := &HandlerWrapper{ - cryptoConf: cryptoConf, - wrapped: handler, + cryptoConf: cryptoConf, + //wrapped: handler, + //http/2 支持 + wrapped: h2c.NewHandler(handler, h2s), dynamicCerts: NewCache(), } err := wrapper.initCrypto() @@ -48,8 +55,9 @@ func (wrapper *HandlerWrapper) ServeHTTP(resp http.ResponseWriter, req *http.Req wrapper.intercept(resp, req) } else { // wrapper.wrapped.ServeHTTP(resp, req) - reqdump, _ := httputil.DumpRequest(req, true) - fmt.Println("dump req:", string(reqdump)) + //reqdump, _ := httputil.DumpRequest(req, true) + //fmt.Println("dump req:", string(reqdump)) + //jlog.Info(string(reqdump)) scf.HandlerHttp(resp, req) } } @@ -73,6 +81,8 @@ func (wrapper *HandlerWrapper) intercept(resp http.ResponseWriter, req *http.Req return } tlsConfig := makeConfig(wrapper.cryptoConf.ServerTLSConfig) + //HTTP/2 支持 + tlsConfig.NextProtos = []string{"h2"} tlsConfig.Certificates = []tls.Certificate{*cert} tlsConnIn := tls.Server(connIn, tlsConfig) @@ -80,6 +90,7 @@ func (wrapper *HandlerWrapper) intercept(resp http.ResponseWriter, req *http.Req listener := &mitmListener{tlsConnIn} handler := http.HandlerFunc(func(resp2 http.ResponseWriter, req2 *http.Request) { + //req2.ProtoMajor req2.URL.Scheme = "https" req2.URL.Host = req2.Host req2.RequestURI = req2.URL.String() @@ -91,7 +102,7 @@ func (wrapper *HandlerWrapper) intercept(resp http.ResponseWriter, req *http.Req go func() { err = http.Serve(listener, handler) if err != nil && err != io.EOF { - log.Printf("Error serving mitm'ed connection: %s", err) + jlog.Printf("Error serving mitm'ed connection: %s", err) } }() @@ -116,7 +127,7 @@ func hostIncludingPort(req *http.Request) (host string) { } func respBadGateway(resp http.ResponseWriter, msg string) { - log.Println(msg) + jlog.Error(msg) resp.WriteHeader(502) resp.Write([]byte(msg)) } diff --git a/pkg/scf/handler.go b/pkg/scf/handler.go index 5f3a023..971e1b1 100644 --- a/pkg/scf/handler.go +++ b/pkg/scf/handler.go @@ -4,10 +4,11 @@ import ( "bytes" "encoding/base64" "encoding/json" + "github.com/chroblert/jlog" "io/ioutil" - "log" "net/http" "net/http/httputil" + "strings" ) var ( @@ -17,55 +18,87 @@ var ( func HandlerHttp(w http.ResponseWriter, r *http.Request) { dumpReq, err := httputil.DumpRequest(r, true) if err != nil { - log.Println(err) + jlog.Error(err) w.WriteHeader(http.StatusServiceUnavailable) return } + // 解决当url为/的问题 event := &DefineEvent{ URL: r.URL.String(), Content: base64.StdEncoding.EncodeToString(dumpReq), } + //jlog.Println(event.URL, event.Content) bytejson, err := json.Marshal(event) if err != nil { - log.Println(err) + jlog.Error(err) w.WriteHeader(http.StatusServiceUnavailable) return } req, err := http.NewRequest("POST", ScfApiProxyUrl, bytes.NewReader(bytejson)) if err != nil { - log.Println(err) + jlog.Error(err) w.WriteHeader(http.StatusServiceUnavailable) return } resp, err := http.DefaultClient.Do(req) if err != nil { - log.Println("client.Do()", err) + jlog.Error(err) w.WriteHeader(http.StatusServiceUnavailable) return } bytersp, _ := ioutil.ReadAll(resp.Body) - var respevent RespEvent if err := json.Unmarshal(bytersp, &respevent); err != nil { - log.Println(err) - w.WriteHeader(http.StatusServiceUnavailable) + jlog.Error(err) + jlog.Error(string(bytersp)) + w.WriteHeader(resp.StatusCode) + w.Write(bytersp) + //w.WriteHeader(http.StatusServiceUnavailable) return } if resp.StatusCode > 0 && resp.StatusCode != 200 { - log.Println(err) - w.WriteHeader(http.StatusServiceUnavailable) + jlog.Error(err) + w.WriteHeader(resp.StatusCode) + w.Write(bytersp) + //w.WriteHeader(http.StatusServiceUnavailable) return } - retByte, err := base64.StdEncoding.DecodeString(respevent.Data) + //处理头+内容 + resp1 := strings.Split(respevent.Data, "^") + respHeaders, err := base64.StdEncoding.DecodeString(resp1[0]) + respBody, err := base64.StdEncoding.DecodeString(resp1[1]) + //retByte, err := base64.StdEncoding.DecodeString(respevent.Data) if err != nil { - log.Println(err) - w.WriteHeader(http.StatusServiceUnavailable) + jlog.Error(err) + w.WriteHeader(resp.StatusCode) + return + } + err1 := resp.Body.Close() + if err1 != nil { + jlog.Error(err1) + w.WriteHeader(resp.StatusCode) + return + } + respHeadersMap := make(map[string][]string) + err = json.Unmarshal(respHeaders, &respHeadersMap) + for k, v := range respHeadersMap { + var s []string + for _, val := range v { + s = append(s, val) + } + w.Header().Set(k, s[0]) + } + _, err2 := w.Write(respBody) + if err2 != nil { + jlog.Error(err2) + w.WriteHeader(resp.StatusCode) return } - resp.Body.Close() - w.Write(retByte) + //w.Write(retByte) return } + +//} diff --git a/pkg/scf/scfhandler.go b/pkg/scf/scfhandler.go index 6d23a82..ef5c7fe 100644 --- a/pkg/scf/scfhandler.go +++ b/pkg/scf/scfhandler.go @@ -7,18 +7,17 @@ import ( "crypto/tls" "encoding/base64" "encoding/json" + log "github.com/sirupsen/logrus" + "github.com/tencentyun/scf-go-lib/events" "io/ioutil" "net" "net/http" "time" - - log "github.com/sirupsen/logrus" - - "github.com/tencentyun/scf-go-lib/events" ) func Handler(ctx context.Context, reqOrigin events.APIGatewayRequest) (resp events.APIGatewayResponse) { var reqEvent = new(DefineEvent) + //b64dreqOrigin, _ := base64.StdEncoding.DecodeString(reqOrigin.Body) if err := json.Unmarshal([]byte(reqOrigin.Body), reqEvent); err != nil { return handlerErr(reqOrigin, err.Error()) } @@ -51,16 +50,28 @@ func forworld(reqevent *DefineEvent) (*RespEvent, error) { if err != nil { return nil, err } + //log.Println("url:", originreq.RequestURI) + //log.Println("url1:", originreq) + //log.Println("url2:", originreq.URL) + //if originreq.RequestURI == "/" { + // originreq.RequestURI = originreq.Proto + //} req, _ := http.NewRequest(originreq.Method, originreq.RequestURI, originreq.Body) for k, v := range originreq.Header { req.Header.Set(k, v[0]) } + // EOF + req.Close = true tr := &http.Transport{ Dial: (&net.Dialer{ //LocalAddr: localAddr, Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, }).Dial, + //DialContext: (&net.Dialer{ + // Timeout: 30 * time.Second, + // KeepAlive: 30 * time.Second, + //}).DialContext, TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } client := http.Client{Transport: tr, Timeout: 60 * time.Second} @@ -72,13 +83,25 @@ func forworld(reqevent *DefineEvent) (*RespEvent, error) { defer resp.Body.Close() byteresp, _ := ioutil.ReadAll(resp.Body) - respvent.Data = base64.StdEncoding.EncodeToString(byteresp) + byterespHeaders, _ := json.Marshal(resp.Header) + respHeaders := base64.StdEncoding.EncodeToString(byterespHeaders) + //响应headers + //{"Accept-Ranges":["bytes"], + //"Content-Length":["573"], + //"Content-Type":["text/html; charset=utf-8"], + //"Date":["Mon, 27 Jun 2022 02:56:52 GMT"], + //"Etag":["\"61419ca8-23d\""], + //"Last-Modified":["Wed, 15 Sep 2021 07:11:36 GMT"], + //"Server":["nginx/1.20.1"]} + respBoby := base64.StdEncoding.EncodeToString(byteresp) + respvent.Data = respHeaders + "^" + respBoby + //respvent.Data = base64.StdEncoding.EncodeToString(byteresp) respvent.Status = true return respvent, nil } -// handleErr 处理错误 +//handleErr 处理错误 func handlerErr(reqOrigin events.APIGatewayRequest, errString string) (resp events.APIGatewayResponse) { // log log.Printf("[出现错误] \n//err %v \n//req %v \n========== \n", errString, reqOrigin) diff --git a/pkg/scf/type.go b/pkg/scf/type.go index 312956e..a4fac75 100644 --- a/pkg/scf/type.go +++ b/pkg/scf/type.go @@ -10,5 +10,5 @@ type DefineEvent struct { type RespEvent struct { Status bool `json:"status"` // 请求是否正常 Error string `json:"error"` // 错误信息 - Data string `json:"data"` // HTTP 响应原始报文, base64 + Data string `json:"data"` // HTTP 响应原始头+报文, base64^base64 } diff --git a/pkg/viper/config.go b/pkg/viper/config.go new file mode 100644 index 0000000..a69e352 --- /dev/null +++ b/pkg/viper/config.go @@ -0,0 +1,35 @@ +package viper + +import ( + "github.com/chroblert/jlog" + "github.com/spf13/viper" +) + +func YamlConfig() (scfurl, proxyport string) { + viper.AddConfigPath(".") + viper.SetConfigName("config") + viper.SetConfigType("yaml") + viper.SetConfigPermissions(0644) + if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + viper.Set("scfurl", "") + viper.Set("proxyport", "1080") + err := viper.SafeWriteConfig() + if err != nil { + jlog.Fatal(err) + } + } else { + jlog.Println("read config error") + } + jlog.Fatal(err) + } + scfurl = viper.GetString("scfurl") + proxyport = viper.GetString("proxyport") + if scfurl == "" { + jlog.Fatal("云函数地址为空") + } + if proxyport == "" { + jlog.Fatal("本地代理端口为空") + } + return scfurl, proxyport +}