// Copyright 2018 clair authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package v3 import ( "net/http" "strconv" "time" "github.com/prometheus/client_golang/prometheus" log "github.com/sirupsen/logrus" "google.golang.org/grpc" pb "github.com/coreos/clair/api/v3/clairpb" "github.com/coreos/clair/database" "github.com/coreos/clair/pkg/grpcutil" ) var ( promResponseDurationMilliseconds = prometheus.NewHistogramVec(prometheus.HistogramOpts{ Name: "clair_v3_api_response_duration_milliseconds", Help: "The duration of time it takes to receive and write a response to an V2 API request", Buckets: prometheus.ExponentialBuckets(9.375, 2, 10), }, []string{"route", "code"}) ) func init() { prometheus.MustRegister(promResponseDurationMilliseconds) } func prometheusHandler(h http.Handler) http.Handler { mux := http.NewServeMux() mux.Handle("/", h) mux.Handle("/metrics", prometheus.Handler()) return mux } type httpStatusWriter struct { http.ResponseWriter StatusCode int } func (w *httpStatusWriter) WriteHeader(code int) { w.StatusCode = code w.ResponseWriter.WriteHeader(code) } func loggingHandler(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() lrw := &httpStatusWriter{ResponseWriter: w, StatusCode: http.StatusOK} h.ServeHTTP(lrw, r) log.WithFields(log.Fields{ "remote addr": r.RemoteAddr, "method": r.Method, "request uri": r.RequestURI, "status": strconv.Itoa(lrw.StatusCode), "elapsed time (ms)": float64(time.Since(start).Nanoseconds()) * 1e-6, }).Info("handled HTTP request") }) } // ListenAndServe serves the Clair v3 API over gRPC and the gRPC Gateway. func ListenAndServe(addr, keyFile, certFile, caPath string, store database.Datastore) error { srv := grpcutil.MuxedGRPCServer{ Addr: addr, ServicesFunc: func(gsrv *grpc.Server) { pb.RegisterAncestryServiceServer(gsrv, &AncestryServer{Store: store}) pb.RegisterNotificationServiceServer(gsrv, &NotificationServer{Store: store}) pb.RegisterStatusServiceServer(gsrv, &StatusServer{Store: store}) }, ServiceHandlerFuncs: []grpcutil.RegisterServiceHandlerFunc{ pb.RegisterAncestryServiceHandler, pb.RegisterNotificationServiceHandler, pb.RegisterStatusServiceHandler, }, } middleware := func(h http.Handler) http.Handler { return prometheusHandler(loggingHandler(h)) } var err error if caPath == "" { err = srv.ListenAndServe(middleware) } else { err = srv.ListenAndServeTLS(certFile, keyFile, caPath, middleware) } return err }