httpserver.go 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164
  1. package main
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "image/jpeg"
  7. "io"
  8. "io/ioutil"
  9. "net"
  10. "net/http"
  11. "os"
  12. "os/exec"
  13. "path"
  14. "path/filepath"
  15. "runtime"
  16. "strconv"
  17. "strings"
  18. "sync"
  19. "syscall"
  20. "time"
  21. "github.com/openatx/atx-agent/jsonrpc"
  22. "github.com/gorilla/mux"
  23. "github.com/gorilla/websocket"
  24. "github.com/mholt/archiver"
  25. "github.com/openatx/androidutils"
  26. "github.com/openatx/atx-agent/cmdctrl"
  27. "github.com/prometheus/procfs"
  28. "github.com/rs/cors"
  29. )
  30. type Server struct {
  31. // tunnel *TunnelProxy
  32. httpServer *http.Server
  33. }
  34. func NewServer() *Server {
  35. server := &Server{}
  36. server.initHTTPServer()
  37. return server
  38. }
  39. func (server *Server) initHTTPServer() {
  40. m := mux.NewRouter()
  41. m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  42. renderHTML(w, "index.html")
  43. })
  44. m.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) {
  45. io.WriteString(w, version)
  46. })
  47. m.HandleFunc("/remote", func(w http.ResponseWriter, r *http.Request) {
  48. renderHTML(w, "remote.html")
  49. })
  50. // jsonrpc client to call uiautomator
  51. rpcc := jsonrpc.NewClient("http://127.0.0.1:9008/jsonrpc/0")
  52. rpcc.ErrorCallback = func() error {
  53. service.Restart("uiautomator")
  54. // if !service.Running("uiautomator") {
  55. // service.Start("uiautomator")
  56. // }
  57. return nil
  58. }
  59. rpcc.ErrorFixTimeout = 40 * time.Second
  60. rpcc.ServerOK = func() bool {
  61. return service.Running("uiautomator")
  62. }
  63. m.HandleFunc("/newCommandTimeout", func(w http.ResponseWriter, r *http.Request) {
  64. var timeout int
  65. err := json.NewDecoder(r.Body).Decode(&timeout) // TODO: auto get rotation
  66. if err != nil {
  67. http.Error(w, "Empty payload", 400) // bad request
  68. return
  69. }
  70. cmdTimeout := time.Duration(timeout) * time.Second
  71. uiautomatorTimer.Reset(cmdTimeout)
  72. renderJSON(w, map[string]interface{}{
  73. "success": true,
  74. "description": fmt.Sprintf("newCommandTimeout updated to %v", cmdTimeout),
  75. })
  76. }).Methods("POST")
  77. // robust communicate with uiautomator
  78. // If the service is down, restart it and wait it recover
  79. m.HandleFunc("/dump/hierarchy", func(w http.ResponseWriter, r *http.Request) {
  80. if !service.Running("uiautomator") {
  81. xmlContent, err := dumpHierarchy()
  82. if err != nil {
  83. log.Println("Err:", err)
  84. http.Error(w, err.Error(), http.StatusInternalServerError)
  85. return
  86. }
  87. renderJSON(w, map[string]interface{}{
  88. "jsonrpc": "2.0",
  89. "id": 1,
  90. "result": xmlContent,
  91. })
  92. return
  93. }
  94. resp, err := rpcc.RobustCall("dumpWindowHierarchy", false) // false: no compress
  95. if err != nil {
  96. log.Println("Err:", err)
  97. http.Error(w, err.Error(), http.StatusInternalServerError)
  98. return
  99. }
  100. renderJSON(w, resp)
  101. })
  102. m.HandleFunc("/proc/list", func(w http.ResponseWriter, r *http.Request) {
  103. ps, err := listAllProcs()
  104. if err != nil {
  105. http.Error(w, err.Error(), 500)
  106. return
  107. }
  108. renderJSON(w, ps)
  109. })
  110. m.HandleFunc("/proc/{pkgname}/meminfo", func(w http.ResponseWriter, r *http.Request) {
  111. pkgname := mux.Vars(r)["pkgname"]
  112. info, err := parseMemoryInfo(pkgname)
  113. if err != nil {
  114. http.Error(w, err.Error(), http.StatusInternalServerError)
  115. return
  116. }
  117. renderJSON(w, info)
  118. })
  119. m.HandleFunc("/proc/{pkgname}/meminfo/all", func(w http.ResponseWriter, r *http.Request) {
  120. pkgname := mux.Vars(r)["pkgname"]
  121. ps, err := listAllProcs()
  122. if err != nil {
  123. http.Error(w, err.Error(), 500)
  124. return
  125. }
  126. mems := make(map[string]map[string]int, 0)
  127. for _, p := range ps {
  128. if len(p.Cmdline) != 1 {
  129. continue
  130. }
  131. if p.Name == pkgname || strings.HasPrefix(p.Name, pkgname+":") {
  132. info, err := parseMemoryInfo(p.Name)
  133. if err != nil {
  134. continue
  135. }
  136. mems[p.Name] = info
  137. }
  138. }
  139. renderJSON(w, mems)
  140. })
  141. // make(map[int][]int)
  142. m.HandleFunc("/proc/{pkgname}/cpuinfo", func(w http.ResponseWriter, r *http.Request) {
  143. pkgname := mux.Vars(r)["pkgname"]
  144. pid, err := pidOf(pkgname)
  145. if err != nil {
  146. http.Error(w, err.Error(), http.StatusGone)
  147. return
  148. }
  149. info, err := readCPUInfo(pid)
  150. if err != nil {
  151. http.Error(w, err.Error(), http.StatusInternalServerError)
  152. return
  153. }
  154. renderJSON(w, info)
  155. })
  156. m.HandleFunc("/webviews", func(w http.ResponseWriter, r *http.Request) {
  157. netUnix, err := procfs.NewNetUnix()
  158. if err != nil {
  159. return
  160. }
  161. unixPaths := make(map[string]bool, 0)
  162. for _, row := range netUnix.Rows {
  163. if !strings.HasPrefix(row.Path, "@") {
  164. continue
  165. }
  166. if !strings.Contains(row.Path, "devtools_remote") {
  167. continue
  168. }
  169. unixPaths[row.Path[1:]] = true
  170. }
  171. socketPaths := make([]string, 0, len(unixPaths))
  172. for key := range unixPaths {
  173. socketPaths = append(socketPaths, key)
  174. }
  175. renderJSON(w, socketPaths)
  176. })
  177. m.HandleFunc("/webviews/{pkgname}", func(w http.ResponseWriter, r *http.Request) {
  178. packageName := mux.Vars(r)["pkgname"]
  179. netUnix, err := procfs.NewNetUnix()
  180. if err != nil {
  181. return
  182. }
  183. unixPaths := make(map[string]bool, 0)
  184. for _, row := range netUnix.Rows {
  185. if !strings.HasPrefix(row.Path, "@") {
  186. continue
  187. }
  188. if !strings.Contains(row.Path, "devtools_remote") {
  189. continue
  190. }
  191. unixPaths[row.Path[1:]] = true
  192. }
  193. result := make([]interface{}, 0)
  194. procs, err := findProcAll(packageName)
  195. for _, proc := range procs {
  196. cmdline, _ := proc.CmdLine()
  197. suffix := "_" + strconv.Itoa(proc.PID)
  198. for socketPath := range unixPaths {
  199. if strings.HasSuffix(socketPath, suffix) ||
  200. (packageName == "com.android.browser" && socketPath == "chrome_devtools_remote") {
  201. result = append(result, map[string]interface{}{
  202. "pid": proc.PID,
  203. "name": cmdline[0],
  204. "socketPath": socketPath,
  205. })
  206. }
  207. }
  208. }
  209. renderJSON(w, result)
  210. })
  211. m.HandleFunc("/pidof/{pkgname}", func(w http.ResponseWriter, r *http.Request) {
  212. pkgname := mux.Vars(r)["pkgname"]
  213. pid, err := pidOf(pkgname)
  214. if err != nil {
  215. http.Error(w, err.Error(), http.StatusGone)
  216. return
  217. }
  218. io.WriteString(w, strconv.Itoa(pid))
  219. })
  220. m.HandleFunc("/session/{pkgname}", func(w http.ResponseWriter, r *http.Request) {
  221. packageName := mux.Vars(r)["pkgname"]
  222. mainActivity, err := mainActivityOf(packageName)
  223. if err != nil {
  224. http.Error(w, err.Error(), http.StatusGone) // 410
  225. return
  226. }
  227. // Refs: https://stackoverflow.com/questions/12131555/leading-dot-in-androidname-really-required
  228. // MainActivity convert to .MainActivity
  229. // com.example.app.MainActivity keep same
  230. // app.MainActivity keep same
  231. // So only words not contains dot, need to add prefix "."
  232. if !strings.Contains(mainActivity, ".") {
  233. mainActivity = "." + mainActivity
  234. }
  235. flags := r.FormValue("flags")
  236. if flags == "" {
  237. flags = "-W -S" // W: wait launched, S: stop before started
  238. }
  239. timeout := r.FormValue("timeout") // supported value: 60s, 1m. 60 is invalid
  240. duration, err := time.ParseDuration(timeout)
  241. if err != nil {
  242. duration = 60 * time.Second
  243. }
  244. output, err := runShellTimeout(duration, "am", "start", flags, "-n", packageName+"/"+mainActivity)
  245. if err != nil {
  246. renderJSON(w, map[string]interface{}{
  247. "success": false,
  248. "error": err.Error(),
  249. "output": string(output),
  250. "mainActivity": mainActivity,
  251. })
  252. } else {
  253. renderJSON(w, map[string]interface{}{
  254. "success": true,
  255. "mainActivity": mainActivity,
  256. "output": string(output),
  257. })
  258. }
  259. }).Methods("POST")
  260. m.HandleFunc("/session/{pid:[0-9]+}:{pkgname}/{url:ping|jsonrpc/0}", func(w http.ResponseWriter, r *http.Request) {
  261. pkgname := mux.Vars(r)["pkgname"]
  262. pid, _ := strconv.Atoi(mux.Vars(r)["pid"])
  263. proc, err := procfs.NewProc(pid)
  264. if err != nil {
  265. http.Error(w, err.Error(), http.StatusGone) // 410
  266. return
  267. }
  268. cmdline, _ := proc.CmdLine()
  269. if len(cmdline) != 1 || cmdline[0] != pkgname {
  270. http.Error(w, fmt.Sprintf("cmdline expect [%s] but got %v", pkgname, cmdline), http.StatusGone)
  271. return
  272. }
  273. r.URL.Path = "/" + mux.Vars(r)["url"]
  274. uiautomatorProxy.ServeHTTP(w, r)
  275. })
  276. m.HandleFunc("/shell", func(w http.ResponseWriter, r *http.Request) {
  277. command := r.FormValue("command")
  278. if command == "" {
  279. command = r.FormValue("c")
  280. }
  281. timeoutSeconds := r.FormValue("timeout")
  282. if timeoutSeconds == "" {
  283. timeoutSeconds = "60"
  284. }
  285. seconds, err := strconv.Atoi(timeoutSeconds)
  286. if err != nil {
  287. http.Error(w, err.Error(), http.StatusBadRequest)
  288. return
  289. }
  290. c := Command{
  291. Args: []string{command},
  292. Shell: true,
  293. Timeout: time.Duration(seconds) * time.Second,
  294. }
  295. output, err := c.CombinedOutput()
  296. exitCode := cmdError2Code(err)
  297. renderJSON(w, map[string]interface{}{
  298. "output": string(output),
  299. "exitCode": exitCode,
  300. "error": err,
  301. })
  302. }).Methods("GET", "POST")
  303. // TODO(ssx): untested
  304. m.HandleFunc("/shell/background", func(w http.ResponseWriter, r *http.Request) {
  305. command := r.FormValue("command")
  306. if command == "" {
  307. command = r.FormValue("c")
  308. }
  309. c := Command{
  310. Args: []string{command},
  311. Shell: true,
  312. }
  313. pid, err := c.StartBackground()
  314. if err != nil {
  315. renderJSON(w, map[string]interface{}{
  316. "success": false,
  317. "description": err.Error(),
  318. })
  319. return
  320. }
  321. renderJSON(w, map[string]interface{}{
  322. "success": true,
  323. "pid": pid,
  324. "description": fmt.Sprintf("Successfully started program: %v", command),
  325. })
  326. })
  327. m.HandleFunc("/shell/stream", func(w http.ResponseWriter, r *http.Request) {
  328. w.Header().Set("Content-Type", "application/octet-stream")
  329. command := r.FormValue("command")
  330. if command == "" {
  331. command = r.FormValue("c")
  332. }
  333. c := exec.Command("sh", "-c", command)
  334. httpWriter := newFakeWriter(func(data []byte) (int, error) {
  335. n, err := w.Write(data)
  336. if err == nil {
  337. if f, ok := w.(http.Flusher); ok {
  338. f.Flush()
  339. }
  340. } else {
  341. log.Println("Write error")
  342. }
  343. return n, err
  344. })
  345. c.Stdout = httpWriter
  346. c.Stderr = httpWriter
  347. // wait until program quit
  348. cmdQuit := make(chan error, 0)
  349. go func() {
  350. cmdQuit <- c.Run()
  351. }()
  352. select {
  353. case <-httpWriter.Err:
  354. if c.Process != nil {
  355. c.Process.Signal(syscall.SIGTERM)
  356. }
  357. case <-cmdQuit:
  358. log.Println("command quit")
  359. }
  360. log.Println("program quit")
  361. })
  362. m.HandleFunc("/stop", func(w http.ResponseWriter, r *http.Request) {
  363. log.Println("stop all service")
  364. service.StopAll()
  365. log.Println("service stopped")
  366. io.WriteString(w, "Finished!")
  367. go func() {
  368. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  369. defer cancel() // The document says need to call cancel(), but I donot known why.
  370. server.httpServer.Shutdown(ctx)
  371. }()
  372. })
  373. m.HandleFunc("/services/{name}", func(w http.ResponseWriter, r *http.Request) {
  374. name := mux.Vars(r)["name"]
  375. var resp map[string]interface{}
  376. if !service.Exists(name) {
  377. w.WriteHeader(400) // bad request
  378. renderJSON(w, map[string]interface{}{
  379. "success": false,
  380. "description": fmt.Sprintf("service %s does not exist", strconv.Quote(name)),
  381. })
  382. return
  383. }
  384. switch r.Method {
  385. case "GET":
  386. resp = map[string]interface{}{
  387. "success": true,
  388. "running": service.Running(name),
  389. }
  390. case "POST":
  391. err := service.Start(name)
  392. switch err {
  393. case nil:
  394. resp = map[string]interface{}{
  395. "success": true,
  396. "description": "successfully started",
  397. }
  398. case cmdctrl.ErrAlreadyRunning:
  399. resp = map[string]interface{}{
  400. "success": true,
  401. "description": "already started",
  402. }
  403. default:
  404. resp = map[string]interface{}{
  405. "success": false,
  406. "description": "failure on start: " + err.Error(),
  407. }
  408. }
  409. case "DELETE":
  410. err := service.Stop(name)
  411. switch err {
  412. case nil:
  413. resp = map[string]interface{}{
  414. "success": true,
  415. "description": "successfully stopped",
  416. }
  417. case cmdctrl.ErrAlreadyStopped:
  418. resp = map[string]interface{}{
  419. "success": true,
  420. "description": "already stopped",
  421. }
  422. default:
  423. resp = map[string]interface{}{
  424. "success": false,
  425. "description": "failure on stop: " + err.Error(),
  426. }
  427. }
  428. default:
  429. resp = map[string]interface{}{
  430. "success": false,
  431. "description": "invalid request method: " + r.Method,
  432. }
  433. }
  434. if ok, success := resp["success"].(bool); ok {
  435. if !success {
  436. w.WriteHeader(400) // bad request
  437. }
  438. }
  439. renderJSON(w, resp)
  440. }).Methods("GET", "POST", "DELETE")
  441. // Deprecated use /services/{name} instead
  442. m.HandleFunc("/uiautomator", func(w http.ResponseWriter, r *http.Request) {
  443. err := service.Start("uiautomator")
  444. if err == nil {
  445. io.WriteString(w, "Successfully started")
  446. } else if err == cmdctrl.ErrAlreadyRunning {
  447. io.WriteString(w, "Already started")
  448. } else {
  449. http.Error(w, err.Error(), 500)
  450. }
  451. }).Methods("POST")
  452. // Deprecated
  453. m.HandleFunc("/uiautomator", func(w http.ResponseWriter, r *http.Request) {
  454. err := service.Stop("uiautomator", true) // wait until program quit
  455. if err == nil {
  456. io.WriteString(w, "Successfully stopped")
  457. } else if err == cmdctrl.ErrAlreadyStopped {
  458. io.WriteString(w, "Already stopped")
  459. } else {
  460. http.Error(w, err.Error(), 500)
  461. }
  462. }).Methods("DELETE")
  463. // Deprecated
  464. m.HandleFunc("/uiautomator", func(w http.ResponseWriter, r *http.Request) {
  465. running := service.Running("uiautomator")
  466. renderJSON(w, map[string]interface{}{
  467. "running": running,
  468. })
  469. }).Methods("GET")
  470. m.HandleFunc("/raw/{filepath:.*}", func(w http.ResponseWriter, r *http.Request) {
  471. filepath := "/" + mux.Vars(r)["filepath"]
  472. http.ServeFile(w, r, filepath)
  473. })
  474. m.HandleFunc("/finfo/{lpath:.*}", func(w http.ResponseWriter, r *http.Request) {
  475. lpath := "/" + mux.Vars(r)["lpath"]
  476. finfo, err := os.Stat(lpath)
  477. if err != nil {
  478. if os.IsNotExist(err) {
  479. http.Error(w, err.Error(), 404)
  480. } else {
  481. http.Error(w, err.Error(), 403) // forbidden
  482. }
  483. return
  484. }
  485. data := make(map[string]interface{}, 5)
  486. data["name"] = finfo.Name()
  487. data["path"] = lpath
  488. data["isDirectory"] = finfo.IsDir()
  489. data["size"] = finfo.Size()
  490. if finfo.IsDir() {
  491. files, err := ioutil.ReadDir(lpath)
  492. if err == nil {
  493. finfos := make([]map[string]interface{}, 0, 3)
  494. for _, f := range files {
  495. finfos = append(finfos, map[string]interface{}{
  496. "name": f.Name(),
  497. "path": filepath.Join(lpath, f.Name()),
  498. "isDirectory": f.IsDir(),
  499. })
  500. }
  501. data["files"] = finfos
  502. }
  503. }
  504. renderJSON(w, data)
  505. })
  506. // keep ApkService always running
  507. // if no activity in 5min, then restart apk service
  508. const apkServiceTimeout = 5 * time.Minute
  509. apkServiceTimer := NewSafeTimer(apkServiceTimeout)
  510. go func() {
  511. for range apkServiceTimer.C {
  512. log.Println("startservice com.github.uiautomator/.Service")
  513. runShell("am", "startservice", "-n", "com.github.uiautomator/.Service")
  514. apkServiceTimer.Reset(apkServiceTimeout)
  515. }
  516. }()
  517. deviceInfo := getDeviceInfo()
  518. m.HandleFunc("/info", func(w http.ResponseWriter, r *http.Request) {
  519. w.Header().Set("Content-Type", "application/json")
  520. json.NewEncoder(w).Encode(deviceInfo)
  521. })
  522. m.HandleFunc("/info/battery", func(w http.ResponseWriter, r *http.Request) {
  523. apkServiceTimer.Reset(apkServiceTimeout)
  524. deviceInfo.Battery.Update()
  525. // if err := server.tunnel.UpdateInfo(deviceInfo); err != nil {
  526. // io.WriteString(w, "Failure "+err.Error())
  527. // return
  528. // }
  529. io.WriteString(w, "Success")
  530. }).Methods("POST")
  531. m.HandleFunc("/info/rotation", func(w http.ResponseWriter, r *http.Request) {
  532. apkServiceTimer.Reset(apkServiceTimeout)
  533. var direction int // 0,1,2,3
  534. err := json.NewDecoder(r.Body).Decode(&direction) // TODO: auto get rotation
  535. if err == nil {
  536. deviceRotation = direction * 90
  537. log.Println("rotation change received:", deviceRotation)
  538. } else {
  539. rotation, er := androidutils.Rotation()
  540. if er != nil {
  541. log.Println("rotation auto get err:", er)
  542. http.Error(w, "Failure", 500)
  543. return
  544. }
  545. deviceRotation = rotation
  546. }
  547. // Kill not controled minicap
  548. killed := false
  549. procWalk(func(proc procfs.Proc) {
  550. executable, _ := proc.Executable()
  551. if filepath.Base(executable) != "minicap" {
  552. return
  553. }
  554. stat, err := proc.NewStat()
  555. if err != nil || stat.PPID != 1 { // only not controled minicap need killed
  556. return
  557. }
  558. if p, err := os.FindProcess(proc.PID); err == nil {
  559. log.Println("Kill", executable)
  560. p.Kill()
  561. killed = true
  562. }
  563. })
  564. if killed {
  565. service.Start("minicap")
  566. }
  567. // minicapHub.broadcast <- []byte("rotation " + strconv.Itoa(deviceRotation))
  568. updateMinicapRotation(deviceRotation)
  569. rotationPublisher.Submit(deviceRotation)
  570. // APK Service will send rotation to atx-agent when rotation changes
  571. runShellTimeout(5*time.Second, "am", "startservice", "--user", "0", "-n", "com.github.uiautomator/.Service")
  572. renderJSON(w, map[string]int{
  573. "rotation": deviceRotation,
  574. })
  575. // fmt.Fprintf(w, "rotation change to %d", deviceRotation)
  576. })
  577. /*
  578. # URLRules:
  579. # URLPath ends with / means directory, eg: $DEVICE_URL/upload/sdcard/
  580. # The rest means file, eg: $DEVICE_URL/upload/sdcard/a.txt
  581. #
  582. # Upload a file to destination
  583. $ curl -X POST -F file=@file.txt -F mode=0755 $DEVICE_URL/upload/sdcard/a.txt
  584. # Upload a directory (file must be zip), URLPath must ends with /
  585. $ curl -X POST -F file=@dir.zip -F dir=true $DEVICE_URL/upload/sdcard/atx-stuffs/
  586. */
  587. m.HandleFunc("/upload/{target:.*}", func(w http.ResponseWriter, r *http.Request) {
  588. target := mux.Vars(r)["target"]
  589. if runtime.GOOS != "windows" {
  590. target = "/" + target
  591. }
  592. isDir := r.FormValue("dir") == "true"
  593. var fileMode os.FileMode
  594. if _, err := fmt.Sscanf(r.FormValue("mode"), "%o", &fileMode); !isDir && err != nil {
  595. log.Printf("invalid file mode: %s", r.FormValue("mode"))
  596. fileMode = 0644
  597. } // %o base 8
  598. file, header, err := r.FormFile("file")
  599. if err != nil {
  600. http.Error(w, err.Error(), http.StatusBadRequest)
  601. return
  602. }
  603. defer func() {
  604. file.Close()
  605. r.MultipartForm.RemoveAll()
  606. }()
  607. var targetDir = target
  608. if !isDir {
  609. if strings.HasSuffix(target, "/") {
  610. target = path.Join(target, header.Filename)
  611. }
  612. targetDir = filepath.Dir(target)
  613. } else {
  614. if !strings.HasSuffix(target, "/") {
  615. http.Error(w, "URLPath must endswith / if upload a directory", 400)
  616. return
  617. }
  618. }
  619. if _, err := os.Stat(targetDir); os.IsNotExist(err) {
  620. os.MkdirAll(targetDir, 0755)
  621. }
  622. if isDir {
  623. err = archiver.Zip.Read(file, target)
  624. } else {
  625. err = copyToFile(file, target)
  626. }
  627. if err != nil {
  628. http.Error(w, err.Error(), http.StatusInternalServerError)
  629. return
  630. }
  631. if !isDir && fileMode != 0 {
  632. os.Chmod(target, fileMode)
  633. }
  634. if fileInfo, err := os.Stat(target); err == nil {
  635. fileMode = fileInfo.Mode()
  636. }
  637. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  638. json.NewEncoder(w).Encode(map[string]interface{}{
  639. "target": target,
  640. "isDir": isDir,
  641. "mode": fmt.Sprintf("0%o", fileMode),
  642. })
  643. })
  644. m.HandleFunc("/download", func(w http.ResponseWriter, r *http.Request) {
  645. dst := r.FormValue("filepath")
  646. url := r.FormValue("url")
  647. var fileMode os.FileMode
  648. if _, err := fmt.Sscanf(r.FormValue("mode"), "%o", &fileMode); err != nil {
  649. log.Printf("invalid file mode: %s", r.FormValue("mode"))
  650. fileMode = 0644
  651. } // %o base 8
  652. key := background.HTTPDownload(url, dst, fileMode)
  653. io.WriteString(w, key)
  654. }).Methods("POST")
  655. m.HandleFunc("/download/{key}", func(w http.ResponseWriter, r *http.Request) {
  656. key := mux.Vars(r)["key"]
  657. status := background.Get(key)
  658. w.Header().Set("Content-Type", "application/json")
  659. json.NewEncoder(w).Encode(status)
  660. }).Methods("GET")
  661. m.HandleFunc("/packages", func(w http.ResponseWriter, r *http.Request) {
  662. var url = r.FormValue("url")
  663. filepath := TempFileName("/sdcard/tmp", ".apk")
  664. key := background.HTTPDownload(url, filepath, 0644)
  665. go func() {
  666. defer os.Remove(filepath) // release sdcard space
  667. state := background.Get(key)
  668. state.Status = "downloading"
  669. if err := background.Wait(key); err != nil {
  670. log.Println("http download error")
  671. state.Error = err.Error()
  672. state.Status = "failure"
  673. state.Message = "http download error"
  674. return
  675. }
  676. state.Status = "installing"
  677. if err := forceInstallAPK(filepath); err != nil {
  678. state.Error = err.Error()
  679. state.Status = "failure"
  680. } else {
  681. state.Status = "success"
  682. }
  683. }()
  684. renderJSON(w, map[string]interface{}{
  685. "success": true,
  686. "data": map[string]string{
  687. "id": key,
  688. },
  689. })
  690. }).Methods("POST")
  691. // id: int
  692. m.HandleFunc("/packages/{id}", func(w http.ResponseWriter, r *http.Request) {
  693. id := mux.Vars(r)["id"]
  694. state := background.Get(id)
  695. w.Header().Set("Content-Type", "application/json")
  696. data, _ := json.Marshal(state.Progress)
  697. renderJSON(w, map[string]interface{}{
  698. "success": true,
  699. "data": map[string]string{
  700. "status": state.Status,
  701. "description": string(data),
  702. },
  703. })
  704. json.NewEncoder(w).Encode(state)
  705. }).Methods("GET")
  706. m.HandleFunc("/packages", func(w http.ResponseWriter, r *http.Request) {
  707. pkgs, err := listPackages()
  708. if err != nil {
  709. w.WriteHeader(500)
  710. renderJSON(w, map[string]interface{}{
  711. "success": false,
  712. "description": err.Error(),
  713. })
  714. return
  715. }
  716. renderJSON(w, pkgs)
  717. }).Methods("GET")
  718. m.HandleFunc("/packages/{pkgname}/info", func(w http.ResponseWriter, r *http.Request) {
  719. pkgname := mux.Vars(r)["pkgname"]
  720. info, err := readPackageInfo(pkgname)
  721. if err != nil {
  722. renderJSON(w, map[string]interface{}{
  723. "success": false,
  724. "description": err.Error(), // "package " + strconv.Quote(pkgname) + " not found",
  725. })
  726. return
  727. }
  728. renderJSON(w, map[string]interface{}{
  729. "success": true,
  730. "data": info,
  731. })
  732. })
  733. m.HandleFunc("/packages/{pkgname}/icon", func(w http.ResponseWriter, r *http.Request) {
  734. pkgname := mux.Vars(r)["pkgname"]
  735. info, err := readPackageInfo(pkgname)
  736. if err != nil {
  737. http.Error(w, "package not found", 403)
  738. return
  739. }
  740. if info.Icon == nil {
  741. http.Error(w, "package not found", 400)
  742. return
  743. }
  744. w.Header().Set("Content-Type", "image/jpeg")
  745. jpeg.Encode(w, info.Icon, &jpeg.Options{Quality: 80})
  746. })
  747. // deprecated
  748. m.HandleFunc("/install", func(w http.ResponseWriter, r *http.Request) {
  749. var url = r.FormValue("url")
  750. var tmpdir = r.FormValue("tmpdir")
  751. if tmpdir == "" {
  752. tmpdir = "/data/local/tmp"
  753. }
  754. filepath := TempFileName(tmpdir, ".apk")
  755. key := background.HTTPDownload(url, filepath, 0644)
  756. go func() {
  757. defer os.Remove(filepath) // release sdcard space
  758. state := background.Get(key)
  759. state.Status = "downloading"
  760. if err := background.Wait(key); err != nil {
  761. log.Println("http download error")
  762. state.Error = err.Error()
  763. state.Message = "http download error"
  764. state.Status = "failure"
  765. return
  766. }
  767. state.Message = "installing"
  768. state.Status = "installing"
  769. if err := forceInstallAPK(filepath); err != nil {
  770. state.Error = err.Error()
  771. state.Message = "error install"
  772. state.Status = "failure"
  773. } else {
  774. state.Message = "success installed"
  775. state.Status = "success"
  776. }
  777. }()
  778. io.WriteString(w, key)
  779. }).Methods("POST")
  780. // deprecated
  781. m.HandleFunc("/install/{id}", func(w http.ResponseWriter, r *http.Request) {
  782. id := mux.Vars(r)["id"]
  783. state := background.Get(id)
  784. w.Header().Set("Content-Type", "application/json")
  785. json.NewEncoder(w).Encode(state)
  786. }).Methods("GET")
  787. // deprecated
  788. m.HandleFunc("/install/{id}", func(w http.ResponseWriter, r *http.Request) {
  789. id := mux.Vars(r)["id"]
  790. state := background.Get(id)
  791. if state.Progress != nil {
  792. if dproxy, ok := state.Progress.(*downloadProxy); ok {
  793. dproxy.Cancel()
  794. io.WriteString(w, "Cancelled")
  795. return
  796. }
  797. }
  798. io.WriteString(w, "Unable to canceled")
  799. }).Methods("DELETE")
  800. // fix minitouch
  801. m.HandleFunc("/minitouch", func(w http.ResponseWriter, r *http.Request) {
  802. if err := installMinitouch(); err == nil {
  803. log.Println("update minitouch success")
  804. io.WriteString(w, "Update minitouch success")
  805. } else {
  806. http.Error(w, err.Error(), http.StatusInternalServerError)
  807. }
  808. }).Methods("PUT")
  809. m.HandleFunc("/minitouch", func(w http.ResponseWriter, r *http.Request) {
  810. service.Stop("minitouch", true)
  811. io.WriteString(w, "minitouch stopped")
  812. }).Methods("DELETE")
  813. m.HandleFunc("/minitouch", singleFightNewerWebsocket(func(w http.ResponseWriter, r *http.Request, ws *websocket.Conn) {
  814. defer ws.Close()
  815. const wsWriteWait = 10 * time.Second
  816. wsWrite := func(messageType int, data []byte) error {
  817. ws.SetWriteDeadline(time.Now().Add(wsWriteWait))
  818. return ws.WriteMessage(messageType, data)
  819. }
  820. wsWrite(websocket.TextMessage, []byte("start @minitouch service"))
  821. if err := service.Start("minitouch"); err != nil && err != cmdctrl.ErrAlreadyRunning {
  822. wsWrite(websocket.TextMessage, []byte("@minitouch service start failed: "+err.Error()))
  823. return
  824. }
  825. unixSocketPath := minitouchSocketPath
  826. wsWrite(websocket.TextMessage, []byte("dial unix:"+unixSocketPath))
  827. log.Printf("minitouch connection: %v", r.RemoteAddr)
  828. retries := 0
  829. quitC := make(chan bool, 2)
  830. operC := make(chan TouchRequest, 10)
  831. defer func() {
  832. wsWrite(websocket.TextMessage, []byte(unixSocketPath+" websocket closed"))
  833. close(operC)
  834. }()
  835. go func() {
  836. for {
  837. if retries > 10 {
  838. log.Printf("unix %s connect failed", unixSocketPath)
  839. wsWrite(websocket.TextMessage, []byte(unixSocketPath+" listen timeout, possibly minitouch not installed"))
  840. ws.Close()
  841. break
  842. }
  843. conn, err := net.Dial("unix", unixSocketPath)
  844. if err != nil {
  845. retries++
  846. log.Printf("dial %s error: %v, wait 0.5s", unixSocketPath, err)
  847. select {
  848. case <-quitC:
  849. return
  850. case <-time.After(500 * time.Millisecond):
  851. }
  852. continue
  853. }
  854. log.Printf("unix %s connected, accepting requests", unixSocketPath)
  855. retries = 0 // connected, reset retries
  856. err = drainTouchRequests(conn, operC)
  857. conn.Close()
  858. if err != nil {
  859. log.Println("drain touch requests err:", err)
  860. } else {
  861. log.Printf("unix %s disconnected", unixSocketPath)
  862. break // operC closed
  863. }
  864. }
  865. }()
  866. var touchRequest TouchRequest
  867. for {
  868. err := ws.ReadJSON(&touchRequest)
  869. if err != nil {
  870. log.Println("readJson err:", err)
  871. quitC <- true
  872. break
  873. }
  874. select {
  875. case operC <- touchRequest:
  876. case <-time.After(2 * time.Second):
  877. wsWrite(websocket.TextMessage, []byte("touch request buffer full"))
  878. }
  879. }
  880. })).Methods("GET")
  881. // fix minicap
  882. m.HandleFunc("/minicap", func(w http.ResponseWriter, r *http.Request) {
  883. if err := installMinicap(); err == nil {
  884. log.Println("update minicap success")
  885. io.WriteString(w, "Update minicap success")
  886. } else {
  887. http.Error(w, err.Error(), http.StatusInternalServerError)
  888. }
  889. }).Methods("PUT")
  890. minicapHandler := broadcastWebsocket()
  891. m.HandleFunc("/minicap/broadcast", minicapHandler).Methods("GET")
  892. m.HandleFunc("/minicap", minicapHandler).Methods("GET")
  893. // TODO(ssx): perfer to delete
  894. // FIXME(ssx): screenrecord is not good enough, need to change later
  895. var recordCmd *exec.Cmd
  896. var recordDone = make(chan bool, 1)
  897. var recordLock sync.Mutex
  898. var recordFolder = "/sdcard/screenrecords/"
  899. var recordRunning = false
  900. m.HandleFunc("/screenrecord", func(w http.ResponseWriter, r *http.Request) {
  901. recordLock.Lock()
  902. defer recordLock.Unlock()
  903. if recordCmd != nil {
  904. http.Error(w, "screenrecord not closed", 400)
  905. return
  906. }
  907. os.RemoveAll(recordFolder)
  908. os.MkdirAll(recordFolder, 0755)
  909. recordCmd = exec.Command("screenrecord", recordFolder+"0.mp4")
  910. if err := recordCmd.Start(); err != nil {
  911. http.Error(w, err.Error(), 500)
  912. return
  913. }
  914. recordRunning = true
  915. go func() {
  916. for i := 1; recordCmd.Wait() == nil && i <= 20 && recordRunning; i++ { // set limit, to prevent too many videos. max 1 hour
  917. recordCmd = exec.Command("screenrecord", recordFolder+strconv.Itoa(i)+".mp4")
  918. if err := recordCmd.Start(); err != nil {
  919. log.Println("screenrecord error:", err)
  920. break
  921. }
  922. }
  923. recordDone <- true
  924. }()
  925. io.WriteString(w, "screenrecord started")
  926. }).Methods("POST")
  927. m.HandleFunc("/screenrecord", func(w http.ResponseWriter, r *http.Request) {
  928. recordLock.Lock()
  929. defer recordLock.Unlock()
  930. recordRunning = false
  931. if recordCmd != nil {
  932. if recordCmd.Process != nil {
  933. recordCmd.Process.Signal(os.Interrupt)
  934. }
  935. select {
  936. case <-recordDone:
  937. case <-time.After(5 * time.Second):
  938. // force kill
  939. exec.Command("pkill", "screenrecord").Run()
  940. }
  941. recordCmd = nil
  942. }
  943. w.Header().Set("Content-Type", "application/json")
  944. files, _ := ioutil.ReadDir(recordFolder)
  945. videos := []string{}
  946. for i := 0; i < len(files); i++ {
  947. videos = append(videos, fmt.Sprintf(recordFolder+"%d.mp4", i))
  948. }
  949. json.NewEncoder(w).Encode(map[string]interface{}{
  950. "videos": videos,
  951. })
  952. }).Methods("PUT")
  953. m.HandleFunc("/upgrade", func(w http.ResponseWriter, r *http.Request) {
  954. ver := r.FormValue("version")
  955. var err error
  956. if ver == "" {
  957. ver, err = getLatestVersion()
  958. if err != nil {
  959. http.Error(w, err.Error(), 500)
  960. return
  961. }
  962. }
  963. if ver == version {
  964. io.WriteString(w, "current version is already "+version)
  965. return
  966. }
  967. err = doUpdate(ver)
  968. if err != nil {
  969. http.Error(w, err.Error(), 500)
  970. return
  971. }
  972. io.WriteString(w, "update finished, restarting")
  973. go func() {
  974. log.Printf("restarting server")
  975. // TODO(ssx): runDaemon()
  976. }()
  977. })
  978. m.HandleFunc("/term", func(w http.ResponseWriter, r *http.Request) {
  979. if r.Header.Get("Upgrade") == "websocket" {
  980. handleTerminalWebsocket(w, r)
  981. return
  982. }
  983. renderHTML(w, "terminal.html")
  984. })
  985. screenshotIndex := -1
  986. nextScreenshotFilename := func() string {
  987. targetFolder := "/data/local/tmp/minicap-images"
  988. if _, err := os.Stat(targetFolder); err != nil {
  989. os.MkdirAll(targetFolder, 0755)
  990. }
  991. screenshotIndex = (screenshotIndex + 1) % 5
  992. return filepath.Join(targetFolder, fmt.Sprintf("%d.jpg", screenshotIndex))
  993. }
  994. m.HandleFunc("/screenshot", func(w http.ResponseWriter, r *http.Request) {
  995. targetURL := "/screenshot/0"
  996. if r.URL.RawQuery != "" {
  997. targetURL += "?" + r.URL.RawQuery
  998. }
  999. http.Redirect(w, r, targetURL, 302)
  1000. }).Methods("GET")
  1001. m.Handle("/jsonrpc/0", uiautomatorProxy)
  1002. m.Handle("/ping", uiautomatorProxy)
  1003. m.HandleFunc("/screenshot/0", func(w http.ResponseWriter, r *http.Request) {
  1004. download := r.FormValue("download")
  1005. if download != "" {
  1006. w.Header().Set("Content-Disposition", "attachment; filename="+download)
  1007. }
  1008. thumbnailSize := r.FormValue("thumbnail")
  1009. filename := nextScreenshotFilename()
  1010. // android emulator use screencap
  1011. // then minicap when binary and .so exists
  1012. // then uiautomator when service(uiautomator) is running
  1013. // last screencap
  1014. method := "screencap"
  1015. if getCachedProperty("ro.product.cpu.abi") == "x86" { // android emulator
  1016. method = "screencap"
  1017. } else if fileExists("/data/local/tmp/minicap") && fileExists("/data/local/tmp/minicap.so") && r.FormValue("minicap") != "false" && strings.ToLower(getCachedProperty("ro.product.manufacturer")) != "meizu" {
  1018. method = "minicap"
  1019. } else if service.Running("uiautomator") {
  1020. method = "uiautomator"
  1021. }
  1022. var err error
  1023. switch method {
  1024. case "screencap":
  1025. err = screenshotWithScreencap(filename)
  1026. case "minicap":
  1027. err = screenshotWithMinicap(filename, thumbnailSize)
  1028. case "uiautomator":
  1029. uiautomatorProxy.ServeHTTP(w, r)
  1030. return
  1031. }
  1032. if err != nil && method != "screencap" {
  1033. method = "screencap"
  1034. err = screenshotWithScreencap(filename)
  1035. }
  1036. if err != nil {
  1037. http.Error(w, err.Error(), 500)
  1038. return
  1039. }
  1040. w.Header().Set("X-Screenshot-Method", method)
  1041. http.ServeFile(w, r, filename)
  1042. })
  1043. m.HandleFunc("/wlan/ip", func(w http.ResponseWriter, r *http.Request) {
  1044. itf, err := net.InterfaceByName("wlan0")
  1045. if err != nil {
  1046. http.Error(w, err.Error(), http.StatusInternalServerError)
  1047. return
  1048. }
  1049. addrs, err := itf.Addrs()
  1050. if err != nil {
  1051. http.Error(w, err.Error(), http.StatusInternalServerError)
  1052. return
  1053. }
  1054. for _, addr := range addrs {
  1055. if v, ok := addr.(*net.IPNet); ok {
  1056. io.WriteString(w, v.IP.String())
  1057. }
  1058. return
  1059. }
  1060. http.Error(w, "wlan0 have no ip address", 500)
  1061. })
  1062. m.Handle("/assets/{(.*)}", http.StripPrefix("/assets", http.FileServer(Assets)))
  1063. var handler = cors.New(cors.Options{
  1064. AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
  1065. }).Handler(m)
  1066. // logHandler := handlers.LoggingHandler(os.Stdout, handler)
  1067. server.httpServer = &http.Server{Handler: handler} // url(/stop) need it.
  1068. }
  1069. func (s *Server) Serve(lis net.Listener) error {
  1070. return s.httpServer.Serve(lis)
  1071. }