github.com/go-kivik/kivik/v4@v4.3.2/x/server/server.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 // use this file except in compliance with the License. You may obtain a copy of 3 // the License at 4 // 5 // http://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 // License for the specific language governing permissions and limitations under 11 // the License. 12 13 //go:build !js 14 15 package server 16 17 import ( 18 "bytes" 19 "context" 20 "encoding/json" 21 "errors" 22 "fmt" 23 "io" 24 "net/http" 25 "strings" 26 "sync" 27 28 "github.com/go-chi/chi/v5" 29 "github.com/monoculum/formam/v3" 30 "gitlab.com/flimzy/httpe" 31 32 "github.com/go-kivik/kivik/v4" 33 "github.com/go-kivik/kivik/v4/x/server/auth" 34 "github.com/go-kivik/kivik/v4/x/server/config" 35 ) 36 37 func init() { 38 chi.RegisterMethod("COPY") 39 } 40 41 // Server is a server instance. 42 type Server struct { 43 mux *chi.Mux 44 client *kivik.Client 45 formDecoder *formam.Decoder 46 userStores userStores 47 authFuncs []auth.AuthenticateFunc 48 config config.Config 49 50 // This is set the first time a sequential UUID is generated, and is used 51 // for all subsequent sequential UUIDs. 52 sequentialMU sync.Mutex 53 sequentialUUIDPrefix string 54 sequentialUUIDMonotonicID int32 55 } 56 57 func e(h httpe.HandlerWithError) httpe.HandlerFunc { 58 return httpe.ToHandler(h).ServeHTTP 59 } 60 61 // New instantiates a new server instance. 62 func New(client *kivik.Client, options ...Option) *Server { 63 s := &Server{ 64 mux: chi.NewMux(), 65 client: client, 66 formDecoder: formam.NewDecoder(&formam.DecoderOptions{ 67 TagName: "form", 68 IgnoreUnknownKeys: true, 69 }), 70 config: config.Default(), 71 } 72 for _, option := range options { 73 option.apply(s) 74 } 75 s.routes(s.mux) 76 return s 77 } 78 79 func (s *Server) routes(mux *chi.Mux) { 80 mux.Use( 81 GetHead, 82 httpe.ToMiddleware(s.handleErrors), 83 ) 84 85 auth := mux.With( 86 httpe.ToMiddleware(s.authMiddleware), 87 ) 88 89 admin := auth.With( 90 httpe.ToMiddleware(adminRequired), 91 ) 92 auth.Get("/", e(s.root())) 93 admin.Get("/_active_tasks", e(s.activeTasks())) 94 admin.Get("/_all_dbs", e(s.allDBs())) 95 auth.Get("/_dbs_info", e(s.allDBsStats())) 96 auth.Post("/_dbs_info", e(s.dbsStats())) 97 admin.Get("/_cluster_setup", e(s.clusterStatus())) 98 admin.Post("/_cluster_setup", e(s.clusterSetup())) 99 admin.Get("/_db_updates", e(s.dbUpdates())) 100 auth.Get("/_membership", e(s.notImplemented())) 101 auth.Post("/_replicate", e(s.notImplemented())) 102 auth.Get("/_scheduler/jobs", e(s.notImplemented())) 103 auth.Get("/_scheduler/docs", e(s.notImplemented())) 104 auth.Get("/_scheduler/docs/{replicator_db}", e(s.notImplemented())) 105 auth.Get("/_scheduler/docs/{replicator_db}/{doc_id}", e(s.notImplemented())) 106 auth.Get("/_node/{node-name}", e(s.notImplemented())) 107 auth.Get("/_node/{node-name}/_stats", e(s.notImplemented())) 108 auth.Get("/_node/{node-name}/_prometheus", e(s.notImplemented())) 109 auth.Get("/_node/{node-name}/_system", e(s.notImplemented())) 110 admin.Post("/_node/{node-name}/_restart", e(s.notImplemented())) 111 auth.Get("/_node/{node-name}/_versions", e(s.notImplemented())) 112 auth.Post("/_search_analyze", e(s.notImplemented())) 113 auth.Get("/_utils", e(s.notImplemented())) 114 auth.Get("/_utils/", e(s.notImplemented())) 115 mux.Get("/_up", e(s.up())) 116 mux.Get("/_uuids", e(s.uuids())) 117 mux.Get("/favicon.ico", e(s.notImplemented())) 118 auth.Get("/_reshard", e(s.notImplemented())) 119 auth.Get("/_reshard/state", e(s.notImplemented())) 120 auth.Put("/_reshard/state", e(s.notImplemented())) 121 auth.Get("/_reshard/jobs", e(s.notImplemented())) 122 auth.Get("/_reshard/jobs/{jobid}", e(s.notImplemented())) 123 auth.Post("/_reshard/jobs", e(s.notImplemented())) 124 auth.Delete("/_reshard/jobs/{jobid}", e(s.notImplemented())) 125 auth.Get("/_reshard/jobs/{jobid}/state", e(s.notImplemented())) 126 auth.Put("/_reshard/jobs/{jobid}/state", e(s.notImplemented())) 127 128 // Config 129 admin.Get("/_node/{node-name}/_config", e(s.allConfig())) 130 admin.Get("/_node/{node-name}/_config/{section}", e(s.configSection())) 131 admin.Get("/_node/{node-name}/_config/{section}/{key}", e(s.configKey())) 132 admin.Put("/_node/{node-name}/_config/{section}/{key}", e(s.setConfigKey())) 133 admin.Delete("/_node/{node-name}/_config/{section}/{key}", e(s.deleteConfigKey())) 134 admin.Post("/_node/{node-name}/_config/_reload", e(s.reloadConfig())) 135 136 // Databases 137 auth.Route("/{db}", func(db chi.Router) { 138 admin := db.With( 139 httpe.ToMiddleware(adminRequired), 140 ) 141 member := db.With( 142 httpe.ToMiddleware(s.dbMembershipRequired), 143 ) 144 dbAdmin := member.With( 145 httpe.ToMiddleware(s.dbAdminRequired), 146 ) 147 148 member.Head("/", e(s.dbExists())) 149 member.Get("/", e(s.db())) 150 admin.Put("/", e(s.createDB())) 151 admin.Delete("/", e(s.deleteDB())) 152 member.Get("/_all_docs", e(s.query())) 153 member.Post("/_all_docs/queries", e(s.query())) 154 member.Post("/_all_docs", e(s.query())) 155 member.Get("/_design_docs", e(s.query())) 156 member.Post("/_design_docs", e(s.query())) 157 member.Post("/_design_docs/queries", e(s.query())) 158 member.Get("/_local_docs", e(s.query())) 159 member.Post("/_local_docs", e(s.query())) 160 member.Post("/_local_docs/queries", e(s.query())) 161 member.Post("/_bulk_get", e(s.notImplemented())) 162 member.Post("/_bulk_docs", e(s.notImplemented())) 163 member.Post("/_find", e(s.notImplemented())) 164 member.Post("/_index", e(s.notImplemented())) 165 member.Get("/_index", e(s.notImplemented())) 166 member.Delete("/_index/{designdoc}/json/{name}", e(s.notImplemented())) 167 member.Post("/_explain", e(s.notImplemented())) 168 member.Get("/_shards", e(s.notImplemented())) 169 member.Get("/_shards/{docid}", e(s.notImplemented())) 170 member.Get("/_sync_shards", e(s.notImplemented())) 171 member.Get("/_changes", e(s.notImplemented())) 172 member.Post("/_changes", e(s.notImplemented())) 173 admin.Post("/_compact", e(s.notImplemented())) 174 admin.Post("/_compact/{ddoc}", e(s.notImplemented())) 175 member.Post("/_ensure_full_commit", e(s.notImplemented())) 176 member.Post("/_view_cleanup", e(s.notImplemented())) 177 member.Get("/_security", e(s.getSecurity())) 178 dbAdmin.Put("/_security", e(s.putSecurity())) 179 member.Post("/_purge", e(s.notImplemented())) 180 member.Get("/_purged_infos_limit", e(s.notImplemented())) 181 member.Put("/_purged_infos_limit", e(s.notImplemented())) 182 member.Post("/_missing_revs", e(s.notImplemented())) 183 member.Post("/_revs_diff", e(s.notImplemented())) 184 member.Get("/_revs_limit", e(s.notImplemented())) 185 dbAdmin.Put("/_revs_limit", e(s.notImplemented())) 186 187 // Documents 188 member.Post("/", e(s.postDoc())) 189 member.Get("/{docid}", e(s.doc())) 190 member.Put("/{docid}", e(s.notImplemented())) 191 member.Delete("/{docid}", e(s.notImplemented())) 192 member.Method("COPY", "/{db}/{docid}", httpe.ToHandler(s.notImplemented())) 193 member.Delete("/{docid}", e(s.notImplemented())) 194 member.Get("/{docid}/{attname}", e(s.notImplemented())) 195 member.Get("/{docid}/{attname}", e(s.notImplemented())) 196 member.Delete("/{docid}/{attname}", e(s.notImplemented())) 197 198 // Design docs 199 member.Get("/_design/{ddoc}", e(s.notImplemented())) 200 dbAdmin.Put("/_design/{ddoc}", e(s.notImplemented())) 201 dbAdmin.Delete("/_design/{ddoc}", e(s.notImplemented())) 202 dbAdmin.Method("COPY", "/{db}/_design/{ddoc}", httpe.ToHandler(s.notImplemented())) 203 member.Get("/_design/{ddoc}/{attname}", e(s.notImplemented())) 204 dbAdmin.Put("/_design/{ddoc}/{attname}", e(s.notImplemented())) 205 dbAdmin.Delete("/_design/{ddoc}/{attname}", e(s.notImplemented())) 206 member.Get("/_design/{ddoc}/_view/{view}", e(s.query())) 207 member.Get("/_design/{ddoc}/_info", e(s.notImplemented())) 208 member.Post("/_design/{ddoc}/_view/{view}", e(s.query())) 209 member.Post("/_design/{ddoc}/_view/{view}/queries", e(s.query())) 210 member.Get("/_design/{ddoc}/_search/{index}", e(s.notImplemented())) 211 member.Get("/_design/{ddoc}/_search_info/{index}", e(s.notImplemented())) 212 member.Post("/_design/{ddoc}/_update/{func}", e(s.notImplemented())) 213 member.Post("/_design/{ddoc}/_update/{func}/{docid}", e(s.notImplemented())) 214 member.Get("/_design/{ddoc}/_rewrite/{path}", e(s.notImplemented())) 215 member.Put("/_design/{ddoc}/_rewrite/{path}", e(s.notImplemented())) 216 member.Post("/_design/{ddoc}/_rewrite/{path}", e(s.notImplemented())) 217 member.Delete("/_design/{ddoc}/_rewrite/{path}", e(s.notImplemented())) 218 219 member.Get("/_partition/{partition}", e(s.notImplemented())) 220 member.Get("/_partition/{partition}/_all_docs", e(s.notImplemented())) 221 member.Get("/_partition/{partition}/_design/{ddoc}/_view/{view}", e(s.notImplemented())) 222 member.Post("/_partition/{partition_id}/_find", e(s.notImplemented())) 223 member.Get("/_partition/{partition_id}/_explain", e(s.notImplemented())) 224 }) 225 } 226 227 func (s *Server) handleErrors(next httpe.HandlerWithError) httpe.HandlerWithError { 228 return httpe.HandlerWithErrorFunc(func(w http.ResponseWriter, r *http.Request) error { 229 if err := next.ServeHTTPWithError(w, r); err != nil { 230 status := kivik.HTTPStatus(err) 231 ce := &couchError{} 232 if !errors.As(err, &ce) { 233 ce.Err = strings.ReplaceAll(strings.ToLower(http.StatusText(status)), " ", "_") 234 ce.Reason = err.Error() 235 } 236 return serveJSON(w, status, ce) 237 } 238 return nil 239 }) 240 } 241 242 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 243 s.mux.ServeHTTP(w, r) 244 } 245 246 func serveJSON(w http.ResponseWriter, status int, payload interface{}) error { 247 body, err := json.Marshal(payload) 248 if err != nil { 249 return err 250 } 251 w.Header().Set("Content-Type", "application/json; charset=utf-8") 252 w.WriteHeader(status) 253 _, err = io.Copy(w, bytes.NewReader(body)) 254 return err 255 } 256 257 func (s *Server) notImplemented() httpe.HandlerWithError { 258 return httpe.HandlerWithErrorFunc(func(http.ResponseWriter, *http.Request) error { 259 return errNotImplimented 260 }) 261 } 262 263 func options(r *http.Request) kivik.Option { 264 query := r.URL.Query() 265 params := make(map[string]interface{}, len(query)) 266 for k := range query { 267 params[k] = query.Get(k) 268 } 269 return kivik.Params(params) 270 } 271 272 func (s *Server) allDBs() httpe.HandlerWithError { 273 return httpe.HandlerWithErrorFunc(func(w http.ResponseWriter, r *http.Request) error { 274 dbs, err := s.client.AllDBs(r.Context(), options(r)) 275 if err != nil { 276 fmt.Println(err) 277 return err 278 } 279 return serveJSON(w, http.StatusOK, dbs) 280 }) 281 } 282 283 func (s *Server) conf(ctx context.Context, section, key string, target interface{}) error { 284 value, err := s.config.Key(ctx, section, key) 285 if err != nil { 286 return err 287 } 288 return json.Unmarshal([]byte(value), target) 289 }