send_over_http.go (3135B)
1 package sendoverhttp 2 3 import ( 4 "errors" 5 "io/fs" 6 "log" 7 "net" 8 "net/http" 9 "net/url" 10 "os" 11 "path/filepath" 12 ) 13 14 // Config contains the configuration parameters for the service. 15 type Config struct { 16 // FilePath is the file to serve. 17 FilePath string 18 // ListenAddress is the network address to listen for connections. 19 ListenAddress string 20 // Network type to listen on. 21 Network string 22 } 23 24 // Source a single listening address to listen on. 25 func getListenAddress(config Config) (string, error) { 26 switch config.Network { 27 case "tcp", "tcp4", "tcp6": 28 default: 29 return config.ListenAddress, nil 30 } 31 32 addr, port, err := net.SplitHostPort(config.ListenAddress) 33 if err != nil { 34 addr = config.ListenAddress 35 port = "0" 36 } 37 38 if addr != "" { 39 ip := net.ParseIP(addr) 40 if ip == nil { 41 return config.ListenAddress, nil 42 } 43 if !ip.IsUnspecified() { 44 return config.ListenAddress, nil 45 } 46 } 47 48 ips, err := net.InterfaceAddrs() 49 if err != nil { 50 return "", err 51 } 52 53 // Valid fallback if no other interfaces available 54 var loopback *net.IP = nil 55 56 for _, addr := range ips { 57 ipNet, ok := addr.(*net.IPNet) 58 if !ok { 59 continue 60 } 61 if config.Network == "tcp6" && ipNet.IP.To4() != nil { 62 continue 63 } 64 if ipNet.IP.IsLoopback() { 65 loopback = &ipNet.IP 66 continue 67 } 68 return net.JoinHostPort(ipNet.IP.String(), port), nil 69 } 70 71 if loopback != nil { 72 return net.JoinHostPort((*loopback).String(), port), nil 73 } 74 return "", errors.New("no valid network address could be found") 75 } 76 77 type Server struct { 78 config Config 79 80 closeCh chan struct{} 81 mr *MultiRunner 82 } 83 84 type SingleFileDir string 85 86 func (s SingleFileDir) Open(name string) (http.File, error) { 87 if name == "index.html" { 88 return nil, fs.ErrNotExist 89 } 90 91 fp := string(s) 92 stat, err := os.Stat(fp) 93 if err != nil { 94 return nil, err 95 } 96 if stat.IsDir() { 97 return http.Dir(fp).Open(name) 98 } 99 100 switch name { 101 case "/": 102 return nil, os.ErrNotExist 103 case "/" + filepath.Base(fp): 104 return os.Open(fp) 105 default: 106 return nil, os.ErrNotExist 107 108 } 109 } 110 111 func NewServer(config Config) *Server { 112 return &Server{ 113 config: config, 114 115 closeCh: make(chan struct{}), 116 mr: NewMultiRunner(), 117 } 118 } 119 120 func (s *Server) Start() error { 121 listenAddr, err := getListenAddress(s.config) 122 if err != nil { 123 log.Fatal(err) 124 } 125 126 l, err := net.Listen(s.config.Network, listenAddr) 127 if err != nil { 128 log.Fatal(err) 129 } 130 131 u := url.URL{ 132 Scheme: "http", 133 Host: l.Addr().String(), 134 } 135 if stat, _ := os.Stat(s.config.FilePath); !stat.IsDir() { 136 u.Path = filepath.Base(s.config.FilePath) 137 } 138 139 qrCodeShower := NewTerminalQRShower(u.String()) 140 go s.mr.Run(qrCodeShower) 141 142 var d http.FileSystem = http.Dir(s.config.FilePath) 143 if stat, _ := os.Stat(s.config.FilePath); !stat.IsDir() { 144 // Show user the name of the file 145 d = SingleFileDir(s.config.FilePath) 146 } 147 httpSrv := &http.Server{ 148 Handler: http.FileServer(d), 149 } 150 151 httpRunner := NewRunner() 152 httpRunner.OnStart = func() error { 153 return httpSrv.Serve(l) 154 } 155 httpRunner.OnStop = func() { 156 _ = httpSrv.Close() 157 } 158 go s.mr.Run(httpRunner) 159 160 <-s.closeCh 161 close(s.closeCh) 162 return nil 163 } 164 165 func (s *Server) Stop() { 166 s.mr.Stop() 167 return 168 }