• 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 #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:
~HttpRequest()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:
~HttpRequestProcessor()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.
JsHttpRequestProcessor(Handle<String> script)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 
LogCallback(const Arguments & args)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.
Initialize(map<string,string> * opts,map<string,string> * output)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 
ExecuteScript(Handle<String> script)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 
InstallMaps(map<string,string> * opts,map<string,string> * output)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 
Process(HttpRequest * request)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 
~JsHttpRequestProcessor()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.
WrapMap(map<string,string> * obj)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.
UnwrapMap(Handle<Object> obj)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.
ObjectToString(Local<Value> value)332 string ObjectToString(Local<Value> value) {
333   String::Utf8Value utf8_value(value);
334   return string(*utf8_value);
335 }
336 
337 
MapGet(Local<String> name,const AccessorInfo & info)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 
MapSet(Local<String> name,Local<Value> value_obj,const AccessorInfo & info)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 
MakeMapTemplate()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  */
WrapRequest(HttpRequest * request)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  */
UnwrapRequest(Handle<Object> obj)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 
GetPath(Local<String> name,const AccessorInfo & info)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 
GetReferrer(Local<String> name,const AccessorInfo & info)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 
GetHost(Local<String> name,const AccessorInfo & info)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 
GetUserAgent(Local<String> name,const AccessorInfo & info)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 
MakeRequestTemplate()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 
Log(const char * event)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);
Path()508   virtual const string& Path() { return path_; }
Referrer()509   virtual const string& Referrer() { return referrer_; }
Host()510   virtual const string& Host() { return host_; }
UserAgent()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 
StringHttpRequest(const string & path,const string & referrer,const string & host,const string & user_agent)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 
ParseOptions(int argc,char * argv[],map<string,string> & options,string * file)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.
ReadFile(const string & name)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 
ProcessEntries(HttpRequestProcessor * processor,int count,StringHttpRequest * reqs)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 
PrintMap(map<string,string> * m)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 
main(int argc,char * argv[])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