go-hep.org/x/hep@v0.38.1/groot/cmd/root-srv/server.go (about) 1 // Copyright ©2017 The go-hep 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 package main 6 7 import ( 8 "bytes" 9 "crypto/md5" 10 "encoding/json" 11 "fmt" 12 "html/template" 13 "io" 14 "log" 15 "net/http" 16 "sort" 17 "strconv" 18 "sync" 19 "time" 20 21 uuid "github.com/hashicorp/go-uuid" 22 "go-hep.org/x/hep/groot/riofs" 23 "go-hep.org/x/hep/groot/rsrv" 24 ) 25 26 const cookieName = "GROOT_SRV" 27 28 type server struct { 29 local bool 30 srv *rsrv.Server 31 quit chan int 32 cmds chan plotRequest 33 34 mu sync.RWMutex 35 cookies map[string]*http.Cookie 36 } 37 38 func newServer(local bool, dir string, mux *http.ServeMux) *server { 39 app := &server{ 40 local: local, 41 srv: rsrv.New(dir), 42 quit: make(chan int), 43 cmds: make(chan plotRequest), 44 cookies: make(map[string]*http.Cookie), 45 } 46 go app.run() 47 48 mux.Handle("/", app.wrap(app.rootHandle)) 49 mux.HandleFunc("/ping", app.srv.Ping) 50 mux.Handle("/root-file-upload", app.wrap(app.uploadHandle)) 51 mux.Handle("/root-file-open", app.wrap(app.openHandle)) 52 mux.Handle("/refresh", app.wrap(app.refreshHandle)) 53 mux.Handle("/plot", app.wrap(app.plotHandle)) 54 mux.HandleFunc("/plot-h1", app.srv.PlotH1) 55 mux.HandleFunc("/plot-h2", app.srv.PlotH2) 56 mux.HandleFunc("/plot-s2", app.srv.PlotS2) 57 mux.HandleFunc("/plot-branch", app.srv.PlotTree) 58 59 return app 60 } 61 62 func (srv *server) run() { 63 defer srv.srv.Shutdown() 64 65 ticker := time.NewTicker(5 * time.Minute) 66 defer ticker.Stop() 67 srv.gc() 68 for { 69 select { 70 case <-ticker.C: 71 srv.gc() 72 case cmd := <-srv.cmds: 73 srv.process(cmd) 74 case <-srv.quit: 75 return 76 } 77 } 78 } 79 80 func (srv *server) Shutdown() { 81 close(srv.quit) 82 } 83 84 func (srv *server) gc() { 85 srv.mu.Lock() 86 defer srv.mu.Unlock() 87 for name, cookie := range srv.cookies { 88 now := time.Now() 89 if now.After(cookie.Expires) { 90 delete(srv.cookies, name) 91 cookie.MaxAge = -1 92 } 93 } 94 } 95 96 // func (srv *server) expired(cookie *http.Cookie) bool { 97 // now := time.Now() 98 // return now.After(cookie.Expires) 99 // } 100 101 func (srv *server) setCookie(w http.ResponseWriter, r *http.Request) error { 102 srv.mu.Lock() 103 defer srv.mu.Unlock() 104 cookie, err := r.Cookie(cookieName) 105 if err != nil && err != http.ErrNoCookie { 106 return fmt.Errorf("could not fetch cookie: %w", err) 107 } 108 109 if cookie != nil { 110 return nil 111 } 112 113 v, err := uuid.GenerateUUID() 114 if err != nil { 115 return fmt.Errorf("could not generate UUID: %w", err) 116 } 117 118 cookie = &http.Cookie{ 119 Name: cookieName, 120 Value: v, 121 Expires: time.Now().Add(24 * time.Hour), 122 } 123 srv.cookies[cookie.Value] = cookie 124 http.SetCookie(w, cookie) 125 return nil 126 } 127 128 func (srv *server) wrap(fn func(w http.ResponseWriter, r *http.Request) error) http.HandlerFunc { 129 return func(w http.ResponseWriter, r *http.Request) { 130 err := srv.setCookie(w, r) 131 if err != nil { 132 log.Printf("error retrieving cookie: %+v\n", err) 133 http.Error(w, err.Error(), http.StatusInternalServerError) 134 return 135 } 136 137 if err := fn(w, r); err != nil { 138 log.Printf("error %q: %+v\n", r.URL.Path, err) 139 http.Error(w, err.Error(), http.StatusInternalServerError) 140 } 141 } 142 } 143 144 func (srv *server) rootHandle(w http.ResponseWriter, r *http.Request) error { 145 switch r.Method { 146 case http.MethodGet: 147 // ok 148 default: 149 return fmt.Errorf("invalid request %q for /", r.Method) 150 } 151 152 crutime := time.Now().Unix() 153 h := md5.New() 154 _, err := io.WriteString(h, strconv.FormatInt(crutime, 10)) 155 if err != nil { 156 return fmt.Errorf("could not hash current time: %w", err) 157 } 158 token := fmt.Sprintf("%x", h.Sum(nil)) 159 160 t, err := template.New("upload").Parse(page) 161 if err != nil { 162 return fmt.Errorf("could not parse HTML template: %w", err) 163 } 164 165 err = srv.ping(r) 166 if err != nil { 167 return fmt.Errorf("could not ping ROOT server: %w", err) 168 } 169 170 return t.Execute(w, struct { 171 Token string 172 Local bool 173 }{token, srv.local}) 174 } 175 176 func (srv *server) uploadHandle(w http.ResponseWriter, r *http.Request) error { 177 cookie, err := r.Cookie(cookieName) 178 if err != nil { 179 return fmt.Errorf("could not retrieve cookie: %w", err) 180 } 181 182 defer r.Body.Close() 183 req, err := http.NewRequest(http.MethodPost, "/file-upload", r.Body) 184 if err != nil { 185 return fmt.Errorf("could not create upload-file request: %w", err) 186 } 187 req.AddCookie(cookie) 188 req.Header.Set("Content-Type", r.Header.Get("Content-Type")) 189 190 ww := newResponseWriter() 191 srv.srv.UploadFile(ww, req) 192 193 if ww.code != http.StatusOK { 194 w.WriteHeader(ww.code) 195 return fmt.Errorf("could not upload file") 196 } 197 198 nodes, err := srv.nodes(r) 199 if err != nil { 200 return fmt.Errorf("could not create nodes: %w", err) 201 } 202 203 return json.NewEncoder(w).Encode(nodes) 204 } 205 206 func (srv *server) openHandle(w http.ResponseWriter, r *http.Request) error { 207 cookie, err := r.Cookie(cookieName) 208 if err != nil { 209 return fmt.Errorf("could not retrieve cookie: %w", err) 210 } 211 212 err = r.ParseMultipartForm(500 << 20) 213 if err != nil { 214 return fmt.Errorf("could not parse multipart form: %w", err) 215 } 216 fname := r.PostFormValue("uri") 217 if fname == "" { 218 w.WriteHeader(http.StatusBadRequest) 219 return json.NewEncoder(w).Encode(nil) 220 } 221 222 body := new(bytes.Buffer) 223 err = json.NewEncoder(body).Encode(rsrv.OpenFileRequest{URI: fname}) 224 if err != nil { 225 return fmt.Errorf("could not encode open-file request: %w", err) 226 } 227 228 req, err := http.NewRequest(http.MethodPost, "/file-open", body) 229 if err != nil { 230 return fmt.Errorf("could not create open-file request: %w", err) 231 } 232 req.AddCookie(cookie) 233 234 ww := newResponseWriter() 235 srv.srv.OpenFile(ww, req) 236 body.Truncate(0) 237 238 if ww.code != http.StatusOK { 239 w.WriteHeader(ww.code) 240 return fmt.Errorf("could not open file %q", fname) 241 } 242 243 nodes, err := srv.nodes(r) 244 if err != nil { 245 return fmt.Errorf("could not create nodes: %w", err) 246 } 247 248 return json.NewEncoder(w).Encode(nodes) 249 } 250 251 func (srv *server) refreshHandle(w http.ResponseWriter, r *http.Request) error { 252 nodes, err := srv.nodes(r) 253 if err != nil { 254 if err == http.ErrNoCookie { 255 return json.NewEncoder(w).Encode(nil) 256 } 257 return err 258 } 259 260 return json.NewEncoder(w).Encode(nodes) 261 } 262 263 func (srv *server) plotHandle(w http.ResponseWriter, r *http.Request) error { 264 cookie, err := r.Cookie(cookieName) 265 if err != nil { 266 return fmt.Errorf("could not retrieve cookie: %w", err) 267 } 268 269 var req plot 270 defer r.Body.Close() 271 272 err = json.NewDecoder(r.Body).Decode(&req) 273 if err != nil { 274 return fmt.Errorf("could not decode plot request: %w", err) 275 } 276 277 cmd := plotRequest{ 278 cookie: cookie, 279 req: req, 280 resp: make(chan plotResponse), 281 } 282 go func() { srv.cmds <- cmd }() 283 timeout := time.NewTimer(30 * time.Minute) 284 defer timeout.Stop() 285 286 select { 287 case resp := <-cmd.resp: 288 if resp.err != nil { 289 return fmt.Errorf("could not process plot request: %w", resp.err) 290 } 291 w.Header().Set("Content-Type", resp.ctype) 292 w.WriteHeader(resp.status) 293 _, err = w.Write(resp.body) 294 return err 295 case <-timeout.C: 296 return fmt.Errorf("plot request timeout") 297 } 298 } 299 300 func (srv *server) process(preq plotRequest) { 301 log.Printf("processing %s uri=%q dir=%q obj=%q vars=%q...", preq.req.Type, preq.req.URI, preq.req.Dir, preq.req.Obj, preq.req.Vars) 302 defer log.Printf("processing %s uri=%q dir=%q obj=%q vars=%q... [done]", preq.req.Type, preq.req.URI, preq.req.Dir, preq.req.Obj, preq.req.Vars) 303 304 var ( 305 h http.HandlerFunc 306 hreq *http.Request 307 req any 308 ep string 309 err error 310 body = new(bytes.Buffer) 311 ) 312 switch pl := preq.req; pl.Type { 313 case plotH1: 314 h = srv.srv.PlotH1 315 ep = "/plot-h1" 316 req = rsrv.PlotH1Request{ 317 URI: pl.URI, 318 Dir: pl.Dir, 319 Obj: pl.Obj, 320 Options: pl.Options, 321 } 322 case plotH2: 323 h = srv.srv.PlotH2 324 ep = "/plot-h2" 325 req = rsrv.PlotH2Request{ 326 URI: pl.URI, 327 Dir: pl.Dir, 328 Obj: pl.Obj, 329 Options: pl.Options, 330 } 331 case plotS2: 332 h = srv.srv.PlotS2 333 ep = "/plot-s2" 334 req = rsrv.PlotS2Request{ 335 URI: pl.URI, 336 Dir: pl.Dir, 337 Obj: pl.Obj, 338 Options: pl.Options, 339 } 340 case plotBranch: 341 h = srv.srv.PlotTree 342 ep = "/plot-branch" 343 req = rsrv.PlotTreeRequest{ 344 URI: pl.URI, 345 Dir: pl.Dir, 346 Obj: pl.Obj, 347 Vars: pl.Vars, 348 Options: pl.Options, 349 } 350 default: 351 preq.resp <- plotResponse{err: fmt.Errorf("root-srv: unknown plot request %q", pl.Type)} 352 return 353 } 354 355 err = json.NewEncoder(body).Encode(req) 356 if err != nil { 357 preq.resp <- plotResponse{err: fmt.Errorf("could not encode %s request: %w", ep, err)} 358 return 359 } 360 361 hreq, err = http.NewRequest(http.MethodPost, ep, body) 362 if err != nil { 363 preq.resp <- plotResponse{err: fmt.Errorf("could not create %s request: %w", ep, err)} 364 return 365 } 366 hreq.AddCookie(preq.cookie) 367 368 w := newResponseWriter() 369 w.code = http.StatusInternalServerError 370 371 h(w, hreq) 372 373 resp := plotResponse{ 374 err: nil, 375 body: w.body.Bytes(), 376 ctype: "application/json", 377 status: w.code, 378 } 379 preq.resp <- resp 380 } 381 382 func (srv *server) nodes(r *http.Request) ([]jsNode, error) { 383 db, err := srv.srv.DB(r) 384 if err != nil { 385 return nil, err 386 } 387 388 var nodes []jsNode 389 uris := db.Files() 390 for _, uri := range uris { 391 err = db.Tx(uri, func(f *riofs.File) error { 392 node, err := fileJsTree(f, uri) 393 if err != nil { 394 return err 395 } 396 nodes = append(nodes, node...) 397 return nil 398 }) 399 if err != nil { 400 return nil, fmt.Errorf("could not build nodes-tree for %q: %w", uri, err) 401 } 402 } 403 404 sort.Sort(jsNodes(nodes)) 405 return nodes, nil 406 } 407 408 func (srv *server) ping(r *http.Request) error { 409 cookie, err := r.Cookie(cookieName) 410 if err != nil { 411 return err 412 } 413 414 req, err := http.NewRequest(http.MethodGet, "/ping", nil) 415 if err != nil { 416 return err 417 } 418 req.AddCookie(cookie) 419 420 ww := newResponseWriter() 421 srv.srv.Ping(ww, req) 422 423 if ww.code != http.StatusOK { 424 return fmt.Errorf("could not ping") 425 } 426 427 return nil 428 }