@ai-content-space/loopx 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +343 -56
- package/README.zh-CN.md +392 -0
- package/package.json +4 -1
- package/plugins/loopx/.codex-plugin/plugin.json +1 -1
- package/plugins/loopx/scripts/plugin-install.test.mjs +1 -0
- package/plugins/loopx/skills/archive/SKILL.md +39 -0
- package/plugins/loopx/skills/build/SKILL.md +111 -9
- package/plugins/loopx/skills/clarify/SKILL.md +121 -1
- package/plugins/loopx/skills/debug/SKILL.md +296 -0
- package/plugins/loopx/skills/debug/condition-based-waiting.md +115 -0
- package/plugins/loopx/skills/debug/defense-in-depth.md +122 -0
- package/plugins/loopx/skills/debug/find-polluter.sh +63 -0
- package/plugins/loopx/skills/debug/root-cause-tracing.md +169 -0
- package/plugins/loopx/skills/go-style/SKILL.md +71 -0
- package/plugins/loopx/skills/kratos/SKILL.md +74 -0
- package/plugins/loopx/skills/kratos/references/advanced-features.md +314 -0
- package/plugins/loopx/skills/kratos/references/architecture.md +488 -0
- package/plugins/loopx/skills/kratos/references/configuration.md +399 -0
- package/plugins/loopx/skills/kratos/references/http-customization.md +512 -0
- package/plugins/loopx/skills/kratos/references/middleware-logging.md +400 -0
- package/plugins/loopx/skills/kratos/references/proto-api-design.md +432 -0
- package/plugins/loopx/skills/kratos/references/security-auth.md +411 -0
- package/plugins/loopx/skills/kratos/references/troubleshooting.md +385 -0
- package/plugins/loopx/skills/plan/SKILL.md +22 -2
- package/plugins/loopx/skills/review/SKILL.md +98 -1
- package/plugins/loopx/skills/tdd/SKILL.md +371 -0
- package/plugins/loopx/skills/tdd/testing-anti-patterns.md +299 -0
- package/plugins/loopx/skills/verify/SKILL.md +139 -0
- package/scripts/codex-stop-hook.mjs +71 -0
- package/scripts/codex-workflow-hook.mjs +153 -0
- package/skills/archive/SKILL.md +39 -0
- package/skills/build/SKILL.md +111 -9
- package/skills/clarify/SKILL.md +121 -1
- package/skills/debug/SKILL.md +296 -0
- package/skills/debug/condition-based-waiting.md +115 -0
- package/skills/debug/defense-in-depth.md +122 -0
- package/skills/debug/find-polluter.sh +63 -0
- package/skills/debug/root-cause-tracing.md +169 -0
- package/skills/go-style/SKILL.md +71 -0
- package/skills/kratos/SKILL.md +74 -0
- package/skills/kratos/references/advanced-features.md +314 -0
- package/skills/kratos/references/architecture.md +488 -0
- package/skills/kratos/references/configuration.md +399 -0
- package/skills/kratos/references/http-customization.md +512 -0
- package/skills/kratos/references/middleware-logging.md +400 -0
- package/skills/kratos/references/proto-api-design.md +432 -0
- package/skills/kratos/references/security-auth.md +411 -0
- package/skills/kratos/references/troubleshooting.md +385 -0
- package/skills/plan/SKILL.md +18 -2
- package/skills/review/SKILL.md +98 -1
- package/skills/tdd/SKILL.md +371 -0
- package/skills/tdd/testing-anti-patterns.md +299 -0
- package/skills/verify/SKILL.md +139 -0
- package/src/build-runtime.mjs +303 -26
- package/src/build-stop-gate.mjs +94 -0
- package/src/cli.mjs +47 -5
- package/src/codex-exec-runtime.mjs +105 -5
- package/src/context-manifest.mjs +172 -0
- package/src/install-discovery.mjs +352 -5
- package/src/next-skill.mjs +57 -5
- package/src/plan-runtime.mjs +79 -122
- package/src/review-runtime.mjs +378 -0
- package/src/runtime-maintenance.mjs +428 -14
- package/src/template-governance.mjs +223 -0
- package/src/workflow.mjs +1941 -117
- package/src/workspace-context.mjs +166 -0
- package/src/workspace-memory.mjs +69 -0
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
# HTTP Customization
|
|
2
|
+
|
|
3
|
+
Guide for customizing HTTP responses, file handling, WebSocket, and CORS.
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
|
|
7
|
+
- Customizing response format
|
|
8
|
+
- Handling file upload/download
|
|
9
|
+
- WebSocket integration
|
|
10
|
+
- CORS configuration
|
|
11
|
+
- Static file serving
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## ResponseEncoder
|
|
16
|
+
|
|
17
|
+
Customize how successful responses are serialized:
|
|
18
|
+
|
|
19
|
+
### Standard Pattern
|
|
20
|
+
|
|
21
|
+
```go
|
|
22
|
+
import (
|
|
23
|
+
"github.com/go-kratos/kratos/v2/encoding"
|
|
24
|
+
"github.com/go-kratos/kratos/v2/transport/http"
|
|
25
|
+
"google.golang.org/protobuf/proto"
|
|
26
|
+
"google.golang.org/protobuf/types/known/anypb"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
func CustomResponseEncoder() http.ServerOption {
|
|
30
|
+
return http.ResponseEncoder(func(w http.ResponseWriter, r *http.Request, i interface{}) error {
|
|
31
|
+
// Handle redirect first
|
|
32
|
+
if rd, ok := i.(http.Redirector); ok {
|
|
33
|
+
url, code := rd.Redirect()
|
|
34
|
+
http.Redirect(w, r, url, code)
|
|
35
|
+
return nil
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Wrap in BaseResponse
|
|
39
|
+
reply := &v1.BaseResponse{Code: 0}
|
|
40
|
+
if m, ok := i.(proto.Message); ok {
|
|
41
|
+
payload, err := anypb.New(m)
|
|
42
|
+
if err != nil {
|
|
43
|
+
return err
|
|
44
|
+
}
|
|
45
|
+
reply.Data = payload
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
codec := encoding.GetCodec("json")
|
|
49
|
+
data, err := codec.Marshal(reply)
|
|
50
|
+
if err != nil {
|
|
51
|
+
return err
|
|
52
|
+
}
|
|
53
|
+
w.Header().Set("Content-Type", "application/json")
|
|
54
|
+
if _, err := w.Write(data); err != nil {
|
|
55
|
+
return err
|
|
56
|
+
}
|
|
57
|
+
return nil
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Proto Definition for BaseResponse
|
|
63
|
+
|
|
64
|
+
```protobuf
|
|
65
|
+
import "google/protobuf/any.proto";
|
|
66
|
+
|
|
67
|
+
message BaseResponse {
|
|
68
|
+
int32 code = 1 [json_name = "code"];
|
|
69
|
+
google.protobuf.Any data = 2 [json_name = "data"];
|
|
70
|
+
string message = 3 [json_name = "message"];
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Registration
|
|
75
|
+
|
|
76
|
+
```go
|
|
77
|
+
srv := http.NewServer(
|
|
78
|
+
http.Address(":8000"),
|
|
79
|
+
CustomResponseEncoder(),
|
|
80
|
+
)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## ErrorEncoder
|
|
86
|
+
|
|
87
|
+
Customize how errors are serialized:
|
|
88
|
+
|
|
89
|
+
```go
|
|
90
|
+
func CustomErrorEncoder() http.ServerOption {
|
|
91
|
+
return http.ErrorEncoder(func(w http.ResponseWriter, r *http.Request, err error) {
|
|
92
|
+
// Convert Kratos error to custom format
|
|
93
|
+
se := errors.FromError(err)
|
|
94
|
+
|
|
95
|
+
reply := &v1.BaseResponse{
|
|
96
|
+
Code: se.Code,
|
|
97
|
+
Message: se.Message,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
codec := encoding.GetCodec("json")
|
|
101
|
+
data, err := codec.Marshal(reply)
|
|
102
|
+
if err != nil {
|
|
103
|
+
w.WriteHeader(http.StatusInternalServerError)
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
w.Header().Set("Content-Type", "application/json")
|
|
108
|
+
w.WriteHeader(se.StatusCode)
|
|
109
|
+
if _, err := w.Write(data); err != nil {
|
|
110
|
+
log.Errorf("write error response failed: %v", err)
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Zero-Value Field Handling
|
|
119
|
+
|
|
120
|
+
Protobuf by default omits zero-value fields. Solutions:
|
|
121
|
+
|
|
122
|
+
### Option 1: EmitUnpopulated
|
|
123
|
+
|
|
124
|
+
```go
|
|
125
|
+
// Custom json codec with EmitUnpopulated
|
|
126
|
+
import "google.golang.org/protobuf/encoding/protojson"
|
|
127
|
+
|
|
128
|
+
var MarshalOptions = protojson.MarshalOptions{
|
|
129
|
+
EmitUnpopulated: true, // Include zero values
|
|
130
|
+
UseEnumNumbers: true, // Enums as numbers, not strings
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
type codec struct{}
|
|
134
|
+
|
|
135
|
+
func (codec) Marshal(v interface{}) ([]byte, error) {
|
|
136
|
+
if m, ok := v.(proto.Message); ok {
|
|
137
|
+
return MarshalOptions.Marshal(m)
|
|
138
|
+
}
|
|
139
|
+
return json.Marshal(v)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
func init() {
|
|
143
|
+
encoding.RegisterCodec(codec{})
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Option 2: anypb.New Wrapper
|
|
148
|
+
|
|
149
|
+
```go
|
|
150
|
+
// In ResponseEncoder, wrap with anypb.New
|
|
151
|
+
payload, err := anypb.New(m)
|
|
152
|
+
if err != nil {
|
|
153
|
+
return err
|
|
154
|
+
}
|
|
155
|
+
reply.Data = payload
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Note:** This adds `@type` field to response.
|
|
159
|
+
|
|
160
|
+
### Option 3: Remove omitempty from pb.go
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# Makefile
|
|
164
|
+
ifeq ($(GOHOSTOS), darwin)
|
|
165
|
+
find ./api -name '*.pb.go' -exec sed -i "" -e "s/,omitempty/,optional/g" {} \;
|
|
166
|
+
else
|
|
167
|
+
find ./api -name '*.pb.go' -exec sed -i -e "s/,omitempty/,optional/g" {} \;
|
|
168
|
+
endif
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## File Upload
|
|
174
|
+
|
|
175
|
+
Proto doesn't support file upload. Use custom route:
|
|
176
|
+
|
|
177
|
+
### Handler Pattern
|
|
178
|
+
|
|
179
|
+
```go
|
|
180
|
+
import (
|
|
181
|
+
"bytes"
|
|
182
|
+
"github.com/gorilla/schema"
|
|
183
|
+
"io"
|
|
184
|
+
"net/http"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
func UploadHandlerWithMiddleware[T comparable](ctx http.Context, fileFormKey string) (
|
|
188
|
+
chain middleware.Middleware,
|
|
189
|
+
request T,
|
|
190
|
+
reader io.Reader,
|
|
191
|
+
filename string,
|
|
192
|
+
err error,
|
|
193
|
+
) {
|
|
194
|
+
if fileFormKey == "" {
|
|
195
|
+
fileFormKey = "file"
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Read file
|
|
199
|
+
file, fileHeader, err := ctx.Request().FormFile(fileFormKey)
|
|
200
|
+
defer file.Close()
|
|
201
|
+
if err != nil {
|
|
202
|
+
return nil, request, nil, "", err
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Buffer file content
|
|
206
|
+
buf := new(bytes.Buffer)
|
|
207
|
+
if _, err := io.Copy(buf, file); err != nil {
|
|
208
|
+
return nil, request, nil, fileHeader.Filename, err
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Parse form parameters
|
|
212
|
+
if err := ctx.Request().ParseForm(); err != nil {
|
|
213
|
+
return nil, request, nil, "", err
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Decode form to struct
|
|
217
|
+
var decoder = schema.NewDecoder()
|
|
218
|
+
t := new(T)
|
|
219
|
+
if err := decoder.Decode(t, ctx.Request().Form); err == nil {
|
|
220
|
+
request = *t
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
h := ctx.Middleware
|
|
224
|
+
return h, request, bytes.NewReader(buf.Bytes()), fileHeader.Filename, nil
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Service Implementation
|
|
229
|
+
|
|
230
|
+
```go
|
|
231
|
+
func (s *UploadService) RegisterUploadServiceHttpServer(svr *http.Server) {
|
|
232
|
+
route := svr.Route("/")
|
|
233
|
+
route.POST("/v1/upload", s.uploadFile)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
func (s *UploadService) uploadFile(ctx http.Context) error {
|
|
237
|
+
http.SetOperation(ctx, "/upload.v1.UploadService/Upload")
|
|
238
|
+
|
|
239
|
+
h, opt, reader, filename, err := UploadHandlerWithMiddleware[biz.UploadOption](ctx, "file")
|
|
240
|
+
if err != nil {
|
|
241
|
+
return v1.ErrorInvalidUploadRequest("invalid request: %v", err)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
handler := s.uc.UploadFile(filename, reader, opt)
|
|
245
|
+
resp, err := h(ctx, opt)
|
|
246
|
+
if err != nil {
|
|
247
|
+
return err
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return ctx.JSON(200, resp)
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## File Download / Redirect
|
|
257
|
+
|
|
258
|
+
### Redirector Interface
|
|
259
|
+
|
|
260
|
+
For redirects, implement `http.Redirector`:
|
|
261
|
+
|
|
262
|
+
```protobuf
|
|
263
|
+
message LuckySearchResponse {
|
|
264
|
+
string redirect_to = 1 [(buf.validate.field).string.uri = true];
|
|
265
|
+
int32 status_code = 2;
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
```go
|
|
270
|
+
// api/helloworld/v1/lucky_search_redirect_impl.go
|
|
271
|
+
package v1
|
|
272
|
+
|
|
273
|
+
import "github.com/go-kratos/kratos/v2/transport/http"
|
|
274
|
+
|
|
275
|
+
var _ http.Redirector = (*LuckySearchResponse)(nil)
|
|
276
|
+
|
|
277
|
+
func (s *LuckySearchResponse) Redirect() (string, int) {
|
|
278
|
+
return s.RedirectTo, int(s.StatusCode)
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
In ResponseEncoder, handle Redirector first:
|
|
283
|
+
|
|
284
|
+
```go
|
|
285
|
+
if rd, ok := i.(http.Redirector); ok {
|
|
286
|
+
url, code := rd.Redirect()
|
|
287
|
+
http.Redirect(w, r, url, code)
|
|
288
|
+
return nil
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### File Download
|
|
293
|
+
|
|
294
|
+
```go
|
|
295
|
+
// In ResponseEncoder
|
|
296
|
+
if asset, ok := i.(*attachment.Attachment); ok {
|
|
297
|
+
w.Header().Set("Content-Disposition", asset.FileName)
|
|
298
|
+
w.Header().Set("Content-Length", strconv.FormatInt(asset.ContentLength, 10))
|
|
299
|
+
w.Header().Set("Content-Type", "application/octet-stream")
|
|
300
|
+
w.Write(asset.Payload)
|
|
301
|
+
return nil
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Proto for attachment:
|
|
306
|
+
```protobuf
|
|
307
|
+
message Attachment {
|
|
308
|
+
string file_name = 1;
|
|
309
|
+
int64 content_length = 2;
|
|
310
|
+
bytes payload = 3;
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## WebSocket
|
|
317
|
+
|
|
318
|
+
### Server Setup
|
|
319
|
+
|
|
320
|
+
```go
|
|
321
|
+
import (
|
|
322
|
+
"github.com/go-kratos/kratos/v2"
|
|
323
|
+
"github.com/go-kratos/kratos/v2/transport/http"
|
|
324
|
+
"github.com/gorilla/mux"
|
|
325
|
+
"github.com/gorilla/websocket"
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
var upgrader = websocket.Upgrader{
|
|
329
|
+
CheckOrigin: func(r *http.Request) bool {
|
|
330
|
+
return true // Allow all origins (configure for production)
|
|
331
|
+
},
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
func main() {
|
|
335
|
+
router := mux.NewRouter()
|
|
336
|
+
router.HandleFunc("/ws", WsHandler)
|
|
337
|
+
|
|
338
|
+
httpSrv := http.NewServer(http.Address(":8000"))
|
|
339
|
+
httpSrv.HandlePrefix("/", router)
|
|
340
|
+
|
|
341
|
+
app := kratos.New(
|
|
342
|
+
kratos.Name("ws"),
|
|
343
|
+
kratos.Server(httpSrv),
|
|
344
|
+
)
|
|
345
|
+
app.Run()
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
func WsHandler(w http.ResponseWriter, r *http.Request) {
|
|
349
|
+
conn, err := upgrader.Upgrade(w, r, nil)
|
|
350
|
+
if err != nil {
|
|
351
|
+
log.Print("upgrade:", err)
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
defer conn.Close()
|
|
355
|
+
|
|
356
|
+
for {
|
|
357
|
+
mt, message, err := conn.ReadMessage()
|
|
358
|
+
if err != nil {
|
|
359
|
+
log.Println("read:", err)
|
|
360
|
+
break
|
|
361
|
+
}
|
|
362
|
+
log.Printf("recv: %s", message)
|
|
363
|
+
err = conn.WriteMessage(mt, message)
|
|
364
|
+
if err != nil {
|
|
365
|
+
log.Println("write:", err)
|
|
366
|
+
break
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### From Service Layer
|
|
373
|
+
|
|
374
|
+
```go
|
|
375
|
+
func (s *MyService) HandleWebSocket(ctx context.Context, req *v1.WsRequest) error {
|
|
376
|
+
if httpCtx, ok := ctx.(http.Context); ok {
|
|
377
|
+
return s.uc.HandleWebSocket(req.Id, httpCtx)
|
|
378
|
+
}
|
|
379
|
+
return errors.New("not http context")
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// UseCase
|
|
383
|
+
func (uc *MyUseCase) HandleWebSocket(id string, httpCtx http.Context) error {
|
|
384
|
+
conn, err := upgrader.Upgrade(httpCtx.Response(), httpCtx.Request(), nil)
|
|
385
|
+
if err != nil {
|
|
386
|
+
return err
|
|
387
|
+
}
|
|
388
|
+
go handleWsMessage(ctx, id, conn) // Pass context for cancellation
|
|
389
|
+
return nil
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
func handleWsMessage(ctx context.Context, id string, conn *websocket.Conn) {
|
|
393
|
+
defer conn.Close()
|
|
394
|
+
for {
|
|
395
|
+
select {
|
|
396
|
+
case <-ctx.Done():
|
|
397
|
+
return // Graceful shutdown
|
|
398
|
+
default:
|
|
399
|
+
mt, message, err := conn.ReadMessage()
|
|
400
|
+
if err != nil {
|
|
401
|
+
return // Connection closed
|
|
402
|
+
}
|
|
403
|
+
conn.WriteMessage(mt, message)
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## CORS Configuration
|
|
412
|
+
|
|
413
|
+
### Using gorilla/handlers
|
|
414
|
+
|
|
415
|
+
```go
|
|
416
|
+
import "github.com/gorilla/handlers"
|
|
417
|
+
|
|
418
|
+
srv := http.NewServer(
|
|
419
|
+
http.Address(":8000"),
|
|
420
|
+
http.Filter(handlers.CORS(
|
|
421
|
+
handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Authorization"}),
|
|
422
|
+
handlers.AllowedMethods([]string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}),
|
|
423
|
+
handlers.AllowedOrigins([]string{"*"}),
|
|
424
|
+
)),
|
|
425
|
+
)
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### Using rs/cors
|
|
429
|
+
|
|
430
|
+
```go
|
|
431
|
+
import "github.com/rs/cors"
|
|
432
|
+
|
|
433
|
+
func CorsHandler() func(http.Handler) http.Handler {
|
|
434
|
+
c := cors.New(cors.Options{
|
|
435
|
+
AllowedOrigins: []string{"https://example.com"},
|
|
436
|
+
AllowCredentials: true,
|
|
437
|
+
AllowedHeaders: []string{"Content-Type", "Authorization"},
|
|
438
|
+
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
|
|
439
|
+
})
|
|
440
|
+
return c.Handler
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
srv := http.NewServer(
|
|
444
|
+
http.Address(":8000"),
|
|
445
|
+
http.Filter(CorsHandler()),
|
|
446
|
+
)
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## Static File Serving
|
|
452
|
+
|
|
453
|
+
### Using embed.FS
|
|
454
|
+
|
|
455
|
+
```go
|
|
456
|
+
import (
|
|
457
|
+
"embed"
|
|
458
|
+
"net/http"
|
|
459
|
+
"github.com/gorilla/mux"
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
//go:embed assets/*
|
|
463
|
+
var f embed.FS
|
|
464
|
+
|
|
465
|
+
func main() {
|
|
466
|
+
router := mux.NewRouter()
|
|
467
|
+
router.PathPrefix("/assets").Handler(http.FileServer(http.FS(f)))
|
|
468
|
+
|
|
469
|
+
httpSrv := http.NewServer(http.Address(":8000"))
|
|
470
|
+
httpSrv.HandlePrefix("/", router)
|
|
471
|
+
|
|
472
|
+
app := kratos.New(
|
|
473
|
+
kratos.Name("static"),
|
|
474
|
+
kratos.Server(httpSrv),
|
|
475
|
+
)
|
|
476
|
+
app.Run()
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## TLS Configuration
|
|
483
|
+
|
|
484
|
+
### Manual TLS
|
|
485
|
+
|
|
486
|
+
```go
|
|
487
|
+
import "crypto/tls"
|
|
488
|
+
|
|
489
|
+
func LoadTLSConfig(certFile, keyFile string) (*tls.Config, error) {
|
|
490
|
+
cer, err := tls.LoadX509KeyPair(certFile, keyFile)
|
|
491
|
+
if err != nil {
|
|
492
|
+
return nil, err
|
|
493
|
+
}
|
|
494
|
+
return &tls.Config{Certificates: []tls.Certificate{cer}}, nil
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
srv := http.NewServer(
|
|
498
|
+
http.Address(":443"),
|
|
499
|
+
http.TLSConfig(LoadTLSConfig("cert.pem", "key.pem")),
|
|
500
|
+
)
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
### Auto TLS (Let's Encrypt)
|
|
504
|
+
|
|
505
|
+
```go
|
|
506
|
+
import "github.com/go-kratos/kratos/v2/transport/http/auto"
|
|
507
|
+
|
|
508
|
+
// Requires port 443
|
|
509
|
+
srv := http.NewServer(
|
|
510
|
+
http.Address(":443"),
|
|
511
|
+
auto.TLSConfig("example.com"),
|
|
512
|
+
)
|