-
Notifications
You must be signed in to change notification settings - Fork 26
Architecture
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/
.
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:
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.
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.
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.
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.
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()
orhasNext()
is called on an unexecutedCursor
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. :(
- Methods that are exported exclusively for testing are prepended with an underscore when exported.