web_handler.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. package main
  2. import (
  3. "bufio"
  4. "context"
  5. "encoding/json"
  6. "fmt"
  7. "io/fs"
  8. "net/http"
  9. "os"
  10. "os/exec"
  11. "strings"
  12. "hnyfkj.com.cn/rtu/linux/baseapp"
  13. "hnyfkj.com.cn/rtu/linux/utils/jsonrpc2"
  14. )
  15. const noValue = "--"
  16. type LoginReq struct {
  17. Username string `json:"username"`
  18. Password string `json:"password"`
  19. }
  20. type LoginResp struct {
  21. Success bool `json:"success"`
  22. }
  23. func loginHandler(w http.ResponseWriter, r *http.Request) {
  24. if r.Method != http.MethodPost {
  25. w.WriteHeader(http.StatusMethodNotAllowed)
  26. return
  27. }
  28. var req LoginReq
  29. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  30. w.WriteHeader(http.StatusBadRequest)
  31. return
  32. }
  33. ok := req.Username == "admin" && req.Password == "admin123456"
  34. if ok {
  35. sessionID := createSession()
  36. addSession(sessionID)
  37. http.SetCookie(w, &http.Cookie{
  38. Name: "session_id",
  39. Value: sessionID,
  40. Path: "/",
  41. HttpOnly: true,
  42. SameSite: http.SameSiteStrictMode,
  43. })
  44. }
  45. w.Header().Set("Content-Type", "application/json")
  46. json.NewEncoder(w).Encode(LoginResp{Success: ok})
  47. }
  48. func logoutHandler(w http.ResponseWriter, r *http.Request) {
  49. if cookie, err := r.Cookie("session_id"); err == nil {
  50. sessionMu.Lock()
  51. delete(sessions, cookie.Value)
  52. sessionMu.Unlock()
  53. }
  54. http.SetCookie(w, &http.Cookie{
  55. Name: "session_id",
  56. Value: "",
  57. Path: "/",
  58. MaxAge: -1,
  59. })
  60. http.Redirect(w, r, "/", http.StatusFound)
  61. }
  62. func getIMEIHandler(w http.ResponseWriter, r *http.Request) {
  63. data, err := os.ReadFile("/var/device_imei.txt")
  64. if err != nil {
  65. w.Write([]byte(noValue))
  66. } else {
  67. w.Write(append([]byte("🆔"), data...))
  68. }
  69. }
  70. func sshdStatusHandler(w http.ResponseWriter, r *http.Request) {
  71. if r.Method != http.MethodGet {
  72. http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
  73. return
  74. }
  75. status := noValue
  76. cmd := exec.Command("systemctl", "is-active", "yfkj-sshd-mqtt-bridge.service")
  77. if out, err := cmd.Output(); err == nil {
  78. if strings.TrimSpace(string(out)) == "active" {
  79. status = "🟢 YFKJ-SSHD"
  80. }
  81. }
  82. w.Header().Set("Content-Type", "application/json")
  83. json.NewEncoder(w).Encode(map[string]string{"status": status})
  84. }
  85. func (ui *WebUI) rootHandler(w http.ResponseWriter, r *http.Request) {
  86. path := r.URL.Path
  87. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  88. w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0")
  89. w.Header().Set("Pragma", "no-cache")
  90. w.Header().Set("Expires", "0")
  91. // 1, 未登录时, 只能看登录页
  92. if !checkSession(r) {
  93. data, err := fs.ReadFile(ui.fs, "login.html")
  94. if err != nil {
  95. http.NotFound(w, r)
  96. } else {
  97. w.Write(data)
  98. }
  99. return
  100. }
  101. // 2, 登录成功, 展示应用首页
  102. if path == "/" {
  103. data, err := fs.ReadFile(ui.fs, "app.html")
  104. if err != nil {
  105. http.NotFound(w, r)
  106. } else {
  107. w.Write(data)
  108. }
  109. return
  110. }
  111. // 3, 在网站内跳转其它功能页
  112. if strings.HasPrefix(path, "/pages/") { // path 形如 "/pages/page1.html"
  113. data, err := fs.ReadFile(ui.fs, path[1:])
  114. if err != nil {
  115. http.NotFound(w, r)
  116. } else {
  117. w.Write(data)
  118. }
  119. return
  120. }
  121. // 4, 访问其它不认识的页面时
  122. http.NotFound(w, r)
  123. }
  124. func serviceLogHandler(w http.ResponseWriter, r *http.Request) {
  125. unit := r.URL.Query().Get("unit")
  126. if unit == "" {
  127. http.Error(w, "missing unit",
  128. http.StatusBadRequest)
  129. return
  130. }
  131. w.Header().Set("Content-Type", "text/event-stream")
  132. w.Header().Set("Cache-Control", "no-cache")
  133. w.Header().Set("Connection", "keep-alive")
  134. w.Header().Set("X-Accel-Buffering", "no")
  135. flusher, ok := w.(http.Flusher)
  136. if !ok {
  137. http.Error(w, "not supported",
  138. http.StatusInternalServerError)
  139. return
  140. }
  141. cmd := exec.Command(
  142. "journalctl",
  143. "-f",
  144. "-u",
  145. unit,
  146. "-n",
  147. "50",
  148. "--no-pager",
  149. "-q",
  150. "-o",
  151. "short-iso",
  152. )
  153. stdout, err := cmd.StdoutPipe()
  154. if err != nil {
  155. http.Error(w, err.Error(), http.StatusInternalServerError)
  156. return
  157. }
  158. if err := cmd.Start(); err != nil {
  159. http.Error(w, err.Error(), http.StatusInternalServerError)
  160. return
  161. }
  162. if true {
  163. baseapp.Logger.Debugf("[服务日志流启动] unit=%s", unit)
  164. }
  165. defer func() {
  166. if cmd.Process != nil {
  167. cmd.Process.Kill()
  168. }
  169. cmd.Wait()
  170. baseapp.Logger.Debugf("[服务日志流关闭] unit=%s", unit)
  171. }()
  172. scanner := bufio.NewScanner(stdout)
  173. for scanner.Scan() {
  174. select {
  175. case <-r.Context().Done():
  176. return
  177. default:
  178. }
  179. if _, err := fmt.Fprintf(w, "data: %s\n\n", scanner.Text()); err != nil {
  180. return
  181. }
  182. flusher.Flush()
  183. }
  184. }
  185. func runShellAndWrite(w http.ResponseWriter, cmd string) {
  186. baseapp.Logger.Debugf("[执行命令] %s", cmd)
  187. out, err := exec.Command("sh", "-c", cmd).CombinedOutput()
  188. if err != nil {
  189. baseapp.Logger.Errorf("[命令失败] %s err=%v", cmd, err)
  190. http.Error(w, err.Error()+"\n"+string(out),
  191. http.StatusInternalServerError)
  192. return
  193. }
  194. w.Write(out)
  195. }
  196. func netInterfaces(w http.ResponseWriter, r *http.Request) {
  197. runShellAndWrite(w, "ifconfig")
  198. }
  199. func netRoutes(w http.ResponseWriter, r *http.Request) {
  200. runShellAndWrite(w, "route -n")
  201. }
  202. func netDNS(w http.ResponseWriter, r *http.Request) {
  203. runShellAndWrite(w, "cat /etc/resolv.conf")
  204. }
  205. func callRPCResult(ctx context.Context, port int, method string, params any, result any) error {
  206. url := fmt.Sprintf("http://127.0.0.1:%d/rpc", port)
  207. req, _ := json.Marshal(params)
  208. baseapp.Logger.Debugf("[接收RPC请求] %s %s params=%s\n", url, method, string(req))
  209. client, err := jsonrpc2.NewRPCClient(url)
  210. if err != nil {
  211. baseapp.Logger.Errorf("[执行RPC错误] %s err=%v\n", method, err)
  212. return err
  213. }
  214. resp, err := client.Call(ctx, method, params)
  215. if err != nil {
  216. return err
  217. }
  218. baseapp.Logger.Debugf("[发送RPC应答] %s result=%s\n", method, string(resp.Result))
  219. return json.Unmarshal(resp.Result, result)
  220. }
  221. func callRPCResponse(w http.ResponseWriter, r *http.Request, port int, method string, params any) {
  222. url := fmt.Sprintf("http://127.0.0.1:%d/rpc", port)
  223. req, _ := json.Marshal(params)
  224. baseapp.Logger.Debugf("[接收RPC请求] %s %s params=%s\n", url, method, string(req))
  225. client, err := jsonrpc2.NewRPCClient(url)
  226. if err != nil {
  227. http.Error(w, err.Error(), http.StatusInternalServerError)
  228. return
  229. }
  230. resp, err := client.Call(r.Context(), method, params)
  231. if err != nil {
  232. baseapp.Logger.Errorf("[执行RPC错误] %s err=%v\n", method, err)
  233. http.Error(w, err.Error(), http.StatusInternalServerError)
  234. return
  235. }
  236. baseapp.Logger.Debugf("[发送RPC应答] %s result=%s\n", method, string(resp.Result))
  237. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  238. json.NewEncoder(w).Encode(resp.Result)
  239. }
  240. func serviceLogLevel(w http.ResponseWriter, r *http.Request, port int) {
  241. switch r.Method {
  242. case http.MethodGet:
  243. callRPCResponse(w, r, port, "basic.getLogLevel", nil)
  244. case http.MethodPost:
  245. var req struct {
  246. Level string `json:"level"`
  247. }
  248. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  249. http.Error(w, err.Error(), http.StatusBadRequest)
  250. return
  251. }
  252. callRPCResponse(w, r, port, "basic.setLogLevel", map[string]string{"log_level": req.Level})
  253. default:
  254. http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
  255. }
  256. }
  257. func serviceLogLevelHandler(port int) http.HandlerFunc {
  258. return func(w http.ResponseWriter, r *http.Request) {
  259. serviceLogLevel(w, r, port)
  260. }
  261. }