mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 01:02:25 -05:00 
			
		
		
		
	[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…
	
	Add table
		Add a link
		
	
		Reference in a new issue