MongoDB源码分析——mongo与JavaScript交互

时间:2024-05-02 23:07:49

mongo与JavaScript交互

源码版本为MongoDB 2.6分支
 
 

  之前已经说过mongo是MongoDB提供的一个执行JavaScript脚本的客户端工具,执行js其实就是一个js和c++互相调用的过程,当然,因为mongo采用了Google V8 JS引擎,所以调用的实现的核心都由V8实现了,本篇只是分析了mongo是如何使用V8实现功能,对V8的源码不做分析。

  为了了解mongo是如何使用js,我们首先从connect说起: 
    mongo::ScriptEngine::setConnectCallback( mongo:: shell_utils:: onConnect );
    mongo::ScriptEngine::setup();
    mongo::globalScriptEngine-> setScopeInitCallback( mongo:: shell_utils:: initScope );
    auto_ptr< mongo::Scope > scope( mongo:: globalScriptEngine-> newScope() );   
     之前已经分析过,newScope后会回调initScope函数,下面来看一下initScope的实现:
        void initScope( Scope &scope ) {
            // Need to define this method before JSFiles::utils is executed.
            scope. injectNative( "_useWriteCommandsDefault", useWriteCommandsDefault);
            scope. injectNative( "_writeMode", writeMode);
            scope. externalSetup();
            mongo:: shell_utils:: installShellUtils( scope );
            scope. execSetup( JSFiles:: servers);
            scope. execSetup( JSFiles:: shardingtest);
            scope. execSetup( JSFiles:: servers_misc);
            scope. execSetup( JSFiles:: replsettest);
            scope. execSetup( JSFiles:: replsetbridge);
            scope. installBenchRun();
            if ( ! _dbConnect. empty() ) {
                uassert( 12513, "connect failed", scope. exec( _dbConnect , "(connect)" , false , true , false ) );
            }
            if ( ! _dbAuth. empty() ) {
                uassert( 12514, "login failed", scope. exec( _dbAuth , "(auth)" , true , true , false ) );
            }
        }
     在解释上面的代码之前,需要先对V8的一些概念有个大概了解,建议先看一下这篇文章(http://blog.****.net/f6991/article/details/9292033),现在来分析一个比较重要的函数,V8要执行js函数首先得找到函数的定义,这个过程就是使用 scope. execSetup( JSFiles:: servers); 来实现的。
     //每个JSFile对象代表了一个JS文件。
    struct JSFile {
        const char* name;          //js文件
        const StringData& source;  //js内容
    };
     在execSetup函数内部会调用下面这个函数,对JSFile对应的js文件进行编译,将对应的JS内容记录下来,之后只要执行这个JS文件里面的函数时V8就能找到对应的函数了。
     //code对应JSFile:: source
     v8::Handle<v8::Script> script =
                v8:: Script:: Compile( v8:: String:: New( code. rawData(), code. size()),
                                    v8:: String:: New( name. c_str(), name. length()));
     现在已经知道V8是如何找到JS函数了,那connect函数的实现又是在什么地方呢?
     initScope( Scope & scope ) -> scope. externalSetup() -> execCoreFiles() -> execSetup(JSFiles ::mongo )
     然后查看了mongo的定义:
     const JSFile mongo = { "shell/mongo.js" , _jscode_raw_mongo };
     在shell/mongo.js文件中搜索“connect = function”就能够找到connect函数,所以initScope函数中的scope. exec( _dbConnect , "(connect)" , false , true , false )执行的js connect函数的定义就在这个地方。
     if (typeof Mongo == "undefined"){
          Mongo = function( host){
                 this. init( host);
          }
     }
     connect = function( url, user, pass) {
           ...
           chatty("connecting to: " + url)
               var db;
           if (slash == -1)
                 db = new Mongo(). getDB( url);
           else
                 db = new Mongo( url.substring(0, slash)). getDB( url.substring( slash + 1));
           print("Called: new Mongo");
           if (user && pass) {
                 if (! db. auth( user, pass)) {
                      throw Error( "couldn't login");
              }
          }
          return db;
     }
     在mongo.js中首先定义了Mongo类,在connect中调用new Mongo()生成对象,那么在new Mongo()过程中又发生了什么呢?然我们继续回到initScope( Scope & scope ) -> scope. externalSetup()函数:
    void V8Scope::externalSetup() {
        ...
        // install the Mongo function object
        _MongoFT = FTPtr::New(getMongoFunctionTemplate( this, false));
        injectV8Function("Mongo", MongoFT(), _global);
        ...
    }
     注释中已经指出这里是初始化Mongo函数对象,没错,这个Mongo function object和mongo.js里面的Mongo对象就是同一个东西,我们来看一下V8是怎么将这两个东西“映射”起来的。
    v8::Handle<v8::FunctionTemplate> getMongoFunctionTemplate(V8Scope * scope , bool local ) {
        v8::Handle<v8::FunctionTemplate> mongo;
        if (local)
            mongo = scope-> createV8Function( mongoConsLocal);
        else
            mongo = scope-> createV8Function( mongoConsExternal);
        mongo->InstanceTemplate()-> SetInternalFieldCount(1);
        v8::Handle<v8::ObjectTemplate> proto = mongo-> PrototypeTemplate();
        scope->injectV8Method("find", mongoFind, proto);
        scope->injectV8Method("insert", mongoInsert, proto);
        scope->injectV8Method("remove", mongoRemove, proto);
        scope->injectV8Method("update", mongoUpdate, proto);
        scope->injectV8Method("auth", mongoAuth, proto);
        scope->injectV8Method("logout", mongoLogout, proto);
        scope->injectV8Method("cursorFromId", mongoCursorFromId, proto);
        fassert(16468, _mongoPrototypeManipulatorsFrozen);
        for (size_t i = 0; i < _mongoPrototypeManipulators.size (); ++i )
            _mongoPrototypeManipulators[i](scope , mongo );
        return mongo;
    }
     首先,mongo = scope-> createV8Function( mongoConsExternal);创建mongo函数模版,这个地方注意“mongoConsExternal”参数,稍后会说明用处。创建mongo模版之后就是很多的scope-> injectV8Method( "find", mongoFind, proto); 这个地方关于函数模版和对象模版的相关东西大家可以看上面的链接。injectV8Method的作用就是将find函数和mongoFind函数“映射”,然后将find函数绑定到mongo函数模版上,实现的效果就是在js中调用Mongo对象的find函数时其实调用的是mongoFind函数。
     上面的getMongoFunctionTemplate 函数生成一个Mongo函数模版,保存在_MongoFT中,调用MongoFT可获取该模版。
     v8:: Handle< v8:: FunctionTemplate> MongoFT() const { return _MongoFT; }
     下面再看一个非常重要的函数调用:
     injectV8Function( "Mongo", MongoFT(), _global);
     这个函数的作用和injectV8Method差不多,也就是说在js中new Mongo()的时候返回的就是上面的mongo模版,同时会回调“mongoConsExternal”函数,在该函数中连接到数据库,所以在new Mongo()过程中其实已经完成了服务端数据库连接。
    v8::Handle<v8::Value> mongoConsExternal(V8Scope* scope, const v8:: Arguments& args) {
            cout << "Call : v8::Handle<v8::Value> mongoConsExternal" << endl;
        char host[255];
        if (args.Length() > 0 && args[0]-> IsString()) {
            uassert(16666, "string argument too long", args[0]-> ToString()-> Utf8Length() < 250);
            args[0]-> ToString()-> WriteAscii( host);
        }
        else {
            strcpy( host, "127.0.0.1");
        }
        // only allow function template to be used by a constructor
        uassert(16859, "Mongo function is only usable as a constructor",
                args. IsConstructCall());
        verify(scope->MongoFT()->HasInstance(args.This()));
        string errmsg;
        ConnectionString cs = ConnectionString::parse(host, errmsg);
        if (!cs.isValid()) {
            return v8AssertionException( errmsg);
        }
        DBClientBase* conn;
        conn = cs.connect(errmsg);
        if (!conn) {
            return v8AssertionException( errmsg);
        }
        v8::Persistent<v8::Object> self = v8:: Persistent< v8:: Object>:: New( args. This());
        v8::Local<v8::External> connHandle = scope-> dbClientBaseTracker. track( self, conn);
        ScriptEngine::runConnectCallback(*conn);
        args.This()->SetInternalField(0, connHandle);
        args.This()->ForceSet(scope->v8StringData("slaveOk"), v8:: Boolean:: New( false));
        args.This()->ForceSet(scope->v8StringData("host"), scope-> v8StringData( host));
        return v8::Undefined();
    }
     下面我们继续回到js connect函数:
     connect = function( url, user, pass) {
           ...
           chatty( "connecting to: " + url)
               var db;
           if ( slash == -1)
                 db = new Mongo(). getDB( url);
           else
                 db = new Mongo( url.substring(0, slash)). getDB( url.substring( slash + 1));
           print( "Called: new Mongo");
           if ( user && pass) {
                 if (! db. auth( user, pass)) {
                      throw Error( "couldn't login");
              }
          }
           return db;
     }
     在connect中会注意到一个认证过程(db. auth( user, pass))。 db是Mongo对象,那个在Mongo的函数模版创建过程中我们把auth函数映射到mongoAuth函数,那么此时我们调用的就是mongoAuth。
     至此,mongo和JavaScript的交互流程已经可以看出来了,之后的DB、 DBQuery、 DBCollection对象的映射都在installDBAccess函数中,感兴趣的可以自己去看一下。