Skip to content

Commit b661c5a

Browse files
committed
Eric-Guo/sponge-user_server-web-http@0babfcb is applied to the generated source code, sponge is the code generator, I want to apply those change to the template on the sponge, so I will get the same result when I generate the code in future. To make it simpler, this time, you only focus on serverType == codeNameHTTP, ie. serverNameExample_httpExample. please modify the sponge template now.
1 parent 1903730 commit b661c5a

File tree

9 files changed

+249
-28
lines changed

9 files changed

+249
-28
lines changed

cmd/serverNameExample_httpExample/initial/createService.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package initial
22

33
import (
4-
"strconv"
5-
64
"github.com/go-dev-frame/sponge/internal/config"
75
"github.com/go-dev-frame/sponge/internal/server"
86

@@ -15,8 +13,7 @@ func CreateServices() []app.IServer {
1513
var servers []app.IServer
1614

1715
// create a http service
18-
httpAddr := ":" + strconv.Itoa(cfg.HTTP.Port)
19-
httpServer := server.NewHTTPServer(httpAddr,
16+
httpServer := server.NewHTTPServer(cfg.HTTP,
2017
server.WithHTTPIsProd(cfg.App.Env == "prod"),
2118
)
2219
servers = append(servers, httpServer)

cmd/sponge/commands/generate/common.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,10 @@ func getHTTPServiceFields() []replacer.Field {
10231023
Old: appConfigFileMark3,
10241024
New: "",
10251025
},
1026+
{
1027+
Old: "http_test.go.noregistry",
1028+
New: "http_test.go",
1029+
},
10261030
{
10271031
Old: "http.go.noregistry",
10281032
New: "http.go",

cmd/sponge/commands/generate/http-pb.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ func (g *httpPbGenerator) generateCode() (string, error) {
137137
"routers_pbExample.go",
138138
},
139139
"internal/server": {
140-
"http.go.noregistry", "http_option.go.noregistry",
140+
"http.go.noregistry", "http_option.go.noregistry", "http_test.go.noregistry",
141141
},
142142
}
143143

cmd/sponge/commands/generate/http.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ func (g *httpGenerator) generateCode() (string, error) {
232232
"routers.go", "userExample.go",
233233
},
234234
"internal/server": {
235-
"http.go.noregistry", "http_option.go.noregistry",
235+
"http.go.noregistry", "http_option.go.noregistry", "http_test.go.noregistry",
236236
},
237237
"internal/types": {
238238
"swagger_types.go", "userExample_types.go",

cmd/sponge/commands/generate/template.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,19 @@ func NewCenter(configFile string) (*Center, error) {
370370
httpServerConfigCode = `# http server settings
371371
http:
372372
port: 8080 # listen port
373-
timeout: 0 # request timeout, unit(second), if 0 means not set, if greater than 0 means set timeout, if enableHTTPProfile is true, it needs to set 0 or greater than 60s`
373+
httpsPort: 8443 # https listen port when tls is enabled
374+
timeout: 0 # request timeout, unit(second), if 0 means not set, if greater than 0 means set timeout, if enableHTTPProfile is true, it needs to set 0 or greater than 60s
375+
idleTimeout: 60 # http idle timeout, unit(second)
376+
readTimeout: 30 # http read timeout, unit(second)
377+
writeTimeout: 30 # http write timeout, unit(second)
378+
tls:
379+
domains:
380+
- "" # list of domains for automatic tls certificates, empty disables tls
381+
acmeDirectory: "https://acme-v02.api.letsencrypt.org/directory" # acme directory url
382+
storagePath: "./storage/autocert" # directory to cache certificates
383+
eab:
384+
kid: "" # external account binding key identifier
385+
hmacKey: "" # base64url encoded external account binding hmac key`
374386

375387
rpcServerConfigCode = `# grpc server settings
376388
grpc:

configs/serverNameExample.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,19 @@ app:
2222
# http server settings
2323
http:
2424
port: 8080 # listen port
25-
timeout: 0 # request timeout, unit(second), if 0 means not set, if greater than 0 means set timeout, if enableHTTPProfile is true, it needs to set 0 or greater than 60s
25+
httpsPort: 8443 # https listen port when tls is enabled
26+
timeout: 0 # request timeout, unit(second), if 0 means not set, if greater than 0 means set timeout, if enableHTTPProfile is true, it needs to set 0 or greater than 60s
27+
idleTimeout: 60 # http idle timeout, unit(second)
28+
readTimeout: 30 # http read timeout, unit(second)
29+
writeTimeout: 30 # http write timeout, unit(second)
30+
tls:
31+
domains:
32+
- "" # list of domains for automatic tls certificates, empty disables tls
33+
acmeDirectory: "https://acme-v02.api.letsencrypt.org/directory" # acme directory url
34+
storagePath: "./storage/autocert" # directory to cache certificates
35+
eab:
36+
kid: "" # external account binding key identifier
37+
hmacKey: "" # base64url encoded external account binding hmac key
2638

2739

2840
# grpc server settings

internal/server/http.go.noregistry

Lines changed: 116 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,102 @@ package server
22

33
import (
44
"context"
5+
"encoding/base64"
6+
"errors"
57
"fmt"
8+
"net"
69
"net/http"
10+
"strings"
711
"time"
812

913
"github.com/gin-gonic/gin"
14+
"golang.org/x/crypto/acme"
15+
"golang.org/x/crypto/acme/autocert"
1016

1117
"github.com/go-dev-frame/sponge/pkg/app"
18+
"github.com/go-dev-frame/sponge/pkg/logger"
1219

20+
"github.com/go-dev-frame/sponge/internal/config"
1321
"github.com/go-dev-frame/sponge/internal/routers"
1422
)
1523

1624
var _ app.IServer = (*httpServer)(nil)
1725

1826
type httpServer struct {
19-
addr string
20-
server *http.Server
27+
httpAddr string
28+
httpsAddr string
29+
httpServer *http.Server
30+
httpsServer *http.Server
31+
tlsEnabled bool
2132
}
2233

23-
// Start http service
34+
// Start http/https service
2435
func (s *httpServer) Start() error {
25-
if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
26-
return fmt.Errorf("listen server error: %v", err)
36+
if s.tlsEnabled {
37+
errCh := make(chan error, 2)
38+
39+
go func() {
40+
errCh <- listenAndServe(s.httpServer)
41+
}()
42+
43+
go func() {
44+
errCh <- listenAndServeTLS(s.httpsServer)
45+
}()
46+
47+
var firstErr error
48+
for i := 0; i < 2; i++ {
49+
if err := <-errCh; err != nil {
50+
if firstErr == nil {
51+
firstErr = err
52+
} else {
53+
logger.Error("http server encountered multiple errors", logger.Err(err))
54+
}
55+
}
56+
}
57+
58+
return firstErr
2759
}
60+
61+
if err := listenAndServe(s.httpServer); err != nil {
62+
return err
63+
}
64+
2865
return nil
2966
}
3067

31-
// Stop http service
68+
// Stop http/https service
3269
func (s *httpServer) Stop() error {
33-
ctx, _ := context.WithTimeout(context.Background(), 3*time.Second) //nolint
34-
return s.server.Shutdown(ctx)
70+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
71+
defer cancel()
72+
73+
var firstErr error
74+
if err := s.httpServer.Shutdown(ctx); err != nil {
75+
firstErr = err
76+
}
77+
78+
if s.tlsEnabled && s.httpsServer != nil {
79+
if err := s.httpsServer.Shutdown(ctx); err != nil && !errors.Is(err, context.Canceled) {
80+
if firstErr == nil {
81+
firstErr = err
82+
} else {
83+
logger.Error("https server shutdown reported additional error", logger.Err(err))
84+
}
85+
}
86+
}
87+
88+
return firstErr
3589
}
3690

37-
// String comment
91+
// String provides a human readable description of listener addresses.
3892
func (s *httpServer) String() string {
39-
return "http service address " + s.addr
93+
if s.tlsEnabled {
94+
return fmt.Sprintf("http service redirecting on %s and https service address %s", s.httpAddr, s.httpsAddr)
95+
}
96+
return "http service address " + s.httpAddr
4097
}
4198

42-
// NewHTTPServer creates a new http server
43-
func NewHTTPServer(addr string, opts ...HTTPOption) app.IServer {
99+
// NewHTTPServer creates an HTTP server with optional automatic TLS.
100+
func NewHTTPServer(cfg config.HTTP, opts ...HTTPOption) app.IServer {
44101
o := defaultHTTPOptions()
45102
o.apply(opts...)
46103

@@ -50,16 +107,57 @@ func NewHTTPServer(addr string, opts ...HTTPOption) app.IServer {
50107
gin.SetMode(gin.DebugMode)
51108
}
52109

53-
router := routers.NewRouter()
54-
server := &http.Server{
55-
Addr: addr,
56-
Handler: router,
110+
appHandler := o.handler
111+
if appHandler == nil {
112+
appHandler = routers.NewRouter()
113+
}
114+
115+
readTimeout := secondsToDuration(cfg.ReadTimeout)
116+
writeTimeout := secondsToDuration(cfg.WriteTimeout)
117+
idleTimeout := secondsToDuration(cfg.IdleTimeout)
118+
119+
httpSrv := &http.Server{
120+
Addr: fmt.Sprintf(":%d", cfg.Port),
121+
Handler: appHandler,
122+
ReadTimeout: readTimeout,
123+
WriteTimeout: writeTimeout,
124+
IdleTimeout: idleTimeout,
57125
MaxHeaderBytes: 1 << 20,
58126
}
59127

128+
domains := filterDomains(cfg.TLS.Domains)
129+
tlsEnabled := len(domains) > 0
130+
131+
var (
132+
httpsSrv *http.Server
133+
httpsAddr string
134+
)
135+
if tlsEnabled {
136+
manager := buildAutocertManager(cfg, domains)
137+
httpSrv.Handler = manager.HTTPHandler(http.HandlerFunc(httpRedirectHandler))
138+
139+
httpsSrv = &http.Server{
140+
Addr: fmt.Sprintf(":%d", cfg.HTTPSPort),
141+
Handler: appHandler,
142+
ReadTimeout: readTimeout,
143+
WriteTimeout: writeTimeout,
144+
IdleTimeout: idleTimeout,
145+
MaxHeaderBytes: 1 << 20,
146+
TLSConfig: manager.TLSConfig(),
147+
}
148+
httpsAddr = httpsSrv.Addr
149+
150+
logger.Info("automatic TLS enabled", logger.String("http_addr", httpSrv.Addr), logger.String("https_addr", httpsSrv.Addr), logger.Any("domains", domains))
151+
} else {
152+
logger.Info("automatic TLS disabled", logger.String("http_addr", httpSrv.Addr))
153+
}
154+
60155
return &httpServer{
61-
addr: addr,
62-
server: server,
156+
httpAddr: httpSrv.Addr,
157+
httpsAddr: httpsAddr,
158+
httpServer: httpSrv,
159+
httpsServer: httpsSrv,
160+
tlsEnabled: tlsEnabled,
63161
}
64162
}
65163

internal/server/http_option.go.noregistry

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
package server
22

3+
import "net/http"
4+
35
// HTTPOption setting up http
46
type HTTPOption func(*httpOptions)
57

68
type httpOptions struct {
7-
isProd bool
9+
isProd bool
10+
handler http.Handler
811
}
912

1013
func defaultHTTPOptions() *httpOptions {
1114
return &httpOptions{
12-
isProd: false,
15+
isProd: false,
16+
handler: nil,
1317
}
1418
}
1519

@@ -25,3 +29,10 @@ func WithHTTPIsProd(isProd bool) HTTPOption {
2529
o.isProd = isProd
2630
}
2731
}
32+
33+
// WithHTTPHandler allows injecting a custom http handler (primarily for testing)
34+
func WithHTTPHandler(handler http.Handler) HTTPOption {
35+
return func(o *httpOptions) {
36+
o.handler = handler
37+
}
38+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package server
2+
3+
import (
4+
"encoding/base64"
5+
"net/http"
6+
"testing"
7+
"time"
8+
9+
"github.com/stretchr/testify/require"
10+
"golang.org/x/crypto/acme"
11+
12+
"github.com/go-dev-frame/sponge/internal/config"
13+
)
14+
15+
func setTestConfig(t *testing.T, httpCfg config.HTTP) {
16+
t.Helper()
17+
config.Set(&config.Config{
18+
App: config.App{
19+
Env: "dev",
20+
},
21+
HTTP: httpCfg,
22+
})
23+
t.Cleanup(func() {
24+
config.Set(nil)
25+
})
26+
}
27+
28+
func TestSecondsToDuration(t *testing.T) {
29+
require.Equal(t, time.Duration(0), secondsToDuration(0))
30+
require.Equal(t, time.Duration(0), secondsToDuration(-1))
31+
require.Equal(t, 30*time.Second, secondsToDuration(30))
32+
}
33+
34+
func TestFilterDomains(t *testing.T) {
35+
input := []string{"example.com", "", " other.example.com "}
36+
result := filterDomains(input)
37+
require.Equal(t, []string{"example.com", "other.example.com"}, result)
38+
}
39+
40+
func TestExternalAccountBinding(t *testing.T) {
41+
// returns nil when kid or key missing
42+
require.Nil(t, externalAccountBinding(config.Eab{}))
43+
44+
key := []byte("secret")
45+
encoded := base64.RawURLEncoding.EncodeToString(key)
46+
binding := externalAccountBinding(config.Eab{Kid: "kid", HmacKey: encoded})
47+
require.NotNil(t, binding)
48+
require.Equal(t, "kid", binding.KID)
49+
require.Equal(t, key, binding.Key)
50+
}
51+
52+
func TestNewHTTPServer_TLSEnabled(t *testing.T) {
53+
httpCfg := config.HTTP{
54+
Port: 8080,
55+
HTTPSPort: 8443,
56+
IdleTimeout: 10,
57+
ReadTimeout: 5,
58+
WriteTimeout: 5,
59+
TLS: config.TLS{
60+
Domains: []string{"example.com"},
61+
AcmeDirectory: acme.LetsEncryptURL,
62+
StoragePath: t.TempDir(),
63+
},
64+
}
65+
66+
setTestConfig(t, httpCfg)
67+
server := NewHTTPServer(httpCfg, WithHTTPHandler(http.NewServeMux())).(*httpServer)
68+
require.True(t, server.tlsEnabled)
69+
require.NotNil(t, server.httpsServer)
70+
require.Equal(t, ":8080", server.httpAddr)
71+
require.Equal(t, ":8443", server.httpsServer.Addr)
72+
}
73+
74+
func TestNewHTTPServer_TLSDisabledWhenNoDomains(t *testing.T) {
75+
httpCfg := config.HTTP{
76+
Port: 8080,
77+
HTTPSPort: 8443,
78+
TLS: config.TLS{
79+
Domains: []string{"", " "},
80+
},
81+
}
82+
83+
setTestConfig(t, httpCfg)
84+
server := NewHTTPServer(httpCfg, WithHTTPHandler(http.NewServeMux())).(*httpServer)
85+
require.False(t, server.tlsEnabled)
86+
require.Nil(t, server.httpsServer)
87+
}

0 commit comments

Comments
 (0)