github.com/janelia-flyem/dvid@v1.0.0/datatype/annotation/handlers.go (about) 1 package annotation 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "net/http" 9 "strconv" 10 "strings" 11 12 "github.com/janelia-flyem/dvid/datastore" 13 "github.com/janelia-flyem/dvid/dvid" 14 "github.com/janelia-flyem/dvid/server" 15 "github.com/janelia-flyem/dvid/storage" 16 ) 17 18 // DoRPC acts as a switchboard for RPC commands. 19 func (d *Data) DoRPC(req datastore.Request, reply *datastore.Response) error { 20 switch req.TypeCommand() { 21 case "reload": 22 var uuidStr, dataName string 23 if _, err := req.FilenameArgs(1, &uuidStr, &dataName); err != nil { 24 return err 25 } 26 uuid, v, err := datastore.MatchingUUID(uuidStr) 27 if err != nil { 28 return err 29 } 30 if err = datastore.AddToNodeLog(uuid, []string{req.Command.String()}); err != nil { 31 return err 32 } 33 var inMemory, check bool 34 setting, found := req.Setting("inmemory") 35 if !found || setting != "false" { 36 inMemory = true 37 } 38 setting, found = req.Setting("check") 39 if found && setting == "true" { 40 check = true 41 } 42 ctx := datastore.NewVersionedCtx(d, v) 43 d.RecreateDenormalizations(ctx, inMemory, check) 44 reply.Text = fmt.Sprintf("Asynchronously checking and restoring label and tag denormalizations for annotation %q\n", d.DataName()) 45 return nil 46 47 default: 48 return fmt.Errorf("unknown command. Data type %q [%s] does not support %q command", 49 d.DataName(), d.TypeName(), req.TypeCommand()) 50 } 51 } 52 53 // ServeHTTP handles all incoming HTTP requests for this data. 54 func (d *Data) ServeHTTP(uuid dvid.UUID, ctx *datastore.VersionedCtx, w http.ResponseWriter, r *http.Request) (activity map[string]interface{}) { 55 timedLog := dvid.NewTimeLog() 56 // versionID := ctx.VersionID() 57 58 // Get the action (GET, POST) 59 action := strings.ToLower(r.Method) 60 d.RLock() 61 if d.denormOngoing && action == "post" { 62 d.RUnlock() 63 server.BadRequest(w, r, "cannot run POST commands while %q instance is being reloaded", d.DataName()) 64 return 65 } 66 d.RUnlock() 67 68 // Break URL request into arguments 69 url := r.URL.Path[len(server.WebAPIPath):] 70 parts := strings.Split(url, "/") 71 if len(parts[len(parts)-1]) == 0 { 72 parts = parts[:len(parts)-1] 73 } 74 75 // Handle POST on data -> setting of configuration 76 if len(parts) == 3 && action == "put" { 77 config, err := server.DecodeJSON(r) 78 if err != nil { 79 server.BadRequest(w, r, err) 80 return 81 } 82 if err := d.ModifyConfig(config); err != nil { 83 server.BadRequest(w, r, err) 84 return 85 } 86 if err := datastore.SaveDataByUUID(uuid, d); err != nil { 87 server.BadRequest(w, r, err) 88 return 89 } 90 fmt.Fprintf(w, "Changed %q based on received configuration:\n%s\n", d.DataName(), config) 91 return 92 } 93 94 if len(parts) < 4 { 95 server.BadRequest(w, r, "Incomplete API request") 96 return 97 } 98 99 // Process help and info. 100 switch parts[3] { 101 case "help": 102 w.Header().Set("Content-Type", "text/plain") 103 fmt.Fprintln(w, dtype.Help()) 104 105 case "info": 106 jsonBytes, err := d.MarshalJSON() 107 if err != nil { 108 server.BadRequest(w, r, err) 109 return 110 } 111 w.Header().Set("Content-Type", "application/json") 112 fmt.Fprint(w, string(jsonBytes)) 113 114 case "sync": 115 if action != "post" { 116 server.BadRequest(w, r, "Only POST allowed to sync endpoint") 117 return 118 } 119 replace := r.URL.Query().Get("replace") == "true" 120 if err := datastore.SetSyncByJSON(d, uuid, replace, r.Body); err != nil { 121 server.BadRequest(w, r, err) 122 return 123 } 124 d.cachedBlockSize = nil 125 126 case "tags": 127 if action == "post" { 128 replace := r.URL.Query().Get("replace") == "true" 129 if err := datastore.SetTagsByJSON(d, uuid, replace, r.Body); err != nil { 130 server.BadRequest(w, r, err) 131 return 132 } 133 } else { 134 jsonBytes, err := d.MarshalJSONTags() 135 if err != nil { 136 server.BadRequest(w, r, err) 137 return 138 } 139 w.Header().Set("Content-Type", "application/json") 140 fmt.Fprint(w, string(jsonBytes)) 141 } 142 143 case "labels": 144 if action != "post" { 145 server.BadRequest(w, r, "Only POST action is available on 'labels' endpoint.") 146 return 147 } 148 if len(parts) >= 5 { 149 server.BadRequest(w, r, "Extraneous arguments after POST /labels endpoint.") 150 return 151 } 152 if err := d.handlePostLabels(ctx, w, r.Body); err != nil { 153 server.BadRequest(w, r, err) 154 return 155 } 156 157 case "label": 158 if action != "get" { 159 server.BadRequest(w, r, "Only GET action is available on 'label' endpoint.") 160 return 161 } 162 if len(parts) < 5 { 163 server.BadRequest(w, r, "Must include label after 'label' endpoint.") 164 return 165 } 166 label, err := strconv.ParseUint(parts[4], 10, 64) 167 if err != nil { 168 server.BadRequest(w, r, err) 169 return 170 } 171 if label == 0 { 172 server.BadRequest(w, r, "Label 0 is protected background value and cannot be used for query.") 173 return 174 } 175 queryStrings := r.URL.Query() 176 jsonBytes, err := d.GetLabelJSON(ctx, label, queryStrings.Get("relationships") == "true") 177 if err != nil { 178 server.BadRequest(w, r, err) 179 return 180 } 181 w.Header().Set("Content-type", "application/json") 182 if _, err := w.Write(jsonBytes); err != nil { 183 server.BadRequest(w, r, err) 184 return 185 } 186 timedLog.Infof("HTTP %s: get synaptic elements for label %d (%s)", r.Method, label, r.URL) 187 188 case "tag": 189 if action != "get" { 190 server.BadRequest(w, r, "Only GET action is available on 'tag' endpoint.") 191 return 192 } 193 if len(parts) < 5 { 194 server.BadRequest(w, r, "Must include tag string after 'tag' endpoint.") 195 return 196 } 197 tag := Tag(parts[4]) 198 queryStrings := r.URL.Query() 199 jsonBytes, err := d.GetTagJSON(ctx, tag, queryStrings.Get("relationships") == "true") 200 if err != nil { 201 server.BadRequest(w, r, err) 202 return 203 } 204 w.Header().Set("Content-type", "application/json") 205 if _, err := w.Write(jsonBytes); err != nil { 206 server.BadRequest(w, r, err) 207 return 208 } 209 timedLog.Infof("HTTP %s: get synaptic elements for tag %s (%s)", r.Method, tag, r.URL) 210 211 case "roi": 212 switch action { 213 case "get": 214 // GET <api URL>/node/<UUID>/<data name>/roi/<ROI specification> 215 if len(parts) < 5 { 216 server.BadRequest(w, r, "Expect ROI specification to follow 'roi' in GET request") 217 return 218 } 219 roiParts := strings.Split(parts[4], ",") 220 var roiSpec string 221 roiSpec = "roi:" 222 switch len(roiParts) { 223 case 1: 224 roiSpec += roiParts[0] + "," + string(uuid) 225 case 2: 226 roiSpec += parts[4] 227 default: 228 server.BadRequest(w, r, "Bad ROI specification: %q", parts[4]) 229 return 230 } 231 elems, err := d.GetROISynapses(ctx, storage.FilterSpec(roiSpec)) 232 if err != nil { 233 server.BadRequest(w, r, err) 234 return 235 } 236 w.Header().Set("Content-type", "application/json") 237 jsonBytes, err := json.Marshal(elems) 238 if err != nil { 239 server.BadRequest(w, r, err) 240 return 241 } 242 if _, err := w.Write(jsonBytes); err != nil { 243 server.BadRequest(w, r, err) 244 return 245 } 246 timedLog.Infof("HTTP %s: synapse elements in ROI (%s) (%s)", r.Method, parts[4], r.URL) 247 248 default: 249 server.BadRequest(w, r, "Only GET action is available on 'roi' endpoint.") 250 return 251 } 252 253 case "all-elements": 254 switch action { 255 case "get": 256 // GET <api URL>/node/<UUID>/<data name>/all-elements 257 if len(parts) > 4 { 258 server.BadRequest(w, r, "Do not expect additional parameters after 'all-elements' in GET request") 259 return 260 } 261 w.Header().Set("Content-type", "application/json") 262 if err := d.StreamAll(ctx, w); err != nil { 263 server.BadRequest(w, r, err) 264 return 265 } 266 timedLog.Infof("HTTP %s: all elements (%s)", r.Method, r.URL) 267 268 default: 269 server.BadRequest(w, r, "Only GET is available on 'all-elements' endpoint.") 270 return 271 } 272 273 case "scan": 274 switch action { 275 case "get": 276 // GET <api URL>/node/<UUID>/<data name>/scan 277 if len(parts) > 4 { 278 server.BadRequest(w, r, "Do not expect additional parameters after 'scan' in GET request") 279 return 280 } 281 byCoord := r.URL.Query().Get("byCoord") == "true" 282 keysOnly := r.URL.Query().Get("keysOnly") == "true" 283 w.Header().Set("Content-type", "application/json") 284 if err := d.scan(ctx, w, byCoord, keysOnly); err != nil { 285 server.BadRequest(w, r, err) 286 return 287 } 288 timedLog.Infof("HTTP %s: scan annotations %q (%s)", r.Method, d.DataName(), r.URL) 289 290 default: 291 server.BadRequest(w, r, "Only GET is available on 'scan' endpoint.") 292 return 293 } 294 295 case "blocks": 296 switch action { 297 case "get": 298 // GET <api URL>/node/<UUID>/<data name>/blocks/<size>/<offset> 299 if len(parts) < 6 { 300 server.BadRequest(w, r, "Expect size and offset to follow 'blocks' in GET request") 301 return 302 } 303 sizeStr, offsetStr := parts[4], parts[5] 304 ext3d, err := dvid.NewExtents3dFromStrings(offsetStr, sizeStr, "_") 305 if err != nil { 306 server.BadRequest(w, r, err) 307 return 308 } 309 w.Header().Set("Content-type", "application/json") 310 if err = d.StreamBlocks(ctx, w, ext3d); err != nil { 311 server.BadRequest(w, r, err) 312 return 313 } 314 timedLog.Infof("HTTP %s: synapse elements in blocks intersecting subvolume (size %s, offset %s) (%s)", r.Method, sizeStr, offsetStr, r.URL) 315 316 case "post": 317 // POST <api URL>/node/<UUID>/<data name>/blocks 318 kafkaOff := r.URL.Query().Get("kafkalog") == "off" 319 numBlocks, err := d.StoreBlocks(ctx, r.Body, kafkaOff) 320 if err != nil { 321 server.BadRequest(w, r, err) 322 return 323 } 324 timedLog.Infof("HTTP %s: posted %d synapse blocks (%s)", r.Method, numBlocks, r.URL) 325 326 default: 327 server.BadRequest(w, r, "Only GET/POST action is available on 'blocks' endpoint.") 328 return 329 } 330 331 case "elements": 332 switch action { 333 case "get": 334 // GET <api URL>/node/<UUID>/<data name>/elements/<size>/<offset> 335 if len(parts) < 6 { 336 server.BadRequest(w, r, "Expect size and offset to follow 'elements' in GET request") 337 return 338 } 339 sizeStr, offsetStr := parts[4], parts[5] 340 ext3d, err := dvid.NewExtents3dFromStrings(offsetStr, sizeStr, "_") 341 if err != nil { 342 server.BadRequest(w, r, err) 343 return 344 } 345 elems, err := d.GetRegionSynapses(ctx, ext3d) 346 if err != nil { 347 server.BadRequest(w, r, err) 348 return 349 } 350 w.Header().Set("Content-type", "application/json") 351 jsonBytes, err := json.Marshal(elems) 352 if err != nil { 353 server.BadRequest(w, r, err) 354 return 355 } 356 if _, err := w.Write(jsonBytes); err != nil { 357 server.BadRequest(w, r, err) 358 return 359 } 360 timedLog.Infof("HTTP %s: synapse elements in subvolume (size %s, offset %s) (%s)", r.Method, sizeStr, offsetStr, r.URL) 361 362 case "post": 363 kafkaOff := r.URL.Query().Get("kafkalog") == "off" 364 if err := d.StoreElements(ctx, r.Body, kafkaOff); err != nil { 365 server.BadRequest(w, r, err) 366 return 367 } 368 default: 369 server.BadRequest(w, r, "Only GET or POST action is available on 'elements' endpoint.") 370 return 371 } 372 373 case "element": 374 // DELETE <api URL>/node/<UUID>/<data name>/element/<coord> 375 if action != "delete" { 376 server.BadRequest(w, r, "Only DELETE action is available on 'element' endpoint.") 377 return 378 } 379 if len(parts) < 5 { 380 server.BadRequest(w, r, "Must include coordinate after DELETE on 'element' endpoint.") 381 return 382 } 383 pt, err := dvid.StringToPoint3d(parts[4], "_") 384 if err != nil { 385 server.BadRequest(w, r, err) 386 return 387 } 388 kafkaOff := r.URL.Query().Get("kafkalog") == "off" 389 if err := d.DeleteElement(ctx, pt, kafkaOff); err != nil { 390 server.BadRequest(w, r, err) 391 return 392 } 393 timedLog.Infof("HTTP %s: delete synaptic element at %s (%s)", r.Method, pt, r.URL) 394 395 case "move": 396 // POST <api URL>/node/<UUID>/<data name>/move/<from_coord>/<to_coord> 397 if action != "post" { 398 server.BadRequest(w, r, "Only POST action is available on 'move' endpoint.") 399 return 400 } 401 if len(parts) < 6 { 402 server.BadRequest(w, r, "Must include 'from' and 'to' coordinate after 'move' endpoint.") 403 return 404 } 405 fromPt, err := dvid.StringToPoint3d(parts[4], "_") 406 if err != nil { 407 server.BadRequest(w, r, err) 408 return 409 } 410 toPt, err := dvid.StringToPoint3d(parts[5], "_") 411 if err != nil { 412 server.BadRequest(w, r, err) 413 return 414 } 415 kafkaOff := r.URL.Query().Get("kafkalog") == "off" 416 if err := d.MoveElement(ctx, fromPt, toPt, kafkaOff); err != nil { 417 server.BadRequest(w, r, err) 418 return 419 } 420 timedLog.Infof("HTTP %s: move synaptic element from %s to %s (%s)", r.Method, fromPt, toPt, r.URL) 421 422 case "reload": 423 // POST <api URL>/node/<UUID>/<data name>/reload 424 if action != "post" { 425 server.BadRequest(w, r, "Only POST action is available on 'reload' endpoint.") 426 return 427 } 428 inMemory := !(r.URL.Query().Get("inmemory") == "false") 429 check := r.URL.Query().Get("check") == "true" 430 d.RecreateDenormalizations(ctx, inMemory, check) 431 432 default: 433 server.BadAPIRequest(w, r, d) 434 } 435 return 436 } 437 438 type labelsJSONStrs map[string]string 439 440 func (d *Data) handlePostLabels(ctx *datastore.VersionedCtx, w http.ResponseWriter, r io.Reader) error { 441 timedLog := dvid.NewTimeLog() 442 443 store, err := datastore.GetOrderedKeyValueDB(d) 444 if err != nil { 445 return err 446 } 447 jsonBytes, err := ioutil.ReadAll(r) 448 if err != nil { 449 return err 450 } 451 var data labelsJSONStrs 452 if err := json.Unmarshal(jsonBytes, &data); err != nil { 453 return err 454 } 455 for k, v := range data { 456 label, err := strconv.ParseUint(k, 10, 64) 457 if err != nil { 458 return err 459 } 460 if label == 0 { 461 continue 462 } 463 tk := NewLabelTKey(label) 464 if err := store.Put(ctx, tk, []byte(v)); err != nil { 465 return err 466 } 467 } 468 timedLog.Infof("HTTP POST: set %d labels (%s)", len(data), ctx) 469 return nil 470 }