You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

125 lines
3.2 KiB

package proxy
import (
"context"
"fmt"
"net"
"net/http"
tmpubsub "github.com/tendermint/tendermint/internal/pubsub"
rpccore "github.com/tendermint/tendermint/internal/rpc/core"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/light"
lrpc "github.com/tendermint/tendermint/light/rpc"
rpchttp "github.com/tendermint/tendermint/rpc/client/http"
rpcserver "github.com/tendermint/tendermint/rpc/jsonrpc/server"
)
// A Proxy defines parameters for running an HTTP server proxy.
type Proxy struct {
Addr string // TCP address to listen on, ":http" if empty
Config *rpcserver.Config
Client *lrpc.Client
Logger log.Logger
Listener net.Listener
}
// NewProxy creates the struct used to run an HTTP server for serving light
// client rpc requests.
func NewProxy(
lightClient *light.Client,
listenAddr, providerAddr string,
config *rpcserver.Config,
logger log.Logger,
opts ...lrpc.Option,
) (*Proxy, error) {
rpcClient, err := rpchttp.NewWithTimeout(providerAddr, config.WriteTimeout)
if err != nil {
return nil, fmt.Errorf("failed to create http client for %s: %w", providerAddr, err)
}
return &Proxy{
Addr: listenAddr,
Config: config,
Client: lrpc.NewClient(logger, rpcClient, lightClient, opts...),
Logger: logger,
}, nil
}
// ListenAndServe configures the rpcserver.WebsocketManager, sets up the RPC
// routes to proxy via Client, and starts up an HTTP server on the TCP network
// address p.Addr.
// See http#Server#ListenAndServe.
func (p *Proxy) ListenAndServe(ctx context.Context) error {
listener, mux, err := p.listen(ctx)
if err != nil {
return err
}
p.Listener = listener
return rpcserver.Serve(
ctx,
listener,
mux,
p.Logger,
p.Config,
)
}
// ListenAndServeTLS acts identically to ListenAndServe, except that it expects
// HTTPS connections.
// See http#Server#ListenAndServeTLS.
func (p *Proxy) ListenAndServeTLS(ctx context.Context, certFile, keyFile string) error {
listener, mux, err := p.listen(ctx)
if err != nil {
return err
}
p.Listener = listener
return rpcserver.ServeTLS(
ctx,
listener,
mux,
certFile,
keyFile,
p.Logger,
p.Config,
)
}
func (p *Proxy) listen(ctx context.Context) (net.Listener, *http.ServeMux, error) {
mux := http.NewServeMux()
// 1) Register regular routes.
r := rpccore.NewRoutesMap(proxyService{Client: p.Client}, nil)
rpcserver.RegisterRPCFuncs(mux, r, p.Logger)
// 2) Allow websocket connections.
wmLogger := p.Logger.With("protocol", "websocket")
wm := rpcserver.NewWebsocketManager(wmLogger, r,
rpcserver.OnDisconnect(func(remoteAddr string) {
err := p.Client.UnsubscribeAll(context.Background(), remoteAddr)
if err != nil && err != tmpubsub.ErrSubscriptionNotFound {
wmLogger.Error("Failed to unsubscribe addr from events", "addr", remoteAddr, "err", err)
}
}),
rpcserver.ReadLimit(p.Config.MaxBodyBytes),
)
mux.HandleFunc("/websocket", wm.WebsocketHandler)
// 3) Start a client.
if !p.Client.IsRunning() {
if err := p.Client.Start(ctx); err != nil {
return nil, mux, fmt.Errorf("can't start client: %w", err)
}
}
// 4) Start listening for new connections.
listener, err := rpcserver.Listen(p.Addr, p.Config.MaxOpenConnections)
if err != nil {
return nil, mux, err
}
return listener, mux, nil
}