mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-20 11:58:39 +00:00
ipn/localapi: add support for multipart POST to file-put
This allows sending multiple files via Taildrop in one request. Progress is tracked via ipn.Notify. Updates tailscale/corp#18202 Signed-off-by: Percy Wegmann <percy@tailscale.com>
This commit is contained in:
parent
bed818a978
commit
66e4d843c1
@ -102,7 +102,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
L github.com/google/nftables/expr from github.com/google/nftables+
|
L github.com/google/nftables/expr from github.com/google/nftables+
|
||||||
L github.com/google/nftables/internal/parseexprfunc from github.com/google/nftables+
|
L github.com/google/nftables/internal/parseexprfunc from github.com/google/nftables+
|
||||||
L github.com/google/nftables/xt from github.com/google/nftables/expr+
|
L github.com/google/nftables/xt from github.com/google/nftables/expr+
|
||||||
github.com/google/uuid from tailscale.com/clientupdate
|
github.com/google/uuid from tailscale.com/clientupdate+
|
||||||
github.com/gorilla/csrf from tailscale.com/client/web
|
github.com/gorilla/csrf from tailscale.com/client/web
|
||||||
github.com/gorilla/securecookie from github.com/gorilla/csrf
|
github.com/gorilla/securecookie from github.com/gorilla/csrf
|
||||||
github.com/hdevalence/ed25519consensus from tailscale.com/clientupdate/distsign+
|
github.com/hdevalence/ed25519consensus from tailscale.com/clientupdate/distsign+
|
||||||
@ -376,6 +376,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
W 💣 tailscale.com/util/osdiag/internal/wsc from tailscale.com/util/osdiag
|
W 💣 tailscale.com/util/osdiag/internal/wsc from tailscale.com/util/osdiag
|
||||||
tailscale.com/util/osshare from tailscale.com/cmd/tailscaled+
|
tailscale.com/util/osshare from tailscale.com/cmd/tailscaled+
|
||||||
tailscale.com/util/osuser from tailscale.com/ipn/localapi+
|
tailscale.com/util/osuser from tailscale.com/ipn/localapi+
|
||||||
|
tailscale.com/util/progresstracking from tailscale.com/ipn/localapi
|
||||||
tailscale.com/util/race from tailscale.com/net/dns/resolver
|
tailscale.com/util/race from tailscale.com/net/dns/resolver
|
||||||
tailscale.com/util/racebuild from tailscale.com/logpolicy
|
tailscale.com/util/racebuild from tailscale.com/logpolicy
|
||||||
tailscale.com/util/rands from tailscale.com/ipn/ipnlocal+
|
tailscale.com/util/rands from tailscale.com/ipn/ipnlocal+
|
||||||
@ -522,7 +523,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
math/rand from github.com/mdlayher/netlink+
|
math/rand from github.com/mdlayher/netlink+
|
||||||
math/rand/v2 from tailscale.com/util/rands
|
math/rand/v2 from tailscale.com/util/rands
|
||||||
mime from github.com/tailscale/xnet/webdav+
|
mime from github.com/tailscale/xnet/webdav+
|
||||||
mime/multipart from net/http
|
mime/multipart from net/http+
|
||||||
mime/quotedprintable from mime/multipart
|
mime/quotedprintable from mime/multipart
|
||||||
net from crypto/tls+
|
net from crypto/tls+
|
||||||
net/http from expvar+
|
net/http from expvar+
|
||||||
|
@ -202,8 +202,8 @@ type PartialFile struct {
|
|||||||
|
|
||||||
// OutgoingFile represents an in-progress outgoing file transfer.
|
// OutgoingFile represents an in-progress outgoing file transfer.
|
||||||
type OutgoingFile struct {
|
type OutgoingFile struct {
|
||||||
ID string `json:"-"` // unique identifier for this transfer (a type 4 UUID)
|
ID string `json:",omitempty"` // unique identifier for this transfer (a type 4 UUID)
|
||||||
PeerID tailcfg.StableNodeID // identifier for the peer to which this is being transferred
|
PeerID tailcfg.StableNodeID `json:",omitempty"` // identifier for the peer to which this is being transferred
|
||||||
Name string `json:",omitempty"` // e.g. "foo.jpg"
|
Name string `json:",omitempty"` // e.g. "foo.jpg"
|
||||||
Started time.Time // time transfer started
|
Started time.Time // time transfer started
|
||||||
DeclaredSize int64 // or -1 if unknown
|
DeclaredSize int64 // or -1 if unknown
|
||||||
|
@ -320,7 +320,7 @@ type LocalBackend struct {
|
|||||||
// notified about.
|
// notified about.
|
||||||
lastNotifiedTailFSShares atomic.Pointer[views.SliceView[*tailfs.Share, tailfs.ShareView]]
|
lastNotifiedTailFSShares atomic.Pointer[views.SliceView[*tailfs.Share, tailfs.ShareView]]
|
||||||
|
|
||||||
// outgoingFiles keeps track of Taildrop outgoing files
|
// outgoingFiles keeps track of Taildrop outgoing files keyed to their OutgoingFile.ID
|
||||||
outgoingFiles map[string]*ipn.OutgoingFile
|
outgoingFiles map[string]*ipn.OutgoingFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,20 +4,21 @@
|
|||||||
package ipnlocal
|
package ipnlocal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"maps"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *LocalBackend) UpdateOutgoingFiles(updates map[string]ipn.OutgoingFile) {
|
// UpdateOutgoingFiles updates b.outgoingFiles to reflect the given updates and
|
||||||
|
// sends an ipn.Notify with the full list of outgoingFiles.
|
||||||
|
func (b *LocalBackend) UpdateOutgoingFiles(updates map[string]*ipn.OutgoingFile) {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
if b.outgoingFiles == nil {
|
if b.outgoingFiles == nil {
|
||||||
b.outgoingFiles = make(map[string]*ipn.OutgoingFile, len(updates))
|
b.outgoingFiles = make(map[string]*ipn.OutgoingFile, len(updates))
|
||||||
}
|
}
|
||||||
for id, file := range updates {
|
maps.Copy(b.outgoingFiles, updates)
|
||||||
b.outgoingFiles[id] = &file
|
|
||||||
}
|
|
||||||
outgoingFiles := make([]*ipn.OutgoingFile, 0, len(b.outgoingFiles))
|
outgoingFiles := make([]*ipn.OutgoingFile, 0, len(b.outgoingFiles))
|
||||||
for _, file := range b.outgoingFiles {
|
for _, file := range b.outgoingFiles {
|
||||||
outgoingFiles = append(outgoingFiles, file)
|
outgoingFiles = append(outgoingFiles, file)
|
||||||
|
@ -1535,11 +1535,10 @@ func (h *Handler) serveFileTargets(w http.ResponseWriter, r *http.Request) {
|
|||||||
// directly, as the Windows GUI always runs in tun mode anyway.
|
// directly, as the Windows GUI always runs in tun mode anyway.
|
||||||
//
|
//
|
||||||
// In addition to single file PUTs, this endpoint accepts multipart file
|
// In addition to single file PUTs, this endpoint accepts multipart file
|
||||||
// POSTS encoded as multipart/form-data. Each part must include a
|
// POSTS encoded as multipart/form-data.The first part should be an
|
||||||
// "Content-Length" in the MIME header indicating the size of the file.
|
// application/json file that contains a manifest consisting of a JSON array of
|
||||||
// The first part should be an application/json file that contains a JSON map
|
// OutgoingFiles which wecan use for tracking progress even before reading the
|
||||||
// of filename -> length, which we can use for tracking progress even before
|
// file parts.
|
||||||
// reading the file parts.
|
|
||||||
//
|
//
|
||||||
// URL format:
|
// URL format:
|
||||||
//
|
//
|
||||||
@ -1599,9 +1598,9 @@ func (h *Handler) serveFilePut(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report progress on outgoing files every 5 seconds
|
// Periodically report progress of outgoing files.
|
||||||
outgoingFiles := make(map[string]ipn.OutgoingFile)
|
outgoingFiles := make(map[string]*ipn.OutgoingFile)
|
||||||
t := time.NewTicker(5 * time.Second)
|
t := time.NewTicker(1 * time.Second)
|
||||||
progressUpdates := make(chan ipn.OutgoingFile)
|
progressUpdates := make(chan ipn.OutgoingFile)
|
||||||
defer close(progressUpdates)
|
defer close(progressUpdates)
|
||||||
|
|
||||||
@ -1614,7 +1613,7 @@ func (h *Handler) serveFilePut(w http.ResponseWriter, r *http.Request) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
outgoingFiles[u.ID] = u
|
outgoingFiles[u.ID] = &u
|
||||||
case <-t.C:
|
case <-t.C:
|
||||||
h.b.UpdateOutgoingFiles(outgoingFiles)
|
h.b.UpdateOutgoingFiles(outgoingFiles)
|
||||||
}
|
}
|
||||||
@ -1672,21 +1671,15 @@ func (h *Handler) multiFilePost(progressUpdates chan (ipn.OutgoingFile), w http.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var manifest map[string]int64
|
var manifest []ipn.OutgoingFile
|
||||||
err := json.NewDecoder(part).Decode(&manifest)
|
err := json.NewDecoder(part).Decode(&manifest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(ww, fmt.Sprintf("invalid manifest: %s", err), http.StatusBadRequest)
|
http.Error(ww, fmt.Sprintf("invalid manifest: %s", err), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for filename, size := range manifest {
|
for _, file := range manifest {
|
||||||
file := ipn.OutgoingFile{
|
outgoingFilesByName[file.Name] = file
|
||||||
ID: uuid.Must(uuid.NewRandom()).String(),
|
|
||||||
Name: filename,
|
|
||||||
PeerID: peerID,
|
|
||||||
DeclaredSize: size,
|
|
||||||
}
|
|
||||||
outgoingFilesByName[filename] = file
|
|
||||||
progressUpdates <- file
|
progressUpdates <- file
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user