fork of https://github.com/sourcegraph/zoekt
0

Configure Feed

Select the types of activity you want to include in your feed.

1package json 2 3import ( 4 "context" 5 "encoding/json" 6 "net/http" 7 "time" 8 9 "github.com/sourcegraph/zoekt" 10 "github.com/sourcegraph/zoekt/query" 11) 12 13// defaultTimeout is the maximum amount of time a search request should 14// take. This is the same default used by Sourcegraph. 15const defaultTimeout = 20 * time.Second 16 17func JSONServer(searcher zoekt.Searcher) http.Handler { 18 s := jsonSearcher{searcher} 19 mux := http.NewServeMux() 20 mux.HandleFunc("/search", s.jsonSearch) 21 mux.HandleFunc("/list", s.jsonList) 22 return mux 23} 24 25type jsonSearcher struct { 26 Searcher zoekt.Searcher 27} 28 29type jsonSearchArgs struct { 30 Q string 31 RepoIDs *[]uint32 32 Opts *zoekt.SearchOptions 33} 34 35type jsonSearchReply struct { 36 Result *zoekt.SearchResult 37} 38 39type jsonListArgs struct { 40 Q string 41 Opts *zoekt.ListOptions 42} 43 44type jsonListReply struct { 45 List *zoekt.RepoList 46} 47 48func (s *jsonSearcher) jsonSearch(w http.ResponseWriter, req *http.Request) { 49 ctx := req.Context() 50 w.Header().Add("Content-Type", "application/json") 51 52 if req.Method != "POST" { 53 jsonError(w, http.StatusMethodNotAllowed, "Only POST is supported") 54 return 55 } 56 57 searchArgs := jsonSearchArgs{} 58 err := json.NewDecoder(req.Body).Decode(&searchArgs) 59 if err != nil { 60 jsonError(w, http.StatusBadRequest, err.Error()) 61 return 62 } 63 if searchArgs.Q == "" { 64 jsonError(w, http.StatusBadRequest, "missing query") 65 return 66 } 67 if searchArgs.Opts == nil { 68 searchArgs.Opts = &zoekt.SearchOptions{} 69 } 70 71 q, err := query.Parse(searchArgs.Q) 72 if err != nil { 73 jsonError(w, http.StatusBadRequest, err.Error()) 74 return 75 } 76 77 if searchArgs.RepoIDs != nil { 78 q = query.NewAnd(q, query.NewRepoIDs(*searchArgs.RepoIDs...)) 79 } 80 81 // Set a timeout if the user hasn't specified one. 82 if searchArgs.Opts.MaxWallTime == 0 { 83 var cancel context.CancelFunc 84 ctx, cancel = context.WithTimeout(ctx, defaultTimeout) 85 defer cancel() 86 } 87 88 if err := CalculateDefaultSearchLimits(ctx, q, s.Searcher, searchArgs.Opts); err != nil { 89 jsonError(w, http.StatusInternalServerError, err.Error()) 90 return 91 } 92 93 searchResult, err := s.Searcher.Search(ctx, q, searchArgs.Opts) 94 if err != nil { 95 jsonError(w, http.StatusInternalServerError, err.Error()) 96 return 97 } 98 99 err = json.NewEncoder(w).Encode(jsonSearchReply{searchResult}) 100 if err != nil { 101 jsonError(w, http.StatusInternalServerError, err.Error()) 102 return 103 } 104} 105 106func jsonError(w http.ResponseWriter, statusCode int, err string) { 107 w.WriteHeader(statusCode) 108 json.NewEncoder(w).Encode(struct{ Error string }{Error: err}) 109} 110 111// Calculates and sets heuristic defaults on opts for various upper bounds on 112// the number of matches when searching, if none are already specified. The 113// defaults are derived from opts.MaxDocDisplayCount, so if none is set, there 114// is no calculation to do. 115func CalculateDefaultSearchLimits(ctx context.Context, 116 q query.Q, 117 searcher zoekt.Searcher, 118 opts *zoekt.SearchOptions, 119) error { 120 if opts.MaxDocDisplayCount == 0 || opts.ShardMaxMatchCount != 0 { 121 return nil 122 } 123 124 maxResultDocs := opts.MaxDocDisplayCount 125 // This is a special mode of Search that _only_ calculates ShardFilesConsidered and bails ASAP. 126 if result, err := searcher.Search(ctx, q, &zoekt.SearchOptions{EstimateDocCount: true}); err != nil { 127 return err 128 } else if numdocs := result.ShardFilesConsidered; numdocs > 10000 { 129 // If the search touches many shards and many files, we 130 // have to limit the number of matches. This setting 131 // is based on the number of documents eligible after 132 // considering reponames, so large repos (both 133 // android, chromium are about 500k files) aren't 134 // covered fairly. 135 136 // 10k docs, 50 maxResultDocs -> max match = (250 + 250 / 10) 137 opts.ShardMaxMatchCount = maxResultDocs*5 + (5*maxResultDocs)/(numdocs/1000) 138 } else { 139 // Virtually no limits for a small corpus. 140 n := numdocs + maxResultDocs*100 141 opts.ShardMaxMatchCount = n 142 opts.TotalMaxMatchCount = n 143 } 144 145 return nil 146} 147 148func (s *jsonSearcher) jsonList(w http.ResponseWriter, req *http.Request) { 149 w.Header().Add("Content-Type", "application/json") 150 151 if req.Method != "POST" { 152 jsonError(w, http.StatusMethodNotAllowed, "Only POST is supported") 153 return 154 } 155 156 listArgs := jsonListArgs{} 157 err := json.NewDecoder(req.Body).Decode(&listArgs) 158 if err != nil { 159 jsonError(w, http.StatusBadRequest, err.Error()) 160 return 161 } 162 163 query, err := query.Parse(listArgs.Q) 164 if err != nil { 165 jsonError(w, http.StatusBadRequest, err.Error()) 166 return 167 } 168 169 listResult, err := s.Searcher.List(req.Context(), query, listArgs.Opts) 170 if err != nil { 171 jsonError(w, http.StatusInternalServerError, err.Error()) 172 return 173 } 174 175 err = json.NewEncoder(w).Encode(jsonListReply{listResult}) 176 if err != nil { 177 jsonError(w, http.StatusInternalServerError, err.Error()) 178 return 179 } 180}