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  }