From 69c50a5306a06930397c07102f8acd255202fd4c Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Wed, 2 Nov 2016 17:57:39 -0700 Subject: [PATCH 1/4] Initial code import --- graphviz.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 graphviz.go diff --git a/graphviz.go b/graphviz.go new file mode 100644 index 0000000..bb02ad7 --- /dev/null +++ b/graphviz.go @@ -0,0 +1,60 @@ +package main + +import ( + "fmt" + "io" + "log" + "net/http" + "os/exec" +) + +func main() { + http.HandleFunc("/", makeSafeHandler(func(w http.ResponseWriter, r *http.Request) { + source := "https:/" + r.URL.Path + resp, e := http.Get(source) + if e != nil { + log.Panicf("Cannot retreive source %s: %v", source, e) + } + defer resp.Body.Close() + + cmd := exec.Command("dot", "-Tpng") + w.Header().Set("Content-Type", "image/png") + + if stdin, e := cmd.StdinPipe(); e != nil { + log.Panicf("Cannot pipe to stdin: %v", e) + } else { + go func() { + if _, e := io.Copy(stdin, resp.Body); e != nil { + log.Panicf("Failed copying source to graphviz: %v", e) + } + }() + } + + if stdout, e := cmd.StdoutPipe(); e != nil { + log.Panicf("Cannot read stdout: %v", e) + } else { + go func() { + if _, e := io.Copy(w, stdout); e != nil { + log.Panicf("Failed delivering output: %v", e) + } + }() + } + + if e := cmd.Start(); e != nil { + log.Panicf("%v", e) + } + })) + + log.Fatal(http.ListenAndServe(":8080", nil)) +} + +func makeSafeHandler(in http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + defer func() { + if e := recover(); e != nil { + http.Error(w, fmt.Sprint(e), http.StatusInternalServerError) + } + }() + in(w, r) + } +} From 067cdbec89140e42f73822e3ea7cf19845a451cc Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Tue, 8 Nov 2016 13:08:02 -0800 Subject: [PATCH 2/4] Make graphviz.go a usable program --- graphviz.go | 87 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 50 insertions(+), 37 deletions(-) diff --git a/graphviz.go b/graphviz.go index bb02ad7..044809b 100644 --- a/graphviz.go +++ b/graphviz.go @@ -1,60 +1,73 @@ +// graphviz is an HTTP server which calls GraphViz's dot command to +// visualize a .dot file given in the `dot` parameter. For example, +// we can run this program as follows +// +// go run graphviz.go +// +// and access it from the Web browser with URL: +// +// http://localhost:8080/?dot=https://gist.githubusercontent.com/wangkuiyi/c4e0015211dd1b9bde2e20455a6cd38e/raw/4d5ec099f98a5f326cf6f108bcf510cadba1a0b4/ci-arch.dot +// +// then the visualization of ci-arch.dot should appear in the browser window. +// package main import ( + "crypto/md5" + "flag" "fmt" - "io" + "io/ioutil" "log" "net/http" + "os" "os/exec" + "path" + + "github.com/topicai/candy" ) func main() { - http.HandleFunc("/", makeSafeHandler(func(w http.ResponseWriter, r *http.Request) { - source := "https:/" + r.URL.Path - resp, e := http.Get(source) - if e != nil { - log.Panicf("Cannot retreive source %s: %v", source, e) - } - defer resp.Body.Close() - - cmd := exec.Command("dot", "-Tpng") - w.Header().Set("Content-Type", "image/png") - - if stdin, e := cmd.StdinPipe(); e != nil { - log.Panicf("Cannot pipe to stdin: %v", e) - } else { - go func() { - if _, e := io.Copy(stdin, resp.Body); e != nil { - log.Panicf("Failed copying source to graphviz: %v", e) - } - }() - } - - if stdout, e := cmd.StdoutPipe(); e != nil { - log.Panicf("Cannot read stdout: %v", e) - } else { - go func() { - if _, e := io.Copy(w, stdout); e != nil { - log.Panicf("Failed delivering output: %v", e) + dir := flag.String("dir", "/tmp", "The cache directory") + addr := flag.String("addr", ":8080", "The listening address") + flag.Parse() + + http.HandleFunc("/", + makeSafeHandler(func(w http.ResponseWriter, r *http.Request) { + if source := r.FormValue("dot"); len(source) > 0 { + dot, e := candy.HTTPGet(source, 0) + candy.Must(e) + + id := fmt.Sprintf("%015x", md5.Sum(dot)) + dotFile := path.Join(*dir, id) + ".dot" + pngFile := path.Join(*dir, id) + ".png" + + if _, e := os.Stat(pngFile); os.IsNotExist(e) { + // TODO(yi): Here we adopt an unlimted-size disk-based cache. We should + // either introduce LRU and limit the size, or periodically clean it. + log.Printf("Update cache for %s", pngFile) + candy.Must(ioutil.WriteFile(dotFile, dot, 0755)) + png, e := exec.Command("dot", "-Tpng", dotFile).Output() + candy.Must(e) + candy.Must(ioutil.WriteFile(pngFile, png, 0755)) } - }() - } - if e := cmd.Start(); e != nil { - log.Panicf("%v", e) - } - })) + png, e := ioutil.ReadFile(pngFile) + candy.Must(e) + _, e = w.Write(png) + candy.Must(e) + } + })) - log.Fatal(http.ListenAndServe(":8080", nil)) + candy.Must(http.ListenAndServe(*addr, nil)) } -func makeSafeHandler(in http.HandlerFunc) http.HandlerFunc { +func makeSafeHandler(h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { defer func() { if e := recover(); e != nil { http.Error(w, fmt.Sprint(e), http.StatusInternalServerError) } }() - in(w, r) + h(w, r) } } From 7d658e36b97ce8548a57bb63005260a8c262d2dd Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Tue, 8 Nov 2016 14:21:31 -0800 Subject: [PATCH 3/4] Add Dockerfile and README.md --- Dockerfile | 14 ++++++++++++++ README.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d2bafa9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:wheezy + +ENV DIST /go/src/github.com/k8sp/graphviz + +RUN apt-get update +RUN apt-get install -y graphviz + +COPY . $DIST +RUN cd $DIST && go get ./... && go get . + +EXPOSE 9090 +VOLUME ["/cache"] +ENTRYPOINT ["graphviz"] +CMD ["-addr=:9090", "-dir=/cache"] diff --git a/README.md b/README.md index e69de29..312a262 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,54 @@ +`graphviz` is an HTTP server which calls +[GraphViz](http://www.graphviz.org/) to visualize a specified .dot +file. + +## Build and Run from Source Code + +if we already got GraphViz and [Go](https://golang.org/) installed, we +can run this program as follows + +``` +go get github.com/k8sp/graphviz +$GOPATH/bin/graphviz -addr=:9090 +``` + +Then we can direct our Web browser to + +``` +http://localhost:9090/?dot=https://gist.githubusercontent.com/wangkuiyi/c4e0015211dd1b9bde2e20455a6cd38e/raw/4d5ec099f98a5f326cf6f108bcf510cadba1a0b4/ci-arch.dot +``` + +so to visualize the +[Gist file `ci-arch.dot`](https://gist.github.com/wangkuiyi/c4e0015211dd1b9bde2e20455a6cd38e) +in the Web browser window. + +## Build and Run the Docker Image + +If we have Docker installed, we can build `graphviz` into a Docker +image without installing GraphViz and Go: + +``` +go get github.com/k8sp/graphviz +cd $GOPATH/src/github.com/k8sp/graphviz +docker build -t graphviz . +``` + +and run it as a Docker container: + +``` +docker run -p 9090:9090 -v /tmp:/cache graphviz +``` + +Now we can direct our Web browser to above link. + +## Run with Images on DockerHub.com + +We can also run the Docker images built from stable releases by +DockerHub.com: + +``` +docker run -p 9090:9090 -v /tmp:/cache k8sp/graphviz +``` + + From a97cee5e74f3523251be8e74c8a416c0812ffd78 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Tue, 8 Nov 2016 14:21:53 -0800 Subject: [PATCH 4/4] Add .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b25c15b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*~