reverseproxy - cmd/reverseproxy/servers.go
1 package main
2
3 import (
4 "encoding/json"
5 "net"
6 "os/exec"
7 "path/filepath"
8 "strconv"
9 "syscall"
10
11 "vimagination.zapto.org/reverseproxy"
12 )
13
14 type servers map[string]*server
15
16 func (s servers) Init() {
17 for name, server := range s {
18 server.Init(name)
19 }
20 }
21
22 func (s servers) Shutdown() {
23 for _, server := range s {
24 server.Shutdown()
25 }
26 }
27
28 type server struct {
29 Redirects map[uint64]*redirect `json:"redirects"`
30 Commands map[uint64]*command `json:"commands"`
31 name string
32 lastRID uint64
33 lastCID uint64
34 }
35
36 func (s *server) Init(name string) {
37 s.name = name
38 for id, r := range s.Redirects {
39 r.Init()
40 if id > s.lastRID {
41 s.lastRID = id
42 }
43 }
44 for id, c := range s.Commands {
45 c.Init(s, id)
46 if id > s.lastCID {
47 s.lastCID = id
48 }
49 }
50 }
51
52 func (s *server) addRedirect(rd redirectData) uint64 {
53 s.lastRID++
54 id := s.lastRID
55 s.Redirects[id] = &redirect{
56 redirectData: rd,
57 matchServiceName: makeMatchService(rd.Match),
58 }
59 saveConfig()
60 return id
61 }
62
63 func (s *server) addCommand(cd commandData) uint64 {
64 s.lastCID++
65 id := s.lastCID
66 s.Commands[id] = &command{
67 commandData: cd,
68 matchServiceName: makeMatchService(cd.Match),
69 id: id,
70 }
71 saveConfig()
72 return id
73 }
74
75 func (s *server) Shutdown() {
76 for _, r := range s.Redirects {
77 r.Shutdown()
78 }
79 for _, c := range s.Commands {
80 c.Shutdown()
81 }
82 }
83
84 type redirectData struct {
85 From uint16 `json:"from"`
86 To string `json:"to"`
87 Match []match `json:"match"`
88 }
89
90 type redirect struct {
91 redirectData
92 matchServiceName reverseproxy.MatchServiceName
93 Start bool `json:"start"`
94 port *reverseproxy.Port
95 err string
96 }
97
98 func (r *redirect) Init() {
99 r.matchServiceName = makeMatchService(r.Match)
100 if r.Start {
101 r.Run()
102 }
103 }
104
105 func (r *redirect) Run() {
106 if r.From > 0 && r.To != "" && r.port == nil {
107 addr, err := net.ResolveTCPAddr("tcp", r.To)
108 if err != nil {
109 r.err = err.Error()
110 } else if r.port, err = reverseproxy.AddRedirect(r.matchServiceName, r.From, addr); err != nil {
111 r.err = err.Error()
112 } else {
113 r.Start = true
114 saveConfig()
115 }
116 }
117 }
118
119 func (r *redirect) Stop() {
120 r.Start = false
121 r.Shutdown()
122 saveConfig()
123 }
124
125 func (r *redirect) Shutdown() {
126 if r.port != nil {
127 r.port.Close()
128 r.port = nil
129 }
130 }
131
132 type user struct {
133 UID uint32 `json:"uid"`
134 GID uint32 `json:"gid"`
135 }
136
137 type commandData struct {
138 Exe string `json:"exe"`
139 Params []string `json:"params"`
140 WorkDir string `json:"workDir"`
141 Env map[string]string `json:"env"`
142 Match []match `json:"match"`
143 User *user `json:"user,omitempty"`
144 }
145
146 type command struct {
147 commandData
148 matchServiceName reverseproxy.MatchServiceName
149 Start bool `json:"start"`
150 status int
151 unixCmd *reverseproxy.UnixCmd
152 err string
153 server *server
154 id uint64
155 }
156
157 func (c *command) Init(server *server, id uint64) {
158 c.server = server
159 c.id = id
160 c.matchServiceName = makeMatchService(c.Match)
161 if c.Start {
162 c.Run()
163 }
164 }
165
166 func (c *command) Run() error {
167 if c.unixCmd == nil {
168 cmd := exec.Command(c.Exe, c.Params...)
169 cmd.Env = make([]string, 0, len(c.Env))
170 for k, v := range c.Env {
171 cmd.Env = append(cmd.Env, k+"="+v)
172 }
173 if c.User != nil {
174 cmd.SysProcAttr = &syscall.SysProcAttr{
175 Credential: &syscall.Credential{
176 Uid: c.User.UID,
177 Gid: c.User.GID,
178 },
179 }
180 }
181 if c.WorkDir == "" {
182 cmd.Dir = filepath.Dir(c.Exe)
183 } else {
184 cmd.Dir = c.WorkDir
185 }
186 uc, err := reverseproxy.RegisterCmd(c.matchServiceName, cmd)
187 if err != nil {
188 c.err = err.Error()
189 c.status = 2
190 return err
191 }
192 c.status = 1
193 c.err = ""
194 c.unixCmd = uc
195 go func() {
196 err := cmd.Wait()
197 config.mu.Lock()
198 if c.unixCmd == uc {
199 if err != nil {
200 c.err = string(err.(*exec.ExitError).Stderr)
201 broadcast(broadcastCommandError, append(strconv.AppendQuote(append(strconv.AppendUint(append(strconv.AppendQuote(json.RawMessage{'{', '"', 's', 'e', 'r', 'v', 'e', 'r', '"', ':'}, c.server.name), ',', '"', 'i', 'd', '"', ':'), c.id, 10), ',', '"', 'e', 'r', 'r', '"', ':'), c.err), '}'), 0)
202 }
203 broadcast(broadcastCommandStopped, append(strconv.AppendUint(append(strconv.AppendQuote(json.RawMessage{'['}, c.server.name), ','), c.id, 10), ']'), 0)
204 c.status = 2
205 }
206 c.unixCmd = nil
207 config.mu.Unlock()
208 }()
209 c.Start = true
210 saveConfig()
211 }
212 return nil
213 }
214
215 func (c *command) Stop() {
216 c.Start = false
217 c.Shutdown()
218 saveConfig()
219 }
220
221 func (c *command) Shutdown() {
222 if c.unixCmd != nil {
223 c.status = 0
224 c.unixCmd.Close()
225 c.unixCmd = nil
226 }
227 }
228
229 type match struct {
230 IsSuffix bool `json:"isSuffix"`
231 Name string `json:"name"`
232 }
233
234 func (m match) makeMatchService() reverseproxy.MatchServiceName {
235 if m.IsSuffix {
236 return reverseproxy.HostNameSuffix(m.Name)
237 }
238 return reverseproxy.HostName(m.Name)
239 }
240
241 func makeMatchService(match []match) reverseproxy.MatchServiceName {
242 if len(match) == 0 {
243 return none{}
244 } else if len(match) == 1 {
245 return match[0].makeMatchService()
246 }
247 ms := make(reverseproxy.Hosts, len(match))
248 for n, m := range match {
249 ms[n] = m.makeMatchService()
250 }
251 return ms
252 }
253
254 type none struct{}
255
256 func (none) MatchService(_ string) bool { return false }
257