blob: 5d1a789c76a972a2b973f5851c299ce2d19409c9 [file] [log] [blame]
joshualitt7f6a1e02016-01-22 11:21:43 -08001/*
2 * Copyright 2016 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "GrCaps.h"
9#include "GrContextFactory.h"
10#include "SkCanvas.h"
11#include "SkCommandLineFlags.h"
joshualitt136f5172016-02-02 11:07:39 -080012#include "SkDebugCanvas.h"
joshualitt609d9792016-01-27 11:07:23 -080013#include "SkJSONCanvas.h"
joshualitt9a4e1882016-01-27 07:03:29 -080014#include "SkPicture.h"
joshualitt7f6a1e02016-01-22 11:21:43 -080015#include "SkStream.h"
16#include "SkSurface.h"
17
joshualitt7f6a1e02016-01-22 11:21:43 -080018#include <sys/socket.h>
joshualitt7f6a1e02016-01-22 11:21:43 -080019#include <microhttpd.h>
20
21// To get image decoders linked in we have to do the below magic
22#include "SkForceLinking.h"
23#include "SkImageDecoder.h"
24__SK_FORCE_IMAGE_DECODER_LINKING;
25
jcgregorio21ab1202016-01-28 06:24:19 -080026DEFINE_string(source, "https://debugger.skia.org", "Where to load the web UI from.");
27DEFINE_int32(port, 8888, "The port to listen on.");
joshualitt7f6a1e02016-01-22 11:21:43 -080028
29// TODO probably want to make this configurable
30static const int kImageWidth = 1920;
31static const int kImageHeight = 1080;
32
joshualitt7f6a1e02016-01-22 11:21:43 -080033// TODO move to template file
34SkString generateTemplate(SkString source) {
35 SkString debuggerTemplate;
36 debuggerTemplate.appendf(
37 "<!DOCTYPE html>\n"
38 "<html>\n"
39 "<head>\n"
40 " <title>SkDebugger</title>\n"
41 " <meta charset=\"utf-8\" />\n"
42 " <meta http-equiv=\"X-UA-Compatible\" content=\"IE=egde,chrome=1\">\n"
43 " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
44 " <script src=\"%s/res/js/core.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n"
45 " <link href=\"%s/res/vul/elements.html\" rel=\"import\" />\n"
46 "</head>\n"
47 "<body class=\"fullbleed layout vertical\">\n"
48 " <debugger-app-sk>This is the app."
49 " </debugger-app-sk>\n"
50 "</body>\n"
51 "</html>", source.c_str(), source.c_str());
52 return debuggerTemplate;
53
54}
55
joshualitt9a4e1882016-01-27 07:03:29 -080056struct UploadContext {
joshualitt609d9792016-01-27 11:07:23 -080057 SkDynamicMemoryWStream fStream;
58 MHD_PostProcessor* fPostProcessor;
joshualitt9a4e1882016-01-27 07:03:29 -080059 MHD_Connection* connection;
60};
61
62struct Request {
63 Request() : fUploadContext(nullptr) {}
64 UploadContext* fUploadContext;
joshualitt609d9792016-01-27 11:07:23 -080065 SkAutoTUnref<SkPicture> fPicture;
joshualitt136f5172016-02-02 11:07:39 -080066 SkAutoTUnref<SkDebugCanvas> fDebugCanvas;
joshualitt9a4e1882016-01-27 07:03:29 -080067};
68
joshualitt609d9792016-01-27 11:07:23 -080069// TODO factor this out into functions, also handle CPU path
joshualitt136f5172016-02-02 11:07:39 -080070SkSurface* setupSurface(GrContextFactory* factory) {
joshualitt609d9792016-01-27 11:07:23 -080071 GrContext* context = factory->get(GrContextFactory::kNative_GLContextType,
72 GrContextFactory::kNone_GLContextOptions);
73 int maxRTSize = context->caps()->maxRenderTargetSize();
74 SkImageInfo info = SkImageInfo::Make(SkTMin(kImageWidth, maxRTSize),
75 SkTMin(kImageHeight, maxRTSize),
76 kN32_SkColorType, kPremul_SkAlphaType);
77 uint32_t flags = 0;
78 SkSurfaceProps props(flags, SkSurfaceProps::kLegacyFontHost_InitType);
joshualitt136f5172016-02-02 11:07:39 -080079 SkSurface* surface = SkSurface::NewRenderTarget(context, SkSurface::kNo_Budgeted, info, 0,
80 &props);
81 SkASSERT(surface);
joshualitt609d9792016-01-27 11:07:23 -080082
83 SkGLContext* gl = factory->getContextInfo(GrContextFactory::kNative_GLContextType,
84 GrContextFactory::kNone_GLContextOptions).fGLContext;
85 gl->makeCurrent();
joshualitt136f5172016-02-02 11:07:39 -080086 return surface;
87}
joshualitt609d9792016-01-27 11:07:23 -080088
joshualitt136f5172016-02-02 11:07:39 -080089SkData* writeCanvasToPng(SkCanvas* canvas) {
joshualitt609d9792016-01-27 11:07:23 -080090 // capture pixels
91 SkBitmap bmp;
92 bmp.setInfo(canvas->imageInfo());
93 if (!canvas->readPixels(&bmp, 0, 0)) {
joshualitt136f5172016-02-02 11:07:39 -080094 fprintf(stderr, "Can't read pixels\n");
95 return nullptr;
joshualitt609d9792016-01-27 11:07:23 -080096 }
97
98 // write to png
joshualitt136f5172016-02-02 11:07:39 -080099 SkData* png = SkImageEncoder::EncodeData(bmp, SkImageEncoder::kPNG_Type, 100);
100 if (!png) {
101 fprintf(stderr, "Can't encode to png\n");
102 return nullptr;
joshualitt609d9792016-01-27 11:07:23 -0800103 }
joshualitt136f5172016-02-02 11:07:39 -0800104 return png;
105}
106
107SkData* setupAndDrawToCanvasReturnPng(SkDebugCanvas* debugCanvas, int n) {
108 GrContextOptions grContextOpts;
109 SkAutoTDelete<GrContextFactory> factory(new GrContextFactory(grContextOpts));
110 SkAutoTUnref<SkSurface> surface(setupSurface(factory.get()));
111
112 SkASSERT(debugCanvas);
113 SkCanvas* canvas = surface->getCanvas();
114 debugCanvas->drawTo(canvas, n);
115 return writeCanvasToPng(canvas);
joshualitt609d9792016-01-27 11:07:23 -0800116}
117
joshualitt9a4e1882016-01-27 07:03:29 -0800118static const size_t kBufferSize = 1024;
119
120static int process_upload_data(void* cls, enum MHD_ValueKind kind,
121 const char* key, const char* filename,
122 const char* content_type, const char* transfer_encoding,
123 const char* data, uint64_t off, size_t size) {
124 struct UploadContext* uc = reinterpret_cast<UploadContext*>(cls);
125
126 if (0 != size) {
joshualitt609d9792016-01-27 11:07:23 -0800127 uc->fStream.write(data, size);
joshualitt9a4e1882016-01-27 07:03:29 -0800128 }
129 return MHD_YES;
130}
131
joshualitt609d9792016-01-27 11:07:23 -0800132static int SendData(MHD_Connection* connection, const SkData* data, const char* type) {
joshualittccfdaa52016-01-27 07:40:29 -0800133 MHD_Response* response = MHD_create_response_from_buffer(data->size(),
134 const_cast<void*>(data->data()),
135 MHD_RESPMEM_MUST_COPY);
joshualitt609d9792016-01-27 11:07:23 -0800136 MHD_add_response_header(response, "Content-Type", type);
joshualittccfdaa52016-01-27 07:40:29 -0800137
138 int ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
139 MHD_destroy_response(response);
140 return ret;
141}
142
joshualitt136f5172016-02-02 11:07:39 -0800143static int SendJSON(MHD_Connection* connection, SkDebugCanvas* debugCanvas, int n) {
joshualitt609d9792016-01-27 11:07:23 -0800144 SkDynamicMemoryWStream stream;
145 SkAutoTUnref<SkJSONCanvas> jsonCanvas(new SkJSONCanvas(kImageWidth, kImageHeight, stream));
joshualitt136f5172016-02-02 11:07:39 -0800146 debugCanvas->drawTo(jsonCanvas, n);
joshualitt609d9792016-01-27 11:07:23 -0800147 jsonCanvas->finish();
148
149 SkAutoTUnref<SkData> data(stream.copyToData());
150 return SendData(connection, data, "application/json");
151}
152
153static int SendTemplate(MHD_Connection* connection, bool redirect = false,
154 const char* redirectUrl = nullptr) {
jcgregorio21ab1202016-01-28 06:24:19 -0800155 SkString debuggerTemplate = generateTemplate(SkString(FLAGS_source[0]));
joshualittccfdaa52016-01-27 07:40:29 -0800156
157 MHD_Response* response = MHD_create_response_from_buffer(
158 debuggerTemplate.size(),
159 (void*) const_cast<char*>(debuggerTemplate.c_str()),
160 MHD_RESPMEM_MUST_COPY);
jcgregorio6a2046e2016-01-28 05:31:31 -0800161 MHD_add_response_header (response, "Access-Control-Allow-Origin", "*");
joshualittccfdaa52016-01-27 07:40:29 -0800162
jcgregorio6f17bc52016-01-27 11:44:38 -0800163 int status = MHD_HTTP_OK;
164
joshualitt609d9792016-01-27 11:07:23 -0800165 if (redirect) {
166 MHD_add_response_header (response, "Location", redirectUrl);
jcgregorio6f17bc52016-01-27 11:44:38 -0800167 status = MHD_HTTP_SEE_OTHER;
joshualitt609d9792016-01-27 11:07:23 -0800168 }
169
jcgregorio6f17bc52016-01-27 11:44:38 -0800170 int ret = MHD_queue_response(connection, status, response);
joshualittccfdaa52016-01-27 07:40:29 -0800171 MHD_destroy_response(response);
172 return ret;
173}
174
joshualitt483b9012016-02-02 07:16:24 -0800175class UrlHandler {
176public:
177 virtual ~UrlHandler() {}
178 virtual bool canHandle(const char* method, const char* url) = 0;
179 virtual int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800180 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800181 const char* upload_data, size_t* upload_data_size) = 0;
182};
joshualittccfdaa52016-01-27 07:40:29 -0800183
joshualitt483b9012016-02-02 07:16:24 -0800184class InfoHandler : public UrlHandler {
185public:
186 bool canHandle(const char* method, const char* url) override {
joshualitt136f5172016-02-02 11:07:39 -0800187 const char* kBasePath = "/cmd";
188 return 0 == strncmp(url, kBasePath, strlen(kBasePath));
joshualittccfdaa52016-01-27 07:40:29 -0800189 }
190
joshualitt483b9012016-02-02 07:16:24 -0800191 int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800192 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800193 const char* upload_data, size_t* upload_data_size) override {
joshualitt136f5172016-02-02 11:07:39 -0800194 SkTArray<SkString> commands;
195 SkStrSplit(url, "/", &commands);
196
197 if (!request->fPicture.get() || commands.count() > 3) {
198 return MHD_NO;
joshualitt483b9012016-02-02 07:16:24 -0800199 }
joshualitt136f5172016-02-02 11:07:39 -0800200
201 // /cmd or /cmd/N or /cmd/N/[0|1]
202 if (commands.count() == 1 && 0 == strcmp(method, MHD_HTTP_METHOD_GET)) {
203 int n = request->fDebugCanvas->getSize() - 1;
204 return SendJSON(connection, request->fDebugCanvas, n);
205 }
206
207 // /cmd/N, for now only delete supported
208 if (commands.count() == 2 && 0 == strcmp(method, MHD_HTTP_METHOD_DELETE)) {
209 int n;
210 sscanf(commands[1].c_str(), "%d", &n);
211 request->fDebugCanvas->deleteDrawCommandAt(n);
212 return MHD_YES;
213 }
214
215 // /cmd/N/[0|1]
216 if (commands.count() == 3 && 0 == strcmp(method, MHD_HTTP_METHOD_POST)) {
217 int n, toggle;
218 sscanf(commands[1].c_str(), "%d", &n);
219 sscanf(commands[2].c_str(), "%d", &toggle);
220 request->fDebugCanvas->toggleCommand(n, toggle);
221 return MHD_YES;
222 }
223
joshualitt483b9012016-02-02 07:16:24 -0800224 return MHD_NO;
225 }
226};
227
228class ImgHandler : public UrlHandler {
229public:
230 bool canHandle(const char* method, const char* url) override {
231 static const char* kBasePath = "/img";
232 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
233 0 == strncmp(url, kBasePath, strlen(kBasePath));
joshualittccfdaa52016-01-27 07:40:29 -0800234 }
235
joshualitt483b9012016-02-02 07:16:24 -0800236 int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800237 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800238 const char* upload_data, size_t* upload_data_size) override {
joshualitt136f5172016-02-02 11:07:39 -0800239 SkTArray<SkString> commands;
240 SkStrSplit(url, "/", &commands);
241
242 if (!request->fPicture.get() || commands.count() > 2) {
243 return MHD_NO;
joshualitt483b9012016-02-02 07:16:24 -0800244 }
joshualitta341b902016-02-02 07:37:21 -0800245
joshualitt136f5172016-02-02 11:07:39 -0800246 int n;
247 // /img or /img/N
248 if (commands.count() == 1) {
249 n = request->fDebugCanvas->getSize() - 1;
250 } else {
251 sscanf(commands[1].c_str(), "%d", &n);
252 }
253
254 SkAutoTUnref<SkData> data(setupAndDrawToCanvasReturnPng(request->fDebugCanvas, n));
255 return SendData(connection, data, "image/png");
joshualitt483b9012016-02-02 07:16:24 -0800256 }
257};
joshualittccfdaa52016-01-27 07:40:29 -0800258
joshualitt483b9012016-02-02 07:16:24 -0800259class PostHandler : public UrlHandler {
260public:
261 bool canHandle(const char* method, const char* url) override {
262 return 0 == strcmp(method, MHD_HTTP_METHOD_POST) &&
263 0 == strcmp(url, "/new");
joshualittccfdaa52016-01-27 07:40:29 -0800264 }
265
joshualitt483b9012016-02-02 07:16:24 -0800266 int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800267 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800268 const char* upload_data, size_t* upload_data_size) override {
269 UploadContext* uc = request->fUploadContext;
joshualittccfdaa52016-01-27 07:40:29 -0800270
joshualitt483b9012016-02-02 07:16:24 -0800271 // New connection
272 if (!uc) {
273 // TODO make this a method on request
274 uc = new UploadContext;
275 uc->connection = connection;
276 uc->fPostProcessor = MHD_create_post_processor(connection, kBufferSize,
277 &process_upload_data, uc);
278 SkASSERT(uc->fPostProcessor);
joshualitt609d9792016-01-27 11:07:23 -0800279
joshualitt483b9012016-02-02 07:16:24 -0800280 request->fUploadContext = uc;
281 return MHD_YES;
282 }
283
284 // in process upload
285 if (0 != *upload_data_size) {
286 SkASSERT(uc->fPostProcessor);
287 MHD_post_process(uc->fPostProcessor, upload_data, *upload_data_size);
288 *upload_data_size = 0;
289 return MHD_YES;
290 }
291
292 // end of upload
293 MHD_destroy_post_processor(uc->fPostProcessor);
294 uc->fPostProcessor = nullptr;
295
joshualitt136f5172016-02-02 11:07:39 -0800296 // parse picture from stream
297 request->fPicture.reset(
298 SkPicture::CreateFromStream(request->fUploadContext->fStream.detachAsStream()));
299 if (!request->fPicture.get()) {
300 fprintf(stderr, "Could not create picture from stream.\n");
301 return MHD_NO;
joshualitt483b9012016-02-02 07:16:24 -0800302 }
303
joshualitt136f5172016-02-02 11:07:39 -0800304 // pour picture into debug canvas
305 request->fDebugCanvas.reset(new SkDebugCanvas(kImageWidth, kImageHeight));
306 request->fDebugCanvas->drawPicture(request->fPicture);
307
joshualitta341b902016-02-02 07:37:21 -0800308 // clear upload context
309 delete request->fUploadContext;
310 request->fUploadContext = nullptr;
311
joshualitt483b9012016-02-02 07:16:24 -0800312 return SendTemplate(connection, true, "/");
joshualittccfdaa52016-01-27 07:40:29 -0800313 }
joshualitt483b9012016-02-02 07:16:24 -0800314};
315
316class RootHandler : public UrlHandler {
317public:
318 bool canHandle(const char* method, const char* url) override {
319 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
320 0 == strcmp(url, "/");
321 }
322
323 int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800324 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800325 const char* upload_data, size_t* upload_data_size) override {
326 return SendTemplate(connection);
327 }
328};
joshualittccfdaa52016-01-27 07:40:29 -0800329
330class UrlManager {
331public:
332 UrlManager() {
333 // Register handlers
joshualitt483b9012016-02-02 07:16:24 -0800334 fHandlers.push_back(new RootHandler);
335 fHandlers.push_back(new PostHandler);
336 fHandlers.push_back(new ImgHandler);
337 fHandlers.push_back(new InfoHandler);
338 }
339
340 ~UrlManager() {
341 for (int i = 0; i < fHandlers.count(); i++) { delete fHandlers[i]; }
joshualittccfdaa52016-01-27 07:40:29 -0800342 }
343
344 // This is clearly not efficient for a large number of urls and handlers
345 int invoke(Request* request, MHD_Connection* connection, const char* url, const char* method,
346 const char* upload_data, size_t* upload_data_size) const {
347 for (int i = 0; i < fHandlers.count(); i++) {
joshualitt483b9012016-02-02 07:16:24 -0800348 if (fHandlers[i]->canHandle(method, url)) {
joshualitt136f5172016-02-02 11:07:39 -0800349 return fHandlers[i]->handle(request, connection, url, method, upload_data,
350 upload_data_size);
joshualittccfdaa52016-01-27 07:40:29 -0800351 }
352 }
353 return MHD_NO;
354 }
355
356private:
joshualitt483b9012016-02-02 07:16:24 -0800357 SkTArray<UrlHandler*> fHandlers;
joshualittccfdaa52016-01-27 07:40:29 -0800358};
359
360const UrlManager kUrlManager;
361
joshualitt7f6a1e02016-01-22 11:21:43 -0800362int answer_to_connection(void* cls, struct MHD_Connection* connection,
363 const char* url, const char* method, const char* version,
364 const char* upload_data, size_t* upload_data_size,
365 void** con_cls) {
joshualitt9a4e1882016-01-27 07:03:29 -0800366 SkDebugf("New %s request for %s using version %s\n", method, url, version);
joshualitt7f6a1e02016-01-22 11:21:43 -0800367
joshualitt9a4e1882016-01-27 07:03:29 -0800368 Request* request = reinterpret_cast<Request*>(cls);
joshualitt483b9012016-02-02 07:16:24 -0800369 int result = kUrlManager.invoke(request, connection, url, method, upload_data,
370 upload_data_size);
371 if (MHD_NO == result) {
372 fprintf(stderr, "Invalid method and / or url: %s %s)\n", method, url);
373 }
374 return result;
joshualitt7f6a1e02016-01-22 11:21:43 -0800375}
376
377int skiaserve_main() {
joshualitt9a4e1882016-01-27 07:03:29 -0800378 Request request; // This simple server has one request
joshualitt7f6a1e02016-01-22 11:21:43 -0800379 struct MHD_Daemon* daemon;
jcgregorio21ab1202016-01-28 06:24:19 -0800380 // TODO Add option to bind this strictly to an address, e.g. localhost, for security.
381 daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, FLAGS_port, nullptr, nullptr,
joshualitt9a4e1882016-01-27 07:03:29 -0800382 &answer_to_connection, &request,
383 MHD_OPTION_END);
joshualitt7f6a1e02016-01-22 11:21:43 -0800384 if (NULL == daemon) {
385 return 1;
386 }
387
388 getchar();
389 MHD_stop_daemon(daemon);
390 return 0;
391}
392
393#if !defined SK_BUILD_FOR_IOS
394int main(int argc, char** argv) {
395 SkCommandLineFlags::Parse(argc, argv);
396 return skiaserve_main();
397}
398#endif