2010-01-05

2 servlets para correr javascript

Estuve con algo de tiempo y me puse a probar como hacer para ejecutar JavaScript en el server. E hice 2 implementaciones (o sea, 2 servlets programados en Java). La primera que hice usa javax.script (que viene por defecto en Java 6) y me parece que el API es más cómoda pero tiene 2 limitaciones que no me gustaron, no se puede heredar de clases Java en JavaScript y no encontré cómo hacer para bindear un método de Java a JavaScript. Este es el código:

package aure;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;

import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class JsServlet extends HttpServlet {
private static final long serialVersionUID = 1L;

private ScriptEngine jsEng = new ScriptEngineManager()
.getEngineByName("js");
private Bindings initBindings = this.jsEng
.getBindings(ScriptContext.ENGINE_SCOPE);

public JsServlet() {
super();
}

@Override
public void init(ServletConfig config) throws ServletException {
try {
super.init(config);

this.initBindings.put("config", config);
this.initBindings.put("servlet", this);

this.runJs("init.js", this.initBindings);
} catch (ScriptException e) {
throw new ServletException("Error running init.js", e);
}
}

public void runJs(String scriptLocation, Bindings bindings)
throws ScriptException {
Reader jsReader = new InputStreamReader(this.getServletContext()
.getResourceAsStream(scriptLocation));

this.jsEng.eval(jsReader, bindings);
}

protected void service(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {

try {
Bindings bindings = jsEng.createBindings();
bindings.putAll(this.initBindings);
bindings.put("request", request);
bindings.put("response", response);

this.runJs("start.js", bindings);
} catch (ScriptException e) {
throw new ServletException("Error running javascript", e);
}
}

}

La segunda usa rhino bajado desde mozilla y por lo tanto no tiene esas limitaciones, pero el API para embeber JavaScript en Java es más fea. Acá está el código de la segunda versión:
package aure;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;

public class RhinoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private Scriptable initialScope = null;

public void runJs(Context cx, String scriptLocation, Scriptable scope) throws IOException {
Reader jsReader = new InputStreamReader(this.getServletContext()
.getResourceAsStream("/WEB-INF/js" + scriptLocation));

cx.evaluateReader(scope, jsReader, scriptLocation, 1, null);
}

public static void addJavaObjectToScope(Scriptable scope, String name, Object obj) {
ScriptableObject.putProperty(scope, name, Context.javaToJS(obj, scope) );
}

@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
Context cx = Context.enter();
try {
Scriptable scope = cx.initStandardObjects();
addJavaObjectToScope(scope, "servlet", this);
addJavaObjectToScope(scope, "config", config);

this.runJs(cx, "/init.js", scope);

this.initialScope = scope;
} catch (IOException e) {
throw new ServletException(e);
} finally {
Context.exit();
}
}

@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Context cx = Context.enter();
try {
Scriptable scope = cx.initStandardObjects();
scope.setParentScope(this.initialScope);

addJavaObjectToScope(scope, "request", request);
addJavaObjectToScope(scope, "response", response);

this.runJs(cx, "/start.js", scope);
} finally {
Context.exit();
}
}
}

Ambas versiones ejecutan WEB-INF/js/init.js cuando se carga el servlet (tiene bindeados el servlet y la configuración del servlet) y WEB-INF/js/start.js cuando se atiende un pedido (agrega a los bindings lo que se haya puesto en init.js, el servlet, la configuración, el request y el response.

La idea de todo esto es ponerme las pilas e implementar las cosas que dije en este post hace casi 3 años, ya que ahora parece que se está poniendo de moda usar JavaScript.

Espero que les parezca interesante, me voy a dormir.

Happy hacking,
Aureliano.

2 comentarios:

thermo dijo...

si el server tiene un bug podes inyectar JS en el mismo?

aurelianito dijo...

Sí.