sourcegraph/cmd/precise-code-intel-api-server/internal/server/handler.go

312 lines
9.1 KiB
Go

package server
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"github.com/gorilla/mux"
"github.com/inconshreveable/log15"
"github.com/sourcegraph/sourcegraph/cmd/precise-code-intel-api-server/internal/api"
)
const DefaultUploadPageSize = 50
func (s *Server) handler() http.Handler {
mux := mux.NewRouter()
mux.Path("/uploads/{id:[0-9]+}").Methods("GET").HandlerFunc(s.handleGetUploadByID)
mux.Path("/uploads/{id:[0-9]+}").Methods("DELETE").HandlerFunc(s.handleDeleteUploadByID)
mux.Path("/uploads/repository/{id:[0-9]+}").Methods("GET").HandlerFunc(s.handleGetUploadsByRepo)
mux.Path("/upload").Methods("POST").HandlerFunc(s.handleEnqueue)
mux.Path("/exists").Methods("GET").HandlerFunc(s.handleExists)
mux.Path("/definitions").Methods("GET").HandlerFunc(s.handleDefinitions)
mux.Path("/references").Methods("GET").HandlerFunc(s.handleReferences)
mux.Path("/hover").Methods("GET").HandlerFunc(s.handleHover)
mux.Path("/uploads").Methods("POST").HandlerFunc(s.handleUploads)
mux.Path("/prune").Methods("POST").HandlerFunc(s.handlePrune)
mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
})
return mux
}
// GET /uploads/{id:[0-9]+}
func (s *Server) handleGetUploadByID(w http.ResponseWriter, r *http.Request) {
upload, exists, err := s.db.GetUploadByID(r.Context(), int(idFromRequest(r)))
if err != nil {
log15.Error("Failed to retrieve upload", "error", err)
http.Error(w, fmt.Sprintf("failed to retrieve upload: %s", err.Error()), http.StatusInternalServerError)
return
}
if !exists {
http.Error(w, "upload not found", http.StatusNotFound)
return
}
writeJSON(w, upload)
}
// DELETE /uploads/{id:[0-9]+}
func (s *Server) handleDeleteUploadByID(w http.ResponseWriter, r *http.Request) {
exists, err := s.db.DeleteUploadByID(r.Context(), int(idFromRequest(r)), getTipCommit)
if err != nil {
log15.Error("Failed to delete upload", "error", err)
http.Error(w, fmt.Sprintf("failed to delete upload: %s", err.Error()), http.StatusInternalServerError)
return
}
if !exists {
http.Error(w, "upload not found", http.StatusNotFound)
return
}
w.WriteHeader(http.StatusNoContent)
}
// GET /uploads/repository/{id:[0-9]+}
func (s *Server) handleGetUploadsByRepo(w http.ResponseWriter, r *http.Request) {
id := int(idFromRequest(r))
limit := getQueryIntDefault(r, "limit", DefaultUploadPageSize)
offset := getQueryInt(r, "offset")
uploads, totalCount, err := s.db.GetUploadsByRepo(
r.Context(),
id,
getQuery(r, "state"),
getQuery(r, "query"),
getQueryBool(r, "visibleAtTip"),
limit,
offset,
)
if err != nil {
log15.Error("Failed to list uploads", "error", err)
http.Error(w, fmt.Sprintf("failed to list uploads: %s", err.Error()), http.StatusInternalServerError)
return
}
if offset+len(uploads) < totalCount {
w.Header().Set("Link", makeNextLink(r.URL, map[string]interface{}{
"limit": limit,
"offset": offset + len(uploads),
}))
}
writeJSON(w, map[string]interface{}{"uploads": uploads, "totalCount": totalCount})
}
// POST /upload
func (s *Server) handleEnqueue(w http.ResponseWriter, r *http.Request) {
f, err := ioutil.TempFile("", "upload-")
if err != nil {
log15.Error("Failed to open target file", "error", err)
http.Error(w, fmt.Sprintf("failed to open target file: %s", err.Error()), http.StatusInternalServerError)
return
}
defer os.Remove(f.Name())
defer f.Close()
if _, err := io.Copy(f, r.Body); err != nil {
log15.Error("Failed to write payload", "error", err)
http.Error(w, fmt.Sprintf("failed to write payload: %s", err.Error()), http.StatusInternalServerError)
return
}
indexerName := getQuery(r, "indexerName")
if indexerName == "" {
if indexerName, err = readIndexerNameFromFile(f); err != nil {
log15.Error("Failed to read indexer name from upload", "error", err)
http.Error(w, fmt.Sprintf("failed to read indexer name from upload: %s", err.Error()), http.StatusInternalServerError)
return
}
}
id, closer, err := s.db.Enqueue(
r.Context(),
getQuery(r, "commit"),
sanitizeRoot(getQuery(r, "root")),
"{}", // TODO(efritz) - write tracing code
getQueryInt(r, "repositoryId"),
indexerName,
)
if err == nil {
err = closer.CloseTx(s.bundleManagerClient.SendUpload(r.Context(), id, f))
}
if err != nil {
log15.Error("Failed to enqueue payload", "error", err)
http.Error(w, fmt.Sprintf("failed to enqueue payload: %s", err.Error()), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusAccepted)
writeJSON(w, map[string]interface{}{"id": id})
}
// GET /exists
func (s *Server) handleExists(w http.ResponseWriter, r *http.Request) {
dumps, err := s.api.FindClosestDumps(
r.Context(),
getQueryInt(r, "repositoryId"),
getQuery(r, "commit"),
getQuery(r, "path"),
)
if err != nil {
log15.Error("Failed to handle exists request", "error", err)
http.Error(w, fmt.Sprintf("failed to handle exists request: %s", err.Error()), http.StatusInternalServerError)
return
}
writeJSON(w, map[string]interface{}{"uploads": dumps})
}
// GET /definitions
func (s *Server) handleDefinitions(w http.ResponseWriter, r *http.Request) {
defs, err := s.api.Definitions(
r.Context(),
getQuery(r, "path"),
getQueryInt(r, "line"),
getQueryInt(r, "character"),
getQueryInt(r, "uploadId"),
)
if err != nil {
if err == api.ErrMissingDump {
http.Error(w, "no such dump", http.StatusNotFound)
return
}
log15.Error("Failed to handle definitions request", "error", err)
http.Error(w, fmt.Sprintf("failed to handle definitions request: %s", err.Error()), http.StatusInternalServerError)
return
}
outers, err := serializeLocations(defs)
if err != nil {
log15.Error("Failed to resolve locations", "error", err)
http.Error(w, fmt.Sprintf("failed to resolve locations: %s", err.Error()), http.StatusInternalServerError)
return
}
writeJSON(w, map[string]interface{}{"locations": outers})
}
// GET /references
func (s *Server) handleReferences(w http.ResponseWriter, r *http.Request) {
cursor, err := api.DecodeOrCreateCursor(
getQuery(r, "path"),
getQueryInt(r, "line"),
getQueryInt(r, "character"),
getQueryInt(r, "uploadId"),
getQuery(r, "rawCursor"),
s.db,
s.bundleManagerClient,
)
if err != nil {
if err == api.ErrMissingDump {
http.Error(w, "no such dump", http.StatusNotFound)
return
}
log15.Error("Failed to prepare cursor", "error", err)
http.Error(w, fmt.Sprintf("failed to prepare cursor: %s", err.Error()), http.StatusInternalServerError)
return
}
locations, newCursor, hasNewCursor, err := s.api.References(
r.Context(),
getQueryInt(r, "repositoryId"),
getQuery(r, "commit"),
getQueryInt(r, "limit"),
cursor,
)
if err != nil {
log15.Error("Failed to handle references request", "error", err)
http.Error(w, fmt.Sprintf("failed to handle references request: %s", err.Error()), http.StatusInternalServerError)
return
}
outers, err := serializeLocations(locations)
if err != nil {
log15.Error("Failed to resolve locations", "error", err)
http.Error(w, fmt.Sprintf("failed to resolve locations: %s", err.Error()), http.StatusInternalServerError)
return
}
if hasNewCursor {
w.Header().Set("Link", makeNextLink(r.URL, map[string]interface{}{
"cursor": api.EncodeCursor(newCursor),
}))
}
writeJSON(w, map[string]interface{}{"locations": outers})
}
// GET /hover
func (s *Server) handleHover(w http.ResponseWriter, r *http.Request) {
text, rn, exists, err := s.api.Hover(
r.Context(),
getQuery(r, "path"),
getQueryInt(r, "line"),
getQueryInt(r, "character"),
getQueryInt(r, "uploadId"),
)
if err != nil {
if err == api.ErrMissingDump {
http.Error(w, "no such dump", http.StatusNotFound)
return
}
log15.Error("Failed to handle hover request", "error", err)
http.Error(w, fmt.Sprintf("failed to handle hover request: %s", err.Error()), http.StatusInternalServerError)
return
}
if !exists {
writeJSON(w, nil)
} else {
writeJSON(w, map[string]interface{}{"text": text, "range": rn})
}
}
// POST /uploads
func (s *Server) handleUploads(w http.ResponseWriter, r *http.Request) {
payload := struct {
IDs []int `json:"ids"`
}{}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
log15.Error("Failed to read request body", "error", err)
http.Error(w, fmt.Sprintf("failed to read request body: %s", err.Error()), http.StatusInternalServerError)
return
}
states, err := s.db.GetStates(r.Context(), payload.IDs)
if err != nil {
log15.Error("Failed to retrieve upload states", "error", err)
http.Error(w, fmt.Sprintf("failed to retrieve upload states: %s", err.Error()), http.StatusInternalServerError)
return
}
pairs := []interface{}{}
for k, v := range states {
pairs = append(pairs, []interface{}{k, v})
}
writeJSON(w, map[string]interface{}{"type": "map", "value": pairs})
}
// POST /prune
func (s *Server) handlePrune(w http.ResponseWriter, r *http.Request) {
id, prunable, err := s.db.DeleteOldestDump(r.Context())
if err != nil {
log15.Error("Failed to prune upload", "error", err)
http.Error(w, fmt.Sprintf("failed to prune upload: %s", err.Error()), http.StatusInternalServerError)
return
}
if !prunable {
writeJSON(w, nil)
} else {
writeJSON(w, map[string]interface{}{"id": id})
}
}