-
Notifications
You must be signed in to change notification settings - Fork 17
/
hostclient.go
117 lines (108 loc) · 3.63 KB
/
hostclient.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package instances
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
apiv1 "github.com/google/cloud-android-orchestration/api/v1"
)
type NetHostClient struct {
url *url.URL
client *http.Client
}
func NewNetHostClient(url *url.URL, allowSelfSigned bool) *NetHostClient {
ret := &NetHostClient{
url: url,
client: http.DefaultClient,
}
if allowSelfSigned {
// This creates a transport similar to http.DefaultTransport according to
// https://pkg.go.dev/net/http#RoundTripper. The object needs to be created
// instead of copied from http.DefaultTransport because it has a mutex which
// could be copied in locked state and produce a copy that's unusable because
// nothing will ever unlock it.
defaultTransport := http.DefaultTransport.(*http.Transport)
transport := &http.Transport{
Proxy: defaultTransport.Proxy,
// Reusing the same dial context allows reusing connections across transport objects.
DialContext: defaultTransport.DialContext,
ForceAttemptHTTP2: defaultTransport.ForceAttemptHTTP2,
MaxIdleConns: defaultTransport.MaxIdleConns,
IdleConnTimeout: defaultTransport.IdleConnTimeout,
TLSHandshakeTimeout: defaultTransport.TLSHandshakeTimeout,
ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout,
}
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
ret.client = &http.Client{Transport: transport}
}
return ret
}
func (c *NetHostClient) Get(path, query string, out *HostResponse) (int, error) {
url := *c.url // Shallow copy
url.Path = path
url.RawQuery = query
res, err := c.client.Get(url.String())
if err != nil {
return -1, fmt.Errorf("failed to connect to device host: %w", err)
}
defer res.Body.Close()
if out != nil {
err = parseReply(res, out.Result, out.Error)
}
return res.StatusCode, err
}
func (c *NetHostClient) Post(path, query string, bodyJSON any, out *HostResponse) (int, error) {
bodyStr, err := json.Marshal(bodyJSON)
if err != nil {
return -1, fmt.Errorf("failed to parse JSON request: %w", err)
}
url := *c.url // Shallow copy
url.Path = path
url.RawQuery = query
res, err := c.client.Post(url.String(), "application/json", bytes.NewBuffer(bodyStr))
if err != nil {
return -1, fmt.Errorf("failed to connecto to device host: %w", err)
}
defer res.Body.Close()
if out != nil {
err = parseReply(res, out.Result, out.Error)
}
return res.StatusCode, err
}
func (c *NetHostClient) GetReverseProxy() *httputil.ReverseProxy {
devProxy := httputil.NewSingleHostReverseProxy(c.url)
if c.client != http.DefaultClient {
// Make sure the reverse proxy has the same customizations as the http client.
devProxy.Transport = c.client.Transport
}
return devProxy
}
func parseReply(res *http.Response, resObj any, resErr *apiv1.Error) error {
var err error
dec := json.NewDecoder(res.Body)
if res.StatusCode < 200 || res.StatusCode > 299 {
err = dec.Decode(resErr)
} else {
err = dec.Decode(resObj)
}
if err != nil {
return fmt.Errorf("failed to parse device response: %w", err)
}
return nil
}