@@ -0,0 +1,92 @@ | |||
/* ---------------------------------------------------------------------- | |||
* ______ ___ __ | |||
* / ____/___ / | ____ __ ___ __/ /_ ___ ________ | |||
* / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \ | |||
* / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/ | |||
* \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/ | |||
* /____/ | |||
* | |||
* (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() | |||
} |
@@ -0,0 +1,108 @@ | |||
/* ---------------------------------------------------------------------- | |||
* ______ ___ __ | |||
* / ____/___ / | ____ __ ___ __/ /_ ___ ________ | |||
* / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \ | |||
* / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/ | |||
* \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/ | |||
* /____/ | |||
* | |||
* (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() | |||
} | |||
} |
@@ -0,0 +1,164 @@ | |||
/* ---------------------------------------------------------------------- | |||
* ______ ___ __ | |||
* / ____/___ / | ____ __ ___ __/ /_ ___ ________ | |||
* / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \ | |||
* / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/ | |||
* \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/ | |||
* /____/ | |||
* | |||
* (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() | |||
} |
@@ -0,0 +1,109 @@ | |||
/* ---------------------------------------------------------------------- | |||
* ______ ___ __ | |||
* / ____/___ / | ____ __ ___ __/ /_ ___ ________ | |||
* / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \ | |||
* / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/ | |||
* \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/ | |||
* /____/ | |||
* | |||
* (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) | |||
} |
@@ -0,0 +1,147 @@ | |||
/* ---------------------------------------------------------------------- | |||
* ______ ___ __ | |||
* / ____/___ / | ____ __ ___ __/ /_ ___ ________ | |||
* / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \ | |||
* / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/ | |||
* \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/ | |||
* /____/ | |||
* | |||
* (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() | |||
}) | |||
} |
@@ -0,0 +1,42 @@ | |||
/* ---------------------------------------------------------------------- | |||
* ______ ___ __ | |||
* / ____/___ / | ____ __ ___ __/ /_ ___ ________ | |||
* / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \ | |||
* / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/ | |||
* \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/ | |||
* /____/ | |||
* | |||
* (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 | |||
} | |||
) |
@@ -0,0 +1,79 @@ | |||
/* ---------------------------------------------------------------------- | |||
* ______ ___ __ | |||
* / ____/___ / | ____ __ ___ __/ /_ ___ ________ | |||
* / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \ | |||
* / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/ | |||
* \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/ | |||
* /____/ | |||
* | |||
* (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() | |||
} |
@@ -51,38 +51,38 @@ var ( | |||
// 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. | |||
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") | |||
// it also fetch the full function name of the handler (with package) to name the route. | |||
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") | |||
// 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. | |||
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") | |||
// it also fetch the full function name of the handler (with package) to name the route. | |||
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") | |||
// it also fetch the full function name of the handler (with package) to name the route. | |||
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") | |||
// it also fetch the full function name of the handler (with package) to name the route. | |||
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. |
@@ -44,7 +44,7 @@ type Router struct { | |||
func New() *Router { | |||
return &Router{ | |||
mod: new(internal.Module), | |||
mux: mux.NewRouter(), | |||
mux: mux.NewRouter().StrictSlash(true), | |||
} | |||
} | |||