1 // Copyright 2012 the V8 project authors. All rights reserved. 2 // Redistribution and use in source and binary forms, with or without 3 // modification, are permitted provided that the following conditions are 4 // met: 5 // 6 // * Redistributions of source code must retain the above copyright 7 // notice, this list of conditions and the following disclaimer. 8 // * Redistributions in binary form must reproduce the above 9 // copyright notice, this list of conditions and the following 10 // disclaimer in the documentation and/or other materials provided 11 // with the distribution. 12 // * Neither the name of Google Inc. nor the names of its 13 // contributors may be used to endorse or promote products derived 14 // from this software without specific prior written permission. 15 // 16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 28 #include <include/v8.h> 29 30 #include <include/libplatform/libplatform.h> 31 32 #include <map> 33 #include <string> 34 35 #ifdef COMPRESS_STARTUP_DATA_BZ2 36 #error Using compressed startup data is not supported for this sample 37 #endif 38 39 using namespace std; 40 using namespace v8; 41 42 // These interfaces represent an existing request processing interface. 43 // The idea is to imagine a real application that uses these interfaces 44 // and then add scripting capabilities that allow you to interact with 45 // the objects through JavaScript. 46 47 /** 48 * A simplified http request. 49 */ 50 class HttpRequest { 51 public: 52 virtual ~HttpRequest() { } 53 virtual const string& Path() = 0; 54 virtual const string& Referrer() = 0; 55 virtual const string& Host() = 0; 56 virtual const string& UserAgent() = 0; 57 }; 58 59 60 /** 61 * The abstract superclass of http request processors. 62 */ 63 class HttpRequestProcessor { 64 public: 65 virtual ~HttpRequestProcessor() { } 66 67 // Initialize this processor. The map contains options that control 68 // how requests should be processed. 69 virtual bool Initialize(map<string, string>* options, 70 map<string, string>* output) = 0; 71 72 // Process a single request. 73 virtual bool Process(HttpRequest* req) = 0; 74 75 static void Log(const char* event); 76 }; 77 78 79 /** 80 * An http request processor that is scriptable using JavaScript. 81 */ 82 class JsHttpRequestProcessor : public HttpRequestProcessor { 83 public: 84 // Creates a new processor that processes requests by invoking the 85 // Process function of the JavaScript script given as an argument. 86 JsHttpRequestProcessor(Isolate* isolate, Handle<String> script) 87 : isolate_(isolate), script_(script) { } 88 virtual ~JsHttpRequestProcessor(); 89 90 virtual bool Initialize(map<string, string>* opts, 91 map<string, string>* output); 92 virtual bool Process(HttpRequest* req); 93 94 private: 95 // Execute the script associated with this processor and extract the 96 // Process function. Returns true if this succeeded, otherwise false. 97 bool ExecuteScript(Handle<String> script); 98 99 // Wrap the options and output map in a JavaScript objects and 100 // install it in the global namespace as 'options' and 'output'. 101 bool InstallMaps(map<string, string>* opts, map<string, string>* output); 102 103 // Constructs the template that describes the JavaScript wrapper 104 // type for requests. 105 static Handle<ObjectTemplate> MakeRequestTemplate(Isolate* isolate); 106 static Handle<ObjectTemplate> MakeMapTemplate(Isolate* isolate); 107 108 // Callbacks that access the individual fields of request objects. 109 static void GetPath(Local<String> name, 110 const PropertyCallbackInfo<Value>& info); 111 static void GetReferrer(Local<String> name, 112 const PropertyCallbackInfo<Value>& info); 113 static void GetHost(Local<String> name, 114 const PropertyCallbackInfo<Value>& info); 115 static void GetUserAgent(Local<String> name, 116 const PropertyCallbackInfo<Value>& info); 117 118 // Callbacks that access maps 119 static void MapGet(Local<String> name, 120 const PropertyCallbackInfo<Value>& info); 121 static void MapSet(Local<String> name, 122 Local<Value> value, 123 const PropertyCallbackInfo<Value>& info); 124 125 // Utility methods for wrapping C++ objects as JavaScript objects, 126 // and going back again. 127 Handle<Object> WrapMap(map<string, string>* obj); 128 static map<string, string>* UnwrapMap(Handle<Object> obj); 129 Handle<Object> WrapRequest(HttpRequest* obj); 130 static HttpRequest* UnwrapRequest(Handle<Object> obj); 131 132 Isolate* GetIsolate() { return isolate_; } 133 134 Isolate* isolate_; 135 Handle<String> script_; 136 Persistent<Context> context_; 137 Persistent<Function> process_; 138 static Persistent<ObjectTemplate> request_template_; 139 static Persistent<ObjectTemplate> map_template_; 140 }; 141 142 143 // ------------------------- 144 // --- P r o c e s s o r --- 145 // ------------------------- 146 147 148 static void LogCallback(const v8::FunctionCallbackInfo<v8::Value>& args) { 149 if (args.Length() < 1) return; 150 HandleScope scope(args.GetIsolate()); 151 Handle<Value> arg = args[0]; 152 String::Utf8Value value(arg); 153 HttpRequestProcessor::Log(*value); 154 } 155 156 157 // Execute the script and fetch the Process method. 158 bool JsHttpRequestProcessor::Initialize(map<string, string>* opts, 159 map<string, string>* output) { 160 // Create a handle scope to hold the temporary references. 161 HandleScope handle_scope(GetIsolate()); 162 163 // Create a template for the global object where we set the 164 // built-in global functions. 165 Handle<ObjectTemplate> global = ObjectTemplate::New(GetIsolate()); 166 global->Set(String::NewFromUtf8(GetIsolate(), "log"), 167 FunctionTemplate::New(GetIsolate(), LogCallback)); 168 169 // Each processor gets its own context so different processors don't 170 // affect each other. Context::New returns a persistent handle which 171 // is what we need for the reference to remain after we return from 172 // this method. That persistent handle has to be disposed in the 173 // destructor. 174 v8::Handle<v8::Context> context = Context::New(GetIsolate(), NULL, global); 175 context_.Reset(GetIsolate(), context); 176 177 // Enter the new context so all the following operations take place 178 // within it. 179 Context::Scope context_scope(context); 180 181 // Make the options mapping available within the context 182 if (!InstallMaps(opts, output)) 183 return false; 184 185 // Compile and run the script 186 if (!ExecuteScript(script_)) 187 return false; 188 189 // The script compiled and ran correctly. Now we fetch out the 190 // Process function from the global object. 191 Handle<String> process_name = String::NewFromUtf8(GetIsolate(), "Process"); 192 Handle<Value> process_val = context->Global()->Get(process_name); 193 194 // If there is no Process function, or if it is not a function, 195 // bail out 196 if (!process_val->IsFunction()) return false; 197 198 // It is a function; cast it to a Function 199 Handle<Function> process_fun = Handle<Function>::Cast(process_val); 200 201 // Store the function in a Persistent handle, since we also want 202 // that to remain after this call returns 203 process_.Reset(GetIsolate(), process_fun); 204 205 // All done; all went well 206 return true; 207 } 208 209 210 bool JsHttpRequestProcessor::ExecuteScript(Handle<String> script) { 211 HandleScope handle_scope(GetIsolate()); 212 213 // We're just about to compile the script; set up an error handler to 214 // catch any exceptions the script might throw. 215 TryCatch try_catch; 216 217 // Compile the script and check for errors. 218 Handle<Script> compiled_script = Script::Compile(script); 219 if (compiled_script.IsEmpty()) { 220 String::Utf8Value error(try_catch.Exception()); 221 Log(*error); 222 // The script failed to compile; bail out. 223 return false; 224 } 225 226 // Run the script! 227 Handle<Value> result = compiled_script->Run(); 228 if (result.IsEmpty()) { 229 // The TryCatch above is still in effect and will have caught the error. 230 String::Utf8Value error(try_catch.Exception()); 231 Log(*error); 232 // Running the script failed; bail out. 233 return false; 234 } 235 return true; 236 } 237 238 239 bool JsHttpRequestProcessor::InstallMaps(map<string, string>* opts, 240 map<string, string>* output) { 241 HandleScope handle_scope(GetIsolate()); 242 243 // Wrap the map object in a JavaScript wrapper 244 Handle<Object> opts_obj = WrapMap(opts); 245 246 v8::Local<v8::Context> context = 247 v8::Local<v8::Context>::New(GetIsolate(), context_); 248 249 // Set the options object as a property on the global object. 250 context->Global()->Set(String::NewFromUtf8(GetIsolate(), "options"), 251 opts_obj); 252 253 Handle<Object> output_obj = WrapMap(output); 254 context->Global()->Set(String::NewFromUtf8(GetIsolate(), "output"), 255 output_obj); 256 257 return true; 258 } 259 260 261 bool JsHttpRequestProcessor::Process(HttpRequest* request) { 262 // Create a handle scope to keep the temporary object references. 263 HandleScope handle_scope(GetIsolate()); 264 265 v8::Local<v8::Context> context = 266 v8::Local<v8::Context>::New(GetIsolate(), context_); 267 268 // Enter this processor's context so all the remaining operations 269 // take place there 270 Context::Scope context_scope(context); 271 272 // Wrap the C++ request object in a JavaScript wrapper 273 Handle<Object> request_obj = WrapRequest(request); 274 275 // Set up an exception handler before calling the Process function 276 TryCatch try_catch; 277 278 // Invoke the process function, giving the global object as 'this' 279 // and one argument, the request. 280 const int argc = 1; 281 Handle<Value> argv[argc] = { request_obj }; 282 v8::Local<v8::Function> process = 283 v8::Local<v8::Function>::New(GetIsolate(), process_); 284 Handle<Value> result = process->Call(context->Global(), argc, argv); 285 if (result.IsEmpty()) { 286 String::Utf8Value error(try_catch.Exception()); 287 Log(*error); 288 return false; 289 } else { 290 return true; 291 } 292 } 293 294 295 JsHttpRequestProcessor::~JsHttpRequestProcessor() { 296 // Dispose the persistent handles. When noone else has any 297 // references to the objects stored in the handles they will be 298 // automatically reclaimed. 299 context_.Reset(); 300 process_.Reset(); 301 } 302 303 304 Persistent<ObjectTemplate> JsHttpRequestProcessor::request_template_; 305 Persistent<ObjectTemplate> JsHttpRequestProcessor::map_template_; 306 307 308 // ----------------------------------- 309 // --- A c c e s s i n g M a p s --- 310 // ----------------------------------- 311 312 // Utility function that wraps a C++ http request object in a 313 // JavaScript object. 314 Handle<Object> JsHttpRequestProcessor::WrapMap(map<string, string>* obj) { 315 // Handle scope for temporary handles. 316 EscapableHandleScope handle_scope(GetIsolate()); 317 318 // Fetch the template for creating JavaScript map wrappers. 319 // It only has to be created once, which we do on demand. 320 if (map_template_.IsEmpty()) { 321 Handle<ObjectTemplate> raw_template = MakeMapTemplate(GetIsolate()); 322 map_template_.Reset(GetIsolate(), raw_template); 323 } 324 Handle<ObjectTemplate> templ = 325 Local<ObjectTemplate>::New(GetIsolate(), map_template_); 326 327 // Create an empty map wrapper. 328 Local<Object> result = templ->NewInstance(); 329 330 // Wrap the raw C++ pointer in an External so it can be referenced 331 // from within JavaScript. 332 Handle<External> map_ptr = External::New(GetIsolate(), obj); 333 334 // Store the map pointer in the JavaScript wrapper. 335 result->SetInternalField(0, map_ptr); 336 337 // Return the result through the current handle scope. Since each 338 // of these handles will go away when the handle scope is deleted 339 // we need to call Close to let one, the result, escape into the 340 // outer handle scope. 341 return handle_scope.Escape(result); 342 } 343 344 345 // Utility function that extracts the C++ map pointer from a wrapper 346 // object. 347 map<string, string>* JsHttpRequestProcessor::UnwrapMap(Handle<Object> obj) { 348 Handle<External> field = Handle<External>::Cast(obj->GetInternalField(0)); 349 void* ptr = field->Value(); 350 return static_cast<map<string, string>*>(ptr); 351 } 352 353 354 // Convert a JavaScript string to a std::string. To not bother too 355 // much with string encodings we just use ascii. 356 string ObjectToString(Local<Value> value) { 357 String::Utf8Value utf8_value(value); 358 return string(*utf8_value); 359 } 360 361 362 void JsHttpRequestProcessor::MapGet(Local<String> name, 363 const PropertyCallbackInfo<Value>& info) { 364 // Fetch the map wrapped by this object. 365 map<string, string>* obj = UnwrapMap(info.Holder()); 366 367 // Convert the JavaScript string to a std::string. 368 string key = ObjectToString(name); 369 370 // Look up the value if it exists using the standard STL ideom. 371 map<string, string>::iterator iter = obj->find(key); 372 373 // If the key is not present return an empty handle as signal 374 if (iter == obj->end()) return; 375 376 // Otherwise fetch the value and wrap it in a JavaScript string 377 const string& value = (*iter).second; 378 info.GetReturnValue().Set(String::NewFromUtf8( 379 info.GetIsolate(), value.c_str(), String::kNormalString, 380 static_cast<int>(value.length()))); 381 } 382 383 384 void JsHttpRequestProcessor::MapSet(Local<String> name, 385 Local<Value> value_obj, 386 const PropertyCallbackInfo<Value>& info) { 387 // Fetch the map wrapped by this object. 388 map<string, string>* obj = UnwrapMap(info.Holder()); 389 390 // Convert the key and value to std::strings. 391 string key = ObjectToString(name); 392 string value = ObjectToString(value_obj); 393 394 // Update the map. 395 (*obj)[key] = value; 396 397 // Return the value; any non-empty handle will work. 398 info.GetReturnValue().Set(value_obj); 399 } 400 401 402 Handle<ObjectTemplate> JsHttpRequestProcessor::MakeMapTemplate( 403 Isolate* isolate) { 404 EscapableHandleScope handle_scope(isolate); 405 406 Local<ObjectTemplate> result = ObjectTemplate::New(isolate); 407 result->SetInternalFieldCount(1); 408 result->SetNamedPropertyHandler(MapGet, MapSet); 409 410 // Again, return the result through the current handle scope. 411 return handle_scope.Escape(result); 412 } 413 414 415 // ------------------------------------------- 416 // --- A c c e s s i n g R e q u e s t s --- 417 // ------------------------------------------- 418 419 /** 420 * Utility function that wraps a C++ http request object in a 421 * JavaScript object. 422 */ 423 Handle<Object> JsHttpRequestProcessor::WrapRequest(HttpRequest* request) { 424 // Handle scope for temporary handles. 425 EscapableHandleScope handle_scope(GetIsolate()); 426 427 // Fetch the template for creating JavaScript http request wrappers. 428 // It only has to be created once, which we do on demand. 429 if (request_template_.IsEmpty()) { 430 Handle<ObjectTemplate> raw_template = MakeRequestTemplate(GetIsolate()); 431 request_template_.Reset(GetIsolate(), raw_template); 432 } 433 Handle<ObjectTemplate> templ = 434 Local<ObjectTemplate>::New(GetIsolate(), request_template_); 435 436 // Create an empty http request wrapper. 437 Local<Object> result = templ->NewInstance(); 438 439 // Wrap the raw C++ pointer in an External so it can be referenced 440 // from within JavaScript. 441 Handle<External> request_ptr = External::New(GetIsolate(), request); 442 443 // Store the request pointer in the JavaScript wrapper. 444 result->SetInternalField(0, request_ptr); 445 446 // Return the result through the current handle scope. Since each 447 // of these handles will go away when the handle scope is deleted 448 // we need to call Close to let one, the result, escape into the 449 // outer handle scope. 450 return handle_scope.Escape(result); 451 } 452 453 454 /** 455 * Utility function that extracts the C++ http request object from a 456 * wrapper object. 457 */ 458 HttpRequest* JsHttpRequestProcessor::UnwrapRequest(Handle<Object> obj) { 459 Handle<External> field = Handle<External>::Cast(obj->GetInternalField(0)); 460 void* ptr = field->Value(); 461 return static_cast<HttpRequest*>(ptr); 462 } 463 464 465 void JsHttpRequestProcessor::GetPath(Local<String> name, 466 const PropertyCallbackInfo<Value>& info) { 467 // Extract the C++ request object from the JavaScript wrapper. 468 HttpRequest* request = UnwrapRequest(info.Holder()); 469 470 // Fetch the path. 471 const string& path = request->Path(); 472 473 // Wrap the result in a JavaScript string and return it. 474 info.GetReturnValue().Set(String::NewFromUtf8( 475 info.GetIsolate(), path.c_str(), String::kNormalString, 476 static_cast<int>(path.length()))); 477 } 478 479 480 void JsHttpRequestProcessor::GetReferrer( 481 Local<String> name, 482 const PropertyCallbackInfo<Value>& info) { 483 HttpRequest* request = UnwrapRequest(info.Holder()); 484 const string& path = request->Referrer(); 485 info.GetReturnValue().Set(String::NewFromUtf8( 486 info.GetIsolate(), path.c_str(), String::kNormalString, 487 static_cast<int>(path.length()))); 488 } 489 490 491 void JsHttpRequestProcessor::GetHost(Local<String> name, 492 const PropertyCallbackInfo<Value>& info) { 493 HttpRequest* request = UnwrapRequest(info.Holder()); 494 const string& path = request->Host(); 495 info.GetReturnValue().Set(String::NewFromUtf8( 496 info.GetIsolate(), path.c_str(), String::kNormalString, 497 static_cast<int>(path.length()))); 498 } 499 500 501 void JsHttpRequestProcessor::GetUserAgent( 502 Local<String> name, 503 const PropertyCallbackInfo<Value>& info) { 504 HttpRequest* request = UnwrapRequest(info.Holder()); 505 const string& path = request->UserAgent(); 506 info.GetReturnValue().Set(String::NewFromUtf8( 507 info.GetIsolate(), path.c_str(), String::kNormalString, 508 static_cast<int>(path.length()))); 509 } 510 511 512 Handle<ObjectTemplate> JsHttpRequestProcessor::MakeRequestTemplate( 513 Isolate* isolate) { 514 EscapableHandleScope handle_scope(isolate); 515 516 Local<ObjectTemplate> result = ObjectTemplate::New(isolate); 517 result->SetInternalFieldCount(1); 518 519 // Add accessors for each of the fields of the request. 520 result->SetAccessor( 521 String::NewFromUtf8(isolate, "path", String::kInternalizedString), 522 GetPath); 523 result->SetAccessor( 524 String::NewFromUtf8(isolate, "referrer", String::kInternalizedString), 525 GetReferrer); 526 result->SetAccessor( 527 String::NewFromUtf8(isolate, "host", String::kInternalizedString), 528 GetHost); 529 result->SetAccessor( 530 String::NewFromUtf8(isolate, "userAgent", String::kInternalizedString), 531 GetUserAgent); 532 533 // Again, return the result through the current handle scope. 534 return handle_scope.Escape(result); 535 } 536 537 538 // --- Test --- 539 540 541 void HttpRequestProcessor::Log(const char* event) { 542 printf("Logged: %s\n", event); 543 } 544 545 546 /** 547 * A simplified http request. 548 */ 549 class StringHttpRequest : public HttpRequest { 550 public: 551 StringHttpRequest(const string& path, 552 const string& referrer, 553 const string& host, 554 const string& user_agent); 555 virtual const string& Path() { return path_; } 556 virtual const string& Referrer() { return referrer_; } 557 virtual const string& Host() { return host_; } 558 virtual const string& UserAgent() { return user_agent_; } 559 private: 560 string path_; 561 string referrer_; 562 string host_; 563 string user_agent_; 564 }; 565 566 567 StringHttpRequest::StringHttpRequest(const string& path, 568 const string& referrer, 569 const string& host, 570 const string& user_agent) 571 : path_(path), 572 referrer_(referrer), 573 host_(host), 574 user_agent_(user_agent) { } 575 576 577 void ParseOptions(int argc, 578 char* argv[], 579 map<string, string>* options, 580 string* file) { 581 for (int i = 1; i < argc; i++) { 582 string arg = argv[i]; 583 size_t index = arg.find('=', 0); 584 if (index == string::npos) { 585 *file = arg; 586 } else { 587 string key = arg.substr(0, index); 588 string value = arg.substr(index+1); 589 (*options)[key] = value; 590 } 591 } 592 } 593 594 595 // Reads a file into a v8 string. 596 Handle<String> ReadFile(Isolate* isolate, const string& name) { 597 FILE* file = fopen(name.c_str(), "rb"); 598 if (file == NULL) return Handle<String>(); 599 600 fseek(file, 0, SEEK_END); 601 int size = ftell(file); 602 rewind(file); 603 604 char* chars = new char[size + 1]; 605 chars[size] = '\0'; 606 for (int i = 0; i < size;) { 607 int read = static_cast<int>(fread(&chars[i], 1, size - i, file)); 608 i += read; 609 } 610 fclose(file); 611 Handle<String> result = 612 String::NewFromUtf8(isolate, chars, String::kNormalString, size); 613 delete[] chars; 614 return result; 615 } 616 617 618 const int kSampleSize = 6; 619 StringHttpRequest kSampleRequests[kSampleSize] = { 620 StringHttpRequest("/process.cc", "localhost", "google.com", "firefox"), 621 StringHttpRequest("/", "localhost", "google.net", "firefox"), 622 StringHttpRequest("/", "localhost", "google.org", "safari"), 623 StringHttpRequest("/", "localhost", "yahoo.com", "ie"), 624 StringHttpRequest("/", "localhost", "yahoo.com", "safari"), 625 StringHttpRequest("/", "localhost", "yahoo.com", "firefox") 626 }; 627 628 629 bool ProcessEntries(HttpRequestProcessor* processor, int count, 630 StringHttpRequest* reqs) { 631 for (int i = 0; i < count; i++) { 632 if (!processor->Process(&reqs[i])) 633 return false; 634 } 635 return true; 636 } 637 638 639 void PrintMap(map<string, string>* m) { 640 for (map<string, string>::iterator i = m->begin(); i != m->end(); i++) { 641 pair<string, string> entry = *i; 642 printf("%s: %s\n", entry.first.c_str(), entry.second.c_str()); 643 } 644 } 645 646 647 int main(int argc, char* argv[]) { 648 v8::V8::InitializeICU(); 649 v8::Platform* platform = v8::platform::CreateDefaultPlatform(); 650 v8::V8::InitializePlatform(platform); 651 v8::V8::Initialize(); 652 map<string, string> options; 653 string file; 654 ParseOptions(argc, argv, &options, &file); 655 if (file.empty()) { 656 fprintf(stderr, "No script was specified.\n"); 657 return 1; 658 } 659 Isolate* isolate = Isolate::New(); 660 Isolate::Scope isolate_scope(isolate); 661 HandleScope scope(isolate); 662 Handle<String> source = ReadFile(isolate, file); 663 if (source.IsEmpty()) { 664 fprintf(stderr, "Error reading '%s'.\n", file.c_str()); 665 return 1; 666 } 667 JsHttpRequestProcessor processor(isolate, source); 668 map<string, string> output; 669 if (!processor.Initialize(&options, &output)) { 670 fprintf(stderr, "Error initializing processor.\n"); 671 return 1; 672 } 673 if (!ProcessEntries(&processor, kSampleSize, kSampleRequests)) 674 return 1; 675 PrintMap(&output); 676 } 677