Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/public-api-server/pkg/proxy/conn.go
2500 views
1
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package proxy
6
7
import (
8
"context"
9
"errors"
10
"fmt"
11
"net/url"
12
"time"
13
14
"github.com/gitpod-io/gitpod/common-go/log"
15
gitpod "github.com/gitpod-io/gitpod/gitpod-protocol"
16
"github.com/gitpod-io/gitpod/public-api-server/pkg/auth"
17
"github.com/gitpod-io/gitpod/public-api-server/pkg/origin"
18
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus"
19
20
lru "github.com/hashicorp/golang-lru"
21
)
22
23
type ServerConnectionPool interface {
24
// Get retrieves or creates a new connection for the specified token
25
// Connections must not be shared across tokens
26
Get(ctx context.Context, token auth.Token) (gitpod.APIInterface, error)
27
}
28
29
// NoConnectionPool is a simple version of the ServerConnectionPool which always creates a new connection.
30
type NoConnectionPool struct {
31
ServerAPI *url.URL
32
}
33
34
func (p *NoConnectionPool) Get(ctx context.Context, token auth.Token) (gitpod.APIInterface, error) {
35
logger := ctxlogrus.Extract(ctx)
36
37
start := time.Now()
38
defer func() {
39
reportConnectionDuration(time.Since(start))
40
}()
41
42
opts := gitpod.ConnectToServerOpts{
43
Context: ctx,
44
Log: logger,
45
Origin: origin.FromContext(ctx),
46
}
47
48
switch token.Type {
49
case auth.AccessTokenType:
50
opts.Token = token.Value
51
case auth.CookieTokenType:
52
opts.Cookie = token.Value
53
default:
54
return nil, errors.New("unknown token type")
55
}
56
57
conn, err := gitpod.ConnectToServer(p.ServerAPI.String(), opts)
58
if err != nil {
59
return nil, fmt.Errorf("failed to create new connection to server: %w", err)
60
}
61
62
return conn, nil
63
}
64
65
func NewConnectionPool(address *url.URL, poolSize int) (*ConnectionPool, error) {
66
cache, err := lru.NewWithEvict(poolSize, func(_, value interface{}) {
67
connectionPoolSize.Dec()
68
69
// We attempt to gracefully close the connection
70
conn, ok := value.(gitpod.APIInterface)
71
if !ok {
72
log.Errorf("Failed to cast cache value to gitpod API Interface")
73
return
74
}
75
76
closeErr := conn.Close()
77
if closeErr != nil {
78
log.Log.WithError(closeErr).Warn("Failed to close connection to server.")
79
}
80
})
81
if err != nil {
82
return nil, fmt.Errorf("failed to create LRU cache: %w", err)
83
}
84
85
return &ConnectionPool{
86
cache: cache,
87
connConstructor: func(ctx context.Context, token auth.Token) (gitpod.APIInterface, error) {
88
opts := gitpod.ConnectToServerOpts{
89
// We're using Background context as we want the connection to persist beyond the lifecycle of a single request
90
Context: context.Background(),
91
Log: log.Log,
92
Origin: origin.FromContext(ctx),
93
CloseHandler: func(_ error) {
94
cache.Remove(token)
95
connectionPoolSize.Dec()
96
},
97
}
98
99
switch token.Type {
100
case auth.AccessTokenType:
101
opts.Token = token.Value
102
case auth.CookieTokenType:
103
opts.Cookie = token.Value
104
default:
105
return nil, errors.New("unknown token type")
106
}
107
108
endpoint, err := getEndpointBasedOnToken(token, address)
109
if err != nil {
110
return nil, fmt.Errorf("failed to construct endpoint: %w", err)
111
}
112
113
conn, err := gitpod.ConnectToServer(endpoint, opts)
114
if err != nil {
115
return nil, fmt.Errorf("failed to create new connection to server: %w", err)
116
}
117
118
return conn, nil
119
},
120
}, nil
121
122
}
123
124
type conenctionPoolCacheKey struct {
125
token auth.Token
126
origin string
127
}
128
129
type ConnectionPool struct {
130
connConstructor func(context.Context, auth.Token) (gitpod.APIInterface, error)
131
132
// cache stores token to connection mapping
133
cache *lru.Cache
134
}
135
136
func (p *ConnectionPool) Get(ctx context.Context, token auth.Token) (gitpod.APIInterface, error) {
137
origin := origin.FromContext(ctx)
138
139
cacheKey := p.cacheKey(token, origin)
140
cached, found := p.cache.Get(cacheKey)
141
reportCacheOutcome(found)
142
if found {
143
conn, ok := cached.(*gitpod.APIoverJSONRPC)
144
if ok {
145
return conn, nil
146
}
147
}
148
149
conn, err := p.connConstructor(ctx, token)
150
if err != nil {
151
return nil, fmt.Errorf("failed to create new connection to server: %w", err)
152
}
153
154
p.cache.Add(cacheKey, conn)
155
connectionPoolSize.Inc()
156
157
return conn, nil
158
}
159
160
func (p *ConnectionPool) cacheKey(token auth.Token, origin string) conenctionPoolCacheKey {
161
return conenctionPoolCacheKey{
162
token: token,
163
origin: origin,
164
}
165
}
166
167
func getEndpointBasedOnToken(t auth.Token, u *url.URL) (string, error) {
168
switch t.Type {
169
case auth.AccessTokenType:
170
return fmt.Sprintf("%s/v1", u.String()), nil
171
case auth.CookieTokenType:
172
return fmt.Sprintf("%s/gitpod", u.String()), nil
173
default:
174
return "", errors.New("unknown token type")
175
}
176
}
177
178