gotour code analysis
GoTour as a web project to study golang
Directory
➜ tour git:(master) ✗ tree -L 1
.
├── app.yaml
├── AUTHORS
├── codereview.cfg
├── content
├── CONTRIBUTING.md
├── CONTRIBUTORS
├── gotour
├── LICENSE
├── pic
├── reader
├── README.md
├── solutions
├── static
├── template
├── TODO
├── tools
├── TRANSLATE
├── tree
└── wc
solutions:文档中execise的答案,与go-tour网站无关
static:这个文件夹是存放静态资源,js,css,html都是放在这里面
tree:这个文件夹存放程序自动生成一个树的代码包。这个是具体的功能才使用到的,与网站无关
wc:测试套件。这个是具体的练习题中才使用的到,与网站无关
pic:对图片的处理包。这个是具体的练习题中才使用到的,与网站无关
gotour:这个才是真正的go-tour的入口,main包在这里
Source code
从gotour/local.go入口
func main() {
...
if err := initTour(root, "SocketTransport"); err != nil {
log.Fatal(err)
}
//response with uiContent generated as following
http.HandleFunc("/", rootHandler)
/*lessons encoded as json
lesson := Lesson{
doc.Title,
doc.Subtitle,
make([]Page, len(doc.Sections)),
}
*/
http.HandleFunc("/lesson/", lessonHandler)
origin := &url.URL{Scheme: "http", Host: host + ":" + port}
//websocket to invoke golang code dynamiclly
http.Handle(socketPath, socket.NewHandler(origin))
// Keep these static file handlers in sync with ../app.yaml.
static := http.FileServer(http.Dir(root))
http.Handle("/content/img/", static)
http.Handle("/static/", static)
imgDir := filepath.Join(root, "static", "img")
http.Handle("/favicon.ico", http.FileServer(http.Dir(imgDir)))
...
}
initTour初始化资源
func initTour(root, transport string) error {
// Make sure playground is enabled before rendering.
present.PlayEnabled = true
// Set up templates.
action := filepath.Join(root, "template", "action.tmpl")//template for lesson
//take https://godoc.org/golang.org/x/tools/present for reference
tmpl, err := present.Template().ParseFiles(action)
if err != nil {
return fmt.Errorf("parse templates: %v", err)
}
// Init lessons.
contentPath := filepath.Join(root, "content")
if err := initLessons(tmpl, contentPath); err != nil {
return fmt.Errorf("init lessons: %v", err)
}
// Init UI
index := filepath.Join(root, "template", "index.tmpl")//template for ui
ui, err := template.ParseFiles(index)
if err != nil {
return fmt.Errorf("parse index.tmpl: %v", err)
}
buf := new(bytes.Buffer)
data := struct {
SocketAddr string
Transport template.JS
}{socketAddr(), template.JS(transport)}
//generate uiContent
if err := ui.Execute(buf, data); err != nil {
return fmt.Errorf("render UI: %v", err)
}
uiContent = buf.Bytes()
return initScript(root)
}
//init lessons
func initLessons(tmpl *template.Template, content string) error {
dir, err := os.Open(content)
if err != nil {
return err
}
files, err := dir.Readdirnames(0)
if err != nil {
return err
}
for _, f := range files {
if filepath.Ext(f) != ".article" {//one lesson for each article
continue
}
/*lesson := Lesson{
doc.Title,
doc.Subtitle,
make([]Page, len(doc.Sections)),
}*/
content, err := parseLesson(tmpl, filepath.Join(content, f))
if err != nil {
return fmt.Errorf("parsing %v: %v", f, err)
}
name := strings.TrimSuffix(f, ".article")
lessons[name] = content
}
return nil
}
// initScript concatenates all the javascript files needed to render
// the tour UI and serves the result on /script.js.
func initScript(root string) error {
modTime := time.Now()
b := new(bytes.Buffer)
content, ok := static.Files["playground.js"]
if !ok {
return fmt.Errorf("playground.js not found in static files")
}
b.WriteString(content)
// Keep this list in dependency order
files := []string{
"static/lib/jquery.min.js",
"static/lib/jquery-ui.min.js",
"static/lib/angular.min.js",
"static/lib/codemirror/lib/codemirror.js",
"static/lib/codemirror/mode/go/go.js",
"static/lib/angular-ui.min.js",
"static/js/app.js",
"static/js/controllers.js",
"static/js/directives.js",
"static/js/services.js",
"static/js/values.js",
}
for _, file := range files {
f, err := ioutil.ReadFile(filepath.Join(root, file))
if err != nil {
return fmt.Errorf("couldn't open %v: %v", file, err)
}
_, err = b.Write(f)
if err != nil {
return fmt.Errorf("error concatenating %v: %v", file, err)
}
}
http.HandleFunc("/script.js", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-type", "application/javascript")
// Set expiration time in one week.
w.Header().Set("Cache-control", "max-age=604800")
http.ServeContent(w, r, "", modTime, bytes.NewReader(b.Bytes()))
})
return nil
}
websocket handler
func NewHandler(origin *url.URL) websocket.Server {
return websocket.Server{
Config: websocket.Config{Origin: origin},
Handshake: handshake,
Handler: websocket.Handler(socketHandler),
}
}
// socketHandler handles the websocket connection for a given present session.
// It handles transcoding Messages to and from JSON format, and starting
// and killing processes.
func socketHandler(c *websocket.Conn) {
in, out := make(chan *Message), make(chan *Message)
errc := make(chan error, 1)
// Decode messages from client and send to the in channel.
go func() {
dec := json.NewDecoder(c)
for {
var m Message
if err := dec.Decode(&m); err != nil {
errc <- err
return
}
in <- &m
}
}()
// Receive messages from the out channel and encode to the client.
go func() {
enc := json.NewEncoder(c)
for m := range out {
if err := enc.Encode(m); err != nil {
errc <- err
return
}
}
}()
defer close(out)
// Start and kill processes and handle errors.
proc := make(map[string]*process)
for {
select {
case m := <-in:
switch m.Kind {
case "run":
log.Println("running snippet from:", c.Request().RemoteAddr)
proc[m.Id].Kill()
proc[m.Id] = startProcess(m.Id, m.Body, out, m.Options)
case "kill":
proc[m.Id].Kill()
}
case err := <-errc:
if err != io.EOF {
// A encode or decode has failed; bail.
log.Println(err)
}
// Shut down any running processes.
for _, p := range proc {
p.Kill()
}
return
}
}
}