server.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. package api
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "os"
  7. "strconv"
  8. "time"
  9. "github.com/smartystreets/goconvey/web/server/contract"
  10. "github.com/smartystreets/goconvey/web/server/messaging"
  11. )
  12. type HTTPServer struct {
  13. watcher chan messaging.WatcherCommand
  14. executor contract.Executor
  15. latest *contract.CompleteOutput
  16. currentRoot string
  17. longpoll chan chan string
  18. paused bool
  19. }
  20. func (self *HTTPServer) ReceiveUpdate(root string, update *contract.CompleteOutput) {
  21. self.currentRoot = root
  22. self.latest = update
  23. }
  24. func (self *HTTPServer) Watch(response http.ResponseWriter, request *http.Request) {
  25. if request.Method == "POST" {
  26. self.adjustRoot(response, request)
  27. } else if request.Method == "GET" {
  28. response.Write([]byte(self.currentRoot))
  29. }
  30. }
  31. func (self *HTTPServer) adjustRoot(response http.ResponseWriter, request *http.Request) {
  32. newRoot := self.parseQueryString("root", response, request)
  33. if newRoot == "" {
  34. return
  35. }
  36. info, err := os.Stat(newRoot) // TODO: how to unit test?
  37. if !info.IsDir() || err != nil {
  38. http.Error(response, err.Error(), http.StatusNotFound)
  39. return
  40. }
  41. self.watcher <- messaging.WatcherCommand{
  42. Instruction: messaging.WatcherAdjustRoot,
  43. Details: newRoot,
  44. }
  45. }
  46. func (self *HTTPServer) Ignore(response http.ResponseWriter, request *http.Request) {
  47. paths := self.parseQueryString("paths", response, request)
  48. if paths != "" {
  49. self.watcher <- messaging.WatcherCommand{
  50. Instruction: messaging.WatcherIgnore,
  51. Details: paths,
  52. }
  53. }
  54. }
  55. func (self *HTTPServer) Reinstate(response http.ResponseWriter, request *http.Request) {
  56. paths := self.parseQueryString("paths", response, request)
  57. if paths != "" {
  58. self.watcher <- messaging.WatcherCommand{
  59. Instruction: messaging.WatcherReinstate,
  60. Details: paths,
  61. }
  62. }
  63. }
  64. func (self *HTTPServer) parseQueryString(key string, response http.ResponseWriter, request *http.Request) string {
  65. value := request.URL.Query()[key]
  66. if len(value) == 0 {
  67. http.Error(response, fmt.Sprintf("No '%s' query string parameter included!", key), http.StatusBadRequest)
  68. return ""
  69. }
  70. path := value[0]
  71. if path == "" {
  72. http.Error(response, "You must provide a non-blank path.", http.StatusBadRequest)
  73. }
  74. return path
  75. }
  76. func (self *HTTPServer) Status(response http.ResponseWriter, request *http.Request) {
  77. status := self.executor.Status()
  78. response.Write([]byte(status))
  79. }
  80. func (self *HTTPServer) LongPollStatus(response http.ResponseWriter, request *http.Request) {
  81. if self.executor.ClearStatusFlag() {
  82. response.Write([]byte(self.executor.Status()))
  83. return
  84. }
  85. timeout, err := strconv.Atoi(request.URL.Query().Get("timeout"))
  86. if err != nil || timeout > 180000 || timeout < 0 {
  87. timeout = 60000 // default timeout is 60 seconds
  88. }
  89. myReqChan := make(chan string)
  90. select {
  91. case self.longpoll <- myReqChan: // this case means the executor's status is changing
  92. case <-time.After(time.Duration(timeout) * time.Millisecond): // this case means the executor hasn't changed status
  93. return
  94. }
  95. out := <-myReqChan
  96. if out != "" { // TODO: Why is this check necessary? Sometimes it writes empty string...
  97. response.Write([]byte(out))
  98. }
  99. }
  100. func (self *HTTPServer) Results(response http.ResponseWriter, request *http.Request) {
  101. response.Header().Set("Content-Type", "application/json")
  102. response.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
  103. response.Header().Set("Pragma", "no-cache")
  104. response.Header().Set("Expires", "0")
  105. if self.latest != nil {
  106. self.latest.Paused = self.paused
  107. }
  108. stuff, _ := json.Marshal(self.latest)
  109. response.Write(stuff)
  110. }
  111. func (self *HTTPServer) Execute(response http.ResponseWriter, request *http.Request) {
  112. go self.execute()
  113. }
  114. func (self *HTTPServer) execute() {
  115. self.watcher <- messaging.WatcherCommand{Instruction: messaging.WatcherExecute}
  116. }
  117. func (self *HTTPServer) TogglePause(response http.ResponseWriter, request *http.Request) {
  118. instruction := messaging.WatcherPause
  119. if self.paused {
  120. instruction = messaging.WatcherResume
  121. }
  122. self.watcher <- messaging.WatcherCommand{Instruction: instruction}
  123. self.paused = !self.paused
  124. fmt.Fprint(response, self.paused) // we could write out whatever helps keep the UI honest...
  125. }
  126. func NewHTTPServer(
  127. root string,
  128. watcher chan messaging.WatcherCommand,
  129. executor contract.Executor,
  130. status chan chan string) *HTTPServer {
  131. self := new(HTTPServer)
  132. self.currentRoot = root
  133. self.watcher = watcher
  134. self.executor = executor
  135. self.longpoll = status
  136. return self
  137. }