func handleEnumerateBlobs(conn http.ResponseWriter, req *http.Request, storage blobserver.BlobEnumerator) { // Potential input parameters formValueLimit := req.FormValue("limit") formValueMaxWaitSec := req.FormValue("maxwaitsec") formValueAfter := req.FormValue("after") maxEnumerate := defaultMaxEnumerate if config, ok := storage.(blobserver.MaxEnumerateConfig); ok { maxEnumerate = config.MaxEnumerate() - 1 // Since we'll add one below. } limit := defaultEnumerateSize if formValueLimit != "" { n, err := strconv.ParseUint(formValueLimit, 10, 32) if err != nil || n > uint64(maxEnumerate) { limit = maxEnumerate } else { limit = int(n) } } waitSeconds := 0 if formValueMaxWaitSec != "" { waitSeconds, _ = strconv.Atoi(formValueMaxWaitSec) if waitSeconds != 0 && formValueAfter != "" { conn.WriteHeader(http.StatusBadRequest) fmt.Fprintf(conn, errMsgMaxWaitSecWithAfter) return } switch { case waitSeconds < 0: waitSeconds = 0 case waitSeconds > 30: // TODO: don't hard-code 30. push this up into a blobserver interface // for getting the configuration of the server (ultimately a flag in // in the binary) waitSeconds = 30 } } conn.Header().Set("Content-Type", "text/javascript; charset=utf-8") fmt.Fprintf(conn, "{\n \"blobs\": [\n") loop := true needsComma := false deadline := time.Now().Add(time.Duration(waitSeconds) * time.Second) after := "" for loop && (waitSeconds == 0 || time.Now().After(deadline)) { if waitSeconds == 0 { loop = false } blobch := make(chan blob.SizedRef, 100) resultch := make(chan error, 1) go func() { resultch <- storage.EnumerateBlobs(blobch, formValueAfter, limit+1) }() endsReached := 0 gotBlobs := 0 for endsReached < 2 { select { case sb, ok := <-blobch: if !ok { endsReached++ if gotBlobs <= limit { after = "" } continue } gotBlobs++ loop = false if gotBlobs > limit { // We requested one more from storage than the user asked for. // Now we know to return a "continueAfter" response key. // But we don't return this blob. continue } blobName := sb.Ref.String() if needsComma { fmt.Fprintf(conn, ",\n") } fmt.Fprintf(conn, " {\"blobRef\": \"%s\", \"size\": %d}", blobName, sb.Size) after = blobName needsComma = true case err := <-resultch: if err != nil { log.Printf("Error during enumerate: %v", err) fmt.Fprintf(conn, "{{{ SERVER ERROR }}}") return } endsReached++ } } if loop { blobserver.WaitForBlob(storage, deadline, nil) } } fmt.Fprintf(conn, "\n ]") if after != "" { fmt.Fprintf(conn, ",\n \"continueAfter\": \"%s\"", after) } const longPollSupported = true if longPollSupported { fmt.Fprintf(conn, ",\n \"canLongPoll\": true") } fmt.Fprintf(conn, "\n}\n") }
func handleStat(conn http.ResponseWriter, req *http.Request, storage blobserver.BlobStatter) { res := new(protocol.StatResponse) if configer, ok := storage.(blobserver.Configer); ok { if conf := configer.Config(); conf != nil { res.CanLongPoll = conf.CanLongPoll } } needStat := map[blob.Ref]bool{} switch req.Method { case "POST": fallthrough case "GET", "HEAD": camliVersion := req.FormValue("camliversion") if camliVersion == "" { httputil.BadRequestError(conn, "No camliversion") return } n := 0 for { n++ key := fmt.Sprintf("blob%v", n) value := req.FormValue(key) if value == "" { n-- break } if n > maxStatBlobs { httputil.BadRequestError(conn, "Too many stat blob checks") return } ref, ok := blob.Parse(value) if !ok { httputil.BadRequestError(conn, "Bogus blobref for key "+key) return } needStat[ref] = true } default: httputil.BadRequestError(conn, "Invalid method.") return } waitSeconds := 0 if waitStr := req.FormValue("maxwaitsec"); waitStr != "" { waitSeconds, _ = strconv.Atoi(waitStr) switch { case waitSeconds < 0: waitSeconds = 0 case waitSeconds > 30: // TODO: don't hard-code 30. push this up into a blobserver interface // for getting the configuration of the server (ultimately a flag in // in the binary) waitSeconds = 30 } } deadline := time.Now().Add(time.Duration(waitSeconds) * time.Second) toStat := make([]blob.Ref, 0, len(needStat)) buildToStat := func() { toStat = toStat[:0] for br := range needStat { toStat = append(toStat, br) } } for len(needStat) > 0 { buildToStat() blobch := make(chan blob.SizedRef) resultch := make(chan error, 1) go func() { err := storage.StatBlobs(blobch, toStat) close(blobch) resultch <- err }() for sb := range blobch { res.Stat = append(res.Stat, &protocol.RefAndSize{ Ref: sb.Ref, Size: uint32(sb.Size), }) delete(needStat, sb.Ref) } err := <-resultch if err != nil { log.Printf("Stat error: %v", err) conn.WriteHeader(http.StatusInternalServerError) return } if len(needStat) == 0 || waitSeconds == 0 || time.Now().After(deadline) { break } buildToStat() blobserver.WaitForBlob(storage, deadline, toStat) } httputil.ReturnJSON(conn, res) }