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