Skip to content

Commit e4c808e

Browse files
feature: add fiber rest server
1 parent dce8755 commit e4c808e

File tree

9 files changed

+450
-5
lines changed

9 files changed

+450
-5
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
example/

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,11 @@ Google Cloud PubSub
1818
</p>
1919

2020
Can be used for AMQ Consumer
21+
22+
## [Fiber REST Server](https://github.com/agungdwiprasetyo/candi-plugin/tree/master/fiber_rest)
23+
24+
Fiber web Framework (https://gofiber.io)
25+
26+
<p align="center">
27+
<img src="https://raw.githubusercontent.com/gofiber/docs/master/static/fiber_v2_logo.svg" width="150" alt="go fiber" />
28+
</p>

fiber_rest/README.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Fiber REST Server
2+
3+
https://github.com/gofiber/fiber
4+
5+
## Install this plugin in your `candi` service
6+
7+
### Add in service.go
8+
9+
```go
10+
package service
11+
12+
import (
13+
fiberrest "github.com/agungdwiprasetyo/candi-plugin/fiber_rest"
14+
...
15+
16+
// Service model
17+
type Service struct {
18+
applications []factory.AppServerFactory
19+
...
20+
21+
// NewService in this service
22+
func NewService(cfg *config.Config) factory.ServiceFactory {
23+
...
24+
25+
s := &Service{
26+
...
27+
28+
// Add custom application runner, must implement `factory.AppServerFactory` methods
29+
s.applications = append(s.applications, []factory.AppServerFactory{
30+
// customApplication
31+
fiberrest.NewFiberServer(s, "[http port]", fiberrest.JaegerTracingMiddleware),,
32+
}...)
33+
34+
...
35+
}
36+
...
37+
```
38+
39+
### Register in module.go
40+
41+
```go
42+
package examplemodule
43+
44+
import (
45+
"example.service/internal/modules/examplemodule/delivery/workerhandler"
46+
47+
fiberrest "github.com/agungdwiprasetyo/candi-plugin/fiber_rest"
48+
49+
"pkg.agungdp.dev/candi/codebase/factory/dependency"
50+
"pkg.agungdp.dev/candi/codebase/factory/types"
51+
"pkg.agungdp.dev/candi/codebase/interfaces"
52+
)
53+
54+
type Module struct {
55+
// ...another delivery handler
56+
serverHandlers map[types.Server]interfaces.ServerHandler
57+
}
58+
59+
func NewModules(deps dependency.Dependency) *Module {
60+
return &Module{
61+
serverHandlers: map[types.Server]interfaces.ServerHandler{
62+
// ...another server handler
63+
// ...
64+
fiberrest.FiberREST: resthandler.NewFiberHandler(usecaseUOW.User(), dependency.GetMiddleware(), dependency.GetValidator()),
65+
},
66+
}
67+
}
68+
69+
// ...another method
70+
```
71+
72+
### Create delivery handler
73+
74+
```go
75+
package workerhandler
76+
77+
import (
78+
"context"
79+
"encoding/json"
80+
81+
fiberrest "github.com/agungdwiprasetyo/candi-plugin/fiber_rest"
82+
"github.com/gofiber/fiber/v2"
83+
84+
"pkg.agungdp.dev/candi/candishared"
85+
"pkg.agungdp.dev/candi/codebase/interfaces"
86+
"pkg.agungdp.dev/candi/tracer"
87+
)
88+
89+
// FiberHandler struct
90+
type FiberHandler struct {
91+
mw interfaces.Middleware
92+
uc usecase.UserUsecase
93+
validator interfaces.Validator
94+
}
95+
96+
// NewFiberHandler constructor
97+
func NewFiberHandler(uc usecase.UserUsecase, mw interfaces.Middleware, validator interfaces.Validator) *FiberHandler {
98+
return &FiberHandler{
99+
uc: uc,
100+
mw: mw,
101+
validator: validator,
102+
}
103+
}
104+
105+
// MountHandlers mount handler group
106+
func (h *FiberHandler) MountHandlers(i interface{}) {
107+
group := fiberrest.ParseGroupHandler(i)
108+
109+
group.Get("/hello", fiberrest.WrapFiberHTTPMiddleware(h.mw.HTTPBearerAuth), helloHandler)
110+
}
111+
112+
func helloHandler(c *fiber.Ctx) error {
113+
trace, ctx := tracer.StartTraceWithContext(fiberrest.FastHTTPParseContext(c.Context()), "DeliveryHandler")
114+
defer trace.Finish()
115+
116+
claim := candishared.ParseTokenClaimFromContext(ctx)
117+
log.Println(claim)
118+
return c.JSON(fiber.Map{"message": "Hello world!"})
119+
}
120+
```

fiber_rest/fiber_http_wrapper.go

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package fiberrest
2+
3+
import (
4+
"context"
5+
"io"
6+
"net/http"
7+
"net/url"
8+
9+
"github.com/gofiber/fiber/v2"
10+
"github.com/valyala/fasthttp"
11+
)
12+
13+
const (
14+
contextKey = "fibercontext"
15+
)
16+
17+
// WrapFiberHTTPMiddleware wraps net/http middleware to fiber middleware
18+
func WrapFiberHTTPMiddleware(mw func(http.Handler) http.Handler) fiber.Handler {
19+
return func(c *fiber.Ctx) error {
20+
var next bool
21+
netHTTPHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
22+
next = true
23+
c.Request().Header.SetMethod(r.Method)
24+
c.Request().SetRequestURI(r.RequestURI)
25+
c.Request().SetHost(r.Host)
26+
c.Locals(contextKey, r.Context())
27+
for key, val := range r.Header {
28+
for _, v := range val {
29+
c.Request().Header.Set(key, v)
30+
}
31+
}
32+
})
33+
fiberHandler := func(ctx context.Context, h http.Handler) fiber.Handler {
34+
return func(c *fiber.Ctx) error {
35+
WrapNetHTTPToFastHTTPHandler(ctx, h)(c.Context())
36+
return nil
37+
}
38+
}
39+
40+
fiberHandler(FastHTTPParseContext(c.Context()), mw(netHTTPHandler))(c)
41+
if next {
42+
return c.Next()
43+
}
44+
return nil
45+
}
46+
}
47+
48+
// WrapNetHTTPToFastHTTPHandler custom from https://github.com/valyala/fasthttp/blob/master/fasthttpadaptor/adaptor.go#L49
49+
// with additional context.Context param for use standard Go context to net/http request context
50+
func WrapNetHTTPToFastHTTPHandler(ctx context.Context, h http.Handler) fasthttp.RequestHandler {
51+
return func(c *fasthttp.RequestCtx) {
52+
var r http.Request
53+
54+
body := c.PostBody()
55+
r.Method = string(c.Method())
56+
r.Proto = "HTTP/1.1"
57+
r.ProtoMajor = 1
58+
r.ProtoMinor = 1
59+
r.RequestURI = string(c.RequestURI())
60+
r.ContentLength = int64(len(body))
61+
r.Host = string(c.Host())
62+
r.RemoteAddr = c.RemoteAddr().String()
63+
64+
hdr := make(http.Header)
65+
c.Request.Header.VisitAll(func(k, v []byte) {
66+
sk := string(k)
67+
sv := string(v)
68+
switch sk {
69+
case "Transfer-Encoding":
70+
r.TransferEncoding = append(r.TransferEncoding, sv)
71+
default:
72+
hdr.Set(sk, sv)
73+
}
74+
})
75+
r.Header = hdr
76+
r.Body = &netHTTPRequestBody{body}
77+
rURL, err := url.ParseRequestURI(r.RequestURI)
78+
if err != nil {
79+
c.Logger().Printf("cannot parse requestURI %q: %s", r.RequestURI, err)
80+
c.Error("Internal Server Error", fasthttp.StatusInternalServerError)
81+
return
82+
}
83+
r.URL = rURL
84+
85+
var w netHTTPResponseWriter
86+
h.ServeHTTP(&w, r.WithContext(ctx))
87+
88+
c.SetStatusCode(w.StatusCode())
89+
haveContentType := false
90+
for k, vv := range w.Header() {
91+
if k == fasthttp.HeaderContentType {
92+
haveContentType = true
93+
}
94+
95+
for _, v := range vv {
96+
c.Response.Header.Set(k, v)
97+
}
98+
}
99+
if !haveContentType {
100+
// From net/http.ResponseWriter.Write:
101+
// If the Header does not contain a Content-Type line, Write adds a Content-Type set
102+
// to the result of passing the initial 512 bytes of written data to DetectContentType.
103+
l := 512
104+
if len(w.body) < 512 {
105+
l = len(w.body)
106+
}
107+
c.Response.Header.Set(fasthttp.HeaderContentType, http.DetectContentType(w.body[:l]))
108+
}
109+
c.Write(w.body) //nolint:errcheck
110+
}
111+
}
112+
113+
type netHTTPResponseWriter struct {
114+
statusCode int
115+
h http.Header
116+
body []byte
117+
}
118+
119+
func (w *netHTTPResponseWriter) StatusCode() int {
120+
if w.statusCode == 0 {
121+
return http.StatusOK
122+
}
123+
return w.statusCode
124+
}
125+
126+
func (w *netHTTPResponseWriter) Header() http.Header {
127+
if w.h == nil {
128+
w.h = make(http.Header)
129+
}
130+
return w.h
131+
}
132+
133+
func (w *netHTTPResponseWriter) WriteHeader(statusCode int) {
134+
w.statusCode = statusCode
135+
}
136+
137+
func (w *netHTTPResponseWriter) Write(p []byte) (int, error) {
138+
w.body = append(w.body, p...)
139+
return len(p), nil
140+
}
141+
142+
type netHTTPRequestBody struct {
143+
b []byte
144+
}
145+
146+
func (r *netHTTPRequestBody) Read(p []byte) (int, error) {
147+
if len(r.b) == 0 {
148+
return 0, io.EOF
149+
}
150+
n := copy(p, r.b)
151+
r.b = r.b[n:]
152+
return n, nil
153+
}
154+
155+
func (r *netHTTPRequestBody) Close() error {
156+
r.b = r.b[:0]
157+
return nil
158+
}
159+
160+
// FastHTTPParseContext get standard Go context from fasthttp request context
161+
func FastHTTPParseContext(c *fasthttp.RequestCtx) context.Context {
162+
ctx, ok := c.UserValue(contextKey).(context.Context)
163+
if !ok {
164+
return c
165+
}
166+
return ctx
167+
}
168+
169+
// FastHTTPSetContext set standard Go context to fasthttp request context
170+
func FastHTTPSetContext(ctx context.Context, c *fasthttp.RequestCtx) {
171+
c.SetUserValue(contextKey, ctx)
172+
}

fiber_rest/fiber_rest_server.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package fiberrest
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/gofiber/fiber/v2"
8+
"pkg.agungdp.dev/candi/codebase/factory"
9+
)
10+
11+
type fiberREST struct {
12+
serverEngine *fiber.App
13+
service factory.ServiceFactory
14+
httpPort string
15+
}
16+
17+
// NewFiberServer create new REST server
18+
func NewFiberServer(service factory.ServiceFactory, httpPort string, rootMiddleware ...func(*fiber.Ctx) error) factory.AppServerFactory {
19+
server := &fiberREST{
20+
serverEngine: fiber.New(),
21+
service: service,
22+
httpPort: httpPort,
23+
}
24+
25+
root := server.serverEngine.Group("/", rootMiddleware...)
26+
for _, m := range service.GetModules() {
27+
if h := m.ServerHandler(FiberREST); h != nil {
28+
h.MountHandlers(root)
29+
}
30+
}
31+
32+
fmt.Printf("\x1b[34;1m⇨ Fiber HTTP server run at port [::]%s\x1b[0m\n\n", server.httpPort)
33+
return server
34+
}
35+
36+
func (s *fiberREST) Serve() {
37+
if err := s.serverEngine.Listen(s.httpPort); err != nil {
38+
panic(err)
39+
}
40+
}
41+
42+
func (s *fiberREST) Shutdown(ctx context.Context) {
43+
// h.serverEngine.Shutdown()
44+
}
45+
46+
func (s *fiberREST) Name() string {
47+
return string(FiberREST)
48+
}

0 commit comments

Comments
 (0)