Path: blob/main/components/public-api-server/pkg/proxy/conn.go
2500 views
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package proxy56import (7"context"8"errors"9"fmt"10"net/url"11"time"1213"github.com/gitpod-io/gitpod/common-go/log"14gitpod "github.com/gitpod-io/gitpod/gitpod-protocol"15"github.com/gitpod-io/gitpod/public-api-server/pkg/auth"16"github.com/gitpod-io/gitpod/public-api-server/pkg/origin"17"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus"1819lru "github.com/hashicorp/golang-lru"20)2122type ServerConnectionPool interface {23// Get retrieves or creates a new connection for the specified token24// Connections must not be shared across tokens25Get(ctx context.Context, token auth.Token) (gitpod.APIInterface, error)26}2728// NoConnectionPool is a simple version of the ServerConnectionPool which always creates a new connection.29type NoConnectionPool struct {30ServerAPI *url.URL31}3233func (p *NoConnectionPool) Get(ctx context.Context, token auth.Token) (gitpod.APIInterface, error) {34logger := ctxlogrus.Extract(ctx)3536start := time.Now()37defer func() {38reportConnectionDuration(time.Since(start))39}()4041opts := gitpod.ConnectToServerOpts{42Context: ctx,43Log: logger,44Origin: origin.FromContext(ctx),45}4647switch token.Type {48case auth.AccessTokenType:49opts.Token = token.Value50case auth.CookieTokenType:51opts.Cookie = token.Value52default:53return nil, errors.New("unknown token type")54}5556conn, err := gitpod.ConnectToServer(p.ServerAPI.String(), opts)57if err != nil {58return nil, fmt.Errorf("failed to create new connection to server: %w", err)59}6061return conn, nil62}6364func NewConnectionPool(address *url.URL, poolSize int) (*ConnectionPool, error) {65cache, err := lru.NewWithEvict(poolSize, func(_, value interface{}) {66connectionPoolSize.Dec()6768// We attempt to gracefully close the connection69conn, ok := value.(gitpod.APIInterface)70if !ok {71log.Errorf("Failed to cast cache value to gitpod API Interface")72return73}7475closeErr := conn.Close()76if closeErr != nil {77log.Log.WithError(closeErr).Warn("Failed to close connection to server.")78}79})80if err != nil {81return nil, fmt.Errorf("failed to create LRU cache: %w", err)82}8384return &ConnectionPool{85cache: cache,86connConstructor: func(ctx context.Context, token auth.Token) (gitpod.APIInterface, error) {87opts := gitpod.ConnectToServerOpts{88// We're using Background context as we want the connection to persist beyond the lifecycle of a single request89Context: context.Background(),90Log: log.Log,91Origin: origin.FromContext(ctx),92CloseHandler: func(_ error) {93cache.Remove(token)94connectionPoolSize.Dec()95},96}9798switch token.Type {99case auth.AccessTokenType:100opts.Token = token.Value101case auth.CookieTokenType:102opts.Cookie = token.Value103default:104return nil, errors.New("unknown token type")105}106107endpoint, err := getEndpointBasedOnToken(token, address)108if err != nil {109return nil, fmt.Errorf("failed to construct endpoint: %w", err)110}111112conn, err := gitpod.ConnectToServer(endpoint, opts)113if err != nil {114return nil, fmt.Errorf("failed to create new connection to server: %w", err)115}116117return conn, nil118},119}, nil120121}122123type conenctionPoolCacheKey struct {124token auth.Token125origin string126}127128type ConnectionPool struct {129connConstructor func(context.Context, auth.Token) (gitpod.APIInterface, error)130131// cache stores token to connection mapping132cache *lru.Cache133}134135func (p *ConnectionPool) Get(ctx context.Context, token auth.Token) (gitpod.APIInterface, error) {136origin := origin.FromContext(ctx)137138cacheKey := p.cacheKey(token, origin)139cached, found := p.cache.Get(cacheKey)140reportCacheOutcome(found)141if found {142conn, ok := cached.(*gitpod.APIoverJSONRPC)143if ok {144return conn, nil145}146}147148conn, err := p.connConstructor(ctx, token)149if err != nil {150return nil, fmt.Errorf("failed to create new connection to server: %w", err)151}152153p.cache.Add(cacheKey, conn)154connectionPoolSize.Inc()155156return conn, nil157}158159func (p *ConnectionPool) cacheKey(token auth.Token, origin string) conenctionPoolCacheKey {160return conenctionPoolCacheKey{161token: token,162origin: origin,163}164}165166func getEndpointBasedOnToken(t auth.Token, u *url.URL) (string, error) {167switch t.Type {168case auth.AccessTokenType:169return fmt.Sprintf("%s/v1", u.String()), nil170case auth.CookieTokenType:171return fmt.Sprintf("%s/gitpod", u.String()), nil172default:173return "", errors.New("unknown token type")174}175}176177178