mirror of
https://github.com/dutchcoders/transfer.sh.git
synced 2024-12-02 17:10:18 +01:00
253 lines
6.4 KiB
Go
253 lines
6.4 KiB
Go
|
// 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
|
||
|
}
|