/
options.go
247 lines (207 loc) · 6.96 KB
/
options.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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
// SPDX-FileCopyrightText: 2018-2024 caixw
//
// SPDX-License-Identifier: MIT
package server
import (
"encoding/json"
"encoding/xml"
"net/http"
"time"
"github.com/issue9/cache"
"github.com/issue9/cache/caches/memory"
"github.com/issue9/config"
"github.com/issue9/localeutil"
"github.com/issue9/logs/v7"
"github.com/issue9/mux/v8/group"
"github.com/issue9/mux/v8/header"
"github.com/issue9/unique/v2"
"golang.org/x/text/language"
"gopkg.in/yaml.v3"
"github.com/issue9/web"
"github.com/issue9/web/filter"
"github.com/issue9/web/internal/locale"
"github.com/issue9/web/locales"
xj "github.com/issue9/web/mimetype/json"
"github.com/issue9/web/selector"
"github.com/issue9/web/server/registry"
)
const DefaultConfigDir = "@.config" // 默认的配置目录地址
const (
typeHTTP int = iota
typeGateway
typeService
)
type (
// Options 初始化 [web.Server] 的参数
//
// NOTE: 这些参数都有默认值,且无法在 [web.Server] 初始化之后进行更改。
Options struct {
// 项目的配置文件管理
//
// 如果为空,则采用 [DefaultConfigDir] 作为配置文件的目录,
// 同时加载 YAML、XML 和 JSON 三种文件类型的序列化方法。
Config *config.Config
// 服务器的时区
//
// 默认值为 [time.Local]
Location *time.Location
// 缓存系统
//
// 如果为空,采用 [github.com/issue9/cache/caches/memory/New] 作为默认值。
Cache cache.Driver
// 日志系统
//
// 如果此值为空,表示不会输出任何信息。
//
// 会调用 [logs.Logs.SetLocale] 设置为 [Language] 的值。
Logs *logs.Logs
// http.Server 实例的值
HTTPServer *http.Server
// 生成唯一字符串的方法
//
// 供 [Server.UniqueID] 使用。
//
// 如果为空,将采用 [unique.NewString] 作为生成方法。
//
// NOTE: 该值的修改,可能造成项目中的唯一 ID 不再唯一。
IDGenerator func() string
// 路由选项
RoutersOptions []web.RouterOption
// 指定获取 x-request-id 内容的报头名
//
// 如果为空,则采用 [header.XRequestID] 作为默认值
RequestIDKey string
// 编码方式
//
// 如果为空,则仅支持 JSON 编码,不支持压缩方式。
Codec *web.Codec
// 默认的语言标签
//
// 在用户请求的报头中没有匹配的语言标签时,会采用此值作为该用户的本地化语言,
// 同时也用来初始化 [Server.Locale.Printer]。
//
// 框架中的日志输出时,如果该信息实现了 [web.LocaleStringer] 接口,
// 将会转换成此设置项的语言。
//
// 如果为空,则会尝试读取当前系统的本地化信息。
Language language.Tag
locale *locale.Locale
// 所有 [web.Problem.Type] 字段的前缀
//
// 如果该值为 [web.ProblemAboutBlank],将不输出 ID 值;其它值则作为前缀添加。
// 空值是合法的值,表示不需要添加前缀。
ProblemTypePrefix string
// OnRender 可实现对渲染结果的调整
//
// 默认为空。
//
// NOTE: 该值的修改,可能造成所有接口返回数据结构的变化。
OnRender func(status int, body any) (int, any)
// 指定对 [web.Server] 进行初始化的插件
//
// 这些插件会在 [web.Server.Serve] 运行之前被调用。
Plugins []web.Plugin
// 以下微服务相关的设置
// 作为微服务时的注册中心实例
//
// NOTE: 仅在 [NewService] 和 [NewGateway] 中才会有效果。
Registry registry.Registry
// 作为微服务终端时的地址
//
// NOTE: 仅在 [NewService] 中才会有效果。
Peer selector.Peer
// 作为微服务网关时的 URL 映射关系
//
// NOTE: 仅在 [NewGateway] 中才会有效果。
Mapper map[string]web.RouterMatcher
}
)
func sanitizeOptions(o *Options, t int) (*Options, *web.FieldError) {
if o == nil {
o = &Options{}
}
if o.Config == nil {
s := make(config.Serializer, 4)
s.Add(json.Marshal, json.Unmarshal, ".json").
Add(yaml.Marshal, yaml.Unmarshal, ".yaml", ".yml").
Add(xml.Marshal, xml.Unmarshal, ".xml")
c, err := config.BuildDir(s, DefaultConfigDir)
if err != nil {
return nil, web.NewFieldError("Config", err)
}
o.Config = c
}
if o.Location == nil {
o.Location = time.Local
}
if o.HTTPServer == nil {
o.HTTPServer = &http.Server{}
}
if o.IDGenerator == nil {
u := unique.NewString(1000)
o.IDGenerator = u.String
o.Plugins = append(o.Plugins, web.PluginFunc(func(s web.Server) {
s.Services().Add(locales.UniqueIdentityGenerator, u)
}))
}
if o.Cache == nil {
o.Cache = memory.New()
}
if o.Language == language.Und {
tag, err := localeutil.DetectUserLanguageTag()
if err != nil {
return nil, web.NewFieldError("Language", err)
}
o.Language = tag
}
o.locale = locale.New(o.Language, o.Config)
if o.Logs == nil {
o.Logs = logs.New(logs.NewNopHandler())
}
o.Logs.SetLocale(o.locale.Printer())
if o.RequestIDKey == "" {
o.RequestIDKey = header.XRequestID
}
if o.Codec == nil {
o.Codec = web.NewCodec().AddMimetype(xj.Mimetype, xj.Marshal, xj.Unmarshal, xj.ProblemMimetype)
}
switch t {
case typeHTTP: // 不需要处理任何数据
return o, nil
case typeGateway:
return o, filter.ToFieldError(
filter.New("Mapper", &o.Mapper, filter.V(func(v map[string]group.Matcher) bool { return v != nil }, locales.CanNotBeEmpty)),
filter.New("Mapper", &o.Mapper, filter.MV[map[string]group.Matcher](func(v group.Matcher) bool { return v != nil }, locales.CanNotBeEmpty)),
filter.New("Registry", &o.Registry, filter.V(func(v registry.Registry) bool { return v != nil }, locales.CanNotBeEmpty)),
)
case typeService:
return o, filter.ToFieldError(
filter.New("Peer", &o.Peer, filter.V(func(v selector.Peer) bool { return v != nil }, locales.CanNotBeEmpty)),
filter.New("Registry", &o.Registry, filter.V(func(v registry.Registry) bool { return v != nil }, locales.CanNotBeEmpty)),
)
default:
panic("参数 t 取值错误")
}
}
func (o *Options) internalServer(name, version string, s web.Server) *web.InternalServer {
return web.InternalNewServer(s, name, version,
o.Location, o.Logs, o.IDGenerator, o.locale,
o.Cache, o.Codec, o.RequestIDKey, o.ProblemTypePrefix,
o.OnRender, o.RoutersOptions...)
}
// Render200 统一 API 的返回格式
//
// 适用 [Options.OnRender]。
//
// 返回值中,状态码统一为 [http.StatusOK]。返回对象统一为 [RenderResponse]。
func Render200(status int, body any) (int, any) {
return http.StatusOK, &RenderResponse{OK: !web.IsProblem(status), Status: status, Body: body}
}
// RenderResponse API 统一的返回格式
type RenderResponse struct {
XMLName struct{} `json:"-" yaml:"-" xml:"body" cbor:"-"`
OK bool `json:"ok" yaml:"ok" xml:"ok,attr" cbor:"ok"` // 是否是错误代码
Status int `json:"status" yaml:"status" xml:"status,attr" cbor:"status"` // 原始的状态码
Body any `json:"body" yaml:"body" xml:"body" cbor:"body"`
}
func (r *RenderResponse) MarshalHTML() (string, any) { return "render-response", r }