
Prehistory
I would like to start with the background. At the moment I am developing some kind of web application in Java, nothing unusual, but the document from the customer has a requirement: future application administrators should be able to fly the business logic code on the server on the fly. It seems to be nothing supernatural, it will be necessary to do loading of java-classes, I thought, until the other day I had an idea: “What if I can program business logic methods in JavaScript?”.
At that moment, the idea seemed very good to me, and I saw a number of advantages of this idea over the simple loading of java-classes:
- First of all, JavaScript is a very simple language for describing logic; any programmer who is familiar with the principles of OOP and C-like syntax can write on it.
- Secondly, because The external server API is designed in the REST style, the js code fits perfectly within the resource framework, it is serialized without any problems into a JSON string and does not require compilation and additional manipulations.
- Thirdly, the execution of JavaScript-code by the interpreter is the execution of code within the security sandbox, which gives us the opportunity to clearly adjust the rules of behavior of the code of business logic.
But do not forget that big power is a big responsibility, therefore, following new opportunities, new questions appear that require a detailed answer.
In this article I would like to briefly describe the idea of ​​describing business logic in pure js, touch on the theoretical and practical parts, and also describe some nuances that may arise after this decision.
')
Choosing a JavaScript interpreter
For the engine that will not be interpreted for js, I didn’t have to go far; the first candidate is the Mozilla Rhino - js engine written entirely in Java.
The engine originates from 1997, born in the walls of Netscape under the name “Javagator”, but later, under mysterious circumstances, was renamed Rhino. Rhino was licensed by some large companies (including Sun) for their projects. Initially, the idea was to compile Java byte code based on JavaScript, but this approach had some problems:
- First, the process of compiling and loading classes was a bit heavy,
- Secondly, memory leaks sometimes happened.
In 1998, the default interpreter mode was added to the engine, i.e. JavaScript code is executed on the fly, bypassing the compilation in the byte code. This avoids problems with heavy compilation and memory leaks. Later this year, the engine opens the source code and is given to mozilla.org.
Download the library binaries and source code can be on of.sayte:
link to the site . In the examples, rhino1.7 R4 will be used.
Rhino can be used in two ways: from the command line and directly by embedding a jar file in the project. We are now interested in the second method - we add the js.jar file to the project.
LiveConnect technology
One of the main ideas of Rhino is the ability to program a Java server using JavaScript code using LiveConnect technology. The technology makes it possible to access Java classes directly from js, without resorting to the help of some third-party code.
Here is a small example of accessing the
File
and
System
classes:
var f = new java.io.File(“test.txt”); java.lang.System.out.println(f.exists());
LiveConnect gives us a lot of space for action, a lot is written about it in the office. documentation engine, but I will not now dwell on it, because We are interested in interpreting code from a non-trusted external source that should not have access to Java classes.
Using Rhino
As an example, we will develop a simple business logic module for the hypothetical project “Student Accounting and Assessment Services” for the university. The application should keep a list of all students and their assessments, as well as be able to perform some actions on them, based on the tasks.
Let's start with the basics of use. Rhino has 2 basic concepts - it is a context (
org.mozilla.javascript.Context
class) and a sphere (
org.mozilla.javascript.Scriptable
class). Context is an interpreter instance that binds to a single stream, therefore, interprets js in a single stream. A sphere is a so-called namespace in which we define all the variables of interest.
Example of creating context and scope:
After we have created the context and scope, we need to restrict the interpreter access to Java classes. This is done using the
setClassShutter
method of the
setClassShutter
instance:
By default, Rhino uses LiveConnect technology, which gives access to java-classes directly from js. It gives a great opportunity to the trusted code, but we have another case - our server will interpret the potentially unsafe code.
It will be very unpleasant if the js-code of the following type gets into the interpreter:
java.lang.System.exit(0);
Therefore, we simply “shut up” LiveConnect and leave access only to those classes that we need. After we got the context and the scope, we have no choice but to interpret the js code:
String script = “var mathStuff = Math.cos(Math.PI)”; c.evaluateString(scope, script, null, 1, null);
That's all, after working with Rhino, we finish the work with the context and release the resources:
Context.exit();
“Sandbox” for business logic
Now that we know how to start working with Rhino, we can proceed to defining the external business logic API in the form of several constant references to top-level modules:
- DatabaseModule , which will be responsible for communication with the database,
- NotificationModule , which will be responsible for notifying system users about any events.
public class DatabaseModule { public DatabaseModule(){ } public String getStudent(int id){ return (id > 0) ? " " : null; } public int getRating(String student){ return student.equals(" ") ? 5 : -1; } public void setRating(String student, int newRating){ System.out.println("setRating() student = "+student+", newRating = "+newRating);
public class NotificationModule { public NotificationModule(){ } public void notifyStudent(String student, String message){ System.out.println("notifyStudent() student = "+student+", message = "+message); } public void notifyCurator(String message){ System.out.println("notifyCurator() message = "+message); } }
Next, we define constant links to modules in a previously defined area:
Programming business logic in javascript
Suppose that we faced a task: to select information about a student by his ID from the database, to get his grade, to calculate whether he has enough points to get admission to the exam and notify the curator and the student himself. In this case, the entire task will fall on the following js-code:
var student = databaseModule.getStudent(1); var rating = databaseModule.getRating(student); var pass = rating >= 40; if(pass){ notificationModule.notifyCurator("Student "+student+" is admitted to the exam."); notificationModule.notifyStudent(student, "You admitted to the exam."); } else { var dif = 40 - rating; notificationModule.notifyCurator("Student "+student+" needs "+dif+" points to be admitted to the exam."); notificationModule.notifyStudent(student, "You need "+dif+" points to be admitted to the exam."); }
All that remains is to configure the interpreter and pass js to it. I'll warn you right away: the engine painfully perceives the Cyrillic alphabet.
Conclusion
In conclusion, I will say that the idea of ​​the possibility of programming business logic on js is quite interesting, although not new. This approach provides some flexibility and ease of implementation.
The programmer, who is faced with the task of adding a new method, may not think about what technology stack is used in the server part, but simply prescribes what is required to be done, easily expand and supplement the functionality.
Following the possibilities that this approach carries, there are a number of questions that need to be taken care of before being implemented in the combat server:
- Script security: does it have recursions and infinite loops, does it not heavily load memory,
- Script validation: is it written correctly, does it interact correctly with the server API,
- Server security: which modules the script has access to.
Fortunately, all issues are completely solvable, the Rhino source code is open for modification.
useful links
Of project website:
https://developer.mozilla.org/ru/docs/RhinoWikipedia article:
http://ru.wikipedia.org/wiki/RhinoOf documentation:
https://developer.mozilla.org/en-US/docs/Rhino_documentationAPI Reference (not office):
http://tool.oschina.net/uploads/apidocs/rhino/Sample sources on GitHub:
https://github.com/andrew-medvedev/rhino-example