| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 #include "bin/vmstats_impl.h" | 5 #include "bin/vmstats_impl.h" |
| 6 | 6 |
| 7 #include <sstream> | 7 #include <sstream> |
| 8 | 8 |
| 9 #include "bin/file.h" | 9 #include "bin/file.h" |
| 10 #include "bin/log.h" | 10 #include "bin/log.h" |
| 11 #include "bin/platform.h" | 11 #include "bin/platform.h" |
| 12 #include "bin/resources.h" |
| 12 #include "bin/socket.h" | 13 #include "bin/socket.h" |
| 13 #include "bin/thread.h" | 14 #include "bin/thread.h" |
| 14 #include "bin/utils.h" | 15 #include "bin/utils.h" |
| 15 #include "include/dart_debugger_api.h" | 16 #include "include/dart_debugger_api.h" |
| 16 #include "platform/json.h" | 17 #include "platform/json.h" |
| 17 | 18 |
| 18 #define BUFSIZE 8192 | 19 #define BUFSIZE 8192 |
| 19 #define RETRY_PAUSE 100 // milliseconds | 20 #define RETRY_PAUSE 100 // milliseconds |
| 20 | 21 |
| 21 static const char* INDEX_HTML = "index.html"; | 22 static const char* INDEX_HTML = "index.html"; |
| (...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 125 return "image/png"; | 126 return "image/png"; |
| 126 } | 127 } |
| 127 if (!strcmp(suffix, ".jpg") || !strcmp(suffix, ".jpeg")) { | 128 if (!strcmp(suffix, ".jpg") || !strcmp(suffix, ".jpeg")) { |
| 128 return "image/jpeg"; | 129 return "image/jpeg"; |
| 129 } | 130 } |
| 130 } | 131 } |
| 131 return "text/plain"; | 132 return "text/plain"; |
| 132 } | 133 } |
| 133 | 134 |
| 134 | 135 |
| 136 // Return a malloc'd string from a format string and arguments. |
| 137 intptr_t alloc_printf(char** result, const char* format, ...) { |
| 138 va_list args; |
| 139 va_start(args, format); |
| 140 intptr_t len = vsnprintf(NULL, 0, format, args) + 1; |
| 141 *result = reinterpret_cast<char*>(malloc(len)); |
| 142 return vsnprintf(*result, len, format, args); |
| 143 } |
| 144 |
| 145 |
| 146 void writeResponse(intptr_t socket, const char* content_type, |
| 147 const char* data, size_t length) { |
| 148 char* header; |
| 149 intptr_t len = alloc_printf(&header, |
| 150 "HTTP/1.1 200 OK\nContent-Type: %s\nContent-Length: %"Pu"\n\n", |
| 151 content_type, length); |
| 152 Socket::Write(socket, header, len); |
| 153 Socket::Write(socket, data, length); |
| 154 Socket::Write(socket, "\n", 1); |
| 155 free(header); |
| 156 } |
| 157 |
| 158 |
| 159 void writeErrorResponse(intptr_t socket, intptr_t error_num, const char* error, |
| 160 const char* description) { |
| 161 if (description != NULL) { |
| 162 // Create body first, so its length is known when creating the header. |
| 163 char* body; |
| 164 intptr_t body_len = alloc_printf(&body, |
| 165 "<html><head><title>%d %s</title></head>\n" |
| 166 "<body>\n<h1>%s</h1>\n%s\n</body></html>\n", |
| 167 error_num, error, error, description); |
| 168 char* header; |
| 169 intptr_t header_len = alloc_printf(&header, |
| 170 "HTTP/1.1 %d %s\n" |
| 171 "Content-Length: %d\n" |
| 172 "Connection: close\n" |
| 173 "Content-Type: text/html\n\n", |
| 174 error_num, error, body_len); |
| 175 Socket::Write(socket, header, header_len); |
| 176 Socket::Write(socket, body, body_len); |
| 177 free(header); |
| 178 free(body); |
| 179 } else { |
| 180 char* response; |
| 181 intptr_t len = |
| 182 alloc_printf(&response, "HTTP/1.1 %d %s\n\n", error_num, error); |
| 183 Socket::Write(socket, response, len); |
| 184 free(response); |
| 185 } |
| 186 } |
| 187 |
| 188 |
| 135 void VmStats::WebServer(uword bind_address) { | 189 void VmStats::WebServer(uword bind_address) { |
| 136 while (true) { | 190 while (true) { |
| 137 intptr_t socket = ServerSocket::Accept(bind_address); | 191 intptr_t socket = ServerSocket::Accept(bind_address); |
| 138 if (socket == ServerSocket::kTemporaryFailure) { | 192 if (socket == ServerSocket::kTemporaryFailure) { |
| 139 // Not a real failure, woke up but no connection available. | 193 // Not a real failure, woke up but no connection available. |
| 140 | 194 |
| 141 // Use MonitorLocker.Wait(), since it has finer granularity than sleep(). | 195 // Use MonitorLocker.Wait(), since it has finer granularity than sleep(). |
| 142 dart::Monitor m; | 196 dart::Monitor m; |
| 143 MonitorLocker ml(&m); | 197 MonitorLocker ml(&m); |
| 144 ml.Wait(RETRY_PAUSE); | 198 ml.Wait(RETRY_PAUSE); |
| (...skipping 15 matching lines...) Expand all Loading... |
| 160 if (len <= 0) { | 214 if (len <= 0) { |
| 161 // Invalid HTTP request, ignore. | 215 // Invalid HTTP request, ignore. |
| 162 continue; | 216 continue; |
| 163 } | 217 } |
| 164 buffer[len] = '\0'; | 218 buffer[len] = '\0'; |
| 165 | 219 |
| 166 // Verify it's a GET request. | 220 // Verify it's a GET request. |
| 167 // TODO(tball): support POST requests. | 221 // TODO(tball): support POST requests. |
| 168 if (strncmp("GET ", buffer, 4) != 0 && strncmp("get ", buffer, 4) != 0) { | 222 if (strncmp("GET ", buffer, 4) != 0 && strncmp("get ", buffer, 4) != 0) { |
| 169 Log::PrintErr("Unsupported HTTP request type"); | 223 Log::PrintErr("Unsupported HTTP request type"); |
| 170 const char* response = "HTTP/1.1 403 Forbidden\n" | 224 writeErrorResponse(socket, 403, "Forbidden", |
| 171 "Content-Length: 120\n" | 225 "Unsupported HTTP request type"); |
| 172 "Connection: close\n" | |
| 173 "Content-Type: text/html\n\n" | |
| 174 "<html><head>\n<title>403 Forbidden</title>\n</head>" | |
| 175 "<body>\n<h1>Forbidden</h1>\nUnsupported HTTP request type\n</body>" | |
| 176 "</html>\n"; | |
| 177 Socket::Write(socket, response, strlen(response)); | |
| 178 Socket::Close(socket); | 226 Socket::Close(socket); |
| 179 continue; | 227 continue; |
| 180 } | 228 } |
| 181 | 229 |
| 182 // Extract GET URL, and null-terminate URL in case request line has | 230 // Extract GET URL, and null-terminate URL in case request line has |
| 183 // HTTP version. | 231 // HTTP version. |
| 184 for (int i = 4; i < len; i++) { | 232 for (int i = 4; i < len; i++) { |
| 185 if (buffer[i] == ' ') { | 233 if (buffer[i] == ' ') { |
| 186 buffer[i] = '\0'; | 234 buffer[i] = '\0'; |
| 187 } | 235 } |
| 188 } | 236 } |
| 189 char* url = &buffer[4]; | 237 char* url = strdup(&buffer[4]); |
| 190 | 238 |
| 191 Log::Print("vmstats: %s requested\n", url); | 239 Log::Print("vmstats: %s requested\n", url); |
| 192 char* content = NULL; | 240 char* content = NULL; |
| 193 | 241 |
| 194 // Check for VmStats-specific URLs. | 242 // Check for VmStats-specific URLs. |
| 195 if (strcmp(url, "/isolates") == 0) { | 243 if (strcmp(url, "/isolates") == 0) { |
| 196 content = instance_->IsolatesStatus(); | 244 content = instance_->IsolatesStatus(); |
| 197 } else { | 245 } else { |
| 198 // Check plug-ins. | 246 // Check plug-ins. |
| 199 content = VmStatusService::GetVmStatus(url); | 247 content = VmStatusService::GetVmStatus(url); |
| 200 } | 248 } |
| 201 | 249 |
| 202 if (content != NULL) { | 250 if (content != NULL) { |
| 203 size_t content_len = strlen(content); | 251 writeResponse(socket, "application/json", content, strlen(content)); |
| 204 len = snprintf(buffer, BUFSIZE, | |
| 205 "HTTP/1.1 200 OK\nContent-Type: application/json; charset=UTF-8\n" | |
| 206 "Content-Length: %"Pu"\n\n", | |
| 207 content_len); | |
| 208 Socket::Write(socket, buffer, strlen(buffer)); | |
| 209 Socket::Write(socket, content, content_len); | |
| 210 Socket::Write(socket, "\n", 1); | |
| 211 Socket::Write(socket, buffer, strlen(buffer)); | |
| 212 free(content); | 252 free(content); |
| 213 } else { | 253 } else { |
| 214 // No status content with this URL, return file or resource content. | 254 // No status content with this URL, return file or resource content. |
| 215 std::string path(instance_->root_directory_); | 255 std::string path(instance_->root_directory_); |
| 216 path.append(url); | 256 path.append(url); |
| 217 | 257 |
| 218 // Expand directory URLs. | 258 // Expand directory URLs. |
| 219 if (strcmp(url, "/") == 0) { | 259 if (strcmp(url, "/") == 0) { |
| 220 path.append(VMSTATS_HTML); | 260 path.append(VMSTATS_HTML); |
| 221 } else if (url[strlen(url) - 1] == '/') { | 261 } else if (url[strlen(url) - 1] == '/') { |
| 222 path.append(INDEX_HTML); | 262 path.append(INDEX_HTML); |
| 223 } | 263 } |
| 224 | 264 |
| 225 bool success = false; | 265 bool success = false; |
| 266 char* text_buffer = NULL; |
| 267 const char* content_type = ContentType(path.c_str()); |
| 226 if (File::Exists(path.c_str())) { | 268 if (File::Exists(path.c_str())) { |
| 227 File* f = File::Open(path.c_str(), File::kRead); | 269 File* f = File::Open(path.c_str(), File::kRead); |
| 228 if (f != NULL) { | 270 if (f != NULL) { |
| 229 intptr_t len = f->Length(); | 271 intptr_t len = f->Length(); |
| 230 char* text_buffer = reinterpret_cast<char*>(malloc(len)); | 272 text_buffer = reinterpret_cast<char*>(malloc(len)); |
| 231 if (f->ReadFully(text_buffer, len)) { | 273 if (f->ReadFully(text_buffer, len)) { |
| 232 const char* content_type = ContentType(path.c_str()); | 274 writeResponse(socket, content_type, text_buffer, len); |
| 233 snprintf(buffer, BUFSIZE, | |
| 234 "HTTP/1.1 200 OK\nContent-Type: %s\n" | |
| 235 "Content-Length: %"Pu"\n\n", | |
| 236 content_type, len); | |
| 237 Socket::Write(socket, buffer, strlen(buffer)); | |
| 238 Socket::Write(socket, text_buffer, len); | |
| 239 Socket::Write(socket, "\n", 1); | |
| 240 success = true; | 275 success = true; |
| 241 } | 276 } |
| 242 free(text_buffer); | 277 free(text_buffer); |
| 243 delete f; | 278 delete f; |
| 244 } | 279 } |
| 245 } else { | 280 } else { |
| 246 // TODO(tball): look up linked in resource. | 281 const char* resource; |
| 282 intptr_t len = Resources::ResourceLookup(path.c_str(), &resource); |
| 283 if (len != Resources::kNoSuchInstance) { |
| 284 ASSERT(len >= 0); |
| 285 writeResponse(socket, content_type, resource, len); |
| 286 success = true; |
| 287 } |
| 247 } | 288 } |
| 248 if (!success) { | 289 if (!success) { |
| 249 const char* response = "HTTP/1.1 404 Not Found\n\n"; | 290 char* description; |
| 250 Socket::Write(socket, response, strlen(response)); | 291 alloc_printf( |
| 292 &description, "URL <a href=\"%s\">%s</a> not found.", url, url); |
| 293 writeErrorResponse(socket, 404, "Not Found", description); |
| 294 free(description); |
| 251 } | 295 } |
| 252 } | 296 } |
| 253 Socket::Close(socket); | 297 Socket::Close(socket); |
| 298 free(url); |
| 254 } | 299 } |
| 255 | 300 |
| 256 Shutdown(); | 301 Shutdown(); |
| 257 } | 302 } |
| 258 | 303 |
| 259 | 304 |
| 260 char* VmStats::IsolatesStatus() { | 305 char* VmStats::IsolatesStatus() { |
| 261 std::ostringstream stream; | 306 std::ostringstream stream; |
| 262 stream << '{' << std::endl; | 307 stream << '{' << std::endl; |
| 263 stream << "\"isolates\": [" << std::endl; | 308 stream << "\"isolates\": [" << std::endl; |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 323 VmStatusPlugin* plugin = instance_->registered_plugin_list_; | 368 VmStatusPlugin* plugin = instance_->registered_plugin_list_; |
| 324 while (plugin != NULL) { | 369 while (plugin != NULL) { |
| 325 char* result = (plugin->callback())(request); | 370 char* result = (plugin->callback())(request); |
| 326 if (result != NULL) { | 371 if (result != NULL) { |
| 327 return result; | 372 return result; |
| 328 } | 373 } |
| 329 plugin = plugin->next(); | 374 plugin = plugin->next(); |
| 330 } | 375 } |
| 331 return NULL; | 376 return NULL; |
| 332 } | 377 } |
| OLD | NEW |