12 htemplate "html/template"
32 "github.com/fiorix/go-web/autogzip"
33 _ "github.com/go-sql-driver/mysql"
34 _ "github.com/mattn/go-sqlite3"
35 "github.com/rcrowley/go-metrics"
39 RUN_GYP = `../../experimental/webtry/gyp_for_webtry %s -Dskia_gpu=0`
40 RUN_NINJA = `ninja -C ../../../inout/Release %s`
42 DEFAULT_SAMPLE = `void draw(SkCanvas* canvas) {
44 p.setColor(SK_ColorRED);
46 p.setStyle(SkPaint::kStroke_Style);
49 canvas->drawLine(20, 20, 100, 100, p);
51 // Don't increase above 2^16 w/o altering the db tables to accept something bigger than TEXT.
56 // codeTemplate is the cpp code template the user's code is copied into.
57 codeTemplate *template.Template = nil
59 // gypTemplate is the GYP file to build the executable containing the user's code.
60 gypTemplate *template.Template = nil
62 // indexTemplate is the main index.html page we serve.
63 indexTemplate *htemplate.Template = nil
65 // iframeTemplate is the main index.html page we serve.
66 iframeTemplate *htemplate.Template = nil
68 // recentTemplate is a list of recent images.
69 recentTemplate *htemplate.Template = nil
71 // workspaceTemplate is the page for workspaces, a series of webtrys.
72 workspaceTemplate *htemplate.Template = nil
74 // db is the database, nil if we don't have an SQL database to store data into.
77 // directLink is the regex that matches URLs paths that are direct links.
78 directLink = regexp.MustCompile("^/c/([a-f0-9]+)$")
80 // iframeLink is the regex that matches URLs paths that are links to iframes.
81 iframeLink = regexp.MustCompile("^/iframe/([a-f0-9]+)$")
83 // imageLink is the regex that matches URLs paths that are direct links to PNGs.
84 imageLink = regexp.MustCompile("^/i/([a-z0-9-]+.png)$")
86 // tryInfoLink is the regex that matches URLs paths that are direct links to data about a single try.
87 tryInfoLink = regexp.MustCompile("^/json/([a-f0-9]+)$")
89 // workspaceLink is the regex that matches URLs paths for workspaces.
90 workspaceLink = regexp.MustCompile("^/w/([a-z0-9-]+)$")
92 // workspaceNameAdj is a list of adjectives for building workspace names.
93 workspaceNameAdj = []string{
94 "autumn", "hidden", "bitter", "misty", "silent", "empty", "dry", "dark",
95 "summer", "icy", "delicate", "quiet", "white", "cool", "spring", "winter",
96 "patient", "twilight", "dawn", "crimson", "wispy", "weathered", "blue",
97 "billowing", "broken", "cold", "damp", "falling", "frosty", "green",
98 "long", "late", "lingering", "bold", "little", "morning", "muddy", "old",
99 "red", "rough", "still", "small", "sparkling", "throbbing", "shy",
100 "wandering", "withered", "wild", "black", "young", "holy", "solitary",
101 "fragrant", "aged", "snowy", "proud", "floral", "restless", "divine",
102 "polished", "ancient", "purple", "lively", "nameless",
105 // workspaceNameNoun is a list of nouns for building workspace names.
106 workspaceNameNoun = []string{
107 "waterfall", "river", "breeze", "moon", "rain", "wind", "sea", "morning",
108 "snow", "lake", "sunset", "pine", "shadow", "leaf", "dawn", "glitter",
109 "forest", "hill", "cloud", "meadow", "sun", "glade", "bird", "brook",
110 "butterfly", "bush", "dew", "dust", "field", "fire", "flower", "firefly",
111 "feather", "grass", "haze", "mountain", "night", "pond", "darkness",
112 "snowflake", "silence", "sound", "sky", "shape", "surf", "thunder",
113 "violet", "water", "wildflower", "wave", "water", "resonance", "sun",
114 "wood", "dream", "cherry", "tree", "fog", "frost", "voice", "paper",
115 "frog", "smoke", "star",
121 requestsCounter = metrics.NewRegisteredCounter("requests", metrics.DefaultRegistry)
126 useChroot = flag.Bool("use_chroot", false, "Run the compiled code in the schroot jail.")
127 port = flag.String("port", ":8000", "HTTP service address (e.g., ':8000')")
130 // lineNumbers adds #line numbering to the user's code.
131 func LineNumbers(c string) string {
132 lines := strings.Split(c, "\n")
134 for i, line := range lines {
135 ret = append(ret, fmt.Sprintf("#line %d", i+1))
136 ret = append(ret, line)
138 return strings.Join(ret, "\n")
142 rand.Seed(time.Now().UnixNano())
144 // Change the current working directory to the directory of the executable.
146 cwd, err := filepath.Abs(filepath.Dir(os.Args[0]))
152 codeTemplate, err = template.ParseFiles(filepath.Join(cwd, "templates/template.cpp"))
156 gypTemplate, err = template.ParseFiles(filepath.Join(cwd, "templates/template.gyp"))
160 indexTemplate, err = htemplate.ParseFiles(
161 filepath.Join(cwd, "templates/index.html"),
162 filepath.Join(cwd, "templates/titlebar.html"),
163 filepath.Join(cwd, "templates/content.html"),
164 filepath.Join(cwd, "templates/headercommon.html"),
165 filepath.Join(cwd, "templates/footercommon.html"),
170 iframeTemplate, err = htemplate.ParseFiles(
171 filepath.Join(cwd, "templates/iframe.html"),
172 filepath.Join(cwd, "templates/content.html"),
173 filepath.Join(cwd, "templates/headercommon.html"),
174 filepath.Join(cwd, "templates/footercommon.html"),
179 recentTemplate, err = htemplate.ParseFiles(
180 filepath.Join(cwd, "templates/recent.html"),
181 filepath.Join(cwd, "templates/titlebar.html"),
182 filepath.Join(cwd, "templates/headercommon.html"),
183 filepath.Join(cwd, "templates/footercommon.html"),
188 workspaceTemplate, err = htemplate.ParseFiles(
189 filepath.Join(cwd, "templates/workspace.html"),
190 filepath.Join(cwd, "templates/titlebar.html"),
191 filepath.Join(cwd, "templates/content.html"),
192 filepath.Join(cwd, "templates/headercommon.html"),
193 filepath.Join(cwd, "templates/footercommon.html"),
199 // The git command returns output of the format:
201 // f672cead70404080a991ebfb86c38316a4589b23 2014-04-27 19:21:51 +0000
203 logOutput, err := doCmd(`git log --format=%H%x20%ai HEAD^..HEAD`, true)
207 logInfo := strings.Split(logOutput, " ")
209 gitInfo = logInfo[1] + " " + logInfo[2] + " " + logInfo[0][0:6]
211 // Connect to MySQL server. First, get the password from the metadata server.
212 // See https://developers.google.com/compute/docs/metadata#custom.
213 req, err := http.NewRequest("GET", "http://metadata/computeMetadata/v1/instance/attributes/password", nil)
217 client := http.Client{}
218 req.Header.Add("X-Google-Metadata-Request", "True")
219 if resp, err := client.Do(req); err == nil {
220 password, err := ioutil.ReadAll(resp.Body)
222 log.Printf("ERROR: Failed to read password from metadata server: %q\n", err)
225 // The IP address of the database is found here:
226 // https://console.developers.google.com/project/31977622648/sql/instances/webtry/overview
227 // And 3306 is the default port for MySQL.
228 db, err = sql.Open("mysql", fmt.Sprintf("webtry:%s@tcp(173.194.83.52:3306)/webtry?parseTime=true", password))
230 log.Printf("ERROR: Failed to open connection to SQL server: %q\n", err)
234 log.Printf("INFO: Failed to find metadata, unable to connect to MySQL server (Expected when running locally): %q\n", err)
235 // Fallback to sqlite for local use.
236 db, err = sql.Open("sqlite3", "./webtry.db")
238 log.Printf("ERROR: Failed to open: %q\n", err)
241 sql := `CREATE TABLE source_images (
242 id INTEGER PRIMARY KEY NOT NULL,
243 image MEDIUMBLOB DEFAULT '' NOT NULL, -- formatted as a PNG.
244 width INTEGER DEFAULT 0 NOT NULL,
245 height INTEGER DEFAULT 0 NOT NULL,
246 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
247 hidden INTEGER DEFAULT 0 NOT NULL
249 _, err = db.Exec(sql)
250 log.Printf("Info: status creating sqlite table for sources: %q\n", err)
252 sql = `CREATE TABLE webtry (
253 code TEXT DEFAULT '' NOT NULL,
254 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
255 hash CHAR(64) DEFAULT '' NOT NULL,
256 source_image_id INTEGER DEFAULT 0 NOT NULL,
260 _, err = db.Exec(sql)
261 log.Printf("Info: status creating sqlite table for webtry: %q\n", err)
263 sql = `CREATE TABLE workspace (
264 name CHAR(64) DEFAULT '' NOT NULL,
265 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
268 _, err = db.Exec(sql)
269 log.Printf("Info: status creating sqlite table for workspace: %q\n", err)
271 sql = `CREATE TABLE workspacetry (
272 name CHAR(64) DEFAULT '' NOT NULL,
273 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
274 hash CHAR(64) DEFAULT '' NOT NULL,
275 hidden INTEGER DEFAULT 0 NOT NULL,
276 source_image_id INTEGER DEFAULT 0 NOT NULL,
278 FOREIGN KEY (name) REFERENCES workspace(name)
280 _, err = db.Exec(sql)
281 log.Printf("Info: status creating sqlite table for workspace try: %q\n", err)
284 // Ping the database to keep the connection fresh.
286 c := time.Tick(1 * time.Minute)
288 if err := db.Ping(); err != nil {
289 log.Printf("ERROR: Database failed to respond: %q\n", err)
294 metrics.RegisterRuntimeMemStats(metrics.DefaultRegistry)
295 go metrics.CaptureRuntimeMemStats(metrics.DefaultRegistry, 1*time.Minute)
297 // Start reporting metrics.
298 // TODO(jcgregorio) We need a centrialized config server for storing things
299 // like the IP address of the Graphite monitor.
300 addr, _ := net.ResolveTCPAddr("tcp", "skia-monitoring-b:2003")
301 go metrics.Graphite(metrics.DefaultRegistry, 1*time.Minute, "webtry", addr)
303 writeOutAllSourceImages()
306 func writeOutAllSourceImages() {
307 // Pull all the source images from the db and write them out to inout.
308 rows, err := db.Query("SELECT id, image, create_ts FROM source_images ORDER BY create_ts DESC")
311 log.Printf("ERROR: Failed to open connection to SQL server: %q\n", err)
317 var create_ts time.Time
318 if err := rows.Scan(&id, &image, &create_ts); err != nil {
319 log.Printf("Error: failed to fetch from database: %q", err)
322 filename := fmt.Sprintf("../../../inout/image-%d.png", id)
323 if _, err := os.Stat(filename); os.IsExist(err) {
324 log.Printf("Skipping write since file exists: %q", filename)
327 if err := ioutil.WriteFile(filename, image, 0666); err != nil {
328 log.Printf("Error: failed to write image file: %q", err)
333 // Titlebar is used in titlebar template expansion.
334 type Titlebar struct {
339 // userCode is used in template expansion.
340 type userCode struct {
347 // writeTemplate creates a given output file and writes the template
349 func writeTemplate(filename string, t *template.Template, context interface{}) error {
350 f, err := os.Create(filename)
355 return t.Execute(f, context)
358 // expandToFile expands the template and writes the result to the file.
359 func expandToFile(filename string, code string, t *template.Template) error {
360 return writeTemplate(filename, t, userCode{Code: code, Titlebar: Titlebar{GitHash: gitHash, GitInfo: gitInfo}})
363 // expandCode expands the template into a file and calculates the MD5 hash.
364 func expandCode(code string, source int) (string, error) {
366 h.Write([]byte(code))
367 binary.Write(h, binary.LittleEndian, int64(source))
368 hash := fmt.Sprintf("%x", h.Sum(nil))
369 // At this point we are running in skia/experimental/webtry, making cache a
370 // peer directory to skia.
371 // TODO(jcgregorio) Make all relative directories into flags.
372 err := expandToFile(fmt.Sprintf("../../../cache/src/%s.cpp", hash), code, codeTemplate)
376 // expandGyp produces the GYP file needed to build the code
377 func expandGyp(hash string) error {
378 return writeTemplate(fmt.Sprintf("../../../cache/%s.gyp", hash), gypTemplate, struct{ Hash string }{hash})
381 // response is serialized to JSON as a response to POSTs.
382 type response struct {
383 Message string `json:"message"`
384 StdOut string `json:"stdout"`
385 Img string `json:"img"`
386 Hash string `json:"hash"`
389 // doCmd executes the given command line string in either the out/Debug
390 // directory or the inout directory. Returns the stdout and stderr.
391 func doCmd(commandLine string, moveToDebug bool) (string, error) {
392 log.Printf("Command: %q\n", commandLine)
393 programAndArgs := strings.SplitN(commandLine, " ", 2)
394 program := programAndArgs[0]
396 if len(programAndArgs) > 1 {
397 args = strings.Split(programAndArgs[1], " ")
399 cmd := exec.Command(program, args...)
400 abs, err := filepath.Abs("../../out/Debug")
402 return "", fmt.Errorf("Failed to find absolute path to Debug directory.")
406 } else if !*useChroot { // Don't set cmd.Dir when using chroot.
407 abs, err := filepath.Abs("../../../inout")
409 return "", fmt.Errorf("Failed to find absolute path to inout directory.")
413 log.Printf("Run in directory: %q\n", cmd.Dir)
414 message, err := cmd.CombinedOutput()
415 log.Printf("StdOut + StdErr: %s\n", string(message))
417 log.Printf("Exit status: %s\n", err.Error())
418 return string(message), fmt.Errorf("Failed to run command.")
420 return string(message), nil
423 // reportError formats an HTTP error response and also logs the detailed error message.
424 func reportError(w http.ResponseWriter, r *http.Request, err error, message string) {
425 log.Printf("Error: %s\n%s", message, err.Error())
426 w.Header().Set("Content-Type", "text/plain")
427 http.Error(w, message, 500)
430 // reportTryError formats an HTTP error response in JSON and also logs the detailed error message.
431 func reportTryError(w http.ResponseWriter, r *http.Request, err error, message, hash string) {
436 log.Printf("Error: %s\n%s", message, err.Error())
437 resp, err := json.Marshal(m)
439 http.Error(w, "Failed to serialize a response", 500)
442 w.Header().Set("Content-Type", "text/plain")
446 func writeToDatabase(hash string, code string, workspaceName string, source int) {
450 if _, err := db.Exec("INSERT INTO webtry (code, hash, source_image_id) VALUES(?, ?, ?)", code, hash, source); err != nil {
451 log.Printf("ERROR: Failed to insert code into database: %q\n", err)
453 if workspaceName != "" {
454 if _, err := db.Exec("INSERT INTO workspacetry (name, hash, source_image_id) VALUES(?, ?, ?)", workspaceName, hash, source); err != nil {
455 log.Printf("ERROR: Failed to insert into workspacetry table: %q\n", err)
460 type Sources struct {
464 // sourcesHandler serves up the PNG of a specific try.
465 func sourcesHandler(w http.ResponseWriter, r *http.Request) {
466 log.Printf("Sources Handler: %q\n", r.URL.Path)
467 if r.Method == "GET" {
468 rows, err := db.Query("SELECT id, create_ts FROM source_images WHERE hidden=0 ORDER BY create_ts DESC")
471 http.Error(w, fmt.Sprintf("Failed to query sources: %s.", err), 500)
473 sources := make([]Sources, 0, 0)
476 var create_ts time.Time
477 if err := rows.Scan(&id, &create_ts); err != nil {
478 log.Printf("Error: failed to fetch from database: %q", err)
481 sources = append(sources, Sources{Id: id})
484 resp, err := json.Marshal(sources)
486 reportError(w, r, err, "Failed to serialize a response.")
489 w.Header().Set("Content-Type", "application/json")
492 } else if r.Method == "POST" {
493 if err := r.ParseMultipartForm(1000000); err != nil {
494 http.Error(w, fmt.Sprintf("Failed to load image: %s.", err), 500)
497 if _, ok := r.MultipartForm.File["upload"]; !ok {
498 http.Error(w, "Invalid upload.", 500)
501 if len(r.MultipartForm.File["upload"]) != 1 {
502 http.Error(w, "Wrong number of uploads.", 500)
505 f, err := r.MultipartForm.File["upload"][0].Open()
507 http.Error(w, fmt.Sprintf("Failed to load image: %s.", err), 500)
511 m, _, err := image.Decode(f)
513 http.Error(w, fmt.Sprintf("Failed to decode image: %s.", err), 500)
519 width := bounds.Max.Y - bounds.Min.Y
520 height := bounds.Max.X - bounds.Min.X
521 if _, err := db.Exec("INSERT INTO source_images (image, width, height) VALUES(?, ?, ?)", b.Bytes(), width, height); err != nil {
522 log.Printf("ERROR: Failed to insert sources into database: %q\n", err)
523 http.Error(w, fmt.Sprintf("Failed to store image: %s.", err), 500)
526 go writeOutAllSourceImages()
528 // Now redirect back to where we came from.
529 http.Redirect(w, r, r.Referer(), 302)
536 // imageHandler serves up the PNG of a specific try.
537 func imageHandler(w http.ResponseWriter, r *http.Request) {
538 log.Printf("Image Handler: %q\n", r.URL.Path)
539 if r.Method != "GET" {
543 match := imageLink.FindStringSubmatch(r.URL.Path)
549 w.Header().Set("Content-Type", "image/png")
550 http.ServeFile(w, r, fmt.Sprintf("../../../inout/%s", filename))
554 Hash string `json:"hash"`
556 CreateTS string `json:"create_ts"`
564 // recentHandler shows the last 20 tries.
565 func recentHandler(w http.ResponseWriter, r *http.Request) {
566 log.Printf("Recent Handler: %q\n", r.URL.Path)
569 rows, err := db.Query("SELECT create_ts, hash FROM webtry ORDER BY create_ts DESC LIMIT 20")
577 var create_ts time.Time
578 if err := rows.Scan(&create_ts, &hash); err != nil {
579 log.Printf("Error: failed to fetch from database: %q", err)
582 recent = append(recent, Try{Hash: hash, CreateTS: create_ts.Format("2006-02-01")})
584 w.Header().Set("Content-Type", "text/html")
585 if err := recentTemplate.Execute(w, Recent{Tries: recent, Titlebar: Titlebar{GitHash: gitHash, GitInfo: gitInfo}}); err != nil {
586 log.Printf("ERROR: Failed to expand template: %q\n", err)
590 type Workspace struct {
599 // newWorkspace generates a new random workspace name and stores it in the database.
600 func newWorkspace() (string, error) {
601 for i := 0; i < 10; i++ {
602 adj := workspaceNameAdj[rand.Intn(len(workspaceNameAdj))]
603 noun := workspaceNameNoun[rand.Intn(len(workspaceNameNoun))]
604 suffix := rand.Intn(1000)
605 name := fmt.Sprintf("%s-%s-%d", adj, noun, suffix)
606 if _, err := db.Exec("INSERT INTO workspace (name) VALUES(?)", name); err == nil {
609 log.Printf("ERROR: Failed to insert workspace into database: %q\n", err)
612 return "", fmt.Errorf("Failed to create a new workspace")
615 // getCode returns the code for a given hash, or the empty string if not found.
616 func getCode(hash string) (string, int, error) {
619 if err := db.QueryRow("SELECT code, source_image_id FROM webtry WHERE hash=?", hash).Scan(&code, &source); err != nil {
620 log.Printf("ERROR: Code for hash is missing: %q\n", err)
621 return code, source, err
623 return code, source, nil
626 func workspaceHandler(w http.ResponseWriter, r *http.Request) {
627 log.Printf("Workspace Handler: %q\n", r.URL.Path)
628 if r.Method == "GET" {
630 match := workspaceLink.FindStringSubmatch(r.URL.Path)
634 rows, err := db.Query("SELECT create_ts, hash, source_image_id FROM workspacetry WHERE name=? ORDER BY create_ts", name)
636 reportError(w, r, err, "Failed to select.")
641 var create_ts time.Time
643 if err := rows.Scan(&create_ts, &hash, &source); err != nil {
644 log.Printf("Error: failed to fetch from database: %q", err)
647 tries = append(tries, Try{Hash: hash, Source: source, CreateTS: create_ts.Format("2006-02-01")})
654 code = DEFAULT_SAMPLE
656 hash = tries[len(tries)-1].Hash
657 code, source, _ = getCode(hash)
659 w.Header().Set("Content-Type", "text/html")
660 if err := workspaceTemplate.Execute(w, Workspace{Tries: tries, Code: code, Name: name, Hash: hash, Source: source, Titlebar: Titlebar{GitHash: gitHash, GitInfo: gitInfo}}); err != nil {
661 log.Printf("ERROR: Failed to expand template: %q\n", err)
663 } else if r.Method == "POST" {
664 name, err := newWorkspace()
666 http.Error(w, "Failed to create a new workspace.", 500)
669 http.Redirect(w, r, "/w/"+name, 302)
673 // hasPreProcessor returns true if any line in the code begins with a # char.
674 func hasPreProcessor(code string) bool {
675 lines := strings.Split(code, "\n")
676 for _, s := range lines {
677 if strings.HasPrefix(strings.TrimSpace(s), "#") {
684 type TryRequest struct {
685 Code string `json:"code"`
686 Name string `json:"name"` // Optional name of the workspace the code is in.
687 Source int `json:"source"` // ID of the source image, 0 if none.
690 // iframeHandler handles the GET and POST of the main page.
691 func iframeHandler(w http.ResponseWriter, r *http.Request) {
692 log.Printf("IFrame Handler: %q\n", r.URL.Path)
693 if r.Method != "GET" {
697 match := iframeLink.FindStringSubmatch(r.URL.Path)
708 code, source, err := getCode(hash)
713 // Expand the template.
714 w.Header().Set("Content-Type", "text/html")
715 if err := iframeTemplate.Execute(w, userCode{Code: code, Hash: hash, Source: source}); err != nil {
716 log.Printf("ERROR: Failed to expand template: %q\n", err)
720 type TryInfo struct {
721 Hash string `json:"hash"`
722 Code string `json:"code"`
723 Source int `json:"source"`
726 // tryInfoHandler returns information about a specific try.
727 func tryInfoHandler(w http.ResponseWriter, r *http.Request) {
728 log.Printf("Try Info Handler: %q\n", r.URL.Path)
729 if r.Method != "GET" {
733 match := tryInfoLink.FindStringSubmatch(r.URL.Path)
739 code, source, err := getCode(hash)
749 resp, err := json.Marshal(m)
751 reportError(w, r, err, "Failed to serialize a response.")
754 w.Header().Set("Content-Type", "application/json")
758 func cleanCompileOutput(s, hash string) string {
759 old := "../../../cache/src/" + hash + ".cpp:"
760 log.Printf("INFO: replacing %q\n", old)
761 return strings.Replace(s, old, "usercode.cpp:", -1)
764 // mainHandler handles the GET and POST of the main page.
765 func mainHandler(w http.ResponseWriter, r *http.Request) {
766 log.Printf("Main Handler: %q\n", r.URL.Path)
767 requestsCounter.Inc(1)
768 if r.Method == "GET" {
769 code := DEFAULT_SAMPLE
771 match := directLink.FindStringSubmatch(r.URL.Path)
773 if len(match) == 2 && r.URL.Path != "/" {
779 // Update 'code' with the code found in the database.
780 if err := db.QueryRow("SELECT code, source_image_id FROM webtry WHERE hash=?", hash).Scan(&code, &source); err != nil {
785 // Expand the template.
786 w.Header().Set("Content-Type", "text/html")
787 if err := indexTemplate.Execute(w, userCode{Code: code, Hash: hash, Source: source, Titlebar: Titlebar{GitHash: gitHash, GitInfo: gitInfo}}); err != nil {
788 log.Printf("ERROR: Failed to expand template: %q\n", err)
790 } else if r.Method == "POST" {
791 w.Header().Set("Content-Type", "application/json")
792 buf := bytes.NewBuffer(make([]byte, 0, MAX_TRY_SIZE))
793 n, err := buf.ReadFrom(r.Body)
795 reportTryError(w, r, err, "Failed to read a request body.", "")
798 if n == MAX_TRY_SIZE {
799 err := fmt.Errorf("Code length equal to, or exceeded, %d", MAX_TRY_SIZE)
800 reportTryError(w, r, err, "Code too large.", "")
803 request := TryRequest{}
804 if err := json.Unmarshal(buf.Bytes(), &request); err != nil {
805 reportTryError(w, r, err, "Coulnd't decode JSON.", "")
808 if hasPreProcessor(request.Code) {
809 err := fmt.Errorf("Found preprocessor macro in code.")
810 reportTryError(w, r, err, "Preprocessor macros aren't allowed.", "")
813 hash, err := expandCode(LineNumbers(request.Code), request.Source)
815 reportTryError(w, r, err, "Failed to write the code to compile.", hash)
818 writeToDatabase(hash, request.Code, request.Name, request.Source)
819 err = expandGyp(hash)
821 reportTryError(w, r, err, "Failed to write the gyp file.", hash)
824 message, err := doCmd(fmt.Sprintf(RUN_GYP, hash), true)
826 message = cleanCompileOutput(message, hash)
827 reportTryError(w, r, err, message, hash)
830 linkMessage, err := doCmd(fmt.Sprintf(RUN_NINJA, hash), true)
832 linkMessage = cleanCompileOutput(linkMessage, hash)
833 reportTryError(w, r, err, linkMessage, hash)
836 message += linkMessage
837 cmd := hash + " --out " + hash + ".png"
838 if request.Source > 0 {
839 cmd += fmt.Sprintf(" --source image-%d.png", request.Source)
842 cmd = "schroot -c webtry --directory=/inout -- /inout/Release/" + cmd
844 abs, err := filepath.Abs("../../../inout/Release")
846 reportTryError(w, r, err, "Failed to find executable directory.", hash)
849 cmd = abs + "/" + cmd
852 execMessage, err := doCmd(cmd, false)
854 reportTryError(w, r, err, "Failed to run the code:\n"+execMessage, hash)
857 png, err := ioutil.ReadFile("../../../inout/" + hash + ".png")
859 reportTryError(w, r, err, "Failed to open the generated PNG.", hash)
866 Img: base64.StdEncoding.EncodeToString([]byte(png)),
869 resp, err := json.Marshal(m)
871 reportTryError(w, r, err, "Failed to serialize a response.", hash)
874 w.Header().Set("Content-Type", "application/json")
881 http.HandleFunc("/i/", autogzip.HandleFunc(imageHandler))
882 http.HandleFunc("/w/", autogzip.HandleFunc(workspaceHandler))
883 http.HandleFunc("/recent/", autogzip.HandleFunc(recentHandler))
884 http.HandleFunc("/iframe/", autogzip.HandleFunc(iframeHandler))
885 http.HandleFunc("/json/", autogzip.HandleFunc(tryInfoHandler))
886 http.HandleFunc("/sources/", autogzip.HandleFunc(sourcesHandler))
888 // Resources are served directly
889 // TODO add support for caching/etags/gzip
890 http.Handle("/res/", autogzip.Handle(http.FileServer(http.Dir("./"))))
892 // TODO Break out /c/ as it's own handler.
893 http.HandleFunc("/", autogzip.HandleFunc(mainHandler))
894 log.Fatal(http.ListenAndServe(*port, nil))