forked from TrueCloudLab/rclone
dlna: refactor the serve mux
Trying to make it a little easier to understand and work on all the available routes, etc.
This commit is contained in:
parent
95a74e02c7
commit
60bb01b22c
2 changed files with 67 additions and 121 deletions
|
@ -69,45 +69,6 @@ const (
|
||||||
serviceControlURL = "/ctl"
|
serviceControlURL = "/ctl"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Groups the service definition with its XML description.
|
|
||||||
type service struct {
|
|
||||||
upnp.Service
|
|
||||||
SCPD string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exposed UPnP AV services.
|
|
||||||
var services = []*service{
|
|
||||||
{
|
|
||||||
Service: upnp.Service{
|
|
||||||
ServiceType: "urn:schemas-upnp-org:service:ContentDirectory:1",
|
|
||||||
ServiceId: "urn:upnp-org:serviceId:ContentDirectory",
|
|
||||||
ControlURL: serviceControlURL,
|
|
||||||
SCPDURL: "/static/ContentDirectory.xml",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Service: upnp.Service{
|
|
||||||
ServiceType: "urn:schemas-upnp-org:service:ConnectionManager:1",
|
|
||||||
ServiceId: "urn:upnp-org:serviceId:ConnectionManager",
|
|
||||||
ControlURL: serviceControlURL,
|
|
||||||
SCPDURL: "/static/ConnectionManager.xml",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func devices() []string {
|
|
||||||
return []string{
|
|
||||||
"urn:schemas-upnp-org:device:MediaServer:1",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func serviceTypes() (ret []string) {
|
|
||||||
for _, s := range services {
|
|
||||||
ret = append(ret, s.ServiceType)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type server struct {
|
type server struct {
|
||||||
// The service SOAP handler keyed by service URN.
|
// The service SOAP handler keyed by service URN.
|
||||||
services map[string]UPnPService
|
services map[string]UPnPService
|
||||||
|
@ -116,7 +77,7 @@ type server struct {
|
||||||
|
|
||||||
HTTPConn net.Listener
|
HTTPConn net.Listener
|
||||||
httpListenAddr string
|
httpListenAddr string
|
||||||
httpServeMux *http.ServeMux
|
handler http.Handler
|
||||||
|
|
||||||
RootDeviceUUID string
|
RootDeviceUUID string
|
||||||
|
|
||||||
|
@ -142,6 +103,7 @@ func newServer(f fs.Fs, opt *dlnaflags.Options) *server {
|
||||||
|
|
||||||
s := &server{
|
s := &server{
|
||||||
AnnounceInterval: 10 * time.Second,
|
AnnounceInterval: 10 * time.Second,
|
||||||
|
Interfaces: listInterfaces(),
|
||||||
FriendlyName: "rclone" + hostName,
|
FriendlyName: "rclone" + hostName,
|
||||||
|
|
||||||
httpListenAddr: opt.ListenAddr,
|
httpListenAddr: opt.ListenAddr,
|
||||||
|
@ -149,17 +111,22 @@ func newServer(f fs.Fs, opt *dlnaflags.Options) *server {
|
||||||
f: f,
|
f: f,
|
||||||
vfs: vfs.New(f, &vfsflags.Opt),
|
vfs: vfs.New(f, &vfsflags.Opt),
|
||||||
}
|
}
|
||||||
|
s.services = map[string]UPnPService{
|
||||||
s.initServicesMap()
|
"ContentDirectory": &contentDirectoryService{
|
||||||
s.listInterfaces()
|
server: s,
|
||||||
|
},
|
||||||
s.httpServeMux = http.NewServeMux()
|
|
||||||
s.RootDeviceUUID = makeDeviceUUID(s.FriendlyName)
|
|
||||||
if err != nil {
|
|
||||||
// Contents are hardcoded, so this will never happen in production.
|
|
||||||
log.Panicf("Marshal root descriptor XML: %v", err)
|
|
||||||
}
|
}
|
||||||
s.initMux(s.httpServeMux)
|
s.RootDeviceUUID = makeDeviceUUID(s.FriendlyName)
|
||||||
|
|
||||||
|
// Setup the various http routes.
|
||||||
|
r := http.NewServeMux()
|
||||||
|
r.HandleFunc(resPath, s.resourceHandler)
|
||||||
|
r.HandleFunc(rootDescPath, s.rootDescHandler)
|
||||||
|
r.HandleFunc(serviceControlURL, s.serviceControlHandler)
|
||||||
|
r.Handle("/static/", http.StripPrefix("/static/",
|
||||||
|
withHeader("Cache-Control", "public, max-age=86400",
|
||||||
|
http.FileServer(data.Assets))))
|
||||||
|
s.handler = withHeader("Server", serverField, r)
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
@ -234,71 +201,6 @@ func (s *server) rootDescHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
buffer.WriteTo(w)
|
buffer.WriteTo(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// initServicesMap is called during initialization of the server to prepare some internal datastructures.
|
|
||||||
func (s *server) initServicesMap() {
|
|
||||||
urn, err := upnp.ParseServiceType(services[0].ServiceType)
|
|
||||||
if err != nil {
|
|
||||||
// The service type is hardcoded, so this error should never happen.
|
|
||||||
log.Panicf("ParseServiceType: %v", err)
|
|
||||||
}
|
|
||||||
s.services = map[string]UPnPService{
|
|
||||||
urn.Type: &contentDirectoryService{
|
|
||||||
server: s,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// listInterfaces is called during initialization of the server to list the network interfaces
|
|
||||||
// on the machine.
|
|
||||||
func (s *server) listInterfaces() {
|
|
||||||
ifs, err := net.Interfaces()
|
|
||||||
if err != nil {
|
|
||||||
fs.Errorf(s.f, "list network interfaces: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var tmp []net.Interface
|
|
||||||
for _, intf := range ifs {
|
|
||||||
if intf.Flags&net.FlagUp == 0 || intf.MTU <= 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s.Interfaces = append(s.Interfaces, intf)
|
|
||||||
tmp = append(tmp, intf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) initMux(mux *http.ServeMux) {
|
|
||||||
mux.HandleFunc(resPath, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
remotePath := r.URL.Query().Get("path")
|
|
||||||
node, err := s.vfs.Stat(remotePath)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Length", strconv.FormatInt(node.Size(), 10))
|
|
||||||
|
|
||||||
file := node.(*vfs.File)
|
|
||||||
in, err := file.Open(os.O_RDONLY)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
defer fs.CheckClose(in, &err)
|
|
||||||
|
|
||||||
http.ServeContent(w, r, remotePath, node.ModTime(), in)
|
|
||||||
return
|
|
||||||
})
|
|
||||||
|
|
||||||
mux.HandleFunc(rootDescPath, s.rootDescHandler)
|
|
||||||
|
|
||||||
mux.Handle("/static/", http.StripPrefix("/static/",
|
|
||||||
withHeader("Cache-Control", "public, max-age=86400",
|
|
||||||
http.FileServer(data.Assets))))
|
|
||||||
|
|
||||||
mux.HandleFunc(serviceControlURL, s.serviceControlHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle a service control HTTP request.
|
// Handle a service control HTTP request.
|
||||||
func (s *server) serviceControlHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *server) serviceControlHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
soapActionString := r.Header.Get("SOAPACTION")
|
soapActionString := r.Header.Get("SOAPACTION")
|
||||||
|
@ -341,6 +243,28 @@ func (s *server) soapActionResponse(sa upnp.SoapAction, actionRequestXML []byte,
|
||||||
return service.Handle(sa.Action, actionRequestXML, r)
|
return service.Handle(sa.Action, actionRequestXML, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Serves actual resources (media files).
|
||||||
|
func (s *server) resourceHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
remotePath := r.URL.Query().Get("path")
|
||||||
|
node, err := s.vfs.Stat(remotePath)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Length", strconv.FormatInt(node.Size(), 10))
|
||||||
|
|
||||||
|
file := node.(*vfs.File)
|
||||||
|
in, err := file.Open(os.O_RDONLY)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
defer fs.CheckClose(in, &err)
|
||||||
|
|
||||||
|
http.ServeContent(w, r, remotePath, node.ModTime(), in)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Serve runs the server - returns the error only if
|
// Serve runs the server - returns the error only if
|
||||||
// the listener was not started; does not block, so
|
// the listener was not started; does not block, so
|
||||||
// use s.Wait() to block on the listener indefinitely.
|
// use s.Wait() to block on the listener indefinitely.
|
||||||
|
@ -416,10 +340,15 @@ func (s *server) ssdpInterface(intf net.Interface) {
|
||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note that the devices and services advertised here via SSDP should be
|
||||||
|
// in agreement with the rootDesc XML descriptor that is defined above.
|
||||||
ssdpServer := ssdp.Server{
|
ssdpServer := ssdp.Server{
|
||||||
Interface: intf,
|
Interface: intf,
|
||||||
Devices: devices(),
|
Devices: []string{
|
||||||
Services: serviceTypes(),
|
"urn:schemas-upnp-org:device:MediaServer:1"},
|
||||||
|
Services: []string{
|
||||||
|
"urn:schemas-upnp-org:service:ContentDirectory:1",
|
||||||
|
"urn:schemas-upnp-org:service:ConnectionManager:1"},
|
||||||
Location: advertiseLocationFn,
|
Location: advertiseLocationFn,
|
||||||
Server: serverField,
|
Server: serverField,
|
||||||
UUID: s.RootDeviceUUID,
|
UUID: s.RootDeviceUUID,
|
||||||
|
@ -461,9 +390,7 @@ func (s *server) ssdpInterface(intf net.Interface) {
|
||||||
|
|
||||||
func (s *server) serveHTTP() error {
|
func (s *server) serveHTTP() error {
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
Handler: s.handler,
|
||||||
s.httpServeMux.ServeHTTP(w, r)
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
err := srv.Serve(s.HTTPConn)
|
err := srv.Serve(s.HTTPConn)
|
||||||
select {
|
select {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/anacrolix/dms/soap"
|
"github.com/anacrolix/dms/soap"
|
||||||
|
@ -21,6 +22,24 @@ func makeDeviceUUID(unique string) string {
|
||||||
return upnp.FormatUUID(buf)
|
return upnp.FormatUUID(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get all available active network interfaces.
|
||||||
|
func listInterfaces() []net.Interface {
|
||||||
|
ifs, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("list network interfaces: %v", err)
|
||||||
|
return []net.Interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var active []net.Interface
|
||||||
|
for _, intf := range ifs {
|
||||||
|
if intf.Flags&net.FlagUp == 0 || intf.MTU <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
active = append(active, intf)
|
||||||
|
}
|
||||||
|
return active
|
||||||
|
}
|
||||||
|
|
||||||
func didlLite(chardata string) string {
|
func didlLite(chardata string) string {
|
||||||
return `<DIDL-Lite` +
|
return `<DIDL-Lite` +
|
||||||
` xmlns:dc="http://purl.org/dc/elements/1.1/"` +
|
` xmlns:dc="http://purl.org/dc/elements/1.1/"` +
|
||||||
|
|
Loading…
Add table
Reference in a new issue