/
dazBlog.go
216 lines (175 loc) · 6.29 KB
/
dazBlog.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
// Copyright 2023 daz-3ux(杨鹏达) <daz-3ux@proton.me>. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. The original repo for
// this file is https://github.com/Daz-3ux/dBlog.
package dazBlog
import (
"context"
"errors"
"fmt"
"net"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"google.golang.org/grpc"
"github.com/Daz-3ux/dBlog/internal/dazBlog/controller/v1/user"
"github.com/Daz-3ux/dBlog/internal/dazBlog/store"
"github.com/Daz-3ux/dBlog/internal/pkg/known"
"github.com/Daz-3ux/dBlog/internal/pkg/log"
mw "github.com/Daz-3ux/dBlog/internal/pkg/middleware"
pb "github.com/Daz-3ux/dBlog/pkg/proto/dazBlog/v1"
"github.com/Daz-3ux/dBlog/pkg/token"
"github.com/Daz-3ux/dBlog/pkg/version/verflag"
)
var cfgFile string
// NewDazBlogCommand create the *cobra.Command object
// so can use the Execute method to start
func NewDazBlogCommand() *cobra.Command {
// cmd is the *cobra.Command object and the top-level command
cmd := &cobra.Command{
// specify the name of the command
Use: "dBlog", // specify the short description of the command
Short: "dBlog is a simple blog system", // specify the long description of the command
Long: `dBlog is a simple but not easy blog system,
Find more dBlog information at:
https://github.com/daz-3ux/dBlog#readme`,
// when an error occurs, the command will not print usage information
SilenceUsage: true, // specify the run function to execute when cmd.Execute() is called
// if the function fails, an error message will be returned
RunE: func(cmd *cobra.Command, args []string) error {
verflag.PrintAndExitIfRequested()
log.Init(logOptions())
defer log.Sync()
return run()
}, // no need to specify command line parameters
Args: func(cmd *cobra.Command, args []string) error {
for _, arg := range args {
if len(arg) > 0 {
return fmt.Errorf("%q does not take any arguments, got %q", cmd.CommandPath(), args)
}
}
return nil
},
}
// The following settings make the initConfig function be called
// every time when the command is executed to read the configuration.
cobra.OnInitialize(initConfig)
// define custom flag and config
// cobra supports a persistent flag
// for providing options that work across all subs-commands of a command
cmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "The path to the dBlog configuration file. Empty string for no configuration file.")
// cobra supports local flag
// which can only be used within the command to which they are bound
cmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
// add --version flag
verflag.AddFlags(cmd.PersistentFlags())
return cmd
}
func run() error {
// init the store layer
if err := initStore(); err != nil {
return err
}
// set the secret key used to sign and parse the token
token.Init(viper.GetString("jwt-secret"), known.XUsernameKey)
// set Gin mode
gin.SetMode(viper.GetString("runmode"))
// create Gin engine
g := gin.New()
// gin.Recovery() middleware is used to capture any panics and recover from them
mws := []gin.HandlerFunc{gin.Recovery(), mw.NoCache, mw.Cors, mw.Secure, mw.RequestID()}
g.Use(mws...)
if err := installRouters(g); err != nil {
return err
}
// create HTTP server
httpsrv := startInsecureServer(g)
// create HTTPS server
httpssrv := startSecureServer(g)
// create gRPC server
grpcsrv := startGRPCServer()
// wait for an interrupt signal to gracefully shut down the server with a 10s timeout
quit := make(chan os.Signal, 1)
// kill (no param) default sends syscall.SIGTERM
// kill -2 is syscall.SIGINT(CTRL + C)
// kill -9 is syscall.SIGKILL but can't be caught, so don't need to add it
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit // block until receive the signal
log.Infow("Shutting down server......")
// create a context with a timeout of 10 seconds
// used to signal server goroutines that they have 10s to complete the current request
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// gracefully shut down the server: handle any unprocessed requests
if err := httpsrv.Shutdown(ctx); err != nil {
log.Fatalw("Insecure Server forced to shutdown:", "err", err)
return err
}
if err := httpssrv.Shutdown(ctx); err != nil {
log.Errorw("Secure Server forced to shutdown:", "err", err)
return err
}
grpcsrv.GracefulStop()
log.Infow("Server exiting")
return nil
}
// startInsecureServer create and Run HTTP server
func startInsecureServer(g *gin.Engine) *http.Server {
// create HTTP server instance
httpsrv := &http.Server{
Addr: viper.GetString("addr"),
Handler: g,
}
// start the server in a goroutine
log.Infow("Start to listening the incoming requests on http address", "addr", viper.GetString("addr"))
fn := func() {
if err := httpsrv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatalw(err.Error())
}
}
go fn()
return httpsrv
}
// startSecureServer create and Run HTTPS server
func startSecureServer(g *gin.Engine) *http.Server {
// create HTTPS Server instance
httpssrv := &http.Server{
Addr: viper.GetString("tls.addr"),
Handler: g,
}
// start the server in a goroutine
log.Infow("Start to listening the incoming requests on https address", "addr", viper.GetString("tls.addr"))
cert, key := viper.GetString("tls.cert"), viper.GetString("tls.key")
if cert != "" && key != "" {
go func() {
if err := httpssrv.ListenAndServeTLS(cert, key); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatalw(err.Error())
}
}()
}
return httpssrv
}
// startGRPCServer create and Run gRPC server
func startGRPCServer() *grpc.Server {
lis, err := net.Listen("tcp", viper.GetString("grpc.addr"))
if err != nil {
log.Fatalw("failed to listen", "err", err)
}
// create gRPC server instance
grpcsrv := grpc.NewServer()
pb.RegisterDazBlogServer(grpcsrv, user.NewUserController(store.S, nil))
// start the server in a goroutine
log.Infow("Start to listening the incoming requests on gRPC address", "addr", viper.GetString("grpc.addr"))
fn := func() {
if err := grpcsrv.Serve(lis); err != nil {
log.Fatalw("failed to serve", "err", err)
}
}
go fn()
return grpcsrv
}