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
		}
	}
}