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 39using namespace std; 40using 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 */ 50class 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 */ 63class 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 */ 82class 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 148static 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. 158bool 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 210bool 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 239bool 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 261bool 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 295JsHttpRequestProcessor::~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 304Persistent<ObjectTemplate> JsHttpRequestProcessor::request_template_; 305Persistent<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. 314Handle<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. 347map<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. 356string ObjectToString(Local<Value> value) { 357 String::Utf8Value utf8_value(value); 358 return string(*utf8_value); 359} 360 361 362void 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 384void 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 402Handle<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 */ 423Handle<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 */ 458HttpRequest* 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 465void 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 480void 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 491void 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 501void 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 512Handle<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 541void HttpRequestProcessor::Log(const char* event) { 542 printf("Logged: %s\n", event); 543} 544 545 546/** 547 * A simplified http request. 548 */ 549class 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 567StringHttpRequest::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 577void 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. 596Handle<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 618const int kSampleSize = 6; 619StringHttpRequest 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 629bool 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 639void 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 647int 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