Repository: goadesign/goa
Pull request #2428 (2019-12-14 02:01:58)

Properly handle server main handler
This is backport fo #2419 to v3.

raphael: Thank you!

Patch:
diff --git a/codegen/example/example_server.go b/codegen/example/example_server.go
index 4463b436..2285e6f4 100644
--- a/codegen/example/example_server.go
+++ b/codegen/example/example_server.go
@@ -278,7 +278,7 @@ func main() {
 			} else if u.Port() == "" {
 				u.Host += ":{{ $u.Port }}"
 			}
-			handle{{ toUpper $u.Transport.Name }}Server(ctx, u, {{ range $.Services }}{{ if .Methods }}{{ .VarName }}Endpoints, {{ end }}{{ end }}&wg, errc, logger, *dbgF)
+			handle{{ toUpper $u.Transport.Name }}Server(ctx, u, {{ range $t := $.Server.Transports }}{{ if eq $t.Type $u.Transport.Type }}{{ range $s := $t.Services }}{{ range $.Services }}{{ if eq $s .Name }}{{ if .Methods }}{{ .VarName }}Endpoints, {{ end }}{{ end }}{{ end }}{{ end }}{{ end }}{{ end }}&wg, errc, logger, *dbgF)
 		}
 	{{- end }}
 	{{ end }}
diff --git a/codegen/example/server_data.go b/codegen/example/server_data.go
index 22494e6b..e27ba660 100644
--- a/codegen/example/server_data.go
+++ b/codegen/example/server_data.go
@@ -92,6 +92,8 @@ type (
 		Type Transport
 		// Name is the transport name.
 		Name string
+		// Services is the list of services supported by the transport.
+		Services []string
 	}
 
 	// Transport is a type for supported goa transports.
@@ -197,7 +199,9 @@ func buildServerData(svr *expr.ServerExpr) *Data {
 	}
 
 	var (
-		transports []*TransportData
+		transports   []*TransportData
+		httpServices []string
+		grpcServices []string
 
 		foundTrans = make(map[Transport]struct{})
 	)
@@ -205,20 +209,30 @@ func buildServerData(svr *expr.ServerExpr) *Data {
 		for _, svc := range svr.Services {
 			_, seenHTTP := foundTrans[TransportHTTP]
 			_, seenGRPC := foundTrans[TransportGRPC]
-			if seenHTTP && seenGRPC {
-				// only HTTP and gRPC are supported right now.
-				break
-			}
-			if expr.Root.API.HTTP.Service(svc) != nil && !seenHTTP {
-				transports = append(transports, newHTTPTransport())
-				foundTrans[TransportHTTP] = struct{}{}
+			if expr.Root.API.HTTP.Service(svc) != nil {
+				httpServices = append(httpServices, svc)
+				if !seenHTTP {
+					transports = append(transports, newHTTPTransport())
+					foundTrans[TransportHTTP] = struct{}{}
+				}
 			}
-			if expr.Root.API.GRPC.Service(svc) != nil && !seenGRPC {
-				transports = append(transports, newGRPCTransport())
-				foundTrans[TransportGRPC] = struct{}{}
+			if expr.Root.API.GRPC.Service(svc) != nil {
+				grpcServices = append(grpcServices, svc)
+				if !seenGRPC {
+					transports = append(transports, newGRPCTransport())
+					foundTrans[TransportGRPC] = struct{}{}
+				}
 			}
 		}
 	}
+	for _, transport := range transports {
+		switch transport.Type {
+		case TransportHTTP:
+			transport.Services = httpServices
+		case TransportGRPC:
+			transport.Services = grpcServices
+		}
+	}
 	return &Data{
 		Name:        svr.Name,
 		Description: svr.Description,


Test patch:
diff --git a/codegen/example/example_server_test.go b/codegen/example/example_server_test.go
index fa616905..8f139411 100644
--- a/codegen/example/example_server_test.go
+++ b/codegen/example/example_server_test.go
@@ -27,6 +27,9 @@ func TestExampleServerFiles(t *testing.T) {
 		{"single-server-multiple-hosts", testdata.SingleServerMultipleHostsDSL, testdata.SingleServerMultipleHostsServerMainCode},
 		{"single-server-multiple-hosts-with-variables", testdata.SingleServerMultipleHostsWithVariablesDSL, testdata.SingleServerMultipleHostsWithVariablesServerMainCode},
 		{"service-name-with-spaces", ctestdata.NamesWithSpacesDSL, testdata.NamesWithSpacesServerMainCode},
+		{"service-for-only-http", testdata.ServiceForOnlyHTTPDSL, testdata.ServiceForOnlyHTTPServerMainCode},
+		{"sercice-for-only-grpc", testdata.ServiceForOnlyGRPCDSL, testdata.ServiceForOnlyGRPCServerMainCode},
+		{"service-for-http-and-part-of-grpc", testdata.ServiceForHTTPAndPartOfGRPCDSL, testdata.ServiceForHTTPAndPartOfGRPCServerMainCode},
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
diff --git a/codegen/example/testdata/dsls.go b/codegen/example/testdata/dsls.go
index 1a78fede..6378f6d7 100644
--- a/codegen/example/testdata/dsls.go
+++ b/codegen/example/testdata/dsls.go
@@ -208,6 +208,42 @@ var SingleServerMultipleHostsWithVariablesDSL = func() {
 	})
 }
 
+var ServiceForOnlyHTTPDSL = func() {
+	Service("Service", func() {
+		Method("Method", func() {
+			HTTP(func() {
+				GET("/")
+			})
+		})
+	})
+}
+
+var ServiceForOnlyGRPCDSL = func() {
+	Service("Service", func() {
+		Method("Method", func() {
+			GRPC(func() {})
+		})
+	})
+}
+
+var ServiceForHTTPAndPartOfGRPCDSL = func() {
+	Service("Service", func() {
+		Method("Method", func() {
+			HTTP(func() {
+				GET("/")
+			})
+			GRPC(func() {})
+		})
+	})
+	Service("AnotherService", func() {
+		Method("Method", func() {
+			HTTP(func() {
+				GET("/")
+			})
+		})
+	})
+}
+
 var ConflictWithAPINameAndServiceNamesIncludingMultipartDSL = func() {
 	var _ = API("aloha", func() {
 		Title("conflict with API name and service names including multipart")
diff --git a/codegen/example/testdata/example_server_code.go b/codegen/example/testdata/example_server_code.go
index d19bfa18..7c0162db 100644
--- a/codegen/example/testdata/example_server_code.go
+++ b/codegen/example/testdata/example_server_code.go
@@ -1179,5 +1179,309 @@ const (
 	wg.Wait()
 	logger.Println("exited")
 }
+`
+
+	ServiceForOnlyHTTPServerMainCode = `func main() {
+	// Define command line flags, add any other flag required to configure the
+	// service.
+	var (
+		hostF     = flag.String("host", "localhost", "Server host (valid values: localhost)")
+		domainF   = flag.String("domain", "", "Host domain name (overrides host domain specified in service design)")
+		httpPortF = flag.String("http-port", "", "HTTP port (overrides host HTTP port specified in service design)")
+		secureF   = flag.Bool("secure", false, "Use secure scheme (https or grpcs)")
+		dbgF      = flag.Bool("debug", false, "Log request and response bodies")
+	)
+	flag.Parse()
+
+	// Setup logger. Replace logger with your own log package of choice.
+	var (
+		logger *log.Logger
+	)
+	{
+		logger = log.New(os.Stderr, "[testapi] ", log.Ltime)
+	}
+
+	// Initialize the services.
+	var (
+		serviceSvc service.Service
+	)
+	{
+		serviceSvc = testapi.NewService(logger)
+	}
+
+	// Wrap the services in endpoints that can be invoked from other services
+	// potentially running in different processes.
+	var (
+		serviceEndpoints *service.Endpoints
+	)
+	{
+		serviceEndpoints = service.NewEndpoints(serviceSvc)
+	}
+
+	// Create channel used by both the signal handler and server goroutines
+	// to notify the main goroutine when to stop the server.
+	errc := make(chan error)
+
+	// Setup interrupt handler. This optional step configures the process so
+	// that SIGINT and SIGTERM signals cause the services to stop gracefully.
+	go func() {
+		c := make(chan os.Signal, 1)
+		signal.Notify(c, os.Interrupt)
+		errc <- fmt.Errorf("%s", <-c)
+	}()
+
+	var wg sync.WaitGroup
+	ctx, cancel := context.WithCancel(context.Background())
+
+	// Start the servers and send errors (if any) to the error channel.
+	switch *hostF {
+	case "localhost":
+		{
+			addr := "http://localhost:80"
+			u, err := url.Parse(addr)
+			if err != nil {
+				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
+				os.Exit(1)
+			}
+			if *secureF {
+				u.Scheme = "https"
+			}
+			if *domainF != "" {
+				u.Host = *domainF
+			}
+			if *httpPortF != "" {
+				h := strings.Split(u.Host, ":")[0]
+				u.Host = h + ":" + *httpPortF
+			} else if u.Port() == "" {
+				u.Host += ":80"
+			}
+			handleHTTPServer(ctx, u, serviceEndpoints, &wg, errc, logger, *dbgF)
+		}
+
+	default:
+		fmt.Fprintf(os.Stderr, "invalid host argument: %q (valid hosts: localhost)\n", *hostF)
+	}
+
+	// Wait for signal.
+	logger.Printf("exiting (%v)", <-errc)
+
+	// Send cancellation signal to the goroutines.
+	cancel()
+
+	wg.Wait()
+	logger.Println("exited")
+}
+`
+
+	ServiceForOnlyGRPCServerMainCode = `func main() {
+	// Define command line flags, add any other flag required to configure the
+	// service.
+	var (
+		hostF     = flag.String("host", "localhost", "Server host (valid values: localhost)")
+		domainF   = flag.String("domain", "", "Host domain name (overrides host domain specified in service design)")
+		grpcPortF = flag.String("grpc-port", "", "gRPC port (overrides host gRPC port specified in service design)")
+		secureF   = flag.Bool("secure", false, "Use secure scheme (https or grpcs)")
+		dbgF      = flag.Bool("debug", false, "Log request and response bodies")
+	)
+	flag.Parse()
+
+	// Setup logger. Replace logger with your own log package of choice.
+	var (
+		logger *log.Logger
+	)
+	{
+		logger = log.New(os.Stderr, "[testapi] ", log.Ltime)
+	}
+
+	// Initialize the services.
+	var (
+		serviceSvc service.Service
+	)
+	{
+		serviceSvc = testapi.NewService(logger)
+	}
+
+	// Wrap the services in endpoints that can be invoked from other services
+	// potentially running in different processes.
+	var (
+		serviceEndpoints *service.Endpoints
+	)
+	{
+		serviceEndpoints = service.NewEndpoints(serviceSvc)
+	}
+
+	// Create channel used by both the signal handler and server goroutines
+	// to notify the main goroutine when to stop the server.
+	errc := make(chan error)
+
+	// Setup interrupt handler. This optional step configures the process so
+	// that SIGINT and SIGTERM signals cause the services to stop gracefully.
+	go func() {
+		c := make(chan os.Signal, 1)
+		signal.Notify(c, os.Interrupt)
+		errc <- fmt.Errorf("%s", <-c)
+	}()
+
+	var wg sync.WaitGroup
+	ctx, cancel := context.WithCancel(context.Background())
+
+	// Start the servers and send errors (if any) to the error channel.
+	switch *hostF {
+	case "localhost":
+
+		{
+			addr := "grpc://localhost:8080"
+			u, err := url.Parse(addr)
+			if err != nil {
+				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
+				os.Exit(1)
+			}
+			if *secureF {
+				u.Scheme = "grpcs"
+			}
+			if *domainF != "" {
+				u.Host = *domainF
+			}
+			if *grpcPortF != "" {
+				h := strings.Split(u.Host, ":")[0]
+				u.Host = h + ":" + *grpcPortF
+			} else if u.Port() == "" {
+				u.Host += ":8080"
+			}
+			handleGRPCServer(ctx, u, serviceEndpoints, &wg, errc, logger, *dbgF)
+		}
+
+	default:
+		fmt.Fprintf(os.Stderr, "invalid host argument: %q (valid hosts: localhost)\n", *hostF)
+	}
+
+	// Wait for signal.
+	logger.Printf("exiting (%v)", <-errc)
+
+	// Send cancellation signal to the goroutines.
+	cancel()
+
+	wg.Wait()
+	logger.Println("exited")
+}
+`
+
+	ServiceForHTTPAndPartOfGRPCServerMainCode = `func main() {
+	// Define command line flags, add any other flag required to configure the
+	// service.
+	var (
+		hostF     = flag.String("host", "localhost", "Server host (valid values: localhost)")
+		domainF   = flag.String("domain", "", "Host domain name (overrides host domain specified in service design)")
+		httpPortF = flag.String("http-port", "", "HTTP port (overrides host HTTP port specified in service design)")
+		grpcPortF = flag.String("grpc-port", "", "gRPC port (overrides host gRPC port specified in service design)")
+		secureF   = flag.Bool("secure", false, "Use secure scheme (https or grpcs)")
+		dbgF      = flag.Bool("debug", false, "Log request and response bodies")
+	)
+	flag.Parse()
+
+	// Setup logger. Replace logger with your own log package of choice.
+	var (
+		logger *log.Logger
+	)
+	{
+		logger = log.New(os.Stderr, "[testapi] ", log.Ltime)
+	}
+
+	// Initialize the services.
+	var (
+		serviceSvc        service.Service
+		anotherServiceSvc anotherservice.Service
+	)
+	{
+		serviceSvc = testapi.NewService(logger)
+		anotherServiceSvc = testapi.NewAnotherService(logger)
+	}
+
+	// Wrap the services in endpoints that can be invoked from other services
+	// potentially running in different processes.
+	var (
+		serviceEndpoints        *service.Endpoints
+		anotherServiceEndpoints *anotherservice.Endpoints
+	)
+	{
+		serviceEndpoints = service.NewEndpoints(serviceSvc)
+		anotherServiceEndpoints = anotherservice.NewEndpoints(anotherServiceSvc)
+	}
+
+	// Create channel used by both the signal handler and server goroutines
+	// to notify the main goroutine when to stop the server.
+	errc := make(chan error)
+
+	// Setup interrupt handler. This optional step configures the process so
+	// that SIGINT and SIGTERM signals cause the services to stop gracefully.
+	go func() {
+		c := make(chan os.Signal, 1)
+		signal.Notify(c, os.Interrupt)
+		errc <- fmt.Errorf("%s", <-c)
+	}()
+
+	var wg sync.WaitGroup
+	ctx, cancel := context.WithCancel(context.Background())
+
+	// Start the servers and send errors (if any) to the error channel.
+	switch *hostF {
+	case "localhost":
+		{
+			addr := "http://localhost:80"
+			u, err := url.Parse(addr)
+			if err != nil {
+				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
+				os.Exit(1)
+			}
+			if *secureF {
+				u.Scheme = "https"
+			}
+			if *domainF != "" {
+				u.Host = *domainF
+			}
+			if *httpPortF != "" {
+				h := strings.Split(u.Host, ":")[0]
+				u.Host = h + ":" + *httpPortF
+			} else if u.Port() == "" {
+				u.Host += ":80"
+			}
+			handleHTTPServer(ctx, u, serviceEndpoints, anotherServiceEndpoints, &wg, errc, logger, *dbgF)
+		}
+
+		{
+			addr := "grpc://localhost:8080"
+			u, err := url.Parse(addr)
+			if err != nil {
+				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
+				os.Exit(1)
+			}
+			if *secureF {
+				u.Scheme = "grpcs"
+			}
+			if *domainF != "" {
+				u.Host = *domainF
+			}
+			if *grpcPortF != "" {
+				h := strings.Split(u.Host, ":")[0]
+				u.Host = h + ":" + *grpcPortF
+			} else if u.Port() == "" {
+				u.Host += ":8080"
+			}
+			handleGRPCServer(ctx, u, serviceEndpoints, &wg, errc, logger, *dbgF)
+		}
+
+	default:
+		fmt.Fprintf(os.Stderr, "invalid host argument: %q (valid hosts: localhost)\n", *hostF)
+	}
+
+	// Wait for signal.
+	logger.Printf("exiting (%v)", <-errc)
+
+	// Send cancellation signal to the goroutines.
+	cancel()
+
+	wg.Wait()
+	logger.Println("exited")
+}
 `
 )