network.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. package main
  2. import (
  3. "encoding/json"
  4. "log"
  5. "net/http"
  6. "strconv"
  7. "strings"
  8. "imuslab.com/arozos/mod/fileservers"
  9. "imuslab.com/arozos/mod/fileservers/servers/dirserv"
  10. "imuslab.com/arozos/mod/fileservers/servers/ftpserv"
  11. "imuslab.com/arozos/mod/fileservers/servers/samba"
  12. "imuslab.com/arozos/mod/fileservers/servers/sftpserv"
  13. "imuslab.com/arozos/mod/fileservers/servers/webdavserv"
  14. network "imuslab.com/arozos/mod/network"
  15. mdns "imuslab.com/arozos/mod/network/mdns"
  16. "imuslab.com/arozos/mod/network/netstat"
  17. ssdp "imuslab.com/arozos/mod/network/ssdp"
  18. upnp "imuslab.com/arozos/mod/network/upnp"
  19. "imuslab.com/arozos/mod/network/websocket"
  20. prout "imuslab.com/arozos/mod/prouter"
  21. "imuslab.com/arozos/mod/utils"
  22. "imuslab.com/arozos/mod/www"
  23. )
  24. var (
  25. //Network Services Managers
  26. MDNS *mdns.MDNSHost
  27. UPNP *upnp.UPnPClient
  28. SSDP *ssdp.SSDPHost
  29. WebSocketRouter *websocket.Router
  30. //File Server Managers
  31. FTPManager *ftpserv.Manager
  32. WebDAVManager *webdavserv.Manager
  33. SFTPManager *sftpserv.Manager
  34. SambaShareManager *samba.ShareManager
  35. DirListManager *dirserv.Manager
  36. )
  37. func NetworkServiceInit() {
  38. systemWideLogger.PrintAndLog("Network", "Starting ArOZ Network Services", nil)
  39. //Create a router that allow users with System Setting access to access these api endpoints
  40. router := prout.NewModuleRouter(prout.RouterOption{
  41. ModuleName: "System Setting",
  42. AdminOnly: false,
  43. UserHandler: userHandler,
  44. DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
  45. utils.SendErrorResponse(w, "Permission Denied")
  46. },
  47. })
  48. /*
  49. Standard Network Utilties
  50. */
  51. //Register handler endpoints
  52. if *allow_hardware_management {
  53. router.HandleFunc("/system/network/getNICinfo", network.GetNICInfo)
  54. router.HandleFunc("/system/network/getPing", network.GetPing)
  55. //Register as a system setting
  56. registerSetting(settingModule{
  57. Name: "Network Info",
  58. Desc: "Network Information",
  59. IconPath: "SystemAO/network/img/ethernet.png",
  60. Group: "Network",
  61. StartDir: "SystemAO/network/hardware.html",
  62. })
  63. }
  64. router.HandleFunc("/system/network/getNICUsage", netstat.HandleGetNetworkInterfaceStats)
  65. //Start the services that depends on network interface
  66. StartNetworkServices()
  67. //Start the port forward configuration interface
  68. portForwardInit()
  69. //Start userhomepage if enabled
  70. //Handle user webroot routings if homepage is enabled
  71. if *allow_homepage {
  72. userWwwHandler = www.NewWebRootHandler(www.Options{
  73. UserHandler: userHandler,
  74. Database: sysdb,
  75. AgiGateway: AGIGateway,
  76. })
  77. router.HandleFunc("/system/network/www/toggle", userWwwHandler.HandleToggleHomepage)
  78. router.HandleFunc("/system/network/www/webRoot", userWwwHandler.HandleSetWebRoot)
  79. //Register as a system setting
  80. registerSetting(settingModule{
  81. Name: "Personal Page",
  82. Desc: "Personal Web Page",
  83. IconPath: "SystemAO/www/img/homepage.png",
  84. Group: "Network",
  85. StartDir: "SystemAO/www/config.html",
  86. })
  87. }
  88. userRouter := prout.NewModuleRouter(prout.RouterOption{
  89. AdminOnly: false,
  90. UserHandler: userHandler,
  91. DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
  92. utils.SendErrorResponse(w, "Permission Denied")
  93. },
  94. })
  95. WebSocketRouter = websocket.NewRouter()
  96. userRouter.HandleFunc("/system/ws", WebSocketRouter.HandleWebSocketRouting)
  97. }
  98. func StartNetworkServices() {
  99. /*
  100. MDNS Services
  101. */
  102. if *allow_mdns {
  103. m, err := mdns.NewMDNS(mdns.NetworkHost{
  104. HostName: *host_name + "_" + deviceUUID, //To handle more than one identical model within the same network, this must be unique
  105. Port: *listen_port,
  106. Domain: "arozos.com",
  107. Model: deviceModel,
  108. UUID: deviceUUID,
  109. Vendor: deviceVendor,
  110. BuildVersion: build_version,
  111. MinorVersion: internal_version,
  112. }, *force_mac)
  113. if err != nil {
  114. systemWideLogger.PrintAndLog("Network", "MDNS Startup Failed. Running in Offline Mode.", err)
  115. } else {
  116. MDNS = m
  117. }
  118. }
  119. /*
  120. SSDP Discovery Services
  121. */
  122. if *allow_ssdp {
  123. //Get outbound ip
  124. obip, err := network.GetOutboundIP()
  125. if err != nil {
  126. systemWideLogger.PrintAndLog("Network", "SSDP Startup Failed. Running in Offline Mode.", err)
  127. } else {
  128. thisIp := obip.String()
  129. adv, err := ssdp.NewSSDPHost(thisIp, *listen_port, "system/ssdp.xml", ssdp.SSDPOption{
  130. URLBase: "http://" + thisIp + ":" + strconv.Itoa(*listen_port), //This must be http if used as local hosting devices
  131. Hostname: *host_name,
  132. Vendor: deviceVendor,
  133. VendorURL: deviceVendorURL,
  134. ModelName: deviceModel,
  135. ModelDesc: deviceModelDesc,
  136. UUID: deviceUUID,
  137. Serial: "generic",
  138. })
  139. if err != nil {
  140. systemWideLogger.PrintAndLog("Network", "SSDP Startup Failed. Running in Offline Mode.", err)
  141. } else {
  142. //OK! Start SSDP Service
  143. SSDP = adv
  144. SSDP.Start()
  145. }
  146. }
  147. }
  148. /*
  149. UPNP / Setup automatic port forwarding
  150. */
  151. if *allow_upnp {
  152. var u *upnp.UPnPClient
  153. var err error = nil
  154. if *use_tls {
  155. u, err = upnp.NewUPNPClient(*tls_listen_port, *host_name+"-https")
  156. } else {
  157. u, err = upnp.NewUPNPClient(*listen_port, *host_name+"-http")
  158. }
  159. if err != nil {
  160. systemWideLogger.PrintAndLog("Network", "UPnP Startup Failed: "+err.Error(), err)
  161. } else {
  162. //Bind the http port if running in https and http server is not disabled
  163. if *use_tls && !*disable_http {
  164. u.ForwardPort(*listen_port, *host_name+"-http")
  165. }
  166. UPNP = u
  167. //Register nightly listener for upnp renew
  168. nightlyManager.RegisterNightlyTask(func() {
  169. UPNP.RenewForwardRules()
  170. })
  171. //Show a tip for success port forward
  172. connectionEndpoint := UPNP.ExternalIP + ":" + strconv.Itoa(*listen_port)
  173. obip, err := network.GetOutboundIP()
  174. obipstring := "[Outbound IP]"
  175. if err != nil {
  176. } else {
  177. obipstring = obip.String()
  178. }
  179. localEndpoint := obipstring + ":" + strconv.Itoa(*listen_port)
  180. systemWideLogger.PrintAndLog("Network", "Automatic Port Forwarding Completed. Forwarding all request from "+connectionEndpoint+" to "+localEndpoint, nil)
  181. }
  182. }
  183. }
  184. func StopNetworkServices() {
  185. //systemWideLogger.PrintAndLog("Shutting Down Network Services...",nil)
  186. //Shutdown uPNP service if enabled
  187. if *allow_upnp {
  188. systemWideLogger.PrintAndLog("System", "<!> Shutting down uPNP service", nil)
  189. UPNP.Close()
  190. }
  191. //Shutdown SSDP service if enabled
  192. if *allow_ssdp {
  193. systemWideLogger.PrintAndLog("System", "<!> Shutting down SSDP service", nil)
  194. SSDP.Close()
  195. }
  196. //Shutdown MDNS if enabled
  197. if *allow_mdns {
  198. systemWideLogger.PrintAndLog("System", "<!> Shutting down MDNS service", nil)
  199. MDNS.Close()
  200. }
  201. }
  202. /*
  203. File Server Services
  204. */
  205. var networkFileServerDaemon []*fileservers.Server = []*fileservers.Server{}
  206. // Initiate all File Server services
  207. func FileServerInit() {
  208. //Register System Setting
  209. registerSetting(settingModule{
  210. Name: "File Servers",
  211. Desc: "Network File Transfer Servers",
  212. IconPath: "SystemAO/disk/smart/img/small_icon.png",
  213. Group: "Network",
  214. StartDir: "SystemAO/disk/services.html",
  215. RequireAdmin: false,
  216. })
  217. //Create request routers
  218. adminRouter := prout.NewModuleRouter(prout.RouterOption{
  219. ModuleName: "System Setting",
  220. AdminOnly: true,
  221. UserHandler: userHandler,
  222. DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
  223. errorHandlePermissionDenied(w, r)
  224. },
  225. })
  226. router := prout.NewModuleRouter(prout.RouterOption{
  227. ModuleName: "System Setting",
  228. AdminOnly: false,
  229. UserHandler: userHandler,
  230. DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
  231. utils.SendErrorResponse(w, "Permission Denied")
  232. },
  233. })
  234. /* Create File Server Managers */
  235. //WebDAV
  236. webdavPort := *listen_port
  237. if *use_tls {
  238. webdavPort = *tls_listen_port
  239. }
  240. WebDAVManager = webdavserv.NewWebDAVManager(&webdavserv.ManagerOption{
  241. Sysdb: sysdb,
  242. Hostname: *host_name,
  243. TmpDir: *tmp_directory,
  244. Port: webdavPort,
  245. UseTls: *use_tls,
  246. UserHandler: userHandler,
  247. })
  248. //FTP
  249. FTPManager = ftpserv.NewFTPManager(&ftpserv.ManagerOption{
  250. Hostname: *host_name,
  251. TmpFolder: *tmp_directory,
  252. Logger: systemWideLogger,
  253. UserManager: userHandler,
  254. FtpServer: nil,
  255. Sysdb: sysdb,
  256. Upnp: UPNP,
  257. AllowUpnp: *allow_upnp,
  258. })
  259. //SFTP
  260. SFTPManager = sftpserv.NewSFTPServer(&sftpserv.ManagerOption{
  261. Hostname: *host_name,
  262. Upnp: UPNP,
  263. UserManager: userHandler,
  264. KeyFile: "system/auth/id_rsa.key",
  265. Logger: systemWideLogger,
  266. Sysdb: sysdb,
  267. })
  268. listeningPort := *listen_port
  269. if *use_tls {
  270. listeningPort = *tls_listen_port
  271. }
  272. DirListManager = dirserv.NewDirectoryServer(&dirserv.Option{
  273. Sysdb: sysdb,
  274. ServerPort: listeningPort,
  275. UserManager: userHandler,
  276. ServerUUID: deviceUUID,
  277. })
  278. //Samba
  279. var err error
  280. SambaShareManager, err = samba.NewSambaShareManager(userHandler)
  281. if err != nil {
  282. //Disable samba if not installed or platform not supported
  283. log.Println("[INFO] Samba Share Manager Disabled: " + err.Error())
  284. }
  285. //Register Endpoints
  286. //WebDAV
  287. http.HandleFunc("/system/network/webdav/list", WebDAVManager.HandleConnectionList)
  288. router.HandleFunc("/system/network/webdav/edit", WebDAVManager.HandlePermissionEdit)
  289. router.HandleFunc("/system/network/webdav/clear", WebDAVManager.HandleClearAllPending)
  290. router.HandleFunc("/system/network/webdav/status", WebDAVManager.HandleStatusChange)
  291. //SFTP
  292. adminRouter.HandleFunc("/system/storage/sftp/port", SFTPManager.HandleListeningPort)
  293. adminRouter.HandleFunc("/system/storage/sftp/upnp", SFTPManager.HandleToogleUPnP)
  294. adminRouter.HandleFunc("/system/storage/sftp/users", SFTPManager.HandleGetConnectedClients)
  295. //FTP
  296. //adminRouter.HandleFunc("/system/storage/ftp/start", FTPManager.HandleFTPServerStart)
  297. //adminRouter.HandleFunc("/system/storage/ftp/stop", FTPManager.HandleFTPServerStop)
  298. adminRouter.HandleFunc("/system/storage/ftp/upnp", FTPManager.HandleFTPUPnP)
  299. adminRouter.HandleFunc("/system/storage/ftp/status", FTPManager.HandleFTPServerStatus)
  300. adminRouter.HandleFunc("/system/storage/ftp/updateGroups", FTPManager.HandleFTPAccessUpdate)
  301. adminRouter.HandleFunc("/system/storage/ftp/setPort", FTPManager.HandleFTPSetPort)
  302. adminRouter.HandleFunc("/system/storage/ftp/passivemode", FTPManager.HandleFTPPassiveModeSettings)
  303. //Samba Shares (Optional)
  304. if SambaShareManager != nil {
  305. //Activate and Deactivate are functions all users can use if admin enabled smbd service
  306. router.HandleFunc("/system/storage/samba/activate", func(w http.ResponseWriter, r *http.Request) {
  307. if !AuthValidateSecureRequest(w, r, false) {
  308. return
  309. }
  310. if !SambaShareManager.IsEnabled() {
  311. utils.SendErrorResponse(w, "smbd is not enabled on this server")
  312. return
  313. }
  314. password, _ := utils.PostPara(r, "password")
  315. SambaShareManager.ActivateUserAccount(w, r, password)
  316. })
  317. adminRouter.HandleFunc("/system/storage/samba/deactivate", SambaShareManager.DeactiveUserAccount)
  318. adminRouter.HandleFunc("/system/storage/samba/myshare", SambaShareManager.HandleUserSmbStatusList)
  319. //adminRouter.HandleFunc("/system/storage/samba/myshare/delete", SambaShareManager.DelUserSambaShare)
  320. adminRouter.HandleFunc("/system/storage/samba/status", SambaShareManager.SmbdStates)
  321. adminRouter.HandleFunc("/system/storage/samba/list", SambaShareManager.ListSambaShares)
  322. adminRouter.HandleFunc("/system/storage/samba/add", SambaShareManager.AddSambaShare)
  323. adminRouter.HandleFunc("/system/storage/samba/editPath", SambaShareManager.HandleSharePathChange)
  324. adminRouter.HandleFunc("/system/storage/samba/remove", SambaShareManager.DelSambaShare)
  325. adminRouter.HandleFunc("/system/storage/samba/addUser", SambaShareManager.NewSambaUser)
  326. adminRouter.HandleFunc("/system/storage/samba/delUser", SambaShareManager.DelSambaUser)
  327. adminRouter.HandleFunc("/system/storage/samba/listUsers", SambaShareManager.ListSambaUsers)
  328. adminRouter.HandleFunc("/system/storage/samba/updateShareUsers", SambaShareManager.HandleAccessUserUpdate)
  329. }
  330. networkFileServerDaemon = append(networkFileServerDaemon, &fileservers.Server{
  331. ID: "webdav",
  332. Name: "WebDAV",
  333. Desc: "WebDAV Server",
  334. IconPath: "img/system/network-folder-blue.svg",
  335. DefaultPorts: []int{},
  336. Ports: []int{},
  337. ForwardPortIfUpnp: false,
  338. ConnInstrPage: "SystemAO/disk/instr/webdav.html",
  339. ConfigPage: "SystemAO/disk/webdav.html",
  340. EnableCheck: WebDAVManager.GetWebDavEnabled,
  341. ToggleFunc: WebDAVManager.WebDavToogle,
  342. GetEndpoints: WebDAVManager.WebDavGetEndpoints,
  343. })
  344. networkFileServerDaemon = append(networkFileServerDaemon, &fileservers.Server{
  345. ID: "sftp",
  346. Name: "SFTP",
  347. Desc: "SSH File Transfer Protocol Server",
  348. IconPath: "img/system/network-folder-sftp.svg",
  349. DefaultPorts: []int{2022},
  350. Ports: []int{},
  351. ForwardPortIfUpnp: true,
  352. ConnInstrPage: "SystemAO/disk/instr/sftp.html",
  353. ConfigPage: "SystemAO/disk/sftp.html",
  354. EnableCheck: SFTPManager.IsEnabled,
  355. ToggleFunc: SFTPManager.ServerToggle,
  356. GetEndpoints: SFTPManager.GetEndpoints,
  357. })
  358. networkFileServerDaemon = append(networkFileServerDaemon, &fileservers.Server{
  359. ID: "ftp",
  360. Name: "FTP",
  361. Desc: "File Transfer Protocol Server",
  362. IconPath: "img/system/network-folder.svg",
  363. DefaultPorts: []int{21, 22, 23},
  364. Ports: []int{},
  365. ForwardPortIfUpnp: true,
  366. ConnInstrPage: "SystemAO/disk/instr/ftp.html",
  367. ConfigPage: "SystemAO/disk/ftp.html",
  368. EnableCheck: FTPManager.IsFtpServerEnabled,
  369. ToggleFunc: FTPManager.FTPServerToggle,
  370. GetEndpoints: FTPManager.FTPGetEndpoints,
  371. })
  372. networkFileServerDaemon = append(networkFileServerDaemon, &fileservers.Server{
  373. ID: "dirserv",
  374. Name: "Directory Server",
  375. Desc: "Web file viewer for legacy devices",
  376. IconPath: "img/system/network-dirserv.svg",
  377. DefaultPorts: []int{},
  378. Ports: []int{},
  379. ForwardPortIfUpnp: false,
  380. ConnInstrPage: "SystemAO/disk/instr/dirserv.html",
  381. ConfigPage: "SystemAO/disk/dirserv.html",
  382. EnableCheck: DirListManager.DirServerEnabled,
  383. ToggleFunc: DirListManager.Toggle,
  384. GetEndpoints: DirListManager.ListEndpoints,
  385. })
  386. if SambaShareManager != nil {
  387. //Samba is external and might not exists on this host
  388. networkFileServerDaemon = append(networkFileServerDaemon, &fileservers.Server{
  389. ID: "smbd",
  390. Name: "Samba Shares",
  391. Desc: "Share local files via SMB using Samba",
  392. IconPath: "img/system/network-samba.svg",
  393. DefaultPorts: []int{},
  394. Ports: []int{},
  395. ForwardPortIfUpnp: false,
  396. ConnInstrPage: "SystemAO/disk/instr/samba.html",
  397. ConfigPage: "SystemAO/disk/samba.html",
  398. EnableCheck: SambaShareManager.IsEnabled,
  399. ToggleFunc: SambaShareManager.ServerToggle,
  400. GetEndpoints: SambaShareManager.GetEndpoints,
  401. })
  402. }
  403. router.HandleFunc("/system/network/server/list", NetworkHandleGetFileServerServiceList)
  404. router.HandleFunc("/system/network/server/endpoints", NetworkHandleGetFileServerEndpoints)
  405. router.HandleFunc("/system/network/server/status", NetworkHandleGetFileServerStatus)
  406. adminRouter.HandleFunc("/system/network/server/toggle", NetworkHandleFileServerToggle)
  407. }
  408. // Toggle the target File Server Services
  409. func NetworkHandleFileServerToggle(w http.ResponseWriter, r *http.Request) {
  410. servid, err := utils.PostPara(r, "id")
  411. if err != nil {
  412. utils.SendErrorResponse(w, "invalid service id given")
  413. return
  414. }
  415. newState, err := utils.PostPara(r, "enable")
  416. if err != nil {
  417. utils.SendErrorResponse(w, "undefined enable state")
  418. return
  419. }
  420. targetfserv := fileservers.GetFileServerById(networkFileServerDaemon, servid)
  421. if targetfserv == nil {
  422. utils.SendErrorResponse(w, "target service not exists")
  423. return
  424. }
  425. if newState == "true" {
  426. //Start up the target service
  427. err = targetfserv.ToggleFunc(true)
  428. if err != nil {
  429. utils.SendErrorResponse(w, "startup failed: "+err.Error())
  430. return
  431. }
  432. } else if newState == "false" {
  433. err = targetfserv.ToggleFunc(false)
  434. if err != nil {
  435. utils.SendErrorResponse(w, "shutdown failed: "+err.Error())
  436. return
  437. }
  438. } else {
  439. utils.SendErrorResponse(w, "unknown state keyword")
  440. return
  441. }
  442. }
  443. // Return a list of supported File Server Services
  444. func NetworkHandleGetFileServerServiceList(w http.ResponseWriter, r *http.Request) {
  445. js, _ := json.Marshal(networkFileServerDaemon)
  446. utils.SendJSONResponse(w, string(js))
  447. }
  448. // Get the status of a file server type.
  449. func NetworkHandleGetFileServerStatus(w http.ResponseWriter, r *http.Request) {
  450. servid, _ := utils.GetPara(r, "id")
  451. if servid == "" {
  452. //List all state in map
  453. result := map[string]bool{}
  454. for _, fserv := range networkFileServerDaemon {
  455. result[fserv.ID] = fserv.EnableCheck()
  456. }
  457. js, _ := json.Marshal(result)
  458. utils.SendJSONResponse(w, string(js))
  459. } else {
  460. //ID is defined. Get the target server and return its status
  461. targetfserv := fileservers.GetFileServerById(networkFileServerDaemon, servid)
  462. if targetfserv == nil {
  463. utils.SendErrorResponse(w, "target file server type not found")
  464. return
  465. }
  466. js, _ := json.Marshal(targetfserv.EnableCheck())
  467. utils.SendJSONResponse(w, string(js))
  468. }
  469. }
  470. // Get a list of endpoint usable by this service
  471. func NetworkHandleGetFileServerEndpoints(w http.ResponseWriter, r *http.Request) {
  472. userinfo, err := userHandler.GetUserInfoFromRequest(w, r)
  473. if err != nil {
  474. utils.SendErrorResponse(w, "user not logged in")
  475. return
  476. }
  477. targetServerTypeID, _ := utils.GetPara(r, "fserv")
  478. targetServerTypeID = strings.TrimSpace(targetServerTypeID)
  479. if targetServerTypeID == "" {
  480. //List all the endpoints
  481. results := map[string][]*fileservers.Endpoint{}
  482. for _, fser := range networkFileServerDaemon {
  483. if fser.GetEndpoints == nil {
  484. results[fser.ID] = []*fileservers.Endpoint{}
  485. continue
  486. }
  487. thisEndpoints := fser.GetEndpoints(userinfo)
  488. results[fser.ID] = thisEndpoints
  489. }
  490. js, _ := json.Marshal(results)
  491. utils.SendJSONResponse(w, string(js))
  492. } else {
  493. //List the target endpoint
  494. for _, fser := range networkFileServerDaemon {
  495. if targetServerTypeID == fser.ID {
  496. thisEndpoints := fser.GetEndpoints(userinfo)
  497. js, _ := json.Marshal(thisEndpoints)
  498. utils.SendJSONResponse(w, string(js))
  499. return
  500. }
  501. }
  502. utils.SendErrorResponse(w, "target service not found")
  503. }
  504. }