| /* ---------------------------------------------------------------------- | |||||
| * ______ ___ __ | |||||
| * / ____/___ / | ____ __ ___ __/ /_ ___ ________ | |||||
| * / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \ | |||||
| * / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/ | |||||
| * \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/ | |||||
| * /____/ | |||||
| * | |||||
| * (C) Copyright 2015 GoAnywhere (http://goanywhere.io). | |||||
| * ---------------------------------------------------------------------- | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ----------------------------------------------------------------------*/ | |||||
| package main | |||||
| import ( | |||||
| "fmt" | |||||
| "os" | |||||
| "runtime" | |||||
| "github.com/codegangsta/cli" | |||||
| "github.com/goanywhere/x/crypto" | |||||
| ) | |||||
| var ( | |||||
| cwd string | |||||
| ) | |||||
| var commands = []cli.Command{ | |||||
| // rex project template supports | |||||
| /* | |||||
| *{ | |||||
| * Name: "new", | |||||
| * Usage: "create a skeleton web application project", | |||||
| * Action: New, | |||||
| *}, | |||||
| */ | |||||
| // rex server (with livereload supports) | |||||
| { | |||||
| Name: "run", | |||||
| Usage: "start application server with livereload supports", | |||||
| Action: Run, | |||||
| Flags: []cli.Flag{ | |||||
| cli.IntFlag{ | |||||
| Name: "port", | |||||
| Value: 5000, | |||||
| Usage: "port to run the application server", | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| // helper to generate a secret key. | |||||
| { | |||||
| Name: "secret", | |||||
| Usage: "generate a new application secret key", | |||||
| Action: func(ctx *cli.Context) { | |||||
| fmt.Println(crypto.Random(ctx.Int("length"))) | |||||
| }, | |||||
| Flags: []cli.Flag{ | |||||
| cli.IntFlag{ | |||||
| Name: "length", | |||||
| Value: 64, | |||||
| Usage: "length of the secret key", | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| } | |||||
| func main() { | |||||
| runtime.GOMAXPROCS(runtime.NumCPU()) | |||||
| cmd := cli.NewApp() | |||||
| cmd.Name = "rex" | |||||
| cmd.Usage = "manage rex application" | |||||
| cmd.Version = "0.9.0" | |||||
| cmd.Author = "GoAnywhere" | |||||
| cmd.Email = "code@goanywhere.io" | |||||
| cmd.Commands = commands | |||||
| cmd.Run(os.Args) | |||||
| } | |||||
| func init() { | |||||
| cwd, _ = os.Getwd() | |||||
| } |
| /* ---------------------------------------------------------------------- | |||||
| * ______ ___ __ | |||||
| * / ____/___ / | ____ __ ___ __/ /_ ___ ________ | |||||
| * / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \ | |||||
| * / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/ | |||||
| * \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/ | |||||
| * /____/ | |||||
| * | |||||
| * (C) Copyright 2015 GoAnywhere (http://goanywhere.io). | |||||
| * ---------------------------------------------------------------------- | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ----------------------------------------------------------------------*/ | |||||
| package main | |||||
| import ( | |||||
| "bufio" | |||||
| "fmt" | |||||
| "os" | |||||
| "os/exec" | |||||
| "path/filepath" | |||||
| "regexp" | |||||
| "runtime" | |||||
| log "github.com/Sirupsen/logrus" | |||||
| "github.com/codegangsta/cli" | |||||
| "github.com/goanywhere/x/cmd" | |||||
| "github.com/goanywhere/x/crypto" | |||||
| ) | |||||
| const endpoint = "https://github.com/goanywhere/rex" | |||||
| var secrets = []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*(-_+)") | |||||
| type project struct { | |||||
| name string | |||||
| root string | |||||
| } | |||||
| func (self *project) create() { | |||||
| cmd.Prompt("Fetching project template\n") | |||||
| var done = make(chan bool) | |||||
| cmd.Loading(done) | |||||
| command := exec.Command("git", "clone", "-b", "scaffolds", endpoint, self.name) | |||||
| command.Dir = cwd | |||||
| if e := command.Run(); e == nil { | |||||
| self.root = filepath.Join(cwd, self.name) | |||||
| // create dotenv under project's root. | |||||
| filename := filepath.Join(self.root, ".env") | |||||
| if dotenv, err := os.Create(filename); err == nil { | |||||
| defer dotenv.Close() | |||||
| buffer := bufio.NewWriter(dotenv) | |||||
| buffer.WriteString(fmt.Sprintf("export Rex_Secret_Keys=\"%s, %s\"\n", crypto.Random(64), crypto.Random(32))) | |||||
| buffer.Flush() | |||||
| // close loading here as nodejs will take over prompt. | |||||
| done <- true | |||||
| // initialize project packages via nodejs. | |||||
| self.setup() | |||||
| } | |||||
| os.RemoveAll(filepath.Join(self.root, ".git")) | |||||
| os.Remove(filepath.Join(self.root, "README.md")) | |||||
| } else { | |||||
| // loading prompt should be closed in anyway. | |||||
| done <- true | |||||
| } | |||||
| } | |||||
| func (self *project) setup() { | |||||
| if e := exec.Command("npm", "-v").Run(); e == nil { | |||||
| cmd.Prompt("Fetching project dependencies\n") | |||||
| command := exec.Command("npm", "install") | |||||
| command.Dir = self.root | |||||
| command.Stdout = os.Stdout | |||||
| command.Stderr = os.Stderr | |||||
| command.Run() | |||||
| } else { | |||||
| log.Fatalf("Failed to setup project dependecies: nodejs is missing.") | |||||
| } | |||||
| } | |||||
| func New(context *cli.Context) { | |||||
| var pattern *regexp.Regexp | |||||
| if runtime.GOOS == "windows" { | |||||
| pattern = regexp.MustCompile(`\A(?:[0-9a-zA-Z\.\_\-]+\\?)+\z`) | |||||
| } else { | |||||
| pattern = regexp.MustCompile(`\A(?:[0-9a-zA-Z\.\_\-]+\/?)+\z`) | |||||
| } | |||||
| args := context.Args() | |||||
| if len(args) != 1 || !pattern.MatchString(args[0]) { | |||||
| log.Fatal("Please provide a valid project name/path") | |||||
| } else { | |||||
| project := new(project) | |||||
| project.name = args[0] | |||||
| project.create() | |||||
| } | |||||
| } |
| /* ---------------------------------------------------------------------- | |||||
| * ______ ___ __ | |||||
| * / ____/___ / | ____ __ ___ __/ /_ ___ ________ | |||||
| * / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \ | |||||
| * / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/ | |||||
| * \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/ | |||||
| * /____/ | |||||
| * | |||||
| * (C) Copyright 2015 GoAnywhere (http://goanywhere.io). | |||||
| * ---------------------------------------------------------------------- | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ----------------------------------------------------------------------*/ | |||||
| package main | |||||
| import ( | |||||
| "fmt" | |||||
| "go/build" | |||||
| "os" | |||||
| "os/exec" | |||||
| "os/signal" | |||||
| "path/filepath" | |||||
| "regexp" | |||||
| "runtime" | |||||
| "syscall" | |||||
| log "github.com/Sirupsen/logrus" | |||||
| "github.com/codegangsta/cli" | |||||
| "github.com/goanywhere/rex/internal" | |||||
| "github.com/goanywhere/rex/modules/livereload" | |||||
| "github.com/goanywhere/x/cmd" | |||||
| "github.com/goanywhere/x/env" | |||||
| "github.com/goanywhere/x/fs" | |||||
| ) | |||||
| var ( | |||||
| port int | |||||
| watchList = regexp.MustCompile(`\.(go|html|atom|rss|xml)$`) | |||||
| ) | |||||
| type app struct { | |||||
| dir string | |||||
| binary string | |||||
| args []string | |||||
| task string // script for npm. | |||||
| } | |||||
| // build compiles the application into rex-bin executable | |||||
| // to run & optionally compiles static assets using npm. | |||||
| func (self *app) build() { | |||||
| var done = make(chan bool) | |||||
| cmd.Loading(done) | |||||
| // * try build the application into rex-bin(.exe) | |||||
| command := exec.Command("go", "build", "-o", self.binary) | |||||
| command.Dir = self.dir | |||||
| if e := command.Run(); e != nil { | |||||
| log.Fatalf("Failed to compile the application: %v", e) | |||||
| } | |||||
| done <- true | |||||
| } | |||||
| // run executes the runnerable executable under package binary root. | |||||
| func (self *app) run() (gorun chan bool) { | |||||
| gorun = make(chan bool) | |||||
| go func() { | |||||
| var proc *os.Process | |||||
| for start := range gorun { | |||||
| if proc != nil { | |||||
| // try soft kill before hard one. | |||||
| if err := proc.Signal(os.Interrupt); err != nil { | |||||
| proc.Kill() | |||||
| } | |||||
| proc.Wait() | |||||
| } | |||||
| if !start { | |||||
| continue | |||||
| } | |||||
| command := exec.Command(self.binary, fmt.Sprintf("--port=%d", port)) | |||||
| command.Dir = self.dir | |||||
| command.Stdout = os.Stdout | |||||
| command.Stderr = os.Stderr | |||||
| if err := command.Start(); err != nil { | |||||
| log.Fatalf("Failed to start the process: %v\n", err) | |||||
| } | |||||
| proc = command.Process | |||||
| } | |||||
| }() | |||||
| return | |||||
| } | |||||
| func (self *app) rerun(gorun chan bool) { | |||||
| self.build() | |||||
| livereload.Reload() | |||||
| gorun <- true | |||||
| } | |||||
| // Starts activates the application server along with | |||||
| // a daemon watcher for monitoring the files's changes. | |||||
| func (self *app) Start() { | |||||
| // ctrl-c: listen removes binary package when application stopped. | |||||
| channel := make(chan os.Signal, 2) | |||||
| signal.Notify(channel, os.Interrupt, syscall.SIGTERM) | |||||
| go func() { | |||||
| <-channel | |||||
| // remove the binary package on stop. | |||||
| os.Remove(self.binary) | |||||
| os.Exit(1) | |||||
| }() | |||||
| // start waiting the signal to start running. | |||||
| var gorun = self.run() | |||||
| self.build() | |||||
| gorun <- true | |||||
| watcher := fs.NewWatcher(self.dir) | |||||
| log.Infof("Start watching: %s", self.dir) | |||||
| watcher.Add(watchList, func(filename string) { | |||||
| relpath, _ := filepath.Rel(self.dir, filename) | |||||
| log.Infof("Changes on %s detected", relpath) | |||||
| self.rerun(gorun) | |||||
| }) | |||||
| watcher.Start() | |||||
| } | |||||
| // Run creates an executable application package with livereload supports. | |||||
| func Run(ctx *cli.Context) { | |||||
| port = ctx.Int("port") | |||||
| if len(ctx.Args()) == 1 { | |||||
| cwd = ctx.Args()[0] | |||||
| } | |||||
| if abspath, err := filepath.Abs(cwd); err == nil { | |||||
| env.Set(internal.Root, abspath) | |||||
| } else { | |||||
| log.Fatalf("Failed to retrieve the directory: %v", err) | |||||
| } | |||||
| pkg, err := build.ImportDir(cwd, build.AllowBinary) | |||||
| if err != nil || pkg.Name != "main" { | |||||
| log.Fatalf("No buildable Go source files found") | |||||
| } | |||||
| app := new(app) | |||||
| app.dir = cwd | |||||
| app.binary = filepath.Join(os.TempDir(), "rex-bin") | |||||
| if runtime.GOOS == "windows" { | |||||
| app.binary += ".exe" | |||||
| } | |||||
| app.task = ctx.String("task") | |||||
| app.Start() | |||||
| } |
| /* ---------------------------------------------------------------------- | |||||
| * ______ ___ __ | |||||
| * / ____/___ / | ____ __ ___ __/ /_ ___ ________ | |||||
| * / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \ | |||||
| * / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/ | |||||
| * \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/ | |||||
| * /____/ | |||||
| * | |||||
| * (C) Copyright 2015 GoAnywhere (http://goanywhere.io). | |||||
| * ---------------------------------------------------------------------- | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ----------------------------------------------------------------------*/ | |||||
| package modules | |||||
| import ( | |||||
| "bytes" | |||||
| "compress/gzip" | |||||
| "compress/zlib" | |||||
| "fmt" | |||||
| "io" | |||||
| "net/http" | |||||
| "regexp" | |||||
| "strings" | |||||
| "github.com/goanywhere/rex/modules/livereload" | |||||
| "github.com/goanywhere/x/env" | |||||
| ) | |||||
| type writer struct { | |||||
| http.ResponseWriter | |||||
| host string | |||||
| } | |||||
| func (self *writer) addJavaScript(data []byte) []byte { | |||||
| javascript := fmt.Sprintf(`<script src="//%s%s"></script> | |||||
| </head>`, self.host, livereload.URL.JavaScript) | |||||
| return regexp.MustCompile(`</head>`).ReplaceAll(data, []byte(javascript)) | |||||
| } | |||||
| func (self *writer) Write(data []byte) (size int, e error) { | |||||
| if strings.Contains(self.Header().Get("Content-Type"), "html") { | |||||
| var encoding = self.Header().Get("Content-Encoding") | |||||
| if encoding == "" { | |||||
| data = self.addJavaScript(data) | |||||
| } else { | |||||
| var reader io.ReadCloser | |||||
| var buffer *bytes.Buffer = new(bytes.Buffer) | |||||
| if encoding == "gzip" { | |||||
| // decode to add javascript reference. | |||||
| reader, _ = gzip.NewReader(bytes.NewReader(data)) | |||||
| io.Copy(buffer, reader) | |||||
| output := self.addJavaScript(buffer.Bytes()) | |||||
| reader.Close() | |||||
| buffer.Reset() | |||||
| // encode back to HTML with added javascript reference. | |||||
| writer := gzip.NewWriter(buffer) | |||||
| writer.Write(output) | |||||
| writer.Close() | |||||
| data = buffer.Bytes() | |||||
| } else if encoding == "deflate" { | |||||
| // decode to add javascript reference. | |||||
| reader, _ = zlib.NewReader(bytes.NewReader(data)) | |||||
| io.Copy(buffer, reader) | |||||
| output := self.addJavaScript(buffer.Bytes()) | |||||
| reader.Close() | |||||
| buffer.Reset() | |||||
| // encode back to HTML with added javascript reference. | |||||
| writer := zlib.NewWriter(buffer) | |||||
| writer.Write(output) | |||||
| writer.Close() | |||||
| data = buffer.Bytes() | |||||
| } | |||||
| } | |||||
| } | |||||
| return self.ResponseWriter.Write(data) | |||||
| } | |||||
| func LiveReload(next http.Handler) http.Handler { | |||||
| // ONLY run this under debug mode. | |||||
| if !env.Bool("DEBUG", true) { | |||||
| return next | |||||
| } | |||||
| livereload.Start() | |||||
| fn := func(w http.ResponseWriter, r *http.Request) { | |||||
| if r.URL.Path == livereload.URL.WebSocket { | |||||
| livereload.ServeWebSocket(w, r) | |||||
| } else if r.URL.Path == livereload.URL.JavaScript { | |||||
| livereload.ServeJavaScript(w, r) | |||||
| } else { | |||||
| writer := &writer{w, r.Host} | |||||
| next.ServeHTTP(writer, r) | |||||
| } | |||||
| } | |||||
| return http.HandlerFunc(fn) | |||||
| } |
| /* ---------------------------------------------------------------------- | |||||
| * ______ ___ __ | |||||
| * / ____/___ / | ____ __ ___ __/ /_ ___ ________ | |||||
| * / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \ | |||||
| * / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/ | |||||
| * \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/ | |||||
| * /____/ | |||||
| * | |||||
| * (C) Copyright 2015 GoAnywhere (http://goanywhere.io). | |||||
| * ---------------------------------------------------------------------- | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ----------------------------------------------------------------------*/ | |||||
| package livereload | |||||
| import ( | |||||
| "encoding/json" | |||||
| "net/http" | |||||
| "sync" | |||||
| "github.com/gorilla/websocket" | |||||
| ) | |||||
| /* ---------------------------------------------------------------------- | |||||
| * WebSocket Server | |||||
| * ----------------------------------------------------------------------*/ | |||||
| var ( | |||||
| once sync.Once | |||||
| broadcast chan []byte | |||||
| tunnels map[*tunnel]bool | |||||
| in chan *tunnel | |||||
| out chan *tunnel | |||||
| mutex sync.RWMutex | |||||
| upgrader = websocket.Upgrader{ | |||||
| ReadBufferSize: 1024, | |||||
| WriteBufferSize: 1024, | |||||
| } | |||||
| URL = struct { | |||||
| WebSocket string | |||||
| JavaScript string | |||||
| }{ | |||||
| WebSocket: "/livereload", | |||||
| JavaScript: "/livereload.js", | |||||
| } | |||||
| ) | |||||
| // Alert sends a notice message to browser's livereload.js. | |||||
| func Alert(message string) { | |||||
| go func() { | |||||
| var bytes, _ = json.Marshal(&alert{ | |||||
| Command: "alert", | |||||
| Message: message, | |||||
| }) | |||||
| broadcast <- bytes | |||||
| }() | |||||
| } | |||||
| // Reload sends a reload message to browser's livereload.js. | |||||
| func Reload() { | |||||
| go func() { | |||||
| var bytes, _ = json.Marshal(&reload{ | |||||
| Command: "reload", | |||||
| Path: URL.WebSocket, | |||||
| LiveCSS: true, | |||||
| }) | |||||
| broadcast <- bytes | |||||
| }() | |||||
| } | |||||
| // run watches/dispatches all tunnel & tunnel messages. | |||||
| func run() { | |||||
| for { | |||||
| select { | |||||
| case tunnel := <-in: | |||||
| mutex.Lock() | |||||
| defer mutex.Unlock() | |||||
| tunnels[tunnel] = true | |||||
| case tunnel := <-out: | |||||
| mutex.Lock() | |||||
| defer mutex.Unlock() | |||||
| delete(tunnels, tunnel) | |||||
| close(tunnel.message) | |||||
| case m := <-broadcast: | |||||
| for tunnel := range tunnels { | |||||
| select { | |||||
| case tunnel.message <- m: | |||||
| default: | |||||
| mutex.Lock() | |||||
| defer mutex.Unlock() | |||||
| delete(tunnels, tunnel) | |||||
| close(tunnel.message) | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| // Serve serves as a livereload server for accepting I/O tunnel messages. | |||||
| func ServeWebSocket(w http.ResponseWriter, r *http.Request) { | |||||
| var socket, err = upgrader.Upgrade(w, r, nil) | |||||
| if err != nil { | |||||
| return | |||||
| } | |||||
| tunnel := new(tunnel) | |||||
| tunnel.socket = socket | |||||
| tunnel.message = make(chan []byte, 256) | |||||
| in <- tunnel | |||||
| defer func() { out <- tunnel }() | |||||
| tunnel.connect() | |||||
| } | |||||
| // ServeJavaScript serves livereload.js for browser. | |||||
| func ServeJavaScript(w http.ResponseWriter, r *http.Request) { | |||||
| w.Header().Set("Content-Type", "application/javascript") | |||||
| w.Write(javascript) | |||||
| } | |||||
| // Start activates livereload server for accepting tunnel messages. | |||||
| func Start() { | |||||
| once.Do(func() { | |||||
| broadcast = make(chan []byte) | |||||
| tunnels = make(map[*tunnel]bool) | |||||
| in = make(chan *tunnel) | |||||
| out = make(chan *tunnel) | |||||
| go run() | |||||
| }) | |||||
| } |
| /* ---------------------------------------------------------------------- | |||||
| * ______ ___ __ | |||||
| * / ____/___ / | ____ __ ___ __/ /_ ___ ________ | |||||
| * / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \ | |||||
| * / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/ | |||||
| * \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/ | |||||
| * /____/ | |||||
| * | |||||
| * (C) Copyright 2015 GoAnywhere (http://goanywhere.io). | |||||
| * ---------------------------------------------------------------------- | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ----------------------------------------------------------------------*/ | |||||
| package livereload | |||||
| type ( | |||||
| hello struct { | |||||
| Command string `json:"command"` | |||||
| Protocols []string `json:"protocols"` | |||||
| ServerName string `json:"serverName"` | |||||
| } | |||||
| alert struct { | |||||
| Command string `json:"command"` | |||||
| Message string `json:"message"` | |||||
| } | |||||
| reload struct { | |||||
| Command string `json:"command"` | |||||
| Path string `json:"path"` // as full as possible/known, absolute path preferred, file name only is OK | |||||
| LiveCSS bool `json:"liveCSS"` // false to disable live CSS refresh | |||||
| } | |||||
| ) |
| /* ---------------------------------------------------------------------- | |||||
| * ______ ___ __ | |||||
| * / ____/___ / | ____ __ ___ __/ /_ ___ ________ | |||||
| * / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \ | |||||
| * / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/ | |||||
| * \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/ | |||||
| * /____/ | |||||
| * | |||||
| * (C) Copyright 2015 GoAnywhere (http://goanywhere.io). | |||||
| * ---------------------------------------------------------------------- | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * ----------------------------------------------------------------------*/ | |||||
| package livereload | |||||
| import ( | |||||
| "encoding/json" | |||||
| "regexp" | |||||
| "github.com/gorilla/websocket" | |||||
| ) | |||||
| var regexHandshake = regexp.MustCompile(`"command"\s*:\s*"hello"`) | |||||
| /* ---------------------------------------------------------------------- | |||||
| * WebSocket Server Tunnel | |||||
| * ----------------------------------------------------------------------*/ | |||||
| type tunnel struct { | |||||
| socket *websocket.Conn | |||||
| message chan []byte | |||||
| } | |||||
| // connect reads/writes message for livereload.js. | |||||
| func (self *tunnel) connect() { | |||||
| // *********************** | |||||
| // WebSocket Tunnel#Write | |||||
| // *********************** | |||||
| go func() { | |||||
| for message := range self.message { | |||||
| if err := self.socket.WriteMessage(websocket.TextMessage, message); err != nil { | |||||
| break | |||||
| } else { | |||||
| if regexHandshake.Find(message) != nil { | |||||
| // Keep the tunnel opened after handshake(hello command). | |||||
| Reload() | |||||
| } | |||||
| } | |||||
| } | |||||
| self.socket.Close() | |||||
| }() | |||||
| // *********************** | |||||
| // WebSocket Tunnel#Read | |||||
| // *********************** | |||||
| for { | |||||
| _, message, err := self.socket.ReadMessage() | |||||
| if err != nil { | |||||
| break | |||||
| } | |||||
| switch true { | |||||
| case regexHandshake.Find(message) != nil: | |||||
| var bytes, _ = json.Marshal(&hello{ | |||||
| Command: "hello", | |||||
| Protocols: []string{"http://livereload.com/protocols/official-7"}, | |||||
| ServerName: "Rex#Livereload", | |||||
| }) | |||||
| self.message <- bytes | |||||
| } | |||||
| } | |||||
| self.socket.Close() | |||||
| } |
| // Get is a shortcut for mux.HandleFunc(pattern, handler).Methods("GET"), | // Get is a shortcut for mux.HandleFunc(pattern, handler).Methods("GET"), | ||||
| // it also fetch the full function name of the handler (with package) to name the route. | // it also fetch the full function name of the handler (with package) to name the route. | ||||
| func Get(pattern string, handler interface{}) { | func Get(pattern string, handler interface{}) { | ||||
| DefaultMux.register("GET", pattern, handler) | |||||
| DefaultMux.Get(pattern, handler) | |||||
| } | } | ||||
| // Head is a shortcut for mux.HandleFunc(pattern, handler).Methods("HEAD") | // Head is a shortcut for mux.HandleFunc(pattern, handler).Methods("HEAD") | ||||
| // it also fetch the full function name of the handler (with package) to name the route. | // it also fetch the full function name of the handler (with package) to name the route. | ||||
| func Head(pattern string, handler interface{}) { | func Head(pattern string, handler interface{}) { | ||||
| DefaultMux.register("HEAD", pattern, handler) | |||||
| DefaultMux.Head(pattern, handler) | |||||
| } | } | ||||
| // Options is a shortcut for mux.HandleFunc(pattern, handler).Methods("OPTIONS") | // Options is a shortcut for mux.HandleFunc(pattern, handler).Methods("OPTIONS") | ||||
| // it also fetch the full function name of the handler (with package) to name the route. | // it also fetch the full function name of the handler (with package) to name the route. | ||||
| // NOTE method OPTIONS is **NOT** cachable, beware of what you are going to do. | // NOTE method OPTIONS is **NOT** cachable, beware of what you are going to do. | ||||
| func Options(pattern string, handler interface{}) { | func Options(pattern string, handler interface{}) { | ||||
| DefaultMux.register("OPTIONS", pattern, handler) | |||||
| DefaultMux.Options(pattern, handler) | |||||
| } | } | ||||
| // Post is a shortcut for mux.HandleFunc(pattern, handler).Methods("POST") | // Post is a shortcut for mux.HandleFunc(pattern, handler).Methods("POST") | ||||
| // it also fetch the full function name of the handler (with package) to name the route. | // it also fetch the full function name of the handler (with package) to name the route. | ||||
| func Post(pattern string, handler interface{}) { | func Post(pattern string, handler interface{}) { | ||||
| DefaultMux.register("POST", pattern, handler) | |||||
| DefaultMux.Post(pattern, handler) | |||||
| } | } | ||||
| // Put is a shortcut for mux.HandleFunc(pattern, handler).Methods("PUT") | // Put is a shortcut for mux.HandleFunc(pattern, handler).Methods("PUT") | ||||
| // it also fetch the full function name of the handler (with package) to name the route. | // it also fetch the full function name of the handler (with package) to name the route. | ||||
| func Put(pattern string, handler interface{}) { | func Put(pattern string, handler interface{}) { | ||||
| DefaultMux.register("PUT", pattern, handler) | |||||
| DefaultMux.Put(pattern, handler) | |||||
| } | } | ||||
| // Delete is a shortcut for mux.HandleFunc(pattern, handler).Methods("DELETE") | // Delete is a shortcut for mux.HandleFunc(pattern, handler).Methods("DELETE") | ||||
| // it also fetch the full function name of the handler (with package) to name the route. | // it also fetch the full function name of the handler (with package) to name the route. | ||||
| func Delete(pattern string, handler interface{}) { | func Delete(pattern string, handler interface{}) { | ||||
| DefaultMux.register("Delete", pattern, handler) | |||||
| DefaultMux.Delete(pattern, handler) | |||||
| } | } | ||||
| // Group creates a new application group under the given path. | // Group creates a new application group under the given path. |
| func New() *Router { | func New() *Router { | ||||
| return &Router{ | return &Router{ | ||||
| mod: new(internal.Module), | mod: new(internal.Module), | ||||
| mux: mux.NewRouter(), | |||||
| mux: mux.NewRouter().StrictSlash(true), | |||||
| } | } | ||||
| } | } | ||||