123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- package api
- import (
- "encoding/json"
- "fmt"
- "net/http"
- "os"
- "strconv"
- "time"
- "github.com/smartystreets/goconvey/web/server/contract"
- "github.com/smartystreets/goconvey/web/server/messaging"
- )
- type HTTPServer struct {
- watcher chan messaging.WatcherCommand
- executor contract.Executor
- latest *contract.CompleteOutput
- currentRoot string
- longpoll chan chan string
- paused bool
- }
- func (self *HTTPServer) ReceiveUpdate(root string, update *contract.CompleteOutput) {
- self.currentRoot = root
- self.latest = update
- }
- func (self *HTTPServer) Watch(response http.ResponseWriter, request *http.Request) {
- if request.Method == "POST" {
- self.adjustRoot(response, request)
- } else if request.Method == "GET" {
- response.Write([]byte(self.currentRoot))
- }
- }
- func (self *HTTPServer) adjustRoot(response http.ResponseWriter, request *http.Request) {
- newRoot := self.parseQueryString("root", response, request)
- if newRoot == "" {
- return
- }
- info, err := os.Stat(newRoot) // TODO: how to unit test?
- if !info.IsDir() || err != nil {
- http.Error(response, err.Error(), http.StatusNotFound)
- return
- }
- self.watcher <- messaging.WatcherCommand{
- Instruction: messaging.WatcherAdjustRoot,
- Details: newRoot,
- }
- }
- func (self *HTTPServer) Ignore(response http.ResponseWriter, request *http.Request) {
- paths := self.parseQueryString("paths", response, request)
- if paths != "" {
- self.watcher <- messaging.WatcherCommand{
- Instruction: messaging.WatcherIgnore,
- Details: paths,
- }
- }
- }
- func (self *HTTPServer) Reinstate(response http.ResponseWriter, request *http.Request) {
- paths := self.parseQueryString("paths", response, request)
- if paths != "" {
- self.watcher <- messaging.WatcherCommand{
- Instruction: messaging.WatcherReinstate,
- Details: paths,
- }
- }
- }
- func (self *HTTPServer) parseQueryString(key string, response http.ResponseWriter, request *http.Request) string {
- value := request.URL.Query()[key]
- if len(value) == 0 {
- http.Error(response, fmt.Sprintf("No '%s' query string parameter included!", key), http.StatusBadRequest)
- return ""
- }
- path := value[0]
- if path == "" {
- http.Error(response, "You must provide a non-blank path.", http.StatusBadRequest)
- }
- return path
- }
- func (self *HTTPServer) Status(response http.ResponseWriter, request *http.Request) {
- status := self.executor.Status()
- response.Write([]byte(status))
- }
- func (self *HTTPServer) LongPollStatus(response http.ResponseWriter, request *http.Request) {
- if self.executor.ClearStatusFlag() {
- response.Write([]byte(self.executor.Status()))
- return
- }
- timeout, err := strconv.Atoi(request.URL.Query().Get("timeout"))
- if err != nil || timeout > 180000 || timeout < 0 {
- timeout = 60000 // default timeout is 60 seconds
- }
- myReqChan := make(chan string)
- select {
- case self.longpoll <- myReqChan: // this case means the executor's status is changing
- case <-time.After(time.Duration(timeout) * time.Millisecond): // this case means the executor hasn't changed status
- return
- }
- out := <-myReqChan
- if out != "" { // TODO: Why is this check necessary? Sometimes it writes empty string...
- response.Write([]byte(out))
- }
- }
- func (self *HTTPServer) Results(response http.ResponseWriter, request *http.Request) {
- response.Header().Set("Content-Type", "application/json")
- response.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
- response.Header().Set("Pragma", "no-cache")
- response.Header().Set("Expires", "0")
- if self.latest != nil {
- self.latest.Paused = self.paused
- }
- stuff, _ := json.Marshal(self.latest)
- response.Write(stuff)
- }
- func (self *HTTPServer) Execute(response http.ResponseWriter, request *http.Request) {
- go self.execute()
- }
- func (self *HTTPServer) execute() {
- self.watcher <- messaging.WatcherCommand{Instruction: messaging.WatcherExecute}
- }
- func (self *HTTPServer) TogglePause(response http.ResponseWriter, request *http.Request) {
- instruction := messaging.WatcherPause
- if self.paused {
- instruction = messaging.WatcherResume
- }
- self.watcher <- messaging.WatcherCommand{Instruction: instruction}
- self.paused = !self.paused
- fmt.Fprint(response, self.paused) // we could write out whatever helps keep the UI honest...
- }
- func NewHTTPServer(
- root string,
- watcher chan messaging.WatcherCommand,
- executor contract.Executor,
- status chan chan string) *HTTPServer {
- self := new(HTTPServer)
- self.currentRoot = root
- self.watcher = watcher
- self.executor = executor
- self.longpoll = status
- return self
- }
|