Outline support for inbox and outbox handlers and routing.

このコミットが含まれているのは:
Cory Slep 2018-01-31 22:43:13 +01:00
コミット 3662ac5bf9
3個のファイルの変更325行の追加86行の削除

ファイルの表示

@ -1,8 +1,11 @@
package pub
import (
"encoding/json"
"fmt"
"github.com/go-fed/activity/streams"
"github.com/go-fed/activity/vocab"
"io/ioutil"
"net/http"
"net/url"
)
@ -103,72 +106,118 @@ type Endpoint interface {
var _ Endpoint = &streams.Object{}
// Storer is a long term storage solution provided by clients so that data can
// be saved and retrieved by the ActivityPub federated server.
type Storer interface {
// TODO
type clienter struct {
}
// Server implements receiving the federated portion of the ActivityPub
// specification.
//
// It implements a single 'sharedinbox' to trade off numerous messages over the
// network for increased internal processing. Additionally, it will keep track
// of externally-known 'sharedInbox' in order to send public messages
// efficiently, as permitted by the spec.
//
// Fields that are able to be 'nil' are marked as such; otherwise assume that
// all fields are required.
type Server struct {
// S is used for long-term storage and retrieval of ActivityPub
// messages.
S Storer
func (c *clienter) PostInbox() (http.HandlerFunc, error) {
return func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusMethodNotAllowed)
}, nil
}
// OnInbox proides a handler function for when an Actor receives an Activity.
func (s *Server) OnInbox() (http.HandlerFunc, error) {
func (c *clienter) handleCreate() error {
// TODO: Enforce object
// TODO: Implement
return nil, nil
return nil
}
func (s *Server) OnOutbox() (http.HandlerFunc, error) {
func (c *clienter) handleUpdate() error {
// TODO: Enforce object
// TODO: Implement
return nil, nil
return nil
}
func (s *Server) OnSharedInbox() (http.HandlerFunc, error) {
func (c *clienter) handleDelete() error {
// TODO: Enforce object
// TODO: Implement
return nil, nil
return nil
}
func (s *Server) OnFollowers() (http.HandlerFunc, error) {
func (c *clienter) handleFollow() error {
// TODO: Enforce object
// TODO: Implement
return nil, nil
return nil
}
func (s *Server) OnFollowing() (http.HandlerFunc, error) {
func (c *clienter) handleAccept() error {
// TODO: Implement
return nil, nil
return nil
}
func (s *Server) OnLiked() (http.HandlerFunc, error) {
func (c *clienter) handleReject() error {
// TODO: Implement
return nil, nil
return nil
}
func (s *Server) OnLikes() (http.HandlerFunc, error) {
func (c *clienter) handleAdd() error {
// TODO: Enforce object & target
// TODO: Implement
return nil, nil
return nil
}
func (s *Server) OnShares() (http.HandlerFunc, error) {
func (c *clienter) handleRemove() error {
// TODO: Enforce object & target
// TODO: Implement
return nil, nil
return nil
}
// Client implements sending the federated portion of the ActivityPub
// specification.
type Client struct {
func (c *clienter) handleLike() error {
// TODO: Enforce object
// TODO: Implement
return nil
}
func (c *clienter) handleUndo() error {
// TODO: Enforce object
// TODO: Implement
return nil
}
// FederatorStorer is a long term storage solution provided by clients so that
// data can be saved and retrieved by the ActivityPub federated server.
type FederatorStorer interface {
// GetInbox returns the OrderedCollection inbox of the actor with the
// provided ID.
GetInbox(id string, r *http.Request) (vocab.OrderedCollectionType, error)
// Create requires the client application to persist the 'object' that
// was created.
Create(id string, s *streams.Create) error
// Update should completely replace the 'object' with the same 'id'.
Update(id string, s *streams.Update) error
// Delete SHOULD completely remove the 'object' with its 'id', or have
// the 'object' be replaced by a 'Tombstone' ActivityStream type.
Delete(id string, s *streams.Delete) error
// Follow means the client application SHOULD reply with an 'Accept' or
// 'Reject' ActivityStream with the 'Follow' as the 'object' and deliver
// it to the 'actor' of the 'Follow'. This can be human-triggered or
// automatically triggered.
Follow(id string, s *streams.Follow) error
// Accept can be client application specific. However, if this 'Accept'
// is in response to a 'Follow' then the follower should be added by the
// client application.
Accept(id string, s *streams.Accept) error
// Reject can be client application specific. However, if this 'Reject'
// is in response to a 'Follow' then the client MUST NOT go forward with
// adding the follower.
Reject(id string, s *streams.Reject) error
// Add is client application specific, generally involving adding an
// 'object' to a specific 'target' collection.
Add(id string, s *streams.Add) error
// Remove is client application specific, generally involving removing
// an 'object' from a specific 'target' collection.
Remove(id string, s *streams.Remove) error
// Like triggers adding the like to an object's `like` collection.
Like(id string, s *streams.Like) error
// Undo negates a previous action. The 'actor' on the 'Undo' MUST be the
// same as the 'actor' on the Activity being undone, and the client
// application is responsible for enforcing this.
Undo(id string, s *streams.Undo) error
}
type federator struct {
// Storer is the permanent storage solution provided by the client
// application.
Storer FederatorStorer
// Client is used to federate with other ActivityPub servers.
Client *http.Client
// Agent is the User-Agent string to use in HTTP headers when
@ -179,62 +228,178 @@ type Client struct {
// delivery. It must be at least 1 to be compliant with the ActivityPub
// spec.
MaxDepth int
// EnableClient enables or disables the Social API, the client to
// server part of ActivityPub. Useful if permitting remote clients to
// act on behalf of the users of the client application.
EnableClient bool
// EnableServer enables or disables the Federated Protocol, or the
// server to server part of ActivityPub. Useful to permit integrating
// with the rest of the federative web.
EnableServer bool
}
func (c *Client) Create() error {
// TODO: Enforce object
// TODO: Implement
return nil
func (f *federator) PostInbox(id string) HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) (bool, error) {
if !isActivityPubPost(r) {
return false, nil
}
if !f.EnableServer {
w.WriteHeader(http.StatusMethodNotAllowed)
return true, nil
}
b, err := ioutil.ReadAll(r.Body)
if err != nil {
return true, err
}
var m map[string]interface{}
if err = json.Unmarshal(b, &m); err != nil {
return true, err
}
if err = f.getPostInboxResolver(id).Deserialize(m); err != nil {
return true, err
}
// TODO: 7.1.2 Inbox forwarding
w.WriteHeader(http.StatusOK)
return true, nil
}
}
func (c *Client) Update() error {
// TODO: Enforce object
// TODO: Implement
return nil
func (f *federator) GetInbox(id string) HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) (bool, error) {
if !isActivityPubGet(r) {
return false, nil
}
oc, err := f.Storer.GetInbox(id, r)
if err != nil {
return true, err
}
oc, err = f.dedupeOrderedItems(oc)
if err != nil {
return true, err
}
m, err := oc.Serialize()
if err != nil {
return true, err
}
b, err := json.Marshal(m)
if err != nil {
return true, err
}
w.Header().Set(contentTypeHeader, responseContentTypeHeader)
w.WriteHeader(http.StatusOK)
n, err := w.Write(b)
if err != nil {
return true, err
} else if n != len(b) {
return true, fmt.Errorf("ResponseWriter.Write wrote %d of %d bytes", n, len(b))
}
return true, nil
}
}
func (c *Client) Delete() error {
// TODO: Enforce object
// TODO: Implement
return nil
func (f *federator) PostOutbox(id string) HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) (bool, error) {
if !isActivityPubPost(r) {
return false, nil
}
if !f.EnableClient {
w.WriteHeader(http.StatusMethodNotAllowed)
return true, nil
}
// TODO: Implement
if f.EnableServer {
// TODO: Hook in delivery
}
return true, nil
}
}
func (c *Client) Follow() error {
// TODO: Enforce object
// TODO: Implement
return nil
func (f *federator) GetOutbox(id string) HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) (bool, error) {
if !isActivityPubGet(r) {
return false, nil
}
// TODO: Implement
return true, nil
}
}
func (c *Client) Accept() error {
// TODO: Implement
return nil
func (f *federator) getPostInboxResolver(id string) *streams.Resolver {
return &streams.Resolver{
CreateCallback: f.handleCreate(id),
UpdateCallback: f.handleUpdate(id),
DeleteCallback: f.handleDelete(id),
FollowCallback: f.handleFollow(id),
AcceptCallback: f.handleAccept(id),
RejectCallback: f.handleReject(id),
AddCallback: f.handleAdd(id),
RemoveCallback: f.handleRemove(id),
LikeCallback: f.handleLike(id),
UndoCallback: f.handleUndo(id),
// TODO: Extended activity types, such as Announce, Arrive, etc.
}
}
func (c *Client) Reject() error {
// TODO: Implement
return nil
func (f *federator) handleCreate(id string) func(s *streams.Create) error {
return func(s *streams.Create) error {
return f.Storer.Create(id, s)
}
}
func (c *Client) Add() error {
// TODO: Enforce object & target
// TODO: Implement
return nil
func (f *federator) handleUpdate(id string) func(s *streams.Update) error {
return func(s *streams.Update) error {
// TODO: The receiving server MUST take care to be sure that the Update
// is authorized to modify its object.
return f.Storer.Update(id, s)
}
}
func (c *Client) Remove() error {
// TODO: Enforce object & target
// TODO: Implement
return nil
func (f *federator) handleDelete(id string) func(s *streams.Delete) error {
return func(s *streams.Delete) error {
// TODO: Verify ownership. I think the spec unintentionally suggests to
// just assume it is owned, so we will actually verify.
return f.Storer.Delete(id, s)
}
}
func (c *Client) Like() error {
// TODO: Enforce object
// TODO: Implement
return nil
func (f *federator) handleFollow(id string) func(s *streams.Follow) error {
return func(s *streams.Follow) error {
return f.Storer.Follow(id, s)
}
}
func (c *Client) Undo() error {
// TODO: Enforce object
// TODO: Implement
return nil
func (f *federator) handleAccept(id string) func(s *streams.Accept) error {
return func(s *streams.Accept) error {
return f.Storer.Accept(id, s)
}
}
func (f *federator) handleReject(id string) func(s *streams.Reject) error {
return func(s *streams.Reject) error {
return f.Storer.Reject(id, s)
}
}
func (f *federator) handleAdd(id string) func(s *streams.Add) error {
return func(s *streams.Add) error {
return f.Storer.Add(id, s)
}
}
func (f *federator) handleRemove(id string) func(s *streams.Remove) error {
return func(s *streams.Remove) error {
return f.Storer.Remove(id, s)
}
}
func (f *federator) handleLike(id string) func(s *streams.Like) error {
return func(s *streams.Like) error {
return f.Storer.Like(id, s)
}
}
func (f *federator) handleUndo(id string) func(s *streams.Undo) error {
return func(s *streams.Undo) error {
return f.Storer.Undo(id, s)
}
}

ファイルの表示

@ -2,9 +2,17 @@ package pub
import (
"github.com/go-fed/activity/vocab"
"net/http"
"net/url"
)
// HandlerFunc returns true if it was able to handle the request as an
// ActivityPub request. If it handled the request then the error should be
// checked. The response will have already been written to when handled. Client
// applications can freely choose how to handle the request if this function
// does not handle it.
type HandlerFunc func(http.ResponseWriter, *http.Request) (bool, error)
// ActorObject is an object that has "actor" or "attributedTo" properties,
// representing the author or originator of the object.
type ActorObject interface {

ファイルの表示

@ -13,13 +13,14 @@ import (
)
const (
postContentTypeHeader = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
getAcceptHeader = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
contentTypeHeader = "Content-Type"
acceptHeader = "Accept"
publicActivityPub = "https://www.w3.org/ns/activitystreams#Public"
publicJsonLD = "Public"
publicJsonLDAS = "as:Public"
postContentTypeHeader = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
responseContentTypeHeader = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
getAcceptHeader = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
contentTypeHeader = "Content-Type"
acceptHeader = "Accept"
publicActivityPub = "https://www.w3.org/ns/activitystreams#Public"
publicJsonLD = "Public"
publicJsonLDAS = "as:Public"
)
var alternatives = []string{"application/activity+json"}
@ -86,7 +87,7 @@ func dereference(c *http.Client, u url.URL, agent string) ([]byte, error) {
// prepare takes a DeliverableObject and returns a list of the proper recipient
// target URIs. Additionally, the DeliverableObject will have any hidden
// hidden recipients ("bto" and "bcc") stripped from it.
func (c *Client) prepare(o DeliverableObject) ([]url.URL, error) {
func (c *federator) prepare(o DeliverableObject) ([]url.URL, error) {
// Get inboxes of recipients
var r []url.URL
r = append(r, getToIRIs(o)...)
@ -94,7 +95,16 @@ func (c *Client) prepare(o DeliverableObject) ([]url.URL, error) {
r = append(r, getCcIRIs(o)...)
r = append(r, getBccIRIs(o)...)
r = append(r, getAudienceIRIs(o)...)
// TODO: Handle public collection
// TODO: Support delivery to shared inbox
// 1. When an object is being delivered to the originating actor's
// followers, a server MAY reduce the number of receiving actors
// delivered to by identifying all followers which share the same
// sharedInbox who would otherwise be individual recipients and instead
// deliver objects to said sharedInbox.
// 2. If an object is addressed to the Public special collection, a
// server MAY deliver that object to all known sharedInbox endpoints on
// the network.
r = filterURLs(r, isPublic)
receiverActors, err := c.resolveInboxes(r, 0, c.MaxDepth)
if err != nil {
return nil, err
@ -115,7 +125,7 @@ func (c *Client) prepare(o DeliverableObject) ([]url.URL, error) {
// resolveInboxes takes a list of Actor id URIs and returns them as concrete
// instances of ActorObject. It applies recursively when it encounters a target
// that is a Collection or OrderedCollection.
func (c *Client) resolveInboxes(r []url.URL, depth int, max int) ([]ActorObject, error) {
func (c *federator) resolveInboxes(r []url.URL, depth int, max int) ([]ActorObject, error) {
if depth >= max {
return nil, nil
}
@ -282,6 +292,62 @@ func dedupeIRIs(recipients, ignored []url.URL) (out []url.URL) {
return
}
// dedupeOrderedItems will deduplicate the 'orderedItems' within an ordered
// collection type. Deduplication happens by simply examining the 'id'.
func (f *federator) dedupeOrderedItems(oc vocab.OrderedCollectionType) (vocab.OrderedCollectionType, error) {
i := 0
seen := make(map[string]bool, oc.OrderedItemsLen())
for i < oc.OrderedItemsLen() {
var id string
var removeFn func(int)
if oc.IsOrderedItemsObject(i) {
removeFn = oc.RemoveOrderedItemsObject
iri := oc.GetOrderedItemsObject(i).GetId()
pIri := &iri
id = pIri.String()
} else if oc.IsOrderedItemsLink(i) {
removeFn = oc.RemoveOrderedItemsLink
iri := oc.GetOrderedItemsLink(i).GetId()
pIri := &iri
id = pIri.String()
} else if oc.IsOrderedItemsIRI(i) {
removeFn = oc.RemoveOrderedItemsIRI
b, err := dereference(f.Client, oc.GetOrderedItemsIRI(i), f.Agent)
var m map[string]interface{}
if err := json.Unmarshal(b, &m); err != nil {
return oc, err
}
var iri url.URL
var hasIri bool
if err = toIdResolver(&hasIri, &iri).Deserialize(m); err != nil {
return oc, err
}
pIri := &iri
id = pIri.String()
}
if seen[id] {
removeFn(i)
} else {
seen[id] = true
i++
}
}
return oc, nil
}
// filterURLs removes urls whose strings match the provided filter
func filterURLs(u []url.URL, fn func(s string) bool) []url.URL {
i := 0
for i < len(u) {
if fn(u[i].String()) {
u = append(u[:i], u[i+1:]...)
} else {
i++
}
}
return u
}
func getToIRIs(o DeliverableObject) []url.URL {
var r []url.URL
for i := 0; i < o.ToLen(); i++ {