Skip to content
This repository has been archived by the owner on Dec 10, 2018. It is now read-only.

Architecture

Ryan Chan edited this page Jun 17, 2013 · 14 revisions

Back-end

The mongo web service files are contained within the mongows/ directory. The Flask application is set up within mongows/app.py. Initial configurations for the server may be found in mongows/configs/. The project makes use of Flask Blueprints, which are essentially standalone modular Flask applications that get registered to a given route. In this case, the mongows/mws/ directory contains the mongo web shell app which gets mounted to the route http://host:port/mws/.

Demo Web Server

The mongo web service, backed by a mongod instance, is intended to be standalone from the web server serving static content. To demonstrate the service, a standalone demo web server was created. However, in order to deploy to the Heroku, which requires the application listen on one port, the demo web server needed to move into the mongo web service. As such, there are two methods to run the web service and demo web server:

Blueprint

The mongows/demo/ directory contains a Blueprint mounted at the route, /. Setting the NO_FRONTEND environment variable will prevent this Blueprint from being registered, allowing one to use the web service alone without the demo Blueprint.

Standalone

A standalone server is located in the standalone_sample/ directory.

Note, however, that this server has not been well tested since the architecture change. Communicating between two servers requires cross-domain requests so the CORS code, located in mongows/mws/views.py, may have bit-rotted. Additionally, the standalone server's template file has not changed, though the frontend configuration may have.

Front-end

The front-end code is contained within the frontend/ directory. The functionality should be entirely contained within the mongo global variable. When the script is included in a page, it dynamically injects its stylesheet and embeds a mongo shell in any div that contains the CSS class, mongo-web-shell. This functionality can be found in the mongo.init function.

User Input

When user input is received, mongo.Shell.handleInput is called. This method will take the input and run it through eval. However, in order to make the various mongo calls, which are not always compliant JavaScript, the input needs to be replaced - this functionality occurs in the same method, before eval. Since eval runs in the global context, a reference to the shell the input was taken from cannot be passed into the mutated input so global references, located in the mongo.shells array, are passed instead.

First when handling user input, statements beginning with mongo specific keywords are swapped out in mongo.mutateSource.swapKeywords with statements of the form mongo.keyword.evaluate(keyword [, args...]).

Next, esprima.js, a JavaScript parser that returns an AST conforming to the Mozilla SpiderMonkey Parser API, and node-falafel, a wrapper around the esprima AST that makes the AST traversal simpler, are used to replace identifiers, which would otherwise be attached to the global object, to shell-local variables of the form, mongo.shells[shellID].vars.varName. Additionally, identifiers of the form, db.collectionName, are replaced with new mongo.Query(mongo.shells[shellID], collectionName. For more information on the mongo.Query object, see the "Life of a Query" section.

Lastly, the mutated source is passed into eval (with some caveats - see the source code for details).

The specifics of each mutation is documented as comments in the code.

Note that esprima cannot parse invalid JavaScript, which is why keyword mutation must occur first. For an example of esprima's output, see the Online Parsing demo.

Life of a Query

A db.collection call begins by replacing the db.collection reference with a mongo.Query instance. This object contains methods for any implemented db.collection methods. For example, db.collection.find(...) becomes mongo.Query.find(...). When evaluated, some queries, such as insert(), will make a request to the server immediately.

However, some queries return a mongo.Cursor object, which gets evaluated lazily due to methods that can modify the result set output (such as sort()). Like mongo.Query, any implemented Cursor methods can be called on this object directly. Private members are (attempted to be) hidden by prepending their name with an underscore. Cursor query execution should mimic that of the desktop mongo shell, which is currently if:

  • An unexecuted Cursor is the final return value of a statement
  • next() or hasNext() is called on an unexecuted Cursor

Dependencies

The frontend code relies on jQuery (tested with v1.9.1) so it must served alongside the mongo web shell script.

node-falafel is originally written for node.js. However, browserify can be used to run node code in the browser. The browserified version of falafel is currently being served as a submodule from @mcomella's fork.

If the entire frontend architecture was moved to browserify (see #104), the external dependency would no longer be necessary as all of the code could be bundled at once into a single entry point.

Also, since falafel is browserify's single entry point, esprima is both bundled with falafel and directly included in the page (via <script>) for mutating the source and re-parsing the mutated source respectively. Serving the same code twice kind of blows. :(

Misc

  • Methods that are exported exclusively for testing are prepended with an underscore when exported.