mirror of
https://github.com/Alexey71/opera-proxy.git
synced 2026-05-14 14:40:59 +00:00
@@ -15,3 +15,4 @@
|
||||
# vendor/
|
||||
bin/
|
||||
*.snap
|
||||
opera-proxy
|
||||
|
||||
@@ -96,6 +96,7 @@ eu3.sec-tunnel.com,77.111.244.22,443
|
||||
| list-countries | - | list available countries and exit |
|
||||
| list-proxies | - | output proxy list and exit |
|
||||
| proxy | String | sets base proxy to use for all dial-outs. Format: `<http\|https\|socks5\|socks5h>://[login:password@]host[:port]` Examples: `http://user:password@192.168.1.1:3128`, `socks5://10.0.0.1:1080` |
|
||||
| refresh | Duration | login refresh interval (default 4h0m0s) |
|
||||
| timeout | Duration | timeout for network operations (default 10s) |
|
||||
| verbosity | Number | logging verbosity (10 - debug, 20 - info, 30 - warning, 40 - error, 50 - critical) (default 20) |
|
||||
| version | - | show program version and exit |
|
||||
|
||||
@@ -7,13 +7,13 @@ import (
|
||||
|
||||
type FixedDialer struct {
|
||||
fixedAddress string
|
||||
next ContextDialer
|
||||
next ContextDialer
|
||||
}
|
||||
|
||||
func NewFixedDialer(address string, next ContextDialer) *FixedDialer {
|
||||
return &FixedDialer{
|
||||
fixedAddress: address,
|
||||
next: next,
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ go 1.16
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/dnsproxy v0.36.0
|
||||
github.com/Snawoot/go-http-digest-auth-client v1.0.0
|
||||
github.com/Snawoot/go-http-digest-auth-client v1.1.3
|
||||
github.com/miekg/dns v1.1.35
|
||||
golang.org/x/net v0.0.0-20210324205630-d1beb07c2056
|
||||
)
|
||||
|
||||
@@ -13,8 +13,8 @@ github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKU
|
||||
github.com/AdguardTeam/golibs v0.4.4 h1:cM9UySQiYFW79zo5XRwnaIWVzfW4eNXmZktMrWbthpw=
|
||||
github.com/AdguardTeam/golibs v0.4.4/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Snawoot/go-http-digest-auth-client v1.0.0 h1:1xNR4muFtT+PjP1Z2QAZG0fooszF8nWuYv6QlTKgi1E=
|
||||
github.com/Snawoot/go-http-digest-auth-client v1.0.0/go.mod h1:WiwNiPXTRGyjTGpBtSQJlM2wDPRRPpFGhMkMWpV4uqg=
|
||||
github.com/Snawoot/go-http-digest-auth-client v1.1.3 h1:Xd/SNBuIUJqotzmxRpbXovBJxmlVZOT19IZZdMdrJ0Q=
|
||||
github.com/Snawoot/go-http-digest-auth-client v1.1.3/go.mod h1:WiwNiPXTRGyjTGpBtSQJlM2wDPRRPpFGhMkMWpV4uqg=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
API_DOMAIN = "api.sec-tunnel.com"
|
||||
API_DOMAIN = "api.sec-tunnel.com"
|
||||
PROXY_SUFFIX = "sec-tunnel.com"
|
||||
)
|
||||
|
||||
@@ -54,6 +54,7 @@ type CLIArgs struct {
|
||||
apiPassword string
|
||||
apiAddress string
|
||||
bootstrapDNS string
|
||||
refresh time.Duration
|
||||
}
|
||||
|
||||
func parse_args() CLIArgs {
|
||||
@@ -74,8 +75,9 @@ func parse_args() CLIArgs {
|
||||
flag.StringVar(&args.apiAddress, "api-address", "", fmt.Sprintf("override IP address of %s", API_DOMAIN))
|
||||
flag.StringVar(&args.bootstrapDNS, "bootstrap-dns", "",
|
||||
"DNS/DoH/DoT/DoQ resolver for initial discovering of SurfEasy API address. "+
|
||||
"See https://github.com/ameshkov/dnslookup/ for upstream DNS URL format. "+
|
||||
"Examples: https://1.1.1.1/dns-query, quic://dns.adguard.com")
|
||||
"See https://github.com/ameshkov/dnslookup/ for upstream DNS URL format. "+
|
||||
"Examples: https://1.1.1.1/dns-query, quic://dns.adguard.com")
|
||||
flag.DurationVar(&args.refresh, "refresh", 4*time.Hour, "login refresh interval")
|
||||
flag.Parse()
|
||||
if args.country == "" {
|
||||
arg_fail("Country can't be empty string.")
|
||||
@@ -227,10 +229,31 @@ func run() int {
|
||||
return 13
|
||||
}
|
||||
|
||||
runTicker(context.Background(), args.refresh, func(ctx context.Context) {
|
||||
mainLogger.Info("Refreshing login...")
|
||||
reqCtx, cl := context.WithTimeout(ctx, args.timeout)
|
||||
defer cl()
|
||||
err := seclient.Login(reqCtx)
|
||||
if err != nil {
|
||||
mainLogger.Error("Login refresh failed: %v", err)
|
||||
return
|
||||
}
|
||||
mainLogger.Info("Login refreshed.")
|
||||
|
||||
mainLogger.Info("Refreshing device password...")
|
||||
reqCtx, cl = context.WithTimeout(ctx, args.timeout)
|
||||
defer cl()
|
||||
err = seclient.DeviceGeneratePassword(reqCtx)
|
||||
if err != nil {
|
||||
mainLogger.Error("Device password refresh failed: %v", err)
|
||||
return
|
||||
}
|
||||
mainLogger.Info("Device password refreshed.")
|
||||
})
|
||||
|
||||
endpoint := ips[0]
|
||||
authHdr := basic_auth_header(seclient.GetProxyCredentials())
|
||||
auth := func() string {
|
||||
return authHdr
|
||||
return basic_auth_header(seclient.GetProxyCredentials())
|
||||
}
|
||||
|
||||
handlerDialer := NewProxyDialer(endpoint.NetAddr(), fmt.Sprintf("%s0.%s", args.country, PROXY_SUFFIX), auth, dialer)
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package seclient
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
type StdJar struct {
|
||||
jar *cookiejar.Jar
|
||||
mux sync.RWMutex
|
||||
}
|
||||
|
||||
func NewStdJar() (*StdJar, error) {
|
||||
var jar StdJar
|
||||
|
||||
err := jar.Reset()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &jar, nil
|
||||
}
|
||||
|
||||
func (j *StdJar) SetCookies(u *url.URL, cookies []*http.Cookie) {
|
||||
j.mux.RLock()
|
||||
j.jar.SetCookies(u, cookies)
|
||||
j.mux.RUnlock()
|
||||
}
|
||||
|
||||
func (j *StdJar) Cookies(u *url.URL) []*http.Cookie {
|
||||
j.mux.RLock()
|
||||
c := j.jar.Cookies(u)
|
||||
j.mux.RUnlock()
|
||||
return c
|
||||
}
|
||||
|
||||
func (j *StdJar) Reset() error {
|
||||
jar, err := cookiejar.New(&cookiejar.Options{
|
||||
PublicSuffixList: publicsuffix.List,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
j.mux.Lock()
|
||||
j.jar = jar
|
||||
j.mux.Unlock()
|
||||
return nil
|
||||
}
|
||||
@@ -62,6 +62,15 @@ type SERegisterDeviceResponse struct {
|
||||
Status SEStatusPair `json:"return_code"`
|
||||
}
|
||||
|
||||
type SEDeviceGeneratePasswordData struct {
|
||||
DevicePassword string `json:"device_password"`
|
||||
}
|
||||
|
||||
type SEDeviceGeneratePasswordResponse struct {
|
||||
Data SEDeviceGeneratePasswordData `json:"data"`
|
||||
Status SEStatusPair `json:"return_code"`
|
||||
}
|
||||
|
||||
type SEGeoEntry struct {
|
||||
Country string `json:"country,omitempty"`
|
||||
CountryCode string `json:"country_code"`
|
||||
@@ -94,3 +103,5 @@ type SEDiscoverResponse struct {
|
||||
} `json:"data"`
|
||||
Status SEStatusPair `json:"return_code"`
|
||||
}
|
||||
|
||||
type SESubscriberLoginResponse SERegisterSubscriberResponse
|
||||
|
||||
+165
-136
@@ -8,12 +8,11 @@ import (
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
dac "github.com/Snawoot/go-http-digest-auth-client"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -24,19 +23,21 @@ const (
|
||||
)
|
||||
|
||||
type SEEndpoints struct {
|
||||
RegisterSubscriber string
|
||||
SubscriberLogin string
|
||||
RegisterDevice string
|
||||
GeoList string
|
||||
Discover string
|
||||
RegisterSubscriber string
|
||||
SubscriberLogin string
|
||||
RegisterDevice string
|
||||
DeviceGeneratePassword string
|
||||
GeoList string
|
||||
Discover string
|
||||
}
|
||||
|
||||
var DefaultSEEndpoints = SEEndpoints{
|
||||
RegisterSubscriber: "https://api.sec-tunnel.com/v4/register_subscriber",
|
||||
SubscriberLogin: "https://api.sec-tunnel.com/v4/subscriber_login",
|
||||
RegisterDevice: "https://api.sec-tunnel.com/v4/register_device",
|
||||
GeoList: "https://api.sec-tunnel.com/v4/geo_list",
|
||||
Discover: "https://api.sec-tunnel.com/v4/discover",
|
||||
RegisterSubscriber: "https://api.sec-tunnel.com/v4/register_subscriber",
|
||||
SubscriberLogin: "https://api.sec-tunnel.com/v4/subscriber_login",
|
||||
RegisterDevice: "https://api.sec-tunnel.com/v4/register_device",
|
||||
DeviceGeneratePassword: "https://api.sec-tunnel.com/v4/device_generate_password",
|
||||
GeoList: "https://api.sec-tunnel.com/v4/geo_list",
|
||||
Discover: "https://api.sec-tunnel.com/v4/discover",
|
||||
}
|
||||
|
||||
type SESettings struct {
|
||||
@@ -58,7 +59,7 @@ var DefaultSESettings = SESettings{
|
||||
}
|
||||
|
||||
type SEClient struct {
|
||||
HttpClient *http.Client
|
||||
httpClient *http.Client
|
||||
Settings SESettings
|
||||
SubscriberEmail string
|
||||
SubscriberPassword string
|
||||
@@ -66,9 +67,12 @@ type SEClient struct {
|
||||
AssignedDeviceID string
|
||||
AssignedDeviceIDHash string
|
||||
DevicePassword string
|
||||
Mux sync.Mutex
|
||||
rng *rand.Rand
|
||||
}
|
||||
|
||||
type StrKV map[string]string
|
||||
|
||||
// Instantiates SurfEasy client with default settings and given API keys.
|
||||
// Optional `transport` parameter allows to override HTTP transport used
|
||||
// for HTTP calls
|
||||
@@ -77,13 +81,6 @@ func NewSEClient(apiUsername, apiSecret string, transport http.RoundTripper) (*S
|
||||
transport = http.DefaultTransport
|
||||
}
|
||||
|
||||
jar, err := cookiejar.New(&cookiejar.Options{
|
||||
PublicSuffixList: publicsuffix.List,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rng := rand.New(RandomSource)
|
||||
|
||||
device_id, err := randomCapitalHexString(rng, DEVICE_ID_BYTES)
|
||||
@@ -91,18 +88,38 @@ func NewSEClient(apiUsername, apiSecret string, transport http.RoundTripper) (*S
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &SEClient{
|
||||
HttpClient: &http.Client{
|
||||
Transport: dac.NewDigestTransport(apiUsername, apiSecret, transport),
|
||||
jar, err := NewStdJar()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := &SEClient{
|
||||
httpClient: &http.Client{
|
||||
Jar: jar,
|
||||
Transport: dac.NewDigestTransport(apiUsername, apiSecret, transport),
|
||||
},
|
||||
Settings: DefaultSESettings,
|
||||
rng: rng,
|
||||
DeviceID: device_id,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (c *SEClient) ResetCookies() error {
|
||||
c.Mux.Lock()
|
||||
defer c.Mux.Unlock()
|
||||
return c.resetCookies()
|
||||
}
|
||||
|
||||
func (c *SEClient) resetCookies() error {
|
||||
return (c.httpClient.Jar.(*StdJar)).Reset()
|
||||
}
|
||||
|
||||
func (c *SEClient) AnonRegister(ctx context.Context) error {
|
||||
c.Mux.Lock()
|
||||
defer c.Mux.Unlock()
|
||||
|
||||
localPart, err := randomEmailLocalPart(c.rng)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -111,40 +128,26 @@ func (c *SEClient) AnonRegister(ctx context.Context) error {
|
||||
c.SubscriberEmail = fmt.Sprintf("%s@%s.best.vpn", localPart, c.Settings.ClientType)
|
||||
c.SubscriberPassword = capitalHexSHA1(c.SubscriberEmail)
|
||||
|
||||
return c.Register(ctx)
|
||||
return c.register(ctx)
|
||||
}
|
||||
|
||||
func (c *SEClient) Register(ctx context.Context) error {
|
||||
registerInput := url.Values{
|
||||
"email": {c.SubscriberEmail},
|
||||
"password": {c.SubscriberPassword},
|
||||
}
|
||||
req, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
"POST",
|
||||
c.Settings.Endpoints.RegisterSubscriber,
|
||||
strings.NewReader(registerInput.Encode()),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.populateRequest(req)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
c.Mux.Lock()
|
||||
defer c.Mux.Unlock()
|
||||
return c.register(ctx)
|
||||
}
|
||||
|
||||
resp, err := c.HttpClient.Do(req)
|
||||
func (c *SEClient) register(ctx context.Context) error {
|
||||
err := c.resetCookies()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("bad http status: %s", resp.Status)
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
var regRes SERegisterSubscriberResponse
|
||||
err = decoder.Decode(®Res)
|
||||
cleanupBody(resp.Body)
|
||||
err = c.rpcCall(ctx, c.Settings.Endpoints.RegisterSubscriber, StrKV{
|
||||
"email": c.SubscriberEmail,
|
||||
"password": c.SubscriberPassword,
|
||||
}, ®Res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -157,38 +160,15 @@ func (c *SEClient) Register(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (c *SEClient) RegisterDevice(ctx context.Context) error {
|
||||
registerDeviceInput := url.Values{
|
||||
"client_type": {c.Settings.ClientType},
|
||||
"device_hash": {c.DeviceID},
|
||||
"device_name": {c.Settings.DeviceName},
|
||||
}
|
||||
req, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
"POST",
|
||||
c.Settings.Endpoints.RegisterDevice,
|
||||
strings.NewReader(registerDeviceInput.Encode()),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.populateRequest(req)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
c.Mux.Lock()
|
||||
defer c.Mux.Unlock()
|
||||
|
||||
resp, err := c.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("bad http status: %s", resp.Status)
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
var regRes SERegisterDeviceResponse
|
||||
err = decoder.Decode(®Res)
|
||||
cleanupBody(resp.Body)
|
||||
|
||||
err := c.rpcCall(ctx, c.Settings.Endpoints.RegisterDevice, StrKV{
|
||||
"client_type": c.Settings.ClientType,
|
||||
"device_hash": c.DeviceID,
|
||||
"device_name": c.Settings.DeviceName,
|
||||
}, ®Res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -205,36 +185,13 @@ func (c *SEClient) RegisterDevice(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (c *SEClient) GeoList(ctx context.Context) ([]SEGeoEntry, error) {
|
||||
geoListInput := url.Values{
|
||||
"device_id": {c.AssignedDeviceIDHash},
|
||||
}
|
||||
req, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
"POST",
|
||||
c.Settings.Endpoints.GeoList,
|
||||
strings.NewReader(geoListInput.Encode()),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.populateRequest(req)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
c.Mux.Lock()
|
||||
defer c.Mux.Unlock()
|
||||
|
||||
resp, err := c.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("bad http status: %s", resp.Status)
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
var geoListRes SEGeoListResponse
|
||||
err = decoder.Decode(&geoListRes)
|
||||
cleanupBody(resp.Body)
|
||||
|
||||
err := c.rpcCall(ctx, c.Settings.Endpoints.GeoList, StrKV{
|
||||
"device_id": c.AssignedDeviceIDHash,
|
||||
}, &geoListRes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -248,37 +205,14 @@ func (c *SEClient) GeoList(ctx context.Context) ([]SEGeoEntry, error) {
|
||||
}
|
||||
|
||||
func (c *SEClient) Discover(ctx context.Context, requestedGeo string) ([]SEIPEntry, error) {
|
||||
geoListInput := url.Values{
|
||||
"serial_no": {c.AssignedDeviceIDHash},
|
||||
"requested_geo": {requestedGeo},
|
||||
}
|
||||
req, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
"POST",
|
||||
c.Settings.Endpoints.Discover,
|
||||
strings.NewReader(geoListInput.Encode()),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.populateRequest(req)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
c.Mux.Lock()
|
||||
defer c.Mux.Unlock()
|
||||
|
||||
resp, err := c.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("bad http status: %s", resp.Status)
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
var discoverRes SEDiscoverResponse
|
||||
err = decoder.Decode(&discoverRes)
|
||||
cleanupBody(resp.Body)
|
||||
|
||||
err := c.rpcCall(ctx, c.Settings.Endpoints.Discover, StrKV{
|
||||
"serial_no": c.AssignedDeviceIDHash,
|
||||
"requested_geo": requestedGeo,
|
||||
}, &discoverRes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -291,7 +225,57 @@ func (c *SEClient) Discover(ctx context.Context, requestedGeo string) ([]SEIPEnt
|
||||
return discoverRes.Data.IPs, nil
|
||||
}
|
||||
|
||||
func (c *SEClient) Login(ctx context.Context) error {
|
||||
c.Mux.Lock()
|
||||
defer c.Mux.Unlock()
|
||||
|
||||
err := c.resetCookies()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var loginRes SESubscriberLoginResponse
|
||||
err = c.rpcCall(ctx, c.Settings.Endpoints.SubscriberLogin, StrKV{
|
||||
"login": c.SubscriberEmail,
|
||||
"password": c.SubscriberPassword,
|
||||
"client_type": c.Settings.ClientType,
|
||||
}, &loginRes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if loginRes.Status.Code != SE_STATUS_OK {
|
||||
return fmt.Errorf("API responded with error message: code=%d, msg=\"%s\"",
|
||||
loginRes.Status.Code, loginRes.Status.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SEClient) DeviceGeneratePassword(ctx context.Context) error {
|
||||
c.Mux.Lock()
|
||||
defer c.Mux.Unlock()
|
||||
|
||||
var genRes SEDeviceGeneratePasswordResponse
|
||||
err := c.rpcCall(ctx, c.Settings.Endpoints.DeviceGeneratePassword, StrKV{
|
||||
"device_id": c.AssignedDeviceID,
|
||||
}, &genRes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if genRes.Status.Code != SE_STATUS_OK {
|
||||
return fmt.Errorf("API responded with error message: code=%d, msg=\"%s\"",
|
||||
genRes.Status.Code, genRes.Status.Message)
|
||||
}
|
||||
|
||||
c.DevicePassword = genRes.Data.DevicePassword
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SEClient) GetProxyCredentials() (string, string) {
|
||||
c.Mux.Lock()
|
||||
defer c.Mux.Unlock()
|
||||
|
||||
return c.AssignedDeviceIDHash, c.DevicePassword
|
||||
}
|
||||
|
||||
@@ -301,6 +285,51 @@ func (c *SEClient) populateRequest(req *http.Request) {
|
||||
req.Header["User-Agent"] = []string{c.Settings.UserAgent}
|
||||
}
|
||||
|
||||
func (c *SEClient) RpcCall(ctx context.Context, endpoint string, params map[string]string, res interface{}) error {
|
||||
c.Mux.Lock()
|
||||
defer c.Mux.Unlock()
|
||||
|
||||
return c.rpcCall(ctx, endpoint, params, res)
|
||||
}
|
||||
|
||||
func (c *SEClient) rpcCall(ctx context.Context, endpoint string, params map[string]string, res interface{}) error {
|
||||
input := make(url.Values)
|
||||
for k, v := range params {
|
||||
input[k] = []string{v}
|
||||
}
|
||||
req, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
"POST",
|
||||
endpoint,
|
||||
strings.NewReader(input.Encode()),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.populateRequest(req)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("bad http status: %s, headers: %#v", resp.Status, resp.Header)
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
err = decoder.Decode(res)
|
||||
cleanupBody(resp.Body)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Does cleanup of HTTP response in order to make it reusable by keep-alive
|
||||
// logic of HTTP client
|
||||
func cleanupBody(body io.ReadCloser) {
|
||||
|
||||
@@ -12,7 +12,10 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const COPY_BUF = 128 * 1024
|
||||
const (
|
||||
COPY_BUF = 128 * 1024
|
||||
WALLCLOCK_PRECISION = 1 * time.Second
|
||||
)
|
||||
|
||||
func basic_auth_header(login, password string) string {
|
||||
return "Basic " + base64.StdEncoding.EncodeToString(
|
||||
@@ -143,3 +146,40 @@ func copyBody(wr io.Writer, body io.Reader) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func AfterWallClock(d time.Duration) <-chan time.Time {
|
||||
ch := make(chan time.Time, 1)
|
||||
deadline := time.Now().Add(d).Truncate(0)
|
||||
after_ch := time.After(d)
|
||||
ticker := time.NewTicker(WALLCLOCK_PRECISION)
|
||||
go func() {
|
||||
var t time.Time
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case t = <-after_ch:
|
||||
ch <- t
|
||||
return
|
||||
case t = <-ticker.C:
|
||||
if t.After(deadline) {
|
||||
ch <- t
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
func runTicker(ctx context.Context, interval time.Duration, cb func(context.Context)) {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-AfterWallClock(interval):
|
||||
cb(ctx)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user