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