Skip to content

Instantly share code, notes, and snippets.

@danhigham
Last active August 29, 2015 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save danhigham/8970438 to your computer and use it in GitHub Desktop.
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
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())
}
}
@kelapure
Copy link

Dan, do you have a short blurb on how to use this reverse proxy when pushing a java application

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment