blob: 409809b91bc43e32c7515611c2e792fb0014f876 [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"
joshualitt609d9792016-01-27 11:07:23 -080012#include "SkJSONCanvas.h"
joshualitt9a4e1882016-01-27 07:03:29 -080013#include "SkPicture.h"
joshualitt7f6a1e02016-01-22 11:21:43 -080014#include "SkStream.h"
15#include "SkSurface.h"
16
joshualitt7f6a1e02016-01-22 11:21:43 -080017#include <sys/socket.h>
joshualitt7f6a1e02016-01-22 11:21:43 -080018#include <microhttpd.h>
19
20// To get image decoders linked in we have to do the below magic
21#include "SkForceLinking.h"
22#include "SkImageDecoder.h"
23__SK_FORCE_IMAGE_DECODER_LINKING;
24
jcgregorio21ab1202016-01-28 06:24:19 -080025DEFINE_string(source, "https://debugger.skia.org", "Where to load the web UI from.");
26DEFINE_int32(port, 8888, "The port to listen on.");
joshualitt7f6a1e02016-01-22 11:21:43 -080027
28// TODO probably want to make this configurable
29static const int kImageWidth = 1920;
30static const int kImageHeight = 1080;
31
joshualitt7f6a1e02016-01-22 11:21:43 -080032// TODO move to template file
33SkString generateTemplate(SkString source) {
34 SkString debuggerTemplate;
35 debuggerTemplate.appendf(
36 "<!DOCTYPE html>\n"
37 "<html>\n"
38 "<head>\n"
39 " <title>SkDebugger</title>\n"
40 " <meta charset=\"utf-8\" />\n"
41 " <meta http-equiv=\"X-UA-Compatible\" content=\"IE=egde,chrome=1\">\n"
42 " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
43 " <script src=\"%s/res/js/core.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n"
44 " <link href=\"%s/res/vul/elements.html\" rel=\"import\" />\n"
45 "</head>\n"
46 "<body class=\"fullbleed layout vertical\">\n"
47 " <debugger-app-sk>This is the app."
48 " </debugger-app-sk>\n"
49 "</body>\n"
50 "</html>", source.c_str(), source.c_str());
51 return debuggerTemplate;
52
53}
54
joshualitt9a4e1882016-01-27 07:03:29 -080055struct UploadContext {
joshualitt609d9792016-01-27 11:07:23 -080056 SkDynamicMemoryWStream fStream;
57 MHD_PostProcessor* fPostProcessor;
joshualitt9a4e1882016-01-27 07:03:29 -080058 MHD_Connection* connection;
59};
60
61struct Request {
62 Request() : fUploadContext(nullptr) {}
63 UploadContext* fUploadContext;
64 SkAutoTUnref<SkData> fPNG;
joshualitt609d9792016-01-27 11:07:23 -080065 SkAutoTUnref<SkPicture> fPicture;
joshualitt9a4e1882016-01-27 07:03:29 -080066};
67
joshualitt609d9792016-01-27 11:07:23 -080068// TODO factor this out into functions, also handle CPU path
69bool setupAndDrawToCanvas(Request* request, SkString* error) {
70 GrContextOptions grContextOpts;
71 SkAutoTDelete<GrContextFactory> factory(new GrContextFactory(grContextOpts));
72
73 GrContext* context = factory->get(GrContextFactory::kNative_GLContextType,
74 GrContextFactory::kNone_GLContextOptions);
75 int maxRTSize = context->caps()->maxRenderTargetSize();
76 SkImageInfo info = SkImageInfo::Make(SkTMin(kImageWidth, maxRTSize),
77 SkTMin(kImageHeight, maxRTSize),
78 kN32_SkColorType, kPremul_SkAlphaType);
79 uint32_t flags = 0;
80 SkSurfaceProps props(flags, SkSurfaceProps::kLegacyFontHost_InitType);
81 SkAutoTUnref<SkSurface> surface(SkSurface::NewRenderTarget(context,
82 SkSurface::kNo_Budgeted, info,
83 0, &props));
84 SkASSERT(surface.get());
85
86 SkGLContext* gl = factory->getContextInfo(GrContextFactory::kNative_GLContextType,
87 GrContextFactory::kNone_GLContextOptions).fGLContext;
88 gl->makeCurrent();
89
90 // draw
91 request->fPicture.reset(
92 SkPicture::CreateFromStream(request->fUploadContext->fStream.detachAsStream()));
93 if (!request->fPicture.get()) {
94 error->appendf("Could not create picture from stream.\n");
95 return false;
96 }
97
98 SkCanvas* canvas = surface->getCanvas();
99 canvas->drawPicture(request->fPicture);
100
101 // capture pixels
102 SkBitmap bmp;
103 bmp.setInfo(canvas->imageInfo());
104 if (!canvas->readPixels(&bmp, 0, 0)) {
105 error->appendf("Can't read canvas pixels.\n");
106 return false;
107 }
108
109 // write to png
110 request->fPNG.reset(SkImageEncoder::EncodeData(bmp, SkImageEncoder::kPNG_Type, 100));
111 if (!request->fPNG) {
112 error->appendf("Can't encode a PNG.\n");
113 return false;
114 }
115 return true;
116}
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
joshualitt609d9792016-01-27 11:07:23 -0800143static int SendJSON(MHD_Connection* connection, SkPicture* picture) {
144 SkDynamicMemoryWStream stream;
145 SkAutoTUnref<SkJSONCanvas> jsonCanvas(new SkJSONCanvas(kImageWidth, kImageHeight, stream));
146 jsonCanvas->drawPicture(picture);
147 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,
180 const char* upload_data, size_t* upload_data_size) = 0;
181};
joshualittccfdaa52016-01-27 07:40:29 -0800182
joshualitt483b9012016-02-02 07:16:24 -0800183class InfoHandler : public UrlHandler {
184public:
185 bool canHandle(const char* method, const char* url) override {
186 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
187 0 == strcmp(url, "/cmd");
joshualittccfdaa52016-01-27 07:40:29 -0800188 }
189
joshualitt483b9012016-02-02 07:16:24 -0800190 int handle(Request* request, MHD_Connection* connection,
191 const char* upload_data, size_t* upload_data_size) override {
192 if (request->fPicture.get()) {
193 return SendJSON(connection, request->fPicture);
194 }
195 return MHD_NO;
196 }
197};
198
199class ImgHandler : public UrlHandler {
200public:
201 bool canHandle(const char* method, const char* url) override {
202 static const char* kBasePath = "/img";
203 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
204 0 == strncmp(url, kBasePath, strlen(kBasePath));
joshualittccfdaa52016-01-27 07:40:29 -0800205 }
206
joshualitt483b9012016-02-02 07:16:24 -0800207 int handle(Request* request, MHD_Connection* connection,
208 const char* upload_data, size_t* upload_data_size) override {
209 if (request->fPNG.get()) {
210 SkData* data = request->fPNG.get();
211 return SendData(connection, data, "image/png");
212 }
joshualitta341b902016-02-02 07:37:21 -0800213
joshualitt483b9012016-02-02 07:16:24 -0800214 return MHD_NO;
215 }
216};
joshualittccfdaa52016-01-27 07:40:29 -0800217
joshualitt483b9012016-02-02 07:16:24 -0800218class PostHandler : public UrlHandler {
219public:
220 bool canHandle(const char* method, const char* url) override {
221 return 0 == strcmp(method, MHD_HTTP_METHOD_POST) &&
222 0 == strcmp(url, "/new");
joshualittccfdaa52016-01-27 07:40:29 -0800223 }
224
joshualitt483b9012016-02-02 07:16:24 -0800225 int handle(Request* request, MHD_Connection* connection,
226 const char* upload_data, size_t* upload_data_size) override {
227 UploadContext* uc = request->fUploadContext;
joshualittccfdaa52016-01-27 07:40:29 -0800228
joshualitt483b9012016-02-02 07:16:24 -0800229 // New connection
230 if (!uc) {
231 // TODO make this a method on request
232 uc = new UploadContext;
233 uc->connection = connection;
234 uc->fPostProcessor = MHD_create_post_processor(connection, kBufferSize,
235 &process_upload_data, uc);
236 SkASSERT(uc->fPostProcessor);
joshualitt609d9792016-01-27 11:07:23 -0800237
joshualitt483b9012016-02-02 07:16:24 -0800238 request->fUploadContext = uc;
239 return MHD_YES;
240 }
241
242 // in process upload
243 if (0 != *upload_data_size) {
244 SkASSERT(uc->fPostProcessor);
245 MHD_post_process(uc->fPostProcessor, upload_data, *upload_data_size);
246 *upload_data_size = 0;
247 return MHD_YES;
248 }
249
250 // end of upload
251 MHD_destroy_post_processor(uc->fPostProcessor);
252 uc->fPostProcessor = nullptr;
253
254 // TODO response
255 SkString error;
256 if (!setupAndDrawToCanvas(request, &error)) {
257 // TODO send error
258 return MHD_YES;
259 }
260
joshualitta341b902016-02-02 07:37:21 -0800261 // clear upload context
262 delete request->fUploadContext;
263 request->fUploadContext = nullptr;
264
joshualitt483b9012016-02-02 07:16:24 -0800265 return SendTemplate(connection, true, "/");
joshualittccfdaa52016-01-27 07:40:29 -0800266 }
joshualitt483b9012016-02-02 07:16:24 -0800267};
268
269class RootHandler : public UrlHandler {
270public:
271 bool canHandle(const char* method, const char* url) override {
272 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
273 0 == strcmp(url, "/");
274 }
275
276 int handle(Request* request, MHD_Connection* connection,
277 const char* upload_data, size_t* upload_data_size) override {
278 return SendTemplate(connection);
279 }
280};
joshualittccfdaa52016-01-27 07:40:29 -0800281
282class UrlManager {
283public:
284 UrlManager() {
285 // Register handlers
joshualitt483b9012016-02-02 07:16:24 -0800286 fHandlers.push_back(new RootHandler);
287 fHandlers.push_back(new PostHandler);
288 fHandlers.push_back(new ImgHandler);
289 fHandlers.push_back(new InfoHandler);
290 }
291
292 ~UrlManager() {
293 for (int i = 0; i < fHandlers.count(); i++) { delete fHandlers[i]; }
joshualittccfdaa52016-01-27 07:40:29 -0800294 }
295
296 // This is clearly not efficient for a large number of urls and handlers
297 int invoke(Request* request, MHD_Connection* connection, const char* url, const char* method,
298 const char* upload_data, size_t* upload_data_size) const {
299 for (int i = 0; i < fHandlers.count(); i++) {
joshualitt483b9012016-02-02 07:16:24 -0800300 if (fHandlers[i]->canHandle(method, url)) {
301 return fHandlers[i]->handle(request, connection, upload_data, upload_data_size);
joshualittccfdaa52016-01-27 07:40:29 -0800302 }
303 }
304 return MHD_NO;
305 }
306
307private:
joshualitt483b9012016-02-02 07:16:24 -0800308 SkTArray<UrlHandler*> fHandlers;
joshualittccfdaa52016-01-27 07:40:29 -0800309};
310
311const UrlManager kUrlManager;
312
joshualitt7f6a1e02016-01-22 11:21:43 -0800313int answer_to_connection(void* cls, struct MHD_Connection* connection,
314 const char* url, const char* method, const char* version,
315 const char* upload_data, size_t* upload_data_size,
316 void** con_cls) {
joshualitt9a4e1882016-01-27 07:03:29 -0800317 SkDebugf("New %s request for %s using version %s\n", method, url, version);
joshualitt7f6a1e02016-01-22 11:21:43 -0800318
joshualitt9a4e1882016-01-27 07:03:29 -0800319 Request* request = reinterpret_cast<Request*>(cls);
joshualitt483b9012016-02-02 07:16:24 -0800320 int result = kUrlManager.invoke(request, connection, url, method, upload_data,
321 upload_data_size);
322 if (MHD_NO == result) {
323 fprintf(stderr, "Invalid method and / or url: %s %s)\n", method, url);
324 }
325 return result;
joshualitt7f6a1e02016-01-22 11:21:43 -0800326}
327
328int skiaserve_main() {
joshualitt9a4e1882016-01-27 07:03:29 -0800329 Request request; // This simple server has one request
joshualitt7f6a1e02016-01-22 11:21:43 -0800330 struct MHD_Daemon* daemon;
jcgregorio21ab1202016-01-28 06:24:19 -0800331 // TODO Add option to bind this strictly to an address, e.g. localhost, for security.
332 daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, FLAGS_port, nullptr, nullptr,
joshualitt9a4e1882016-01-27 07:03:29 -0800333 &answer_to_connection, &request,
334 MHD_OPTION_END);
joshualitt7f6a1e02016-01-22 11:21:43 -0800335 if (NULL == daemon) {
336 return 1;
337 }
338
339 getchar();
340 MHD_stop_daemon(daemon);
341 return 0;
342}
343
344#if !defined SK_BUILD_FOR_IOS
345int main(int argc, char** argv) {
346 SkCommandLineFlags::Parse(argc, argv);
347 return skiaserve_main();
348}
349#endif