// This is an example for serving HTTP, HTTPS, and GoRPC/TLS on the same port. func Example_recursiveCmux() { // Create the TCP listener. l, err := net.Listen("tcp", "127.0.0.1:50051") if err != nil { log.Panic(err) } // Create a mux. tcpm := cmux.New(l) // We first match on HTTP 1.1 methods. httpl := tcpm.Match(cmux.HTTP1Fast()) // If not matched, we assume that its TLS. tlsl := tcpm.Match(cmux.Any()) tlsl = tlsListener(tlsl) // Now, we build another mux recursively to match HTTPS and GoRPC. // You can use the same trick for SSH. tlsm := cmux.New(tlsl) httpsl := tlsm.Match(cmux.HTTP1Fast()) gorpcl := tlsm.Match(cmux.Any()) go recursiveServeHTTP(httpl) go recursiveServeHTTP(httpsl) go recursiveServeRPC(gorpcl) go func() { if err := tlsm.Serve(); err != cmux.ErrListenerClosed { panic(err) } }() if err := tcpm.Serve(); !strings.Contains(err.Error(), "use of closed network connection") { panic(err) } }
// This is an example for serving HTTP and HTTPS on the same port. func Example_bothHTTPAndHTTPS() { // Create the TCP listener. l, err := net.Listen("tcp", "127.0.0.1:50051") if err != nil { log.Panic(err) } // Create a mux. m := cmux.New(l) // We first match on HTTP 1.1 methods. httpl := m.Match(cmux.HTTP1Fast()) // If not matched, we assume that its TLS. // // Note that you can take this listener, do TLS handshake and // create another mux to multiplex the connections over TLS. tlsl := m.Match(cmux.Any()) go serveHTTP1(httpl) go serveHTTPS(tlsl) if err := m.Serve(); !strings.Contains(err.Error(), "use of closed network connection") { panic(err) } }
func Example() { l, err := net.Listen("tcp", "127.0.0.1:50051") if err != nil { log.Panic(err) } m := cmux.New(l) // We first match the connection against HTTP2 fields. If matched, the // connection will be sent through the "grpcl" listener. grpcl := m.Match(cmux.HTTP2HeaderField("content-type", "application/grpc")) //Otherwise, we match it againts a websocket upgrade request. wsl := m.Match(cmux.HTTP1HeaderField("Upgrade", "websocket")) // Otherwise, we match it againts HTTP1 methods. If matched, // it is sent through the "httpl" listener. httpl := m.Match(cmux.HTTP1Fast()) // If not matched by HTTP, we assume it is an RPC connection. rpcl := m.Match(cmux.Any()) // Then we used the muxed listeners. go serveGRPC(grpcl) go serveWS(wsl) go serveHTTP(httpl) go serveRPC(rpcl) if err := m.Serve(); !strings.Contains(err.Error(), "use of closed network connection") { panic(err) } }
// Start starts the server on the specified port, starts gossip and // initializes the node using the engines from the server's context. func (s *Server) Start() error { s.initHTTP() tlsConfig, err := s.ctx.GetServerTLSConfig() if err != nil { return err } // 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 } unresolvedAddr, err := officialAddr(s.ctx.Addr, ln.Addr()) if err != nil { return err } s.ctx.Addr = unresolvedAddr.String() s.rpcContext.SetLocalInternalServer(s.node, s.ctx.Addr) s.stopper.RunWorker(func() { <-s.stopper.ShouldDrain() if err := ln.Close(); err != nil { log.Fatal(err) } }) 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.ShouldDrain() if err := httpLn.Close(); err != nil { log.Fatal(err) } }) if tlsConfig != nil { httpMux := cmux.New(httpLn) clearL := httpMux.Match(cmux.HTTP1Fast()) tlsL := httpMux.Match(cmux.Any()) s.stopper.RunWorker(func() { util.FatalIfUnexpected(httpMux.Serve()) }) util.ServeHandler(s.stopper, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // TODO(tamird): s/308/http.StatusPermanentRedirect/ when it exists. http.Redirect(w, r, "https://"+r.Host+r.RequestURI, 308) }), clearL, tlsConfig) httpLn = tls.NewListener(tlsL, tlsConfig) } serveConn := util.ServeHandler(s.stopper, s, httpLn, tlsConfig) s.stopper.RunWorker(func() { util.FatalIfUnexpected(s.grpc.Serve(anyL)) }) s.stopper.RunWorker(func() { util.FatalIfUnexpected(serveConn(pgL, func(conn net.Conn) { if err := s.pgServer.ServeConn(conn); err != nil && !util.IsClosedConnection(err) { log.Error(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.ShouldDrain() if err := unixLn.Close(); err != nil { log.Fatal(err) } }) s.stopper.RunWorker(func() { util.FatalIfUnexpected(serveConn(unixLn, func(conn net.Conn) { if err := s.pgServer.ServeConn(conn); err != nil && !util.IsClosedConnection(err) { log.Error(err) } })) }) } s.gossip.Start(s.grpc, unresolvedAddr) if err := s.node.start(unresolvedAddr, s.ctx.Engines, s.ctx.NodeAttributes); err != nil { return err } // 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. s.schemaChangeManager = sql.NewSchemaChangeManager(*s.db, s.gossip, s.leaseMgr) s.schemaChangeManager.Start(s.stopper) s.periodicallyCheckForUpdates() log.Infof("starting %s server at %s", s.ctx.HTTPRequestScheme(), unresolvedHTTPAddr) log.Infof("starting grpc/postgres server at %s", unresolvedAddr) if len(s.ctx.SocketFile) != 0 { log.Infof("starting postgres server at unix:%s", s.ctx.SocketFile) } s.stopper.RunWorker(func() { util.FatalIfUnexpected(m.Serve()) }) // Register admin service. Must happen after serving starts. s.stopper.AddCloser(s.admin) RegisterAdminServer(s.grpc, s.admin) return s.admin.RegisterGRPCGateway(s.ctx) }
// Start starts the server on the specified port, starts gossip and // initializes the node using the engines from the server's context. func (s *Server) Start() error { 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) { // TODO(tamird): s/308/http.StatusPermanentRedirect/ when it exists. http.Redirect(w, r, "https://"+r.Host+r.RequestURI, 308) })) // 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 } unresolvedAddr, err := officialAddr(s.ctx.Addr, ln.Addr()) if err != nil { return err } s.ctx.Addr = unresolvedAddr.String() s.rpcContext.SetLocalInternalServer(s.node) s.stopper.RunWorker(func() { <-s.stopper.ShouldDrain() if err := ln.Close(); err != nil { log.Fatal(err) } }) 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.ShouldDrain() if err := httpLn.Close(); err != nil { log.Fatal(err) } }) if tlsConfig != nil { httpMux := cmux.New(httpLn) clearL := httpMux.Match(cmux.HTTP1Fast()) 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() { netutil.FatalIfUnexpected(s.grpc.Serve(anyL)) }) s.stopper.RunWorker(func() { netutil.FatalIfUnexpected(httpServer.ServeWith(pgL, func(conn net.Conn) { if err := s.pgServer.ServeConn(conn); err != nil && !netutil.IsClosedConnection(err) { log.Error(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.ShouldDrain() if err := unixLn.Close(); err != nil { log.Fatal(err) } }) s.stopper.RunWorker(func() { netutil.FatalIfUnexpected(httpServer.ServeWith(unixLn, func(conn net.Conn) { if err := s.pgServer.ServeConn(conn); err != nil && !netutil.IsClosedConnection(err) { log.Error(err) } })) }) } s.gossip.Start(s.grpc, unresolvedAddr) if err := s.node.start(unresolvedAddr, s.ctx.Engines, s.ctx.NodeAttributes); err != nil { return err } // 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("starting %s server at %s", s.ctx.HTTPRequestScheme(), unresolvedHTTPAddr) log.Infof("starting grpc/postgres server at %s", unresolvedAddr) if len(s.ctx.SocketFile) != 0 { log.Infof("starting postgres server at unix:%s", s.ctx.SocketFile) } s.stopper.RunWorker(func() { netutil.FatalIfUnexpected(m.Serve()) }) // 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(context.Background()) s.stopper.AddCloser(stop.CloserFn(gwCancel)) // Setup HTTP<->gRPC handlers. var opts []grpc.DialOption if s.ctx.Insecure { opts = append(opts, grpc.WithInsecure()) } else { tlsConfig, err := s.ctx.GetClientTLSConfig() if err != nil { return err } opts = append( opts, // TODO(tamird): remove this timeout. It is currently necessary because // GRPC will not actually bail on a bad certificate error - it will just // retry indefinitely. See https://github.com/grpc/grpc-go/issues/622. grpc.WithTimeout(base.NetworkTimeout), grpc.WithBlock(), grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)), ) } conn, err := s.rpcContext.GRPCDial(s.ctx.Addr, opts...) 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 if envutil.EnvOrDefaultBool("debug_ui", false) { uiFileSystem = http.Dir("ui") } else { uiFileSystem = &assetfs.AssetFS{ Asset: ui.Asset, AssetDir: ui.AssetDir, AssetInfo: ui.AssetInfo, } } s.mux.Handle("/", http.FileServer(uiFileSystem)) // TODO(marc): when cookie-based authentication exists, // apply it for all web endpoints. s.mux.HandleFunc(debugEndpoint, http.HandlerFunc(handleDebug)) s.mux.Handle(adminEndpoint, gwMux) s.mux.Handle(ts.URLPrefix, gwMux) s.mux.Handle(statusPrefix, s.status) s.mux.Handle(healthEndpoint, s.status) if err := sdnotify.Ready(); err != nil { log.Errorf("failed to signal readiness using systemd protocol: %s", err) } return nil }