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