forked from mirrors/gotosocial
[performance] Add dereference shortcuts to avoid making http calls to self (#430)
* update transport (controller) to allow shortcuts * go fmt * expose underlying sig transport to allow test sigs
This commit is contained in:
parent
4b4c935e02
commit
e63b653199
7 changed files with 114 additions and 22 deletions
|
@ -117,7 +117,7 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
||||||
return fmt.Errorf("error creating media manager: %s", err)
|
return fmt.Errorf("error creating media manager: %s", err)
|
||||||
}
|
}
|
||||||
oauthServer := oauth.New(ctx, dbService)
|
oauthServer := oauth.New(ctx, dbService)
|
||||||
transportController := transport.NewController(dbService, &federation.Clock{}, http.DefaultClient)
|
transportController := transport.NewController(dbService, federatingDB, &federation.Clock{}, http.DefaultClient)
|
||||||
federator := federation.NewFederator(dbService, federatingDB, transportController, typeConverter, mediaManager)
|
federator := federation.NewFederator(dbService, federatingDB, transportController, typeConverter, mediaManager)
|
||||||
|
|
||||||
// decide whether to create a noop email sender (won't send emails) or a real one
|
// decide whether to create a noop email sender (won't send emails) or a real one
|
||||||
|
|
|
@ -21,14 +21,18 @@ package transport
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/go-fed/httpsig"
|
"github.com/go-fed/httpsig"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/superseriousbusiness/activity/pub"
|
"github.com/superseriousbusiness/activity/pub"
|
||||||
|
"github.com/superseriousbusiness/activity/streams"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/federation/federatingdb"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Controller generates transports for use in making federation requests to other servers.
|
// Controller generates transports for use in making federation requests to other servers.
|
||||||
|
@ -42,10 +46,54 @@ type controller struct {
|
||||||
clock pub.Clock
|
clock pub.Clock
|
||||||
client pub.HttpClient
|
client pub.HttpClient
|
||||||
appAgent string
|
appAgent string
|
||||||
|
|
||||||
|
// dereferenceFollowersShortcut is a shortcut to dereference followers of an
|
||||||
|
// account on this instance, without making any external api/http calls.
|
||||||
|
//
|
||||||
|
// It is passed to new transports, and should only be invoked when the iri.Host == this host.
|
||||||
|
dereferenceFollowersShortcut func(ctx context.Context, iri *url.URL) ([]byte, error)
|
||||||
|
|
||||||
|
// dereferenceUserShortcut is a shortcut to dereference followers an account on
|
||||||
|
// this instance, without making any external api/http calls.
|
||||||
|
//
|
||||||
|
// It is passed to new transports, and should only be invoked when the iri.Host == this host.
|
||||||
|
dereferenceUserShortcut func(ctx context.Context, iri *url.URL) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func dereferenceFollowersShortcut(federatingDB federatingdb.DB) func(context.Context, *url.URL) ([]byte, error) {
|
||||||
|
return func(ctx context.Context, iri *url.URL) ([]byte, error) {
|
||||||
|
followers, err := federatingDB.Followers(ctx, iri)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
i, err := streams.Serialize(followers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dereferenceUserShortcut(federatingDB federatingdb.DB) func(context.Context, *url.URL) ([]byte, error) {
|
||||||
|
return func(ctx context.Context, iri *url.URL) ([]byte, error) {
|
||||||
|
user, err := federatingDB.Get(ctx, iri)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
i, err := streams.Serialize(user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(i)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewController returns an implementation of the Controller interface for creating new transports
|
// NewController returns an implementation of the Controller interface for creating new transports
|
||||||
func NewController(db db.DB, clock pub.Clock, client pub.HttpClient) Controller {
|
func NewController(db db.DB, federatingDB federatingdb.DB, clock pub.Clock, client pub.HttpClient) Controller {
|
||||||
applicationName := viper.GetString(config.Keys.ApplicationName)
|
applicationName := viper.GetString(config.Keys.ApplicationName)
|
||||||
host := viper.GetString(config.Keys.Host)
|
host := viper.GetString(config.Keys.Host)
|
||||||
appAgent := fmt.Sprintf("%s %s", applicationName, host)
|
appAgent := fmt.Sprintf("%s %s", applicationName, host)
|
||||||
|
@ -55,6 +103,8 @@ func NewController(db db.DB, clock pub.Clock, client pub.HttpClient) Controller
|
||||||
clock: clock,
|
clock: clock,
|
||||||
client: client,
|
client: client,
|
||||||
appAgent: appAgent,
|
appAgent: appAgent,
|
||||||
|
dereferenceFollowersShortcut: dereferenceFollowersShortcut(federatingDB),
|
||||||
|
dereferenceUserShortcut: dereferenceUserShortcut(federatingDB),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +137,8 @@ func (c *controller) NewTransport(pubKeyID string, privkey crypto.PrivateKey) (T
|
||||||
sigTransport: sigTransport,
|
sigTransport: sigTransport,
|
||||||
getSigner: getSigner,
|
getSigner: getSigner,
|
||||||
getSignerMu: &sync.Mutex{},
|
getSignerMu: &sync.Mutex{},
|
||||||
|
dereferenceFollowersShortcut: c.dereferenceFollowersShortcut,
|
||||||
|
dereferenceUserShortcut: c.dereferenceUserShortcut,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,8 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t *transport) BatchDeliver(ctx context.Context, b []byte, recipients []*url.URL) error {
|
func (t *transport) BatchDeliver(ctx context.Context, b []byte, recipients []*url.URL) error {
|
||||||
|
@ -30,6 +32,11 @@ func (t *transport) BatchDeliver(ctx context.Context, b []byte, recipients []*ur
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *transport) Deliver(ctx context.Context, b []byte, to *url.URL) error {
|
func (t *transport) Deliver(ctx context.Context, b []byte, to *url.URL) error {
|
||||||
|
// if the 'to' host is our own, just skip this delivery since we by definition already have the message!
|
||||||
|
if to.Host == viper.GetString(config.Keys.Host) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
l := logrus.WithField("func", "Deliver")
|
l := logrus.WithField("func", "Deliver")
|
||||||
l.Debugf("performing POST to %s", to.String())
|
l.Debugf("performing POST to %s", to.String())
|
||||||
return t.sigTransport.Deliver(ctx, b, to)
|
return t.sigTransport.Deliver(ctx, b, to)
|
||||||
|
|
|
@ -23,10 +23,29 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/uris"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t *transport) Dereference(ctx context.Context, iri *url.URL) ([]byte, error) {
|
func (t *transport) Dereference(ctx context.Context, iri *url.URL) ([]byte, error) {
|
||||||
l := logrus.WithField("func", "Dereference")
|
l := logrus.WithField("func", "Dereference")
|
||||||
|
|
||||||
|
// if the request is to us, we can shortcut for certain URIs rather than going through
|
||||||
|
// the normal request flow, thereby saving time and energy
|
||||||
|
if iri.Host == viper.GetString(config.Keys.Host) {
|
||||||
|
if uris.IsFollowersPath(iri) {
|
||||||
|
// the request is for followers of one of our accounts, which we can shortcut
|
||||||
|
return t.dereferenceFollowersShortcut(ctx, iri)
|
||||||
|
}
|
||||||
|
|
||||||
|
if uris.IsUserPath(iri) {
|
||||||
|
// the request is for one of our accounts, which we can shortcut
|
||||||
|
return t.dereferenceUserShortcut(ctx, iri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the request is either for a remote host or for us but we don't have a shortcut, so continue as normal
|
||||||
l.Debugf("performing GET to %s", iri.String())
|
l.Debugf("performing GET to %s", iri.String())
|
||||||
return t.sigTransport.Dereference(ctx, iri)
|
return t.sigTransport.Dereference(ctx, iri)
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,11 @@ import (
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Transport wraps the pub.Transport interface with some additional
|
// Transport wraps the pub.Transport interface with some additional functionality for fetching remote media.
|
||||||
// functionality for fetching remote media.
|
//
|
||||||
|
// Since the transport has the concept of 'shortcuts' for fetching data locally rather than remotely, it is
|
||||||
|
// not *always* the case that calling a Transport function does an http call, but it usually will for remote
|
||||||
|
// hosts or resources for which a shortcut isn't provided by the transport controller (also in this package).
|
||||||
type Transport interface {
|
type Transport interface {
|
||||||
pub.Transport
|
pub.Transport
|
||||||
// DereferenceMedia fetches the given media attachment IRI, returning the reader and filesize.
|
// DereferenceMedia fetches the given media attachment IRI, returning the reader and filesize.
|
||||||
|
@ -40,6 +43,8 @@ type Transport interface {
|
||||||
DereferenceInstance(ctx context.Context, iri *url.URL) (*gtsmodel.Instance, error)
|
DereferenceInstance(ctx context.Context, iri *url.URL) (*gtsmodel.Instance, error)
|
||||||
// Finger performs a webfinger request with the given username and domain, and returns the bytes from the response body.
|
// Finger performs a webfinger request with the given username and domain, and returns the bytes from the response body.
|
||||||
Finger(ctx context.Context, targetUsername string, targetDomains string) ([]byte, error)
|
Finger(ctx context.Context, targetUsername string, targetDomains string) ([]byte, error)
|
||||||
|
// SigTransport returns the underlying http signature transport wrapped by the GoToSocial transport.
|
||||||
|
SigTransport() pub.Transport
|
||||||
}
|
}
|
||||||
|
|
||||||
// transport implements the Transport interface
|
// transport implements the Transport interface
|
||||||
|
@ -53,4 +58,13 @@ type transport struct {
|
||||||
sigTransport *pub.HttpSigTransport
|
sigTransport *pub.HttpSigTransport
|
||||||
getSigner httpsig.Signer
|
getSigner httpsig.Signer
|
||||||
getSignerMu *sync.Mutex
|
getSignerMu *sync.Mutex
|
||||||
|
|
||||||
|
// shortcuts for dereferencing things that exist on our instance without making an http call to ourself
|
||||||
|
|
||||||
|
dereferenceFollowersShortcut func(ctx context.Context, iri *url.URL) ([]byte, error)
|
||||||
|
dereferenceUserShortcut func(ctx context.Context, iri *url.URL) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *transport) SigTransport() pub.Transport {
|
||||||
|
return t.sigTransport
|
||||||
}
|
}
|
||||||
|
|
|
@ -1749,8 +1749,8 @@ func GetSignatureForActivity(activity pub.Activity, pubKeyID string, privkey cry
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// trigger the delivery function, which will trigger the 'do' function of the recorder above
|
// trigger the delivery function for the underlying signature transport, which will trigger the 'do' function of the recorder above
|
||||||
if err := tp.Deliver(context.Background(), bytes, destination); err != nil {
|
if err := tp.SigTransport().Deliver(context.Background(), bytes, destination); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1781,8 +1781,8 @@ func GetSignatureForDereference(pubKeyID string, privkey crypto.PrivateKey, dest
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// trigger the delivery function, which will trigger the 'do' function of the recorder above
|
// trigger the dereference function for the underlying signature transport, which will trigger the 'do' function of the recorder above
|
||||||
if _, err := tp.Dereference(context.Background(), destination); err != nil {
|
if _, err := tp.SigTransport().Dereference(context.Background(), destination); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ import (
|
||||||
// PER TEST rather than per suite, so that the do function can be set on a test by test (or even more granular)
|
// PER TEST rather than per suite, so that the do function can be set on a test by test (or even more granular)
|
||||||
// basis.
|
// basis.
|
||||||
func NewTestTransportController(client pub.HttpClient, db db.DB) transport.Controller {
|
func NewTestTransportController(client pub.HttpClient, db db.DB) transport.Controller {
|
||||||
return transport.NewController(db, &federation.Clock{}, client)
|
return transport.NewController(db, NewTestFederatingDB(db), &federation.Clock{}, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockHTTPClient returns a client that conforms to the pub.HttpClient interface,
|
// NewMockHTTPClient returns a client that conforms to the pub.HttpClient interface,
|
||||||
|
|
Loading…
Reference in a new issue