// Copyright 2013 The Go Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd. // Package talksapp implements the go-talks.appspot.com server. package talksapp import ( "bytes" "errors" "fmt" "html/template" "io" "net/http" "os" "path" "time" "google.golang.org/appengine" "google.golang.org/appengine/log" "google.golang.org/appengine/memcache" "google.golang.org/appengine/urlfetch" "github.com/golang/gddo/gosrc" "github.com/golang/gddo/httputil" "golang.org/x/net/context" "golang.org/x/tools/present" ) var ( presentTemplates = map[string]*template.Template{ ".article": parsePresentTemplate("article.tmpl"), ".slide": parsePresentTemplate("slides.tmpl"), } homeArticle = loadHomeArticle() contactEmail = "golang-dev@googlegroups.com" // used for mocking in tests getPresentation = gosrc.GetPresentation playCompileURL = "https://play.golang.org/compile" ) func init() { http.Handle("/", handlerFunc(serveRoot)) http.Handle("/compile", handlerFunc(serveCompile)) http.Handle("/bot.html", handlerFunc(serveBot)) present.PlayEnabled = true if s := os.Getenv("CONTACT_EMAIL"); s != "" { contactEmail = s } if appengine.IsDevAppServer() { return } github := httputil.NewAuthTransportFromEnvironment(nil) if github.Token == "" || github.ClientID == "" || github.ClientSecret == "" { panic("missing GitHub metadata, follow the instructions on README.md") } } func playable(c present.Code) bool { return present.PlayEnabled && c.Play && c.Ext == ".go" } func parsePresentTemplate(name string) *template.Template { t := present.Template() t = t.Funcs(template.FuncMap{"playable": playable}) if _, err := t.ParseFiles("present/templates/"+name, "present/templates/action.tmpl"); err != nil { panic(err) } t = t.Lookup("root") if t == nil { panic("root template not found for " + name) } return t } func loadHomeArticle() []byte { const fname = "assets/home.article" f, err := os.Open(fname) if err != nil { panic(err) } defer f.Close() doc, err := present.Parse(f, fname, 0) if err != nil { panic(err) } var buf bytes.Buffer if err := renderPresentation(&buf, fname, doc); err != nil { panic(err) } return buf.Bytes() } func renderPresentation(w io.Writer, fname string, doc *present.Doc) error { t := presentTemplates[path.Ext(fname)] if t == nil { return errors.New("unknown template extension") } data := struct { *present.Doc Template *template.Template PlayEnabled bool NotesEnabled bool }{doc, t, true, true} return t.Execute(w, &data) } type presFileNotFoundError string func (s presFileNotFoundError) Error() string { return fmt.Sprintf("File %s not found.", string(s)) } func writeHTMLHeader(w http.ResponseWriter, status int) { w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(status) } func writeTextHeader(w http.ResponseWriter, status int) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.WriteHeader(status) } func httpClient(r *http.Request) *http.Client { ctx, _ := context.WithTimeout(appengine.NewContext(r), 10*time.Second) github := httputil.NewAuthTransportFromEnvironment(nil) return &http.Client{ Transport: &httputil.AuthTransport{ Token: github.Token, ClientID: github.ClientID, ClientSecret: github.ClientSecret, Base: &urlfetch.Transport{Context: ctx}, UserAgent: fmt.Sprintf("%s (+http://%s/-/bot)", appengine.AppID(ctx), r.Host), }, } } type handlerFunc func(http.ResponseWriter, *http.Request) error func (f handlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) { ctx := appengine.NewContext(r) err := f(w, r) if err == nil { return } else if gosrc.IsNotFound(err) { writeTextHeader(w, 400) io.WriteString(w, "Not Found.") } else if e, ok := err.(*gosrc.RemoteError); ok { writeTextHeader(w, 500) fmt.Fprintf(w, "Error accessing %s.\n%v", e.Host, e) log.Infof(ctx, "Remote error %s: %v", e.Host, e) } else if e, ok := err.(presFileNotFoundError); ok { writeTextHeader(w, 200) io.WriteString(w, e.Error()) } else if err != nil { writeTextHeader(w, 500) io.WriteString(w, "Internal server error.") log.Errorf(ctx, "Internal error %v", err) } } func serveRoot(w http.ResponseWriter, r *http.Request) error { switch { case r.Method != "GET" && r.Method != "HEAD": writeTextHeader(w, 405) _, err := io.WriteString(w, "Method not supported.") return err case r.URL.Path == "/": writeHTMLHeader(w, 200) _, err := w.Write(homeArticle) return err default: return servePresentation(w, r) } } func servePresentation(w http.ResponseWriter, r *http.Request) error { ctx := appengine.NewContext(r) importPath := r.URL.Path[1:] item, err := memcache.Get(ctx, importPath) if err == nil { writeHTMLHeader(w, 200) w.Write(item.Value) return nil } else if err != memcache.ErrCacheMiss { log.Errorf(ctx, "Could not get item from Memcache: %v", err) } log.Infof(ctx, "Fetching presentation %s.", importPath) pres, err := getPresentation(httpClient(r), importPath) if err != nil { return err } parser := &present.Context{ ReadFile: func(name string) ([]byte, error) { if p, ok := pres.Files[name]; ok { return p, nil } return nil, presFileNotFoundError(name) }, } doc, err := parser.Parse(bytes.NewReader(pres.Files[pres.Filename]), pres.Filename, 0) if err != nil { return err } var buf bytes.Buffer if err := renderPresentation(&buf, importPath, doc); err != nil { return err } if err := memcache.Add(ctx, &memcache.Item{ Key: importPath, Value: buf.Bytes(), Expiration: time.Hour, }); err != nil { log.Errorf(ctx, "Could not cache presentation %s: %v", importPath, err) return nil } writeHTMLHeader(w, 200) _, err = w.Write(buf.Bytes()) return err } func serveCompile(w http.ResponseWriter, r *http.Request) error { client := urlfetch.Client(appengine.NewContext(r)) if err := r.ParseForm(); err != nil { return err } resp, err := client.PostForm(playCompileURL, r.Form) if err != nil { return err } defer resp.Body.Close() w.Header().Set("Content-Type", resp.Header.Get("Content-Type")) _, err = io.Copy(w, resp.Body) return err } func serveBot(w http.ResponseWriter, r *http.Request) error { ctx := appengine.NewContext(r) writeTextHeader(w, 200) _, err := fmt.Fprintf(w, "Contact %s for help with the %s bot.", contactEmail, appengine.AppID(ctx)) return err }