1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16
17 package handlers
18
19 import (
20 "fmt"
21 "log"
22 "net/http"
23 "strconv"
24 "time"
25
26 "perkeep.org/internal/httputil"
27 "perkeep.org/pkg/blob"
28 "perkeep.org/pkg/blobserver"
29 "perkeep.org/pkg/blobserver/protocol"
30 )
31
32 func CreateStatHandler(storage blobserver.BlobStatter) http.Handler {
33 return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
34 handleStat(rw, req, storage)
35 })
36 }
37
38 const maxStatBlobs = 1000
39
40 func handleStat(rw http.ResponseWriter, req *http.Request, storage blobserver.BlobStatter) {
41 res := new(protocol.StatResponse)
42
43 if configer, ok := storage.(blobserver.Configer); ok {
44 if conf := configer.Config(); conf != nil {
45 res.CanLongPoll = conf.CanLongPoll
46 }
47 }
48
49 needStat := map[blob.Ref]bool{}
50
51 switch req.Method {
52 case "POST", "GET", "HEAD":
53 default:
54 httputil.BadRequestError(rw, "Invalid method.")
55 return
56 }
57 camliVersion := req.FormValue("camliversion")
58 if camliVersion == "" {
59 httputil.BadRequestError(rw, "No camliversion")
60 return
61 }
62 n := 0
63 for {
64 n++
65 key := fmt.Sprintf("blob%v", n)
66 value := req.FormValue(key)
67 if value == "" {
68 n--
69 break
70 }
71 if n > maxStatBlobs {
72 httputil.BadRequestError(rw, "Too many stat blob checks")
73 return
74 }
75 ref, ok := blob.Parse(value)
76 if !ok {
77 httputil.BadRequestError(rw, "Bogus blobref for key "+key)
78 return
79 }
80 needStat[ref] = true
81 }
82
83 waitSeconds := 0
84 if waitStr := req.FormValue("maxwaitsec"); waitStr != "" {
85 waitSeconds, _ = strconv.Atoi(waitStr)
86 switch {
87 case waitSeconds < 0:
88 waitSeconds = 0
89 case waitSeconds > 30:
90
91
92
93 waitSeconds = 30
94 }
95 }
96
97 deadline := time.Now().Add(time.Duration(waitSeconds) * time.Second)
98
99 toStat := make([]blob.Ref, 0, len(needStat))
100 buildToStat := func() {
101 toStat = toStat[:0]
102 for br := range needStat {
103 toStat = append(toStat, br)
104 }
105 }
106
107 buildToStat()
108 for len(needStat) > 0 {
109 err := storage.StatBlobs(req.Context(), toStat, func(sb blob.SizedRef) error {
110 res.Stat = append(res.Stat, sb)
111 delete(needStat, sb.Ref)
112 return nil
113 })
114 if err != nil {
115 log.Printf("Stat error: %v", err)
116 rw.WriteHeader(http.StatusInternalServerError)
117 return
118 }
119 if len(needStat) == 0 || waitSeconds == 0 || time.Now().After(deadline) {
120 break
121 }
122 buildToStat()
123 blobserver.WaitForBlob(storage, deadline, toStat)
124 }
125
126 httputil.ReturnJSON(rw, res)
127 }