From fc3a90ba2a5959730d624c0c989ce20d86058d1e Mon Sep 17 00:00:00 2001 From: atavism Date: Thu, 5 Dec 2024 11:04:52 -0800 Subject: [PATCH] update http.RoundTripper used by Auth client (#1251) * update http.RoundTripper used by Auth client * remove unused code * clean-ups * use custom http.RoundTripper for auth client * update createProClient * Add comment * use ChainedThenFronted --- desktop/app/app.go | 5 +- internalsdk/auth/auth.go | 27 ++------ internalsdk/pro/http.go | 25 -------- internalsdk/pro/pro.go | 31 +++++---- internalsdk/pro/proxy.go | 115 ---------------------------------- internalsdk/pro/proxy_test.go | 64 ------------------- internalsdk/utils.go | 45 ++++++------- 7 files changed, 40 insertions(+), 272 deletions(-) delete mode 100644 internalsdk/pro/http.go delete mode 100644 internalsdk/pro/proxy.go delete mode 100644 internalsdk/pro/proxy_test.go diff --git a/desktop/app/app.go b/desktop/app/app.go index 2c2fb1a98..f276bd73d 100644 --- a/desktop/app/app.go +++ b/desktop/app/app.go @@ -40,7 +40,6 @@ import ( "github.com/getlantern/lantern-client/internalsdk/common" proclient "github.com/getlantern/lantern-client/internalsdk/pro" "github.com/getlantern/lantern-client/internalsdk/protos" - "github.com/getlantern/lantern-client/internalsdk/webclient" ) var ( @@ -164,9 +163,7 @@ func (app *App) Run(ctx context.Context) { userConfig := func() common.UserConfig { return settings.UserConfig(app.Settings()) } - proClient := proclient.NewClient(fmt.Sprintf("https://%s", common.ProAPIHost), &webclient.Opts{ - UserConfig: userConfig, - }) + proClient := proclient.NewClient(fmt.Sprintf("https://%s", common.ProAPIHost), userConfig) authClient := auth.NewClient(fmt.Sprintf("https://%s", common.DFBaseUrl), userConfig) app.mu.Lock() diff --git a/internalsdk/auth/auth.go b/internalsdk/auth/auth.go index d05ff93b8..1f0f07ce6 100644 --- a/internalsdk/auth/auth.go +++ b/internalsdk/auth/auth.go @@ -3,7 +3,6 @@ package auth import ( "context" - "fmt" "net/http" "strings" "time" @@ -12,7 +11,6 @@ import ( "github.com/getlantern/golog" "github.com/getlantern/lantern-client/internalsdk/common" - "github.com/getlantern/lantern-client/internalsdk/pro" "github.com/getlantern/lantern-client/internalsdk/protos" "github.com/getlantern/lantern-client/internalsdk/webclient" @@ -53,33 +51,20 @@ type AuthClient interface { // NewClient creates a new instance of AuthClient func NewClient(baseURL string, userConfig func() common.UserConfig) AuthClient { - // The default http.RoundTripper is ChainedNonPersistent which proxies requests through chained servers - // and does not use keep alive connections. Since no root CA is specified, we do not need to check for an error. - - var httpClient *http.Client - - if baseURL == fmt.Sprintf("https://%s", common.APIBaseUrl) { - log.Debug("using proxied.Fronted") - //this is ios version - httpClient = &http.Client{ - Transport: proxied.Fronted("auth_fronted_roundtrip", 30*time.Second), - } - } else { - log.Debug("using proxied.ChainedNonPersistent") - rt, _ := proxied.ChainedNonPersistent("") - httpClient = pro.NewHTTPClient(rt, 30*time.Second) - } - rc := webclient.NewRESTClient(&webclient.Opts{ BaseURL: baseURL, OnBeforeRequest: func(client *resty.Client, req *http.Request) error { prepareUserRequest(req, userConfig()) return nil }, - HttpClient: httpClient, + // The Auth client uses an http.Client that first attempts to connect via chained proxies + // and then falls back to using domain fronting with the custom op name above + HttpClient: &http.Client{ + Transport: proxied.ChainedThenFronted(), + Timeout: 30 * time.Second, + }, UserConfig: userConfig, }) - return &authClient{rc} } diff --git a/internalsdk/pro/http.go b/internalsdk/pro/http.go deleted file mode 100644 index 738f82abf..000000000 --- a/internalsdk/pro/http.go +++ /dev/null @@ -1,25 +0,0 @@ -package pro - -import ( - "net/http" - "time" - - "github.com/getlantern/flashlight/v7/proxied" -) - -// NewHTTPClient creates a new http.Client that is configured to use the given options and http.RoundTripper wrapped with -// proxied.AsRoundTripper to process requests -func NewHTTPClient(rt http.RoundTripper, timeout time.Duration) *http.Client { - if timeout == 0 { - timeout = 30 * time.Second - } - return &http.Client{ - Transport: proxied.AsRoundTripper( - func(req *http.Request) (*http.Response, error) { - log.Tracef("Pro client processing request to: %v (%v)", req.Host, req.URL.Host) - return rt.RoundTrip(req) - }, - ), - Timeout: timeout, - } -} diff --git a/internalsdk/pro/pro.go b/internalsdk/pro/pro.go index cde7bab66..de98aaee3 100644 --- a/internalsdk/pro/pro.go +++ b/internalsdk/pro/pro.go @@ -68,24 +68,23 @@ type ProClient interface { } // NewClient creates a new instance of ProClient -func NewClient(baseURL string, opts *webclient.Opts) ProClient { - if opts.HttpClient == nil { - // The default http.RoundTripper used by the ProClient is ParallelForIdempotent which - // attempts to send requests through both chained and direct fronted routes in parallel - // for HEAD and GET requests and ChainedThenFronted for all others. - opts.HttpClient = NewHTTPClient(proxied.ParallelForIdempotent(), opts.Timeout) - } - if opts.OnBeforeRequest == nil { - opts.OnBeforeRequest = func(client *resty.Client, req *http.Request) error { - prepareProRequest(req, common.ProAPIHost, opts.UserConfig()) - return nil - } - } - +func NewClient(baseURL string, userConfig func() common.UserConfig) ProClient { return &proClient{ - userConfig: opts.UserConfig, + userConfig: userConfig, backoffRunner: &backoffRunner{}, - RESTClient: webclient.NewRESTClient(opts), + RESTClient: webclient.NewRESTClient(&webclient.Opts{ + // The default http.RoundTripper used by the ProClient is ParallelForIdempotent which + // attempts to send requests through both chained and direct fronted routes in parallel + // for HEAD and GET requests and ChainedThenFronted for all others. + HttpClient: &http.Client{ + Transport: proxied.ParallelForIdempotent(), + Timeout: 30 * time.Second, + }, + OnBeforeRequest: func(client *resty.Client, req *http.Request) error { + prepareProRequest(req, common.ProAPIHost, userConfig()) + return nil + }, + }), } } diff --git a/internalsdk/pro/proxy.go b/internalsdk/pro/proxy.go deleted file mode 100644 index 61fb3f70c..000000000 --- a/internalsdk/pro/proxy.go +++ /dev/null @@ -1,115 +0,0 @@ -package pro - -import ( - "bytes" - "compress/gzip" - "encoding/json" - "io" - "io/ioutil" - "net/http" - "net/http/httputil" - "strconv" - "strings" - "time" - - "github.com/getlantern/lantern-client/internalsdk/common" - "github.com/getlantern/lantern-client/internalsdk/protos" -) - -type proxyTransport struct { - // Satisfies http.RoundTripper -} - -func (pt *proxyTransport) processOptions(req *http.Request) *http.Response { - resp := &http.Response{ - StatusCode: http.StatusOK, - Header: http.Header{ - "Connection": {"keep-alive"}, - "Via": {"Lantern Client"}, - }, - Body: ioutil.NopCloser(strings.NewReader("preflight complete")), - } - if !common.ProcessCORS(resp.Header, req) { - return &http.Response{ - StatusCode: http.StatusForbidden, - } - } - return resp -} - -func (pt *proxyTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) { - if req.Method == "OPTIONS" { - // No need to proxy the OPTIONS request. - return pt.processOptions(req), nil - } - origin := req.Header.Get("Origin") - // Workaround for https://github.com/getlantern/pro-server/issues/192 - req.Header.Del("Origin") - resp, err = NewHTTPClient(http.DefaultTransport, time.Duration(time.Second*30)).Do(req) - if err != nil { - log.Errorf("Could not issue HTTP request? %v", err) - return - } - - // Put the header back for subsequent CORS processing. - req.Header.Set("Origin", origin) - common.ProcessCORS(resp.Header, req) - if req.URL.Path != "/user-data" || resp.StatusCode != http.StatusOK { - return - } - // Try to update user data implicitly - _userID := req.Header.Get("X-Lantern-User-Id") - if _userID == "" { - log.Error("Request has an empty user ID") - return - } - userID, parseErr := strconv.ParseInt(_userID, 10, 64) - if parseErr != nil { - log.Errorf("User ID %s is invalid", _userID) - return - } - body, readErr := ioutil.ReadAll(resp.Body) - if readErr != nil { - log.Errorf("Error read response body: %v", readErr) - return - } - resp.Body = ioutil.NopCloser(bytes.NewReader(body)) - encoding := resp.Header.Get("Content-Encoding") - var br io.Reader = bytes.NewReader(body) - switch encoding { - case "gzip": - gzr, readErr := gzip.NewReader(bytes.NewReader(body)) - if readErr != nil { - log.Errorf("Unable to decode gzipped data: %v", readErr) - return - } - br = gzr - case "": - default: - log.Errorf("Unsupported response encoding %s", encoding) - return - } - user := protos.User{} - readErr = json.NewDecoder(br).Decode(&user) - if readErr != nil { - log.Errorf("Error decoding JSON: %v", readErr) - return - } - log.Debugf("Updating user data implicitly for user %v", userID) - return -} - -// APIHandler returns an HTTP handler that specifically looks for and properly handles pro server requests. -func APIHandler(proAPIHost string, userConfig common.UserConfig) http.Handler { - log.Debugf("Returning pro API handler hitting host: %v", proAPIHost) - return &httputil.ReverseProxy{ - Transport: &proxyTransport{}, - Director: func(r *http.Request) { - // Strip /pro from path. - if strings.HasPrefix(r.URL.Path, "/pro/") { - r.URL.Path = r.URL.Path[4:] - } - prepareProRequest(r, proAPIHost, userConfig) - }, - } -} diff --git a/internalsdk/pro/proxy_test.go b/internalsdk/pro/proxy_test.go deleted file mode 100644 index 80acdc18a..000000000 --- a/internalsdk/pro/proxy_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package pro - -// func TestProxy(t *testing.T) { -// uc := common.NewUserConfigData(common.DefaultAppName, "device", 0, "token", nil, "en-US") -// m := &testutils.MockRoundTripper{Header: http.Header{}, Body: strings.NewReader("GOOD")} -// // httpClient := &http.Client{Transport: m} -// l, err := net.Listen("tcp", "localhost:0") -// if !assert.NoError(t, err) { -// return -// } - -// addr := l.Addr() -// url := fmt.Sprintf("http://%s/pro/abc", addr) -// t.Logf("Test server listening at %s", url) -// go http.Serve(l, APIHandler(addr.String(), uc)) - -// req, err := http.NewRequest("OPTIONS", url, nil) -// if !assert.NoError(t, err) { -// return -// } - -// origin := "http://localhost:48933" -// req.Header.Set("Origin", origin) -// resp, err := (&http.Client{}).Do(req) -// if assert.NoError(t, err, "OPTIONS request should succeed") { -// assert.Equal(t, 200, resp.StatusCode, "should respond 200 to OPTIONS") -// assert.Equal(t, origin, resp.Header.Get("Access-Control-Allow-Origin"), "should respond with correct header") -// _ = resp.Body.Close() -// } -// assert.Nil(t, m.Req, "should not pass the OPTIONS request to origin server") - -// req, err = http.NewRequest("GET", url, nil) -// if !assert.NoError(t, err) { -// return -// } -// req.Header.Set("Origin", origin) -// resp, err = (&http.Client{}).Do(req) -// if assert.NoError(t, err, "GET request should have no error") { -// assert.Equal(t, 200, resp.StatusCode, "should respond 200 ok") -// assert.Equal(t, origin, resp.Header.Get("Access-Control-Allow-Origin"), "should respond with correct header") -// assert.NotEmpty(t, resp.Header.Get("Access-Control-Allow-Methods"), "should respond with correct header") -// msg, _ := ioutil.ReadAll(resp.Body) -// _ = resp.Body.Close() -// assert.Equal(t, "GOOD", string(msg), "should respond expected body") -// } -// if assert.NotNil(t, m.Req, "should pass through non-OPTIONS requests to origin server") { -// t.Log(m.Req) -// assert.Equal(t, origin, resp.Header.Get("Access-Control-Allow-Origin"), "should respond with correct header") -// assert.NotEmpty(t, resp.Header.Get("Access-Control-Allow-Methods"), "should respond with correct header") -// } - -// url = fmt.Sprintf("http://%s/pro/user-data", addr) -// msg, _ := json.Marshal(&protos.User{Email: "a@a.com"}) -// m.Body = bytes.NewReader(msg) -// req, err = http.NewRequest("GET", url, nil) -// if !assert.NoError(t, err) { -// return -// } -// req.Header.Set("X-Lantern-User-Id", "1234") -// resp, err = (&http.Client{}).Do(req) -// if assert.NoError(t, err, "GET request should have no error") { -// assert.Equal(t, 200, resp.StatusCode, "should respond 200 ok") -// } -// } diff --git a/internalsdk/utils.go b/internalsdk/utils.go index 3c6c254d7..ad17a2670 100644 --- a/internalsdk/utils.go +++ b/internalsdk/utils.go @@ -18,7 +18,6 @@ import ( "github.com/getlantern/lantern-client/internalsdk/common" "github.com/getlantern/lantern-client/internalsdk/pro" "github.com/getlantern/lantern-client/internalsdk/protos" - "github.com/getlantern/lantern-client/internalsdk/webclient" "github.com/getlantern/pathdb" "golang.org/x/crypto/pbkdf2" "google.golang.org/protobuf/proto" @@ -27,32 +26,24 @@ import ( // createProClient creates a new instance of ProClient with the given client session information func createProClient(session Session, platform string) pro.ProClient { - dialTimeout := 30 * time.Second - if platform == "ios" { - dialTimeout = 20 * time.Second - } - webclientOpts := &webclient.Opts{ - Timeout: dialTimeout, - UserConfig: func() common.UserConfig { - internalHeaders := map[string]string{ - common.PlatformHeader: platform, - common.AppVersionHeader: common.ApplicationVersion, - } - deviceID, _ := session.GetDeviceID() - userID, _ := session.GetUserID() - token, _ := session.GetToken() - lang, _ := session.Locale() - return common.NewUserConfig( - common.DefaultAppName, - deviceID, - userID, - token, - internalHeaders, - lang, - ) - }, - } - return pro.NewClient(fmt.Sprintf("https://%s", common.ProAPIHost), webclientOpts) + return pro.NewClient(fmt.Sprintf("https://%s", common.ProAPIHost), func() common.UserConfig { + internalHeaders := map[string]string{ + common.PlatformHeader: platform, + common.AppVersionHeader: common.ApplicationVersion, + } + deviceID, _ := session.GetDeviceID() + userID, _ := session.GetUserID() + token, _ := session.GetToken() + lang, _ := session.Locale() + return common.NewUserConfig( + common.DefaultAppName, + deviceID, + userID, + token, + internalHeaders, + lang, + ) + }) } func BytesToFloat64LittleEndian(b []byte) (float64, error) {