...

Source file src/github.com/go-kivik/kivik/v4/x/server/server.go

Documentation: github.com/go-kivik/kivik/v4/x/server

     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  }
   290  

View as plain text