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