Skip to content

Commit

Permalink
Merge pull request #772 from wakatime/develop
Browse files Browse the repository at this point in the history
Release v1.55.1
  • Loading branch information
gandarez authored Sep 8, 2022
2 parents 67bc07e + eb33ca0 commit b064b30
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 48 deletions.
40 changes: 28 additions & 12 deletions cmd/params/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net/url"
"os"
"regexp"
"strconv"
Expand All @@ -22,6 +23,7 @@ import (

"github.com/mitchellh/go-homedir"
"github.com/spf13/viper"
"golang.org/x/net/http/httpproxy"
)

const errMsgTemplate = "invalid url %q. Must be in format" +
Expand Down Expand Up @@ -220,18 +222,23 @@ func LoadAPIParams(v *viper.Viper) (API, error) {
})
}

apiURL := api.BaseURL
apiURLStr := api.BaseURL

if u, ok := vipertools.FirstNonEmptyString(v, "api-url", "apiurl", "settings.api_url"); ok {
apiURL = u
apiURLStr = u
}

// remove endpoint from api base url to support legacy api_url param
apiURL = strings.TrimSuffix(apiURL, "/")
apiURL = strings.TrimSuffix(apiURL, ".bulk")
apiURL = strings.TrimSuffix(apiURL, "/users/current/heartbeats")
apiURL = strings.TrimSuffix(apiURL, "/heartbeats")
apiURL = strings.TrimSuffix(apiURL, "/heartbeat")
apiURLStr = strings.TrimSuffix(apiURLStr, "/")
apiURLStr = strings.TrimSuffix(apiURLStr, ".bulk")
apiURLStr = strings.TrimSuffix(apiURLStr, "/users/current/heartbeats")
apiURLStr = strings.TrimSuffix(apiURLStr, "/heartbeats")
apiURLStr = strings.TrimSuffix(apiURLStr, "/heartbeat")

apiURL, err := url.Parse(apiURLStr)
if err != nil {
return API{}, fmt.Errorf("invalid api url: %s", err)
}

var backoffAt time.Time

Expand All @@ -257,10 +264,7 @@ func LoadAPIParams(v *viper.Viper) (API, error) {
}
}

var (
hostname string
err error
)
var hostname string

hostname, ok = vipertools.FirstNonEmptyString(v, "hostname", "settings.hostname")
if !ok {
Expand All @@ -281,6 +285,18 @@ func LoadAPIParams(v *viper.Viper) (API, error) {
return API{}, fmt.Errorf(errMsgTemplate, proxyURL)
}

proxyEnv := httpproxy.FromEnvironment()

proxyEnvURL, err := proxyEnv.ProxyFunc()(apiURL)
if err != nil {
log.Warnf("failed to get proxy url from environment for api url: %s", err)
}

// try use proxy from environment if no custom proxy is set
if proxyURL == "" && proxyEnvURL != nil {
proxyURL = proxyEnvURL.String()
}

var sslCertFilepath string

sslCertFilepath, ok = vipertools.FirstNonEmptyString(v, "ssl-certs-file", "settings.ssl_certs_file")
Expand Down Expand Up @@ -309,7 +325,7 @@ func LoadAPIParams(v *viper.Viper) (API, error) {
ProxyURL: proxyURL,
SSLCertFilepath: sslCertFilepath,
Timeout: timeout,
URL: apiURL,
URL: apiURL.String(),
}, nil
}

Expand Down
46 changes: 46 additions & 0 deletions cmd/params/params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1846,6 +1846,22 @@ func TestLoad_API_ProxyURL_FlagTakesPrecedence(t *testing.T) {
assert.Equal(t, "https://john:[email protected]:8888", params.ProxyURL)
}

func TestLoad_API_ProxyURL_UserDefinedTakesPrecedenceOverEnvironment(t *testing.T) {
v := viper.New()
v.Set("key", "00000000-0000-4000-8000-000000000000")
v.Set("proxy", "https://john:[email protected]:8888")

err := os.Setenv("HTTPS_PROXY", "https://papa:[email protected]:9000")
require.NoError(t, err)

defer os.Unsetenv("HTTPS_PROXY")

params, err := paramscmd.LoadAPIParams(v)
require.NoError(t, err)

assert.Equal(t, "https://john:[email protected]:8888", params.ProxyURL)
}

func TestLoad_API_ProxyURL_FromConfig(t *testing.T) {
v := viper.New()
v.Set("key", "00000000-0000-4000-8000-000000000000")
Expand All @@ -1857,6 +1873,36 @@ func TestLoad_API_ProxyURL_FromConfig(t *testing.T) {
assert.Equal(t, "https://john:[email protected]:8888", params.ProxyURL)
}

func TestLoad_API_ProxyURL_FromEnvironment(t *testing.T) {
v := viper.New()
v.Set("key", "00000000-0000-4000-8000-000000000000")

err := os.Setenv("HTTPS_PROXY", "https://john:[email protected]:8888")
require.NoError(t, err)

defer os.Unsetenv("HTTPS_PROXY")

params, err := paramscmd.LoadAPIParams(v)
require.NoError(t, err)

assert.Equal(t, "https://john:[email protected]:8888", params.ProxyURL)
}

func TestLoad_API_ProxyURL_NoProxyFromEnvironment(t *testing.T) {
v := viper.New()
v.Set("key", "00000000-0000-4000-8000-000000000000")

err := os.Setenv("NO_PROXY", "https://some.org,https://api.wakatime.com")
require.NoError(t, err)

defer os.Unsetenv("NO_PROXY")

params, err := paramscmd.LoadAPIParams(v)
require.NoError(t, err)

assert.Empty(t, params.ProxyURL)
}

func TestLoad_API_ProxyURL_InvalidFormat(t *testing.T) {
v := viper.New()
v.Set("key", "00000000-0000-4000-8000-000000000000")
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
github.com/yookoala/realpath v1.0.0
go.etcd.io/bbolt v1.3.6
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c
gopkg.in/ini.v1 v1.66.6
)

Expand All @@ -44,7 +45,7 @@ require (
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.0 // indirect
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d // indirect
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
9 changes: 5 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y=
golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c h1:JVAXQ10yGGVbSyoer5VILysz6YKjdNT2bsvlayjqhes=
golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
Expand Down Expand Up @@ -415,10 +416,10 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d h1:Zu/JngovGLVi6t2J3nmAf3AoTDwuzw85YZ3b9o4yU7s=
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
52 changes: 45 additions & 7 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@ package api

import (
"errors"
"fmt"
"net"
"net/http"
"strings"

"github.com/wakatime/wakatime-cli/pkg/log"
)

const (
// BaseURL is the base url of the wakatime api.
BaseURL = "https://api.wakatime.com/api/v1"
// baseIPAddrv4 is the base ip address v4 of the wakatime api.
baseIPAddrv4 = "143.244.210.202"
// baseIPAddrv6 is the base ip address v6 of the wakatime api.
baseIPAddrv6 = "2604:a880:4:1d0::2a7:b000"
// DefaultTimeoutSecs is the default timeout used for requests to the wakatime api.
DefaultTimeoutSecs = 120
)
Expand Down Expand Up @@ -59,19 +65,51 @@ func NewClient(baseURL string, opts ...Option) *Client {
func (c *Client) Do(req *http.Request) (*http.Response, error) {
resp, err := c.doFunc(c, req)
if err != nil {
// don't set alternate host if there's a custom api url
if !strings.HasPrefix(c.baseURL, BaseURL) {
return nil, err
}

var dnsError *net.DNSError
if errors.As(err, &dnsError) {
log.Warnf("dns error: %s. Retrying with fallback dns resolver", req.URL)
if !errors.As(err, &dnsError) {
return nil, err
}

t, err := NewTransportWithHostVerificationDisabled()
if err != nil {
return nil, err
}

c.client = &http.Client{
Transport: t,
}

c.client = &http.Client{
Transport: NewTransportWithCloudfareDNS(),
}
var alternateHost = baseIPAddrv4

return c.doFunc(c, req)
if isLocalIPv6() {
alternateHost = baseIPAddrv6
}

return nil, err
req.URL.Host = alternateHost

log.Warnf("dns error, it will retry with host ip address '%s'", alternateHost)

return c.doFunc(c, req)
}

return resp, nil
}

func isLocalIPv6() bool {
conn, err := net.Dial("udp", fmt.Sprintf("%s:80", baseIPAddrv4))
if err != nil {
log.Warnf("failed dialing to detect default local ip address: %s", err)
return true
}

defer conn.Close()

localAddr := conn.LocalAddr().(*net.UDPAddr)

return localAddr.IP.To4() == nil
}
34 changes: 10 additions & 24 deletions pkg/api/transport.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package api

import (
"context"
"crypto/tls"
"crypto/x509"
"net"
"net/http"
"strings"
"time"

"github.com/wakatime/wakatime-cli/pkg/log"
)

const serverName = "api.wakatime.com"

// NewTransport initializes a new http.Transport.
func NewTransport() *http.Transport {
return &http.Transport{
Expand All @@ -23,31 +23,17 @@ func NewTransport() *http.Transport {
}
}

// NewTransportWithCloudfareDNS initializes a new http.Transport with cloudfare DNS resolver.
func NewTransportWithCloudfareDNS() *http.Transport {
// NewTransportWithHostVerificationDisabled initializes a new http.Transport with disabled host verification.
func NewTransportWithHostVerificationDisabled() (*http.Transport, error) {
t := NewTransport()

t.DialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
r := &net.Resolver{
PreferGo: false,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
d := net.Dialer{
Timeout: DefaultTimeoutSecs * time.Second,
}

// ipv6 only
if strings.HasSuffix(network, "6") {
return d.DialContext(ctx, network, "[2606:4700:4700::1111]:53")
}

return d.DialContext(ctx, network, "1.1.1.1:53")
},
}

return r.Dial(ctx, network, addr)
t.TLSClientConfig = &tls.Config{
MinVersion: tls.VersionTLS12,
RootCAs: CACerts(),
ServerName: serverName,
}

return t
return t, nil
}

// LazyCreateNewTransport uses the client's Transport if exists, or creates a new one.
Expand Down

0 comments on commit b064b30

Please sign in to comment.