OLD | NEW |
---|---|
(Empty) | |
1 package main | |
2 | |
3 import ( | |
4 "bytes" | |
5 "crypto/md5" | |
6 "encoding/base64" | |
7 "encoding/json" | |
8 "flag" | |
9 "fmt" | |
10 "io/ioutil" | |
11 "log" | |
12 "net/http" | |
13 "os" | |
14 "os/exec" | |
15 "path/filepath" | |
16 "strings" | |
17 "text/template" | |
18 ) | |
19 | |
20 const ( | |
21 RESULT_COMPILE = `c++ -DSK_GAMMA_SRGB -DSK_GAMMA_APPLY_TO_A8 -DSK_SCALAR _TO_FLOAT_EXCLUDED -DSK_ALLOW_STATIC_GLOBAL_INITIALIZERS=1 -DSK_SUPPORT_GPU=0 -D SK_SUPPORT_OPENCL=0 -DSK_FORCE_DISTANCEFIELD_FONTS=0 -DSK_SCALAR_IS_FLOAT -DSK_C AN_USE_FLOAT -DSK_SAMPLES_FOR_X -DSK_BUILD_FOR_UNIX -DSK_USE_POSIX_THREADS -DSK_ SYSTEM_ZLIB=1 -DSK_DEBUG -DSK_DEVELOPER=1 -I../../src/core -I../../src/images -I ../../tools/flags -I../../include/config -I../../include/core -I../../include/pa thops -I../../include/pipe -I../../include/effects -I../../include/ports -I../.. /src/sfnt -I../../include/utils -I../../src/utils -I../../include/images -g -fno -exceptions -fstrict-aliasing -Wall -Wextra -Winit-self -Wpointer-arith -Wno-unu sed-parameter -Wno-c++11-extensions -Werror -m64 -fno-rtti -Wnon-virtual-dtor -c ../../../cache/%s.cpp -o ../../../cache/%s.o` | |
22 LINK = `c++ -m64 -lstdc++ -lm -o ../../../inout/%s -Wl,--start -group ../../../cache/%s.o obj/experimental/webtry/webtry.main.o obj/experimenta l/webtry/webtry.syscall_reporter.o obj/gyp/libflags.a libskia_images.a libskia_c ore.a libskia_effects.a obj/gyp/libjpeg.a obj/gyp/libwebp_dec.a obj/gyp/libwebp_ demux.a obj/gyp/libwebp_dsp.a obj/gyp/libwebp_enc.a obj/gyp/libwebp_utils.a libs kia_utils.a libskia_opts.a libskia_opts_ssse3.a libskia_ports.a libskia_sfnt.a - Wl,--end-group -lpng -lz -lgif -lpthread -lfontconfig -ldl -lfreetype` | |
23 ) | |
24 | |
25 var ( | |
26 // codeTemplate is the cpp code template the user's code is copied into. | |
27 codeTemplate *template.Template = nil | |
28 | |
29 // index is the main index.html page we serve. | |
30 index []byte | |
31 ) | |
32 | |
33 // flags | |
34 var ( | |
35 useChroot = flag.Bool("use_chroot", false, "Run the compiled code in the schroot jail.") | |
mtklein
2014/04/08 19:00:23
My paranoia wants this to default to true now that
jcgregorio
2014/04/09 14:10:31
Not just local testing, but anyone can run this lo
| |
36 ) | |
37 | |
38 // lineNumbers adds #line numbering to the user's code. | |
39 func LineNumbers(c string) string { | |
40 lines := strings.Split(c, "\n") | |
41 ret := []string{} | |
42 for i, line := range lines { | |
43 ret = append(ret, fmt.Sprintf("#line %d", i+1)) | |
44 ret = append(ret, line) | |
45 } | |
46 return strings.Join(ret, "\n") | |
47 } | |
48 | |
49 func init() { | |
50 // Change the current working directory to be where the directory of the executable. | |
mtklein
2014/04/08 19:00:23
Funky wording?
jcgregorio
2014/04/09 14:10:31
Done.
| |
51 var err error | |
52 cwd, err := filepath.Abs(filepath.Dir(os.Args[0])) | |
53 if err != nil { | |
54 log.Fatal(err) | |
55 } | |
56 os.Chdir(cwd) | |
57 | |
58 codeTemplate, err = template.ParseFiles(filepath.Join(cwd, "templates/te mplate.cpp")) | |
59 if err != nil { | |
60 panic(err) | |
61 } | |
62 index, err = ioutil.ReadFile(filepath.Join(cwd, "templates/index.html")) | |
63 if err != nil { | |
64 panic(err) | |
65 } | |
66 } | |
67 | |
68 // userCode is used in template expansion. | |
69 type userCode struct { | |
mtklein
2014/04/08 19:00:23
I've got no problem with the struct if you like it
jcgregorio
2014/04/09 14:10:31
I think that would make the template for expansion
mtklein
2014/04/09 15:03:00
Yeah, it would and it does work. I think it's eve
| |
70 UserCode string | |
71 } | |
72 | |
73 // expandToFile expands the template and writes the result to the file. | |
74 func expandToFile(filename string, c userCode, t *template.Template) error { | |
75 f, err := os.Create(filename) | |
76 if err != nil { | |
77 return err | |
78 } | |
79 defer f.Close() | |
80 | |
81 return t.Execute(f, c) | |
82 } | |
83 | |
84 // ExpandCode expands the template into a file and calculate the MD5 hash. | |
85 func ExpandCode(code string) (string, error) { | |
mtklein
2014/04/08 19:00:23
expandCode?
jcgregorio
2014/04/09 14:10:31
Done.
| |
86 h := md5.New() | |
87 h.Write([]byte(code)) | |
88 hash := fmt.Sprintf("%x", h.Sum(nil)) | |
89 content := userCode{ | |
90 UserCode: code, | |
91 } | |
92 err := expandToFile(fmt.Sprintf("../../../cache/%s.cpp", hash), content, codeTemplate) | |
mtklein
2014/04/08 19:00:23
Could be helpful to remind the reader where we are
jcgregorio
2014/04/09 14:10:31
Done. Also added a TODO to make into flags.
| |
93 return hash, err | |
94 } | |
95 | |
96 // Response is serialed to JSON as a response to POSTs. | |
97 type Response struct { | |
mtklein
2014/04/08 19:00:23
response?
jcgregorio
2014/04/09 14:10:31
Done.
| |
98 Message string `json:"message"` | |
99 Img string `json:"img"` | |
100 } | |
101 | |
102 // doCmd executes the given command line string in either the out/Debug director y or the inout directory. | |
103 func doCmd(commandLine string, moveToDebug bool) (string, error) { | |
mtklein
2014/04/08 19:00:23
Not clear here what it returns? can this be (stdo
jcgregorio
2014/04/09 14:10:31
Done.
| |
104 log.Printf("Command: %q\n", commandLine) | |
105 programAndArgs := strings.SplitN(commandLine, " ", 2) | |
106 program := programAndArgs[0] | |
107 args := []string{} | |
108 if len(programAndArgs) > 1 { | |
109 args = strings.Split(programAndArgs[1], " ") | |
110 } | |
111 cmd := exec.Command(program, args...) | |
112 abs, err := filepath.Abs("../../out/Debug") | |
113 if err != nil { | |
114 return "", fmt.Errorf("Failed to find absolute path to Debug dir ectory.") | |
115 } | |
116 if moveToDebug { | |
117 cmd.Dir = abs | |
118 } else if *useChroot { | |
mtklein
2014/04/08 19:00:23
?
if moveToDebug {
} else if !*useChroot {
}
?
jcgregorio
2014/04/09 14:10:31
Done.
| |
119 } else { | |
120 abs, err := filepath.Abs("../../../inout") | |
mtklein
2014/04/08 19:00:23
Might help readability to make some of these liter
jcgregorio
2014/04/09 14:10:31
Added as TODO above.
| |
121 if err != nil { | |
122 return "", fmt.Errorf("Failed to find absolute path to i nout directory.") | |
123 } | |
124 cmd.Dir = abs | |
125 } | |
126 log.Printf("Run in directory: %q\n", cmd.Dir) | |
127 var stdOut bytes.Buffer | |
128 cmd.Stdout = &stdOut | |
129 var stdErr bytes.Buffer | |
130 cmd.Stderr = &stdErr | |
131 cmd.Start() | |
132 err = cmd.Wait() | |
133 message := stdOut.String() | |
134 log.Printf("StdOut: %s\n", message) | |
135 if err != nil { | |
136 log.Printf("Exit status: %s\n", err.Error()) | |
137 log.Printf("StdErr: %s\n", stdErr.String()) | |
138 message += stdErr.String() | |
139 return message, fmt.Errorf("Failed to run command.") | |
140 } | |
141 return message, nil | |
142 } | |
143 | |
144 // reportError formats an HTTP error response and also logs the detailed error m essage. | |
145 func reportError(w http.ResponseWriter, r *http.Request, err error, message stri ng) { | |
146 m := Response{ | |
147 Message: message, | |
148 } | |
149 log.Printf("Error: %s\n%s", message, err.Error()) | |
150 resp, err := json.Marshal(m) | |
151 if err != nil { | |
152 http.Error(w, "Failed to serialize a response", 500) | |
153 return | |
154 } | |
155 w.Write(resp) | |
156 } | |
157 | |
158 // mainHandler handles the GET and POST of the main page. | |
159 func mainHandler(w http.ResponseWriter, r *http.Request) { | |
160 if r.Method == "GET" { | |
161 w.Write(index) | |
162 } else if r.Method == "POST" { | |
163 w.Header().Set("Content-Type", "application/json") | |
164 b, err := ioutil.ReadAll(r.Body) | |
165 if err != nil { | |
166 reportError(w, r, err, "Failed to read a request body.") | |
167 return | |
168 } | |
169 hash, err := ExpandCode(LineNumbers(string(b))) | |
170 if err != nil { | |
171 reportError(w, r, err, "Failed to write the code to comp ile.") | |
172 return | |
173 } | |
174 message, err := doCmd(fmt.Sprintf(RESULT_COMPILE, hash, hash), t rue) | |
175 if err != nil { | |
176 reportError(w, r, err, "Failed to compile the code.") | |
177 return | |
178 } | |
179 linkMessage, err := doCmd(fmt.Sprintf(LINK, hash, hash), true) | |
180 if err != nil { | |
181 reportError(w, r, err, "Failed to link the code.") | |
182 return | |
183 } | |
184 message += linkMessage | |
185 cmd := hash + " --out " + hash + ".png" | |
186 if *useChroot { | |
187 cmd = "schroot -c webtry --directory=/inout -- /inout/" + cmd | |
188 } else { | |
189 abs, err := filepath.Abs("../../../inout") | |
190 if err != nil { | |
191 reportError(w, r, err, "Failed to find executabl e directory.") | |
192 return | |
193 } | |
194 cmd = abs + "/" + cmd | |
195 } | |
196 | |
197 execMessage, err := doCmd(cmd, false) | |
198 if err != nil { | |
199 reportError(w, r, err, "Failed to run the code: "+execMe ssage) | |
200 return | |
201 } | |
202 png, err := ioutil.ReadFile("../../../inout/" + hash + ".png") | |
203 if err != nil { | |
204 reportError(w, r, err, "Failed to open the generated PNG .") | |
205 return | |
206 } | |
207 | |
208 m := Response{ | |
209 Message: message, | |
210 Img: base64.StdEncoding.EncodeToString([]byte(png)), | |
211 } | |
212 resp, err := json.Marshal(m) | |
213 if err != nil { | |
214 reportError(w, r, err, "Failed to serialize a response." ) | |
215 return | |
216 } | |
217 w.Write(resp) | |
218 } | |
219 } | |
220 | |
221 func main() { | |
222 flag.Parse() | |
223 | |
224 http.HandleFunc("/", mainHandler) | |
225 log.Fatal(http.ListenAndServe(":8000", nil)) | |
226 } | |
OLD | NEW |