exp.upspin.io@v0.0.0-20230625230448-5076e5b595ec/cmd/demoserver/main.go (about) 1 // Copyright 2017 The Upspin Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Command demoserver serves an Upspin tree containing a series of boxes 6 // (files) containing Schrödinger's cats. The cats inside the boxes are in the 7 // superposition of dead and alive until a client does a Lookup of the box, at 8 // which point the superposition collapses and the reality of the cat's state 9 // is revealed. 10 // 11 // The purpose of this program is to demonstrate the implementation of a 12 // combined Upspin DirServer and StoreServer that serves dynamic content. 13 // 14 // See also: https://en.wikipedia.org/wiki/Schrödinger's_cat 15 package main // import "exp.upspin.io/cmd/demoserver" 16 17 import ( 18 "fmt" 19 "math/rand" 20 "net/http" 21 "strconv" 22 "strings" 23 "sync" 24 "time" 25 26 "upspin.io/access" 27 "upspin.io/cloud/https" 28 "upspin.io/config" 29 "upspin.io/errors" 30 "upspin.io/flags" 31 "upspin.io/log" 32 "upspin.io/pack" 33 "upspin.io/path" 34 "upspin.io/rpc/dirserver" 35 "upspin.io/rpc/storeserver" 36 "upspin.io/serverutil" 37 "upspin.io/upspin" 38 39 _ "upspin.io/key/transports" 40 _ "upspin.io/pack/eeintegrity" 41 ) 42 43 func main() { 44 rand.Seed(time.Now().UnixNano()) 45 flags.Parse(flags.Server) 46 47 addr := upspin.NetAddr(flags.NetAddr) 48 ep := upspin.Endpoint{ 49 Transport: upspin.Remote, 50 NetAddr: addr, 51 } 52 cfg, err := config.FromFile(flags.Config) 53 if err != nil { 54 log.Fatal(err) 55 } 56 57 s, err := newServer(ep, cfg) 58 if err != nil { 59 log.Fatal(err) 60 } 61 62 http.Handle("/api/Store/", storeserver.New(cfg, s.StoreServer(), addr)) 63 http.Handle("/api/Dir/", dirserver.New(cfg, s.DirServer(), addr)) 64 65 https.ListenAndServeFromFlags(nil) 66 } 67 68 // box represents an opened box. 69 type box struct { 70 *upspin.DirEntry 71 data []byte 72 } 73 74 // server provides implementations of upspin.DirServer and upspin.StoreServer 75 // (accessed by calling the respective methods) that serve a tree containing 76 // many boxes containing Schrödinger's Cats. 77 type server struct { 78 ep upspin.Endpoint 79 cfg upspin.Config 80 81 accessEntry *upspin.DirEntry 82 accessBytes []byte 83 84 mu sync.Mutex 85 open *sync.Cond // Broadcast when a box is opened for the first time. 86 boxes []box 87 } 88 89 type dirServer struct { 90 *server 91 } 92 93 type storeServer struct { 94 *server 95 } 96 97 func (s *server) DirServer() upspin.DirServer { 98 return &dirServer{s} 99 } 100 101 func (s *server) StoreServer() upspin.StoreServer { 102 return &storeServer{s} 103 } 104 105 const ( 106 accessRef = upspin.Reference(access.AccessFile) 107 accessFile = "read,list:all\n" 108 ) 109 110 var accessRefdata = upspin.Refdata{Reference: accessRef} 111 112 func newServer(ep upspin.Endpoint, cfg upspin.Config) (*server, error) { 113 s := &server{ 114 ep: ep, 115 cfg: cfg, 116 } 117 s.open = sync.NewCond(&s.mu) 118 119 var err error 120 s.accessEntry, s.accessBytes, err = s.pack(access.AccessFile, 0, []byte(accessFile)) 121 if err != nil { 122 return nil, err 123 } 124 125 return s, nil 126 } 127 128 const packing = upspin.EEIntegrityPack 129 130 func (s *server) pack(filePath string, seq int64, data []byte) (*upspin.DirEntry, []byte, error) { 131 name := upspin.PathName(s.cfg.UserName()) + "/" + upspin.PathName(filePath) 132 de := &upspin.DirEntry{ 133 Writer: s.cfg.UserName(), 134 Name: name, 135 SignedName: name, 136 Packing: packing, 137 Time: upspin.Now(), 138 Sequence: seq, 139 } 140 141 bp, err := pack.Lookup(packing).Pack(s.cfg, de) 142 if err != nil { 143 return nil, nil, err 144 } 145 cipher, err := bp.Pack(data) 146 if err != nil { 147 return nil, nil, err 148 } 149 bp.SetLocation(upspin.Location{ 150 Endpoint: s.ep, 151 Reference: upspin.Reference(filePath), 152 }) 153 return de, cipher, bp.Close() 154 } 155 156 // These methods implement upspin.Service. 157 158 func (s *server) Endpoint() upspin.Endpoint { return s.ep } 159 func (*server) Ping() bool { return true } 160 func (*server) Close() {} 161 162 // These methods implement upspin.Dialer. 163 164 func (s *storeServer) Dial(upspin.Config, upspin.Endpoint) (upspin.Service, error) { return s, nil } 165 func (s *dirServer) Dial(upspin.Config, upspin.Endpoint) (upspin.Service, error) { return s, nil } 166 167 // These methods implement upspin.DirServer. 168 169 func (s *dirServer) Lookup(name upspin.PathName) (*upspin.DirEntry, error) { 170 p, err := path.Parse(name) 171 if err != nil { 172 return nil, err 173 } 174 175 fp := p.FilePath() 176 switch fp { 177 case "": // Root directory. 178 s.mu.Lock() 179 total := len(s.boxes) 180 s.mu.Unlock() 181 return &upspin.DirEntry{ 182 Name: p.Path(), 183 SignedName: p.Path(), 184 Attr: upspin.AttrDirectory, 185 Time: upspin.Now(), 186 Sequence: int64(total * 2), 187 }, nil 188 case access.AccessFile: 189 return s.accessEntry, nil 190 } 191 192 n := matchBox(fp) 193 if n < 0 { 194 return nil, errors.E(name, errors.NotExist) 195 } 196 197 s.mu.Lock() 198 defer s.mu.Unlock() 199 200 total := len(s.boxes) 201 if n > total { 202 return nil, errors.E(name, errors.NotExist) 203 } 204 205 if n == total { 206 // A new box is opened! 207 de, data, err := s.pack(fp, int64(total*2+1), randomState()) 208 if err != nil { 209 return nil, errors.E(name, err) 210 } 211 s.boxes = append(s.boxes, box{de, data}) 212 s.open.Broadcast() 213 } 214 215 return s.boxes[n].DirEntry, nil 216 } 217 218 func (s *dirServer) Glob(pattern string) ([]*upspin.DirEntry, error) { 219 return serverutil.Glob(pattern, s.Lookup, s.listDir) 220 } 221 222 func (s *dirServer) listDir(name upspin.PathName) ([]*upspin.DirEntry, error) { 223 p, err := path.Parse(name) 224 if err != nil { 225 return nil, err 226 } 227 if p.User() != s.cfg.UserName() || p.FilePath() != "" { 228 return nil, errors.E(name, errors.NotExist) 229 } 230 231 s.mu.Lock() 232 defer s.mu.Unlock() 233 234 des := []*upspin.DirEntry{ 235 s.accessEntry, 236 } 237 238 // List all the opened boxes in numerical order. 239 for n := range s.boxes { 240 de := s.boxes[n].DirEntry.Copy() 241 de.MarkIncomplete() 242 des = append(des, de) 243 } 244 245 // The final, closed box. 246 des = append(des, s.closedBox(len(s.boxes))) 247 248 return des, nil 249 } 250 251 func (s *dirServer) closedBox(n int) *upspin.DirEntry { 252 name := upspin.PathName(s.cfg.UserName()) + "/" + upspin.PathName(fmtBox(n)) 253 return &upspin.DirEntry{ 254 Name: name, 255 SignedName: name, 256 Attr: upspin.AttrIncomplete, 257 Time: upspin.Now(), 258 Writer: s.cfg.UserName(), 259 Sequence: int64(n * 2), 260 } 261 } 262 263 func (s *dirServer) Watch(name upspin.PathName, seq int64, done <-chan struct{}) (<-chan upspin.Event, error) { 264 p, err := path.Parse(name) 265 if err != nil { 266 return nil, err 267 } 268 if p.User() != s.cfg.UserName() { 269 return nil, errors.E(name, errors.NotExist) 270 } 271 272 fp := p.FilePath() 273 match := func(de *upspin.DirEntry) bool { 274 return fp == "" || name == de.Name 275 276 } 277 278 n := int(seq) 279 if n < 0 { 280 n = 0 281 } 282 isDone := func() bool { 283 select { 284 case <-done: 285 return true 286 default: 287 return false 288 } 289 } 290 events := make(chan upspin.Event) 291 go func() { 292 <-done 293 s.open.Broadcast() 294 }() 295 go func() { 296 defer close(events) 297 for { 298 s.mu.Lock() 299 if n == len(s.boxes) { 300 // Send the closed box. 301 go func(n int) { 302 if de := s.closedBox(n); match(de) { 303 events <- upspin.Event{ 304 Entry: s.closedBox(n), 305 } 306 } 307 }(n) 308 } 309 for !isDone() && n >= len(s.boxes) { 310 s.open.Wait() 311 } 312 if isDone() { 313 s.mu.Unlock() 314 return 315 } 316 de := s.boxes[n].DirEntry 317 s.mu.Unlock() 318 319 // Send the next opened box. 320 if match(de) { 321 events <- upspin.Event{ 322 Entry: de, 323 } 324 } 325 n++ 326 } 327 }() 328 return events, nil 329 } 330 331 func (s *dirServer) WhichAccess(name upspin.PathName) (*upspin.DirEntry, error) { 332 return s.accessEntry, nil 333 } 334 335 // This method implements upspin.StoreServer. 336 337 func (s *storeServer) Get(ref upspin.Reference) ([]byte, *upspin.Refdata, []upspin.Location, error) { 338 if ref == accessRef { 339 return s.accessBytes, &accessRefdata, nil, nil 340 } 341 342 n := matchBox(string(ref)) 343 344 s.mu.Lock() 345 defer s.mu.Unlock() 346 347 if n < 0 || n >= len(s.boxes) { 348 return nil, nil, nil, errors.E(errors.NotExist, errors.Errorf("unknown reference %q", ref)) 349 } 350 351 return s.boxes[n].data, &upspin.Refdata{Reference: ref}, nil, nil 352 } 353 354 // The DirServer and StoreServer methods below are not implemented. 355 356 var errNotImplemented = errors.E(errors.Permission, "method not implemented: demoserver is read-only") 357 358 func (*dirServer) Put(entry *upspin.DirEntry) (*upspin.DirEntry, error) { 359 return nil, errNotImplemented 360 } 361 362 func (*dirServer) Delete(name upspin.PathName) (*upspin.DirEntry, error) { 363 return nil, errNotImplemented 364 } 365 366 func (*storeServer) Put(data []byte) (*upspin.Refdata, error) { 367 return nil, errNotImplemented 368 } 369 370 func (*storeServer) Delete(ref upspin.Reference) error { 371 return errNotImplemented 372 } 373 374 // Utility functions. 375 376 const boxName = "box" 377 378 func fmtBox(n int) string { 379 return fmt.Sprintf("%s%d", boxName, n) 380 } 381 382 func matchBox(filePath string) int { 383 if !strings.HasPrefix(filePath, boxName) { 384 return -1 385 } 386 n := filePath[len(boxName):] 387 i, _ := strconv.ParseInt(n, 10, 32) 388 if i < 0 || fmtBox(int(i)) != filePath { 389 return -1 390 } 391 return int(i) 392 } 393 394 var states = [][]byte{ 395 []byte("A dead cat.\n"), 396 []byte("A live cat.\n"), 397 } 398 399 func randomState() []byte { 400 return states[rand.Intn(2)] 401 }