func (sctx *serveCtx) registerGateway(opts []grpc.DialOption) (*gw.ServeMux, error) { ctx := sctx.ctx addr := sctx.l.Addr().String() gwmux := gw.NewServeMux() err := pb.RegisterKVHandlerFromEndpoint(ctx, gwmux, addr, opts) if err != nil { return nil, err } err = pb.RegisterWatchHandlerFromEndpoint(ctx, gwmux, addr, opts) if err != nil { return nil, err } err = pb.RegisterLeaseHandlerFromEndpoint(ctx, gwmux, addr, opts) if err != nil { return nil, err } err = pb.RegisterClusterHandlerFromEndpoint(ctx, gwmux, addr, opts) if err != nil { return nil, err } err = pb.RegisterMaintenanceHandlerFromEndpoint(ctx, gwmux, addr, opts) if err != nil { return nil, err } err = pb.RegisterAuthHandlerFromEndpoint(ctx, gwmux, addr, opts) if err != nil { return nil, err } return gwmux, nil }
// GetJSONGateway returns the JSON gateway for the gRPC API. func GetJSONGateway(ctx context.Context, lsCtx loraserver.Context, grpcBind string) (http.Handler, error) { bindParts := strings.SplitN(grpcBind, ":", 2) if len(bindParts) != 2 { return nil, errors.New("get port from http-bind failed") } apiEndpoint := fmt.Sprintf("localhost:%s", bindParts[1]) opts := []grpc.DialOption{ grpc.WithInsecure(), } mux := runtime.NewServeMux() if err := pb.RegisterApplicationHandlerFromEndpoint(ctx, mux, apiEndpoint, opts); err != nil { return nil, fmt.Errorf("register application handler error: %s", err) } if err := pb.RegisterNodeHandlerFromEndpoint(ctx, mux, apiEndpoint, opts); err != nil { return nil, fmt.Errorf("register node handler error: %s", err) } if err := pb.RegisterChannelListHandlerFromEndpoint(ctx, mux, apiEndpoint, opts); err != nil { return nil, fmt.Errorf("register channel-list handler error: %s", err) } if err := pb.RegisterChannelHandlerFromEndpoint(ctx, mux, apiEndpoint, opts); err != nil { return nil, fmt.Errorf("register channel handler error: %s", err) } if err := pb.RegisterNodeSessionHandlerFromEndpoint(ctx, mux, apiEndpoint, opts); err != nil { return nil, fmt.Errorf("register node-session handler error: %s", err) } return mux, nil }
func main() { var rcpAddr, port string flag.StringVar(&rcpAddr, "rcp", ":3323", "rcp addr (default: ':3323')") flag.StringVar(&port, "http", ":3343", "http port (default: '3343')") flag.Parse() //START2 OMIT ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() m := runtime.NewServeMux() opts := []grpc.DialOption{grpc.WithInsecure()} err := idl.RegisterUserServiceHandlerFromEndpoint(ctx, m, rcpAddr, opts) if err != nil { fmt.Fprintf(os.Stderr, "cannot register service handler: %v\n", err) os.Exit(1) } // custom routes first, and cors handling on all requests h := cors(preMuxRouter(m)) if err = http.ListenAndServe(port, h); err != nil { fmt.Fprintf(os.Stderr, "http server error: %v\n", err) os.Exit(1) } //END2 OMIT }
// newGateway returns a new gateway server which translates HTTP into gRPC. func newGateway(ctx context.Context, opts ...runtime.ServeMuxOption) (http.Handler, error) { mux := runtime.NewServeMux(opts...) dialOpts := []grpc.DialOption{grpc.WithInsecure()} err := examplepb.RegisterEchoServiceHandlerFromEndpoint(ctx, mux, *echoEndpoint, dialOpts) if err != nil { return nil, err } err = examplepb.RegisterStreamServiceHandlerFromEndpoint(ctx, mux, *abeEndpoint, dialOpts) if err != nil { return nil, err } err = examplepb.RegisterABitOfEverythingServiceHandlerFromEndpoint(ctx, mux, *abeEndpoint, dialOpts) if err != nil { return nil, err } err = examplepb.RegisterFlowCombinationHandlerFromEndpoint(ctx, mux, *flowEndpoint, dialOpts) if err != nil { return nil, err } return mux, nil }
// ServeWithHTTP serves stuff. func ServeWithHTTP( registerFunc func(*grpc.Server), httpRegisterFunc func(context.Context, *runtime.ServeMux, *grpc.ClientConn) error, options ServeWithHTTPOptions, serveEnv ServeEnv, handlerEnv pkghttp.HandlerEnv, ) (retErr error) { defer func(start time.Time) { logServerFinished(start, retErr) }(time.Now()) if registerFunc == nil || httpRegisterFunc == nil { return ErrMustSpecifyRegisterFunc } if serveEnv.GRPCPort == 0 { serveEnv.GRPCPort = 7070 } if handlerEnv.Port == 0 { handlerEnv.Port = 8080 } grpcServer := grpc.NewServer( grpc.MaxConcurrentStreams(math.MaxUint32), grpc.UnaryInterceptor(protorpclog.LoggingUnaryServerInterceptor), ) registerFunc(grpcServer) if options.Version != nil { protoversion.RegisterAPIServer(grpcServer, protoversion.NewAPIServer(options.Version, protoversion.APIServerOptions{})) } listener, err := net.Listen("tcp", fmt.Sprintf(":%d", serveEnv.GRPCPort)) if err != nil { return err } grpcErrC := make(chan error) go func() { grpcErrC <- grpcServer.Serve(listener) }() time.Sleep(1 * time.Second) ctx, cancel := context.WithCancel(context.Background()) conn, err := grpc.Dial(fmt.Sprintf("0.0.0.0:%d", serveEnv.GRPCPort), grpc.WithInsecure()) if err != nil { cancel() return err } go func() { <-ctx.Done() _ = conn.Close() }() mux := runtime.NewServeMux() if options.Version != nil { if err := protoversion.RegisterAPIHandler(ctx, mux, conn); err != nil { cancel() return err } } if err := httpRegisterFunc(ctx, mux, conn); err != nil { cancel() return err } var handler http.Handler handler = mux if options.HTTPHandlerModifier != nil { handler, err = options.HTTPHandlerModifier(mux) if err != nil { cancel() return err } } httpErrC := make(chan error) go func() { httpErrC <- pkghttp.ListenAndServe(handler, handlerEnv) }() protolion.Info( &ServerStarted{ Port: uint32(serveEnv.GRPCPort), HttpPort: uint32(handlerEnv.Port), }, ) var errs []error grpcStopped := false for i := 0; i < 2; i++ { select { case grpcErr := <-grpcErrC: if grpcErr != nil { errs = append(errs, fmt.Errorf("grpc error: %s", grpcErr.Error())) } grpcStopped = true case httpErr := <-httpErrC: if httpErr != nil { errs = append(errs, fmt.Errorf("http error: %s", httpErr.Error())) } if !grpcStopped { grpcServer.Stop() _ = listener.Close() grpcStopped = true } } } if len(errs) > 0 { return fmt.Errorf("%v", errs) } return nil }
// Start starts the server on the specified port, starts gossip and initializes // the node using the engines from the server's context. // // The passed context can be used to trace the server startup. The context // should represent the general startup operation. func (s *Server) Start(ctx context.Context) error { ctx = s.AnnotateCtx(ctx) startTime := timeutil.Now() tlsConfig, err := s.cfg.GetServerTLSConfig() if err != nil { return err } httpServer := netutil.MakeServer(s.stopper, tlsConfig, s) plainRedirectServer := netutil.MakeServer(s.stopper, tlsConfig, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "https://"+r.Host+r.RequestURI, http.StatusPermanentRedirect) })) // The following code is a specialization of util/net.go's ListenAndServe // which adds pgwire support. A single port is used to serve all protocols // (pg, http, h2) via the following construction: // // non-TLS case: // net.Listen -> cmux.New // | // - -> pgwire.Match -> pgwire.Server.ServeConn // - -> cmux.Any -> grpc.(*Server).Serve // // TLS case: // net.Listen -> cmux.New // | // - -> pgwire.Match -> pgwire.Server.ServeConn // - -> cmux.Any -> grpc.(*Server).Serve // // Note that the difference between the TLS and non-TLS cases exists due to // Go's lack of an h2c (HTTP2 Clear Text) implementation. See inline comments // in util.ListenAndServe for an explanation of how h2c is implemented there // and here. ln, err := net.Listen("tcp", s.cfg.Addr) if err != nil { return err } log.Eventf(ctx, "listening on port %s", s.cfg.Addr) unresolvedListenAddr, err := officialAddr(s.cfg.Addr, ln.Addr()) if err != nil { return err } s.cfg.Addr = unresolvedListenAddr.String() unresolvedAdvertAddr, err := officialAddr(s.cfg.AdvertiseAddr, ln.Addr()) if err != nil { return err } s.cfg.AdvertiseAddr = unresolvedAdvertAddr.String() s.rpcContext.SetLocalInternalServer(s.node) m := cmux.New(ln) pgL := m.Match(pgwire.Match) anyL := m.Match(cmux.Any()) httpLn, err := net.Listen("tcp", s.cfg.HTTPAddr) if err != nil { return err } unresolvedHTTPAddr, err := officialAddr(s.cfg.HTTPAddr, httpLn.Addr()) if err != nil { return err } s.cfg.HTTPAddr = unresolvedHTTPAddr.String() workersCtx := s.AnnotateCtx(context.Background()) s.stopper.RunWorker(func() { <-s.stopper.ShouldQuiesce() if err := httpLn.Close(); err != nil { log.Fatal(workersCtx, err) } }) if tlsConfig != nil { httpMux := cmux.New(httpLn) clearL := httpMux.Match(cmux.HTTP1()) tlsL := httpMux.Match(cmux.Any()) s.stopper.RunWorker(func() { netutil.FatalIfUnexpected(httpMux.Serve()) }) s.stopper.RunWorker(func() { netutil.FatalIfUnexpected(plainRedirectServer.Serve(clearL)) }) httpLn = tls.NewListener(tlsL, tlsConfig) } s.stopper.RunWorker(func() { netutil.FatalIfUnexpected(httpServer.Serve(httpLn)) }) s.stopper.RunWorker(func() { <-s.stopper.ShouldQuiesce() netutil.FatalIfUnexpected(anyL.Close()) <-s.stopper.ShouldStop() s.grpc.Stop() }) s.stopper.RunWorker(func() { netutil.FatalIfUnexpected(s.grpc.Serve(anyL)) }) s.stopper.RunWorker(func() { pgCtx := s.pgServer.AmbientCtx.AnnotateCtx(context.Background()) netutil.FatalIfUnexpected(httpServer.ServeWith(s.stopper, pgL, func(conn net.Conn) { connCtx := log.WithLogTagStr(pgCtx, "client", conn.RemoteAddr().String()) if err := s.pgServer.ServeConn(connCtx, conn); err != nil && !netutil.IsClosedConnection(err) { // Report the error on this connection's context, so that we // know which remote client caused the error when looking at // the logs. log.Error(connCtx, err) } })) }) if len(s.cfg.SocketFile) != 0 { // Unix socket enabled: postgres protocol only. unixLn, err := net.Listen("unix", s.cfg.SocketFile) if err != nil { return err } s.stopper.RunWorker(func() { <-s.stopper.ShouldQuiesce() if err := unixLn.Close(); err != nil { log.Fatal(workersCtx, err) } }) s.stopper.RunWorker(func() { pgCtx := s.pgServer.AmbientCtx.AnnotateCtx(context.Background()) netutil.FatalIfUnexpected(httpServer.ServeWith(s.stopper, unixLn, func(conn net.Conn) { connCtx := log.WithLogTagStr(pgCtx, "client", conn.RemoteAddr().String()) if err := s.pgServer.ServeConn(connCtx, conn); err != nil && !netutil.IsClosedConnection(err) { // Report the error on this connection's context, so that we // know which remote client caused the error when looking at // the logs. log.Error(connCtx, err) } })) }) } // Enable the debug endpoints first to provide an earlier window // into what's going on with the node in advance of exporting node // functionality. // TODO(marc): when cookie-based authentication exists, // apply it for all web endpoints. s.mux.HandleFunc(debugEndpoint, http.HandlerFunc(handleDebug)) s.gossip.Start(unresolvedAdvertAddr) log.Event(ctx, "started gossip") s.engines, err = s.cfg.CreateEngines() if err != nil { return errors.Wrap(err, "failed to create engines") } s.stopper.AddCloser(&s.engines) // We might have to sleep a bit to protect against this node producing non- // monotonic timestamps. Before restarting, its clock might have been driven // by other nodes' fast clocks, but when we restarted, we lost all this // information. For example, a client might have written a value at a // timestamp that's in the future of the restarted node's clock, and if we // don't do something, the same client's read would not return the written // value. So, we wait up to MaxOffset; we couldn't have served timestamps more // than MaxOffset in the future (assuming that MaxOffset was not changed, see // #9733). // // As an optimization for tests, we don't sleep if all the stores are brand // new. In this case, the node will not serve anything anyway until it // synchronizes with other nodes. { anyStoreBootstrapped := false for _, e := range s.engines { if _, err := storage.ReadStoreIdent(ctx, e); err != nil { // NotBootstrappedError is expected. if _, ok := err.(*storage.NotBootstrappedError); !ok { return err } } else { anyStoreBootstrapped = true break } } if anyStoreBootstrapped { sleepDuration := s.clock.MaxOffset() - timeutil.Since(startTime) if sleepDuration > 0 { log.Infof(ctx, "sleeping for %s to guarantee HLC monotonicity", sleepDuration) time.Sleep(sleepDuration) } } } // Now that we have a monotonic HLC wrt previous incarnations of the process, // init all the replicas. err = s.node.start( ctx, unresolvedAdvertAddr, s.engines, s.cfg.NodeAttributes, s.cfg.Locality, ) if err != nil { return err } log.Event(ctx, "started node") s.nodeLiveness.StartHeartbeat(ctx, s.stopper) // We can now add the node registry. s.recorder.AddNode(s.registry, s.node.Descriptor, s.node.startedAt) // Begin recording runtime statistics. s.startSampleEnvironment(s.cfg.MetricsSampleInterval) // Begin recording time series data collected by the status monitor. s.tsDB.PollSource( s.cfg.AmbientCtx, s.recorder, s.cfg.MetricsSampleInterval, ts.Resolution10s, s.stopper, ) // Begin recording status summaries. s.node.startWriteSummaries(s.cfg.MetricsSampleInterval) // Create and start the schema change manager only after a NodeID // has been assigned. testingKnobs := &sql.SchemaChangerTestingKnobs{} if s.cfg.TestingKnobs.SQLSchemaChanger != nil { testingKnobs = s.cfg.TestingKnobs.SQLSchemaChanger.(*sql.SchemaChangerTestingKnobs) } sql.NewSchemaChangeManager(testingKnobs, *s.db, s.gossip, s.leaseMgr).Start(s.stopper) s.distSQLServer.Start() log.Infof(ctx, "starting %s server at %s", s.cfg.HTTPRequestScheme(), unresolvedHTTPAddr) log.Infof(ctx, "starting grpc/postgres server at %s", unresolvedListenAddr) log.Infof(ctx, "advertising CockroachDB node at %s", unresolvedAdvertAddr) if len(s.cfg.SocketFile) != 0 { log.Infof(ctx, "starting postgres server at unix:%s", s.cfg.SocketFile) } s.stopper.RunWorker(func() { netutil.FatalIfUnexpected(m.Serve()) }) log.Event(ctx, "accepting connections") // Initialize grpc-gateway mux and context. jsonpb := &protoutil.JSONPb{ EnumsAsInts: true, EmitDefaults: true, Indent: " ", } protopb := new(protoutil.ProtoPb) gwMux := gwruntime.NewServeMux( gwruntime.WithMarshalerOption(gwruntime.MIMEWildcard, jsonpb), gwruntime.WithMarshalerOption(httputil.JSONContentType, jsonpb), gwruntime.WithMarshalerOption(httputil.AltJSONContentType, jsonpb), gwruntime.WithMarshalerOption(httputil.ProtoContentType, protopb), gwruntime.WithMarshalerOption(httputil.AltProtoContentType, protopb), ) gwCtx, gwCancel := context.WithCancel(s.AnnotateCtx(context.Background())) s.stopper.AddCloser(stop.CloserFn(gwCancel)) // Setup HTTP<->gRPC handlers. conn, err := s.rpcContext.GRPCDial(s.cfg.Addr) if err != nil { return errors.Errorf("error constructing grpc-gateway: %s; are your certificates valid?", err) } for _, gw := range []grpcGatewayServer{s.admin, s.status, &s.tsServer} { if err := gw.RegisterGateway(gwCtx, gwMux, conn); err != nil { return err } } var uiFileSystem http.FileSystem uiDebug := envutil.EnvOrDefaultBool("COCKROACH_DEBUG_UI", false) if uiDebug { uiFileSystem = http.Dir("pkg/ui") } else { uiFileSystem = &assetfs.AssetFS{ Asset: ui.Asset, AssetDir: ui.AssetDir, AssetInfo: ui.AssetInfo, } } uiFileServer := http.FileServer(uiFileSystem) s.mux.HandleFunc("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/" { if uiDebug { r.URL.Path = "debug.html" } else { r.URL.Path = "release.html" } } uiFileServer.ServeHTTP(w, r) })) // TODO(marc): when cookie-based authentication exists, // apply it for all web endpoints. s.mux.Handle(adminPrefix, gwMux) s.mux.Handle(ts.URLPrefix, gwMux) s.mux.Handle(statusPrefix, gwMux) s.mux.Handle("/health", gwMux) s.mux.Handle(statusVars, http.HandlerFunc(s.status.handleVars)) log.Event(ctx, "added http endpoints") if err := sdnotify.Ready(); err != nil { log.Errorf(ctx, "failed to signal readiness using systemd protocol: %s", err) } log.Event(ctx, "server ready") return nil }
// Start starts the server on the specified port, starts gossip and initializes // the node using the engines from the server's context. // // The passed context can be used to trace the server startup. The context // should represent the general startup operation, and is different from // contexts used at runtime for server's background work (like `s.Ctx()`). func (s *Server) Start(ctx context.Context) error { // Copy log tags from s.Ctx() ctx = log.WithLogTagsFromCtx(ctx, s.Ctx()) tlsConfig, err := s.ctx.GetServerTLSConfig() if err != nil { return err } httpServer := netutil.MakeServer(s.stopper, tlsConfig, s) plainRedirectServer := netutil.MakeServer(s.stopper, tlsConfig, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "https://"+r.Host+r.RequestURI, http.StatusPermanentRedirect) })) // The following code is a specialization of util/net.go's ListenAndServe // which adds pgwire support. A single port is used to serve all protocols // (pg, http, h2) via the following construction: // // non-TLS case: // net.Listen -> cmux.New // | // - -> pgwire.Match -> pgwire.Server.ServeConn // - -> cmux.Any -> grpc.(*Server).Serve // // TLS case: // net.Listen -> cmux.New // | // - -> pgwire.Match -> pgwire.Server.ServeConn // - -> cmux.Any -> grpc.(*Server).Serve // // Note that the difference between the TLS and non-TLS cases exists due to // Go's lack of an h2c (HTTP2 Clear Text) implementation. See inline comments // in util.ListenAndServe for an explanation of how h2c is implemented there // and here. ln, err := net.Listen("tcp", s.ctx.Addr) if err != nil { return err } log.Tracef(ctx, "listening on port %s", s.ctx.Addr) unresolvedAddr, err := officialAddr(s.ctx.Addr, ln.Addr()) if err != nil { return err } s.ctx.Addr = unresolvedAddr.String() s.rpcContext.SetLocalInternalServer(s.node) m := cmux.New(ln) pgL := m.Match(pgwire.Match) anyL := m.Match(cmux.Any()) httpLn, err := net.Listen("tcp", s.ctx.HTTPAddr) if err != nil { return err } unresolvedHTTPAddr, err := officialAddr(s.ctx.HTTPAddr, httpLn.Addr()) if err != nil { return err } s.ctx.HTTPAddr = unresolvedHTTPAddr.String() s.stopper.RunWorker(func() { <-s.stopper.ShouldQuiesce() if err := httpLn.Close(); err != nil { log.Fatal(s.Ctx(), err) } }) if tlsConfig != nil { httpMux := cmux.New(httpLn) clearL := httpMux.Match(cmux.HTTP1()) tlsL := httpMux.Match(cmux.Any()) s.stopper.RunWorker(func() { netutil.FatalIfUnexpected(httpMux.Serve()) }) s.stopper.RunWorker(func() { netutil.FatalIfUnexpected(plainRedirectServer.Serve(clearL)) }) httpLn = tls.NewListener(tlsL, tlsConfig) } s.stopper.RunWorker(func() { netutil.FatalIfUnexpected(httpServer.Serve(httpLn)) }) s.stopper.RunWorker(func() { <-s.stopper.ShouldQuiesce() netutil.FatalIfUnexpected(anyL.Close()) <-s.stopper.ShouldStop() s.grpc.Stop() }) s.stopper.RunWorker(func() { netutil.FatalIfUnexpected(s.grpc.Serve(anyL)) }) s.stopper.RunWorker(func() { netutil.FatalIfUnexpected(httpServer.ServeWith(s.stopper, pgL, func(conn net.Conn) { if err := s.pgServer.ServeConn(conn); err != nil && !netutil.IsClosedConnection(err) { log.Error(s.Ctx(), err) } })) }) if len(s.ctx.SocketFile) != 0 { // Unix socket enabled: postgres protocol only. unixLn, err := net.Listen("unix", s.ctx.SocketFile) if err != nil { return err } s.stopper.RunWorker(func() { <-s.stopper.ShouldQuiesce() if err := unixLn.Close(); err != nil { log.Fatal(s.Ctx(), err) } }) s.stopper.RunWorker(func() { netutil.FatalIfUnexpected(httpServer.ServeWith(s.stopper, unixLn, func(conn net.Conn) { if err := s.pgServer.ServeConn(conn); err != nil && !netutil.IsClosedConnection(err) { log.Error(s.Ctx(), err) } })) }) } // Enable the debug endpoints first to provide an earlier window // into what's going on with the node in advance of exporting node // functionality. // TODO(marc): when cookie-based authentication exists, // apply it for all web endpoints. s.mux.HandleFunc(debugEndpoint, http.HandlerFunc(handleDebug)) s.gossip.Start(unresolvedAddr) log.Trace(ctx, "started gossip") if err := s.node.start(ctx, unresolvedAddr, s.ctx.Engines, s.ctx.NodeAttributes); err != nil { return err } log.Trace(ctx, "started node") // Set the NodeID in the base context (which was inherited by the // various components of the server). s.nodeLogTagVal.Set(int64(s.node.Descriptor.NodeID)) // We can now add the node registry. s.recorder.AddNode(s.registry, s.node.Descriptor, s.node.startedAt) // Begin recording runtime statistics. s.startSampleEnvironment(s.ctx.MetricsSampleInterval) // Begin recording time series data collected by the status monitor. s.tsDB.PollSource(s.recorder, s.ctx.MetricsSampleInterval, ts.Resolution10s, s.stopper) // Begin recording status summaries. s.node.startWriteSummaries(s.ctx.MetricsSampleInterval) s.sqlExecutor.SetNodeID(s.node.Descriptor.NodeID) // Create and start the schema change manager only after a NodeID // has been assigned. testingKnobs := new(sql.SchemaChangeManagerTestingKnobs) if s.ctx.TestingKnobs.SQLSchemaChangeManager != nil { testingKnobs = s.ctx.TestingKnobs.SQLSchemaChangeManager.(*sql.SchemaChangeManagerTestingKnobs) } sql.NewSchemaChangeManager(testingKnobs, *s.db, s.gossip, s.leaseMgr).Start(s.stopper) log.Infof(s.Ctx(), "starting %s server at %s", s.ctx.HTTPRequestScheme(), unresolvedHTTPAddr) log.Infof(s.Ctx(), "starting grpc/postgres server at %s", unresolvedAddr) if len(s.ctx.SocketFile) != 0 { log.Infof(s.Ctx(), "starting postgres server at unix:%s", s.ctx.SocketFile) } s.stopper.RunWorker(func() { netutil.FatalIfUnexpected(m.Serve()) }) log.Trace(ctx, "accepting connections") // Initialize grpc-gateway mux and context. jsonpb := &util.JSONPb{ EnumsAsInts: true, EmitDefaults: true, Indent: " ", } protopb := new(util.ProtoPb) gwMux := gwruntime.NewServeMux( gwruntime.WithMarshalerOption(gwruntime.MIMEWildcard, jsonpb), gwruntime.WithMarshalerOption(util.JSONContentType, jsonpb), gwruntime.WithMarshalerOption(util.AltJSONContentType, jsonpb), gwruntime.WithMarshalerOption(util.ProtoContentType, protopb), gwruntime.WithMarshalerOption(util.AltProtoContentType, protopb), ) gwCtx, gwCancel := context.WithCancel(s.Ctx()) s.stopper.AddCloser(stop.CloserFn(gwCancel)) // Setup HTTP<->gRPC handlers. conn, err := s.rpcContext.GRPCDial(s.ctx.Addr) if err != nil { return errors.Errorf("error constructing grpc-gateway: %s; are your certificates valid?", err) } for _, gw := range []grpcGatewayServer{&s.admin, s.status, &s.tsServer} { if err := gw.RegisterGateway(gwCtx, gwMux, conn); err != nil { return err } } var uiFileSystem http.FileSystem uiDebug := envutil.EnvOrDefaultBool("COCKROACH_DEBUG_UI", false) if uiDebug { uiFileSystem = http.Dir("ui") } else { uiFileSystem = &assetfs.AssetFS{ Asset: ui.Asset, AssetDir: ui.AssetDir, AssetInfo: ui.AssetInfo, } } uiFileServer := http.FileServer(uiFileSystem) s.mux.HandleFunc("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/" { if uiDebug { r.URL.Path = "debug.html" } else { r.URL.Path = "release.html" } } uiFileServer.ServeHTTP(w, r) })) // TODO(marc): when cookie-based authentication exists, // apply it for all web endpoints. s.mux.Handle(adminEndpoint, gwMux) s.mux.Handle(ts.URLPrefix, gwMux) s.mux.Handle(statusPrefix, s.status) s.mux.Handle(healthEndpoint, s.status) log.Trace(ctx, "added http endpoints") if err := sdnotify.Ready(); err != nil { log.Errorf(s.Ctx(), "failed to signal readiness using systemd protocol: %s", err) } log.Trace(ctx, "server ready") return nil }
func TestMuxServeHTTP(t *testing.T) { type stubPattern struct { method string ops []int pool []string verb string } for _, spec := range []struct { patterns []stubPattern reqMethod string reqPath string headers map[string]string respStatus int respContent string }{ { patterns: nil, reqMethod: "GET", reqPath: "/", respStatus: http.StatusNotFound, }, { patterns: []stubPattern{ { method: "GET", ops: []int{int(utilities.OpLitPush), 0}, pool: []string{"foo"}, }, }, reqMethod: "GET", reqPath: "/foo", respStatus: http.StatusOK, respContent: "GET /foo", }, { patterns: []stubPattern{ { method: "GET", ops: []int{int(utilities.OpLitPush), 0}, pool: []string{"foo"}, }, }, reqMethod: "GET", reqPath: "/bar", respStatus: http.StatusNotFound, }, { patterns: []stubPattern{ { method: "GET", ops: []int{int(utilities.OpLitPush), 0}, pool: []string{"foo"}, }, { method: "GET", ops: []int{int(utilities.OpPush), 0}, }, }, reqMethod: "GET", reqPath: "/foo", respStatus: http.StatusOK, respContent: "GET /foo", }, { patterns: []stubPattern{ { method: "GET", ops: []int{int(utilities.OpLitPush), 0}, pool: []string{"foo"}, }, { method: "POST", ops: []int{int(utilities.OpLitPush), 0}, pool: []string{"foo"}, }, }, reqMethod: "POST", reqPath: "/foo", respStatus: http.StatusOK, respContent: "POST /foo", }, { patterns: []stubPattern{ { method: "GET", ops: []int{int(utilities.OpLitPush), 0}, pool: []string{"foo"}, }, }, reqMethod: "DELETE", reqPath: "/foo", respStatus: http.StatusMethodNotAllowed, }, { patterns: []stubPattern{ { method: "GET", ops: []int{int(utilities.OpLitPush), 0}, pool: []string{"foo"}, }, }, reqMethod: "POST", reqPath: "/foo", headers: map[string]string{ "Content-Type": "application/x-www-form-urlencoded", }, respStatus: http.StatusOK, respContent: "GET /foo", }, { patterns: []stubPattern{ { method: "GET", ops: []int{int(utilities.OpLitPush), 0}, pool: []string{"foo"}, }, { method: "POST", ops: []int{int(utilities.OpLitPush), 0}, pool: []string{"foo"}, }, }, reqMethod: "POST", reqPath: "/foo", headers: map[string]string{ "Content-Type": "application/x-www-form-urlencoded", "X-HTTP-Method-Override": "GET", }, respStatus: http.StatusOK, respContent: "GET /foo", }, { patterns: []stubPattern{ { method: "GET", ops: []int{int(utilities.OpLitPush), 0}, pool: []string{"foo"}, }, }, reqMethod: "POST", reqPath: "/foo", headers: map[string]string{ "Content-Type": "application/json", }, respStatus: http.StatusMethodNotAllowed, }, { patterns: []stubPattern{ { method: "POST", ops: []int{int(utilities.OpLitPush), 0}, pool: []string{"foo"}, verb: "bar", }, }, reqMethod: "POST", reqPath: "/foo:bar", headers: map[string]string{ "Content-Type": "application/json", }, respStatus: http.StatusOK, respContent: "POST /foo:bar", }, } { mux := runtime.NewServeMux() for _, p := range spec.patterns { func(p stubPattern) { pat, err := runtime.NewPattern(1, p.ops, p.pool, p.verb) if err != nil { t.Fatalf("runtime.NewPattern(1, %#v, %#v, %q) failed with %v; want success", p.ops, p.pool, p.verb, err) } mux.Handle(p.method, pat, func(w http.ResponseWriter, r *http.Request, pathParams map[string]string) { fmt.Fprintf(w, "%s %s", p.method, pat.String()) }) }(p) } url := fmt.Sprintf("http://host.example%s", spec.reqPath) r, err := http.NewRequest(spec.reqMethod, url, bytes.NewReader(nil)) if err != nil { t.Fatalf("http.NewRequest(%q, %q, nil) failed with %v; want success", spec.reqMethod, url, err) } for name, value := range spec.headers { r.Header.Set(name, value) } w := httptest.NewRecorder() mux.ServeHTTP(w, r) if got, want := w.Code, spec.respStatus; got != want { t.Errorf("w.Code = %d; want %d; patterns=%v; req=%v", got, want, spec.patterns, r) } if spec.respContent != "" { if got, want := w.Body.String(), spec.respContent; got != want { t.Errorf("w.Body = %q; want %q; patterns=%v; req=%v", got, want, spec.patterns, r) } } } }
} grpc := grpc.NewServer(component.ServerOptions()...) // Register and Listen component.RegisterHealthServer(grpc) handler.RegisterRPC(grpc) handler.RegisterManager(grpc) go grpc.Serve(lis) defer grpc.Stop() if httpActive { proxyConn, err := component.Identity.Dial() if err != nil { ctx.WithError(err).Fatal("Could not start client for gRPC proxy") } mux := runtime.NewServeMux() netCtx, cancel := context.WithCancel(context.Background()) defer cancel() pb.RegisterApplicationManagerHandler(netCtx, mux, proxyConn) prxy := proxy.WithToken(mux) prxy = proxy.WithLogger(prxy, ctx) go func() { err := http.ListenAndServe( fmt.Sprintf("%s:%d", viper.GetString("handler.http-address"), viper.GetInt("handler.http-port")), prxy, ) if err != nil { ctx.WithError(err).Fatal("Error in gRPC proxy") }
func TestMarshalerForRequest(t *testing.T) { r, err := http.NewRequest("GET", "http://example.com", nil) if err != nil { t.Fatalf(`http.NewRequest("GET", "http://example.com", nil) failed with %v; want success`, err) } r.Header.Set("Accept", "application/x-out") r.Header.Set("Content-Type", "application/x-in") mux := runtime.NewServeMux() in, out := runtime.MarshalerForRequest(mux, r) if _, ok := in.(*runtime.JSONPb); !ok { t.Errorf("in = %#v; want a runtime.JSONPb", in) } if _, ok := out.(*runtime.JSONPb); !ok { t.Errorf("out = %#v; want a runtime.JSONPb", in) } var marshalers [3]dummyMarshaler specs := []struct { opt runtime.ServeMuxOption wantIn runtime.Marshaler wantOut runtime.Marshaler }{ { opt: runtime.WithMarshalerOption(runtime.MIMEWildcard, &marshalers[0]), wantIn: &marshalers[0], wantOut: &marshalers[0], }, { opt: runtime.WithMarshalerOption("application/x-in", &marshalers[1]), wantIn: &marshalers[1], wantOut: &marshalers[0], }, { opt: runtime.WithMarshalerOption("application/x-out", &marshalers[2]), wantIn: &marshalers[1], wantOut: &marshalers[2], }, } for i, spec := range specs { var opts []runtime.ServeMuxOption for _, s := range specs[:i+1] { opts = append(opts, s.opt) } mux = runtime.NewServeMux(opts...) in, out = runtime.MarshalerForRequest(mux, r) if got, want := in, spec.wantIn; got != want { t.Errorf("in = %#v; want %#v", got, want) } if got, want := out, spec.wantOut; got != want { t.Errorf("out = %#v; want %#v", got, want) } } r.Header.Set("Content-Type", "application/x-another") in, out = runtime.MarshalerForRequest(mux, r) if got, want := in, &marshalers[1]; got != want { t.Errorf("in = %#v; want %#v", got, want) } if got, want := out, &marshalers[0]; got != want { t.Errorf("out = %#v; want %#v", got, want) } }
// lndMain is the true entry point for lnd. This function is required since // defers created in the top-level scope of a main method aren't executed if // os.Exit() is called. func lndMain() error { // Load the configuration, and parse any command line options. This // function will also set up logging properly. loadedConfig, err := loadConfig() if err != nil { return err } cfg = loadedConfig defer backendLog.Flush() // Show version at startup. ltndLog.Infof("Version %s", version()) // Enable http profiling server if requested. if cfg.Profile != "" { go func() { listenAddr := net.JoinHostPort("", cfg.Profile) profileRedirect := http.RedirectHandler("/debug/pprof", http.StatusSeeOther) http.Handle("/", profileRedirect) fmt.Println(http.ListenAndServe(listenAddr, nil)) }() } // Open the channeldb, which is dedicated to storing channel, and // network related meta-data. chanDB, err := channeldb.Open(cfg.DataDir, activeNetParams.Params) if err != nil { fmt.Println("unable to open channeldb: ", err) return err } defer chanDB.Close() // Next load btcd's TLS cert for the RPC connection. If a raw cert was // specified in the config, then we'll se that directly. Otherwise, we // attempt to read the cert from the path specified in the config. var rpcCert []byte if cfg.RawRPCCert != "" { rpcCert, err = hex.DecodeString(cfg.RawRPCCert) if err != nil { return err } } else { certFile, err := os.Open(cfg.RPCCert) if err != nil { return err } rpcCert, err = ioutil.ReadAll(certFile) if err != nil { return err } if err := certFile.Close(); err != nil { return err } } rpcIP, err := net.LookupHost(cfg.RPCHost) if err != nil { fmt.Printf("unable to resolve rpc host: %v", err) return err } btcdHost := fmt.Sprintf("%v:%v", cfg.RPCHost, activeNetParams.rpcPort) btcdUser := cfg.RPCUser btcdPass := cfg.RPCPass // TODO(roasbeef): parse config here and select chosen notifier instead rpcConfig := &btcrpcclient.ConnConfig{ Host: btcdHost, Endpoint: "ws", User: btcdUser, Pass: btcdPass, Certificates: rpcCert, DisableTLS: false, DisableConnectOnNew: true, DisableAutoReconnect: false, } notifier, err := btcdnotify.New(rpcConfig) if err != nil { return err } // TODO(roasbeef): parse config here select chosen WalletController walletConfig := &btcwallet.Config{ PrivatePass: []byte("hello"), DataDir: filepath.Join(cfg.DataDir, "lnwallet"), RpcHost: fmt.Sprintf("%v:%v", rpcIP[0], activeNetParams.rpcPort), RpcUser: cfg.RPCUser, RpcPass: cfg.RPCPass, CACert: rpcCert, NetParams: activeNetParams.Params, } wc, err := btcwallet.New(walletConfig) if err != nil { fmt.Printf("unable to create wallet controller: %v\n", err) return err } signer := wc bio := wc // Create, and start the lnwallet, which handles the core payment // channel logic, and exposes control via proxy state machines. wallet, err := lnwallet.NewLightningWallet(chanDB, notifier, wc, signer, bio, activeNetParams.Params) if err != nil { fmt.Printf("unable to create wallet: %v\n", err) return err } if err := wallet.Startup(); err != nil { fmt.Printf("unable to start wallet: %v\n", err) return err } ltndLog.Info("LightningWallet opened") // Set up the core server which will listen for incoming peer // connections. defaultListenAddrs := []string{ net.JoinHostPort("", strconv.Itoa(cfg.PeerPort)), } server, err := newServer(defaultListenAddrs, notifier, bio, wallet, chanDB) if err != nil { srvrLog.Errorf("unable to create server: %v\n", err) return err } if err := server.Start(); err != nil { srvrLog.Errorf("unable to create to start: %v\n", err) return err } addInterruptHandler(func() { ltndLog.Infof("Gracefully shutting down the server...") server.Stop() server.WaitForShutdown() }) // Initialize, and register our implementation of the gRPC server. var opts []grpc.ServerOption grpcServer := grpc.NewServer(opts...) lnrpc.RegisterLightningServer(grpcServer, server.rpcServer) // Next, Start the grpc server listening for HTTP/2 connections. grpcEndpoint := fmt.Sprintf("localhost:%d", loadedConfig.RPCPort) lis, err := net.Listen("tcp", grpcEndpoint) if err != nil { fmt.Printf("failed to listen: %v", err) return err } go func() { rpcsLog.Infof("RPC server listening on %s", lis.Addr()) grpcServer.Serve(lis) }() // Finally, start the REST proxy for our gRPC server above. ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() mux := proxy.NewServeMux() swaggerPattern := proxy.MustPattern(proxy.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "swagger"}, "")) // TODO(roasbeef): accept path to swagger file as command-line option mux.Handle("GET", swaggerPattern, func(w http.ResponseWriter, r *http.Request, p map[string]string) { http.ServeFile(w, r, "lnrpc/rpc.swagger.json") }) proxyOpts := []grpc.DialOption{grpc.WithInsecure()} err = lnrpc.RegisterLightningHandlerFromEndpoint(ctx, mux, grpcEndpoint, proxyOpts) if err != nil { return err } go func() { rpcsLog.Infof("gRPC proxy started") http.ListenAndServe(":8080", mux) }() // Wait for shutdown signal from either a graceful server stop or from // the interrupt handler. <-shutdownChannel ltndLog.Info("Shutdown complete") return nil }