Newer
Older
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
package cmd
import (
"io/fs"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"github.com/timewasted/go-accept-headers"
"orus.io/orus-io/go-orusapi"
)
type UIOptions struct {
fs fs.FS
paths []string
External string `long:"ui-external" ini-name:"ui-ui-external" description:"UI external server"`
}
func NewIgnoreNotFoundResponseWriter(rw http.ResponseWriter) *IgnoreNotFoundResponseWriter {
return &IgnoreNotFoundResponseWriter{
header: rw.Header().Clone(),
}
}
type IgnoreNotFoundResponseWriter struct {
header http.Header
notfound *bool
next http.ResponseWriter
}
func (rw *IgnoreNotFoundResponseWriter) NotFound() bool {
return rw.notfound != nil && *rw.notfound
}
func (rw *IgnoreNotFoundResponseWriter) Header() http.Header {
return rw.header
}
func (rw *IgnoreNotFoundResponseWriter) flushHeader() {
nh := rw.next.Header()
for k := range nh {
if _, ok := rw.header[k]; !ok {
nh.Del(k)
}
}
for k, v := range rw.header {
nh[k] = v
}
}
func (rw *IgnoreNotFoundResponseWriter) WriteHeader(statusCode int) {
notFound := statusCode == http.StatusNotFound
rw.notfound = ¬Found
if !notFound {
rw.flushHeader()
rw.next.WriteHeader(statusCode)
}
}
func (rw *IgnoreNotFoundResponseWriter) Write(data []byte) (int, error) {
if rw.notfound == nil {
var value bool
rw.notfound = &value
rw.flushHeader()
}
if *rw.notfound {
return 0, nil
}
return rw.next.Write(data)
}
func WithUI(uifs fs.FS, prefix string) Option {
return PostInit(func(program *Program) {
uiOptions := UIOptions{
fs: uifs,
}
var serveFound bool
for _, cmd := range program.Parser.Commands() {
if cmd.Name == "serve" {
_, err := cmd.AddGroup("ui", "User Interface", &uiOptions)
if err != nil {
panic(err)
}
serveFound = true
break
}
}
if !serveFound {
panic("serve command not found")
}
middleware := func(next http.Handler) (http.Handler, error) {
var uiHandler http.Handler
if uiOptions.External == "" {
uiHandler = orusapi.NewSPAFileServer(
http.FS(uiOptions.fs),
prefix,
)
} else {
u, err := url.Parse(uiOptions.External)
if err != nil {
return nil, err
}
uiHandler = httputil.NewSingleHostReverseProxy(u)
}
if prefix != "" {
uiHandler = http.StripPrefix(prefix, uiHandler)
}
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if prefix != "" {
if !strings.HasPrefix(r.URL.Path, prefix) {
next.ServeHTTP(rw, r)
}
}
accepted, err := accept.Negotiate(
r.Header.Get("Accept"),
"text/html", "application/json")
if err != nil || accepted == "application/json" {
next.ServeHTTP(rw, r)
} else {
rwWrapper := NewIgnoreNotFoundResponseWriter(rw)
uiHandler.ServeHTTP(rwWrapper, r)
if rwWrapper.NotFound() {
next.ServeHTTP(rw, r)
}
}
}), nil
}
WithMiddleware(middleware)(program)
})
}