Last active
August 29, 2015 13:56
-
-
Save danhigham/8970438 to your computer and use it in GitHub Desktop.
Proxying server for Cloud Foundry that also provides a web console for the application container
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bufio" | |
"code.google.com/p/go.net/websocket" | |
"github.com/kr/pty" | |
"io" | |
"flag" | |
"fmt" | |
"log" | |
"net/http" | |
"net/http/httputil" | |
"net/url" | |
"os" | |
"os/exec" | |
"regexp" | |
"strings" | |
"time" | |
) | |
func printToLog(s string) { | |
t := time.Now() | |
ts := fmt.Sprintf("[%04d-%02d-%02d %02d:%02d:%02d]", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second()) | |
os.Stdout.Write([]byte(ts + " " + s)) | |
} | |
func logPipe(r io.Reader) { | |
for { | |
buf := make([]byte, 1024) | |
n, err := r.Read(buf[:]) | |
if err != nil { | |
return | |
} | |
os.Stdout.Write(buf[0:n]) | |
} | |
} | |
func consolePage(uri string, version string, index string) (string) { | |
page := ` | |
<html> | |
<head> | |
<title>Console</title> | |
<style> | |
body { background-color: #000; color : #eee; } | |
div#console { margin : 10px; float : left;} | |
pre { margin : 0; padding : 0; } | |
pre.output { margin : 1px 0 0 0; float : left; clear : left; } | |
pre.input, div#cursor { float : left; margin : 1px 0 0 0; } | |
div#cursor { height : 13px; width : 7px; background: #eee; margin-top : 2px;} | |
.blink { | |
-webkit-animation-name: blinker; | |
-webkit-animation-duration: 1s; | |
-webkit-animation-timing-function: linear; | |
-webkit-animation-iteration-count: infinite; | |
-moz-animation-name: blinker; | |
-moz-animation-duration: 1s; | |
-moz-animation-timing-function: linear; | |
-moz-animation-iteration-count: infinite; | |
animation-name: blinker; | |
animation-duration: 1s; | |
animation-timing-function: linear; | |
animation-iteration-count: infinite; | |
} | |
@-moz-keyframes blinker { | |
0% { opacity: 1.0; } | |
50% { opacity: 0.0; } | |
100% { opacity: 1.0; } | |
} | |
@-webkit-keyframes blinker { | |
0% { opacity: 1.0; } | |
50% { opacity: 0.0; } | |
100% { opacity: 1.0; } | |
} | |
@keyframes blinker { | |
0% { opacity: 1.0; } | |
50% { opacity: 0.0; } | |
100% { opacity: 1.0; } | |
} | |
</style> | |
<script> | |
window.onload = function(){ | |
//fake console | |
if(typeof console === "undefined") { | |
console = { | |
log: function() { }, | |
debug: function() { } | |
}; | |
} | |
//initialise socket | |
var connection = new WebSocket('wss://` + uri + `:4443/` + version + `/` + index + `'); | |
//prepare pre | |
var consoleDiv = document.getElementById("console"); | |
var cursor = document.getElementById("cursor"); | |
var pre = document.createElement('pre'); | |
consoleDiv.newPre = function(){ | |
if (pre.innerText == '') { pre.remove(); } | |
pre = document.createElement("pre"); | |
pre.setAttribute("class", "input") | |
this.insertBefore(pre, cursor); | |
} | |
consoleDiv.newPre(); | |
connection.onopen = function(){ | |
console.log('Connection open!'); | |
} | |
connection.onmessage = function(e){ | |
var msg = e.data; | |
if (msg.trim() == pre.innerText.trim()) return false; | |
var lines = msg.split("\n"); | |
for(var line in lines) { | |
var outPre = document.createElement("pre"); | |
outPre.setAttribute("class", "output") | |
outPre.innerText = lines[line]; | |
consoleDiv.insertBefore(outPre, cursor); | |
consoleDiv.newPre(); | |
} | |
cursor.style.animationPlayState = "running"; | |
cursor.style.WebkitAnimationPlayState = "running"; | |
window.scrollTo(0,document.body.scrollHeight); | |
} | |
window.onkeydown=function(e) { | |
if (e.keyCode == 8) { | |
e.preventDefault(); | |
pre.innerText = pre.innerText.substring(0, pre.innerText.length - 1); | |
} | |
} | |
window.onkeypress=function(e){ | |
if (e.keyCode == 13) { // send the line | |
connection.send(pre.innerText); | |
cursor.style.animationPlayState = "paused"; | |
cursor.style.WebkitAnimationPlayState = "paused"; | |
} else { | |
pre.innerText += String.fromCharCode(e.keyCode); | |
} | |
}; | |
setInterval(function(){ | |
console.log("PING!") | |
connection.send("***PING***"); | |
}, 10000); | |
}; | |
</script> | |
</head> | |
<body><div id="console"><pre></pre><div class="blink" id="cursor"></div></div></body> | |
</html> | |
` | |
return page | |
} | |
func runMainWebProc(mainProcCommand string) { | |
bin := "" | |
a := strings.Split(mainProcCommand, " ") | |
bin, a = a[0], a[1:len(a)] | |
cmd := exec.Command(bin, a...) | |
stdout, err := cmd.StdoutPipe() | |
if err != nil { | |
log.Print(err) | |
} | |
stderr, err := cmd.StderrPipe() | |
if err != nil { | |
log.Print(err) | |
} | |
errbr := bufio.NewReader(stderr) | |
outbr := bufio.NewReader(stdout) | |
go logPipe(outbr) | |
go logPipe(errbr) | |
cmd.Run() | |
} | |
func main() { | |
var consoleProcess = flag.String("console-process", "bash", "The process to be started and connected to by the console command line tool") | |
var mainProcess = flag.String("main-process", "", "The main application to be run (e.g rails s)") | |
wsHandler := websocket.Handler(func (ws *websocket.Conn) { | |
bin := "" | |
a := strings.Split(*consoleProcess, " ") | |
bin, a = a[0], a[1:len(a)] | |
cmd := exec.Command(bin, a...) | |
f, err := pty.Start(cmd) | |
if err != nil { | |
log.Print(err) | |
} | |
scriptPid := cmd.Process.Pid; | |
log.Print("Console pid is ", scriptPid) | |
go func() { | |
for { | |
msg := make([]byte, 1024) | |
n, err := ws.Read(msg) | |
if err != nil { | |
log.Print(err) | |
ws.Close() | |
// p, _ := os.FindProcess(scriptPid) | |
// p.Kill() | |
cmd.Process.Kill() | |
return | |
} | |
strMsg := string(msg[:n]) | |
if strMsg != "***PING***" { | |
f.WriteString(strMsg + "\n") | |
} | |
} | |
}() | |
io.Copy(ws, f) | |
}) | |
flag.Parse() | |
appVersionRegex, _ := regexp.Compile(`,\"application_version\":\"([^\"]+)`) | |
instanceIndexRegex, _ := regexp.Compile(`,\"instance_index\"\:(\d{1,2})`) | |
uriRegex, _ := regexp.Compile(`,\"uris\":\[\"([^\"]+)`) | |
vcap_data := os.Getenv("VCAP_APPLICATION") | |
appVersion := appVersionRegex.FindStringSubmatch(vcap_data)[1] | |
instanceIndex := instanceIndexRegex.FindStringSubmatch(vcap_data)[1] | |
uri := uriRegex.FindStringSubmatch(vcap_data)[1] | |
if (*mainProcess != "") { | |
printToLog("Running main process :- " + *mainProcess + "\n") | |
go runMainWebProc(*mainProcess) | |
} | |
serverUrl, _ := url.Parse("http://127.0.0.1:8080") | |
reverseProxy := httputil.NewSingleHostReverseProxy(serverUrl) | |
printToLog("Mounting reverse proxy on " + serverUrl.String() + "\n") | |
http.Handle("/", reverseProxy) | |
mount := "/" + appVersion + "/" + instanceIndex | |
printToLog("Mounting console on " + mount + "\n") | |
http.Handle(mount, wsHandler) | |
consoleHtml := consolePage(uri, appVersion, instanceIndex); | |
http.HandleFunc("/cfconsole", func(w http.ResponseWriter, r *http.Request) { | |
fmt.Fprint(w, consoleHtml) | |
}) | |
err := http.ListenAndServe(":" + os.Getenv("PORT"), nil) | |
if err != nil { | |
panic("ListenAndServe: " + err.Error()) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Dan, do you have a short blurb on how to use this reverse proxy when pushing a java application