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  }