Mantis Bugtracker
  

Viewing Issue Simple Details Jump to Notes ] View Advanced ] Issue History ] Print ]
ID Category Severity Reproducibility Date Submitted Last Update
0004080 [Hessian] major always 06-16-10 01:12 06-16-10 01:12
Reporter dilbert View Status public  
Assigned To
Priority normal Resolution open  
Status new   Product Version 4.0.6
Summary 0004080: SecurityException thrown when serializing Throwable on Google App Engine
Description When an exception is thrown on GAE (a servlet that extends HessianServlet) it is not forwarded to the client, instead a SecurityException is thrown. Here is an example. First the exception declaration:

public class TestException extends RuntimeException {
}

Next the service declaration:
public interface IService {
    void testException();
}

And finally the service implementation:
public class Service extends HessianServlet implements IService {
    public void testException() {
        throw new TestException();
    }
}
As You can see this is a trivial implementation to test the exception. When executed on Google servers it dies like this:

java.lang.SecurityException: java.lang.IllegalAccessException: Reflection is not allowed on private java.lang.Throwable java.lang.Throwable.cause
    at com.google.appengine.runtime.Request.process-9880ff155b30e983(Request.java)
    at java.lang.reflect.Field.setAccessible(Field.java:166)
    at com.caucho.hessian.io.JavaSerializer.introspect(JavaSerializer.java:122)
    at com.caucho.hessian.io.JavaSerializer.<init>(JavaSerializer.java:81)
    at com.caucho.hessian.io.ThrowableSerializer.<init> ThrowableSerializer.java:59)
    at com.caucho.hessian.io.SerializerFactory.loadSerializer(SerializerFactory.java:301)
    at com.caucho.hessian.io.SerializerFactory.getSerializer(SerializerFactory.java:224)
    at com.caucho.hessian.io.SerializerFactory.getObjectSerializer(SerializerFactory.java:197)
    at com.caucho.hessian.io.Hessian2Output.writeObject(Hessian2Output.java:418)
    at com.caucho.hessian.io.Hessian2Output.writeFault(Hessian2Output.java:400)
    at com.caucho.hessian.server.HessianSkeleton.invoke(HessianSkeleton.java:314)
    at com.caucho.hessian.server.HessianSkeleton.invoke(HessianSkeleton.java:202)
    at com.caucho.hessian.server.HessianServlet.invoke(HessianServlet.java:389)
    at com.caucho.hessian.server.HessianServlet.service(HessianServlet.java:369)
    at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1166)
    at com.google.apphosting.utils.servlet.ParseBlobUploadFilter.doFilter(ParseBlobUploadFilter.java:97)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
    at com.google.apphosting.runtime.jetty.SaveSessionFilter.doFilter(SaveSessionFilter.java:35)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
    at com.google.apphosting.utils.servlet.TransactionCleanupFilter.doFilter(TransactionCleanupFilter.java:43)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
    at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388)
    at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
    at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
    at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
    at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418)
    at com.google.apphosting.runtime.jetty.AppVersionHandlerMap.handle(AppVersionHandlerMap.java:238)
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
    at org.mortbay.jetty.Server.handle(Server.java:326)
    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
    at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:923)
    at com.google.apphosting.runtime.jetty.RpcRequestParser.parseAvailable(RpcRequestParser.java:76)
    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
    at com.google.apphosting.runtime.jetty.JettyServletEngineAdapter.serviceRequest(JettyServletEngineAdapter.java:135)
    at com.google.apphosting.runtime.JavaRuntime.handleRequest(JavaRuntime.java:250)
    at com.google.apphosting.base.RuntimePb$EvaluationRuntime$6.handleBlockingRequest(RuntimePb.java:5838)
    at com.google.apphosting.base.RuntimePb$EvaluationRuntime$6.handleBlockingRequest(RuntimePb.java:5836)
    at com.google.net.rpc.impl.BlockingApplicationHandler.handleRequest(BlockingApplicationHandler.java:24)
    at com.google.net.rpc.impl.RpcUtil.runRpcInApplication(RpcUtil.java:398)
    at com.google.net.rpc.impl.Server$2.run(Server.java:852)
    at com.google.tracing.LocalTraceSpanRunnable.run(LocalTraceSpanRunnable.java:56)
    at com.google.tracing.LocalTraceSpanBuilder.internalContinueSpan(LocalTraceSpanBuilder.java:576)
    at com.google.net.rpc.impl.Server.startRpc(Server.java:807)
    at com.google.net.rpc.impl.Server.processRequest(Server.java:369)
    at com.google.net.rpc.impl.ServerConnection.messageReceived(ServerConnection.java:442)
    at com.google.net.rpc.impl.RpcConnection.parseMessages(RpcConnection.java:319)
    at com.google.net.rpc.impl.RpcConnection.dataReceived(RpcConnection.java:290)
    at com.google.net.async.Connection.handleReadEvent(Connection.java:474)
    at com.google.net.async.EventDispatcher.processNetworkEvents(EventDispatcher.java:831)
    at com.google.net.async.EventDispatcher.internalLoop(EventDispatcher.java:207)
    at com.google.net.async.EventDispatcher.loop(EventDispatcher.java:103)
    at com.google.net.rpc.RpcService.runUntilServerShutdown(RpcService.java:251)
    at com.google.apphosting.runtime.JavaRuntime$RpcRunnable.run(JavaRuntime.java:413)
    at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.IllegalAccessException: Reflection is not allowed on private java.lang.Throwable java.lang.Throwable.cause
    ... 54 more

On the client I get something like this:
java.lang.reflect.UndeclaredThrowableException
    at $Proxy0.testException(Unknown Source)
    at com.noveideje.testHessian.client.Main.main(Main.java:37)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:616)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:110)
Caused by: java.io.EOFException: readObject: unexpected end of file
    at com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2133)
    at com.caucho.hessian.io.MapDeserializer.readMap(MapDeserializer.java:114)
    at com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:1653)
    at com.caucho.hessian.io.Hessian2Input.readReply(Hessian2Input.java:348)
    at com.caucho.hessian.client.HessianProxy.invoke(HessianProxy.java:194)
    ... 7 more

I have found a workaround by using a custom serializer. Here is how. First the Serializer:

public class ThrowableSerializer extends AbstractSerializer {
    @Override
    public void writeObject(Object obj, AbstractHessianOutput out) throws IOException {
        if (obj != null) {
            final Class cl = obj.getClass();
            if (out.addRef(obj))
                return;
            int ref = out.writeObjectBegin(cl.getName());
            Throwable tr = (Throwable) obj;
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            try {
                oos.writeObject(tr);

                if (ref < -1) {
                    out.writeString("value");
                    out.writeBytes(bos.toByteArray());
                    out.writeMapEnd();
                } else {
                    if (ref == -1) {
                        out.writeInt(1);
                        out.writeString("value");
                        out.writeObjectBegin(cl.getName());
                    }
                    out.writeBytes(bos.toByteArray());
                }
            } finally {
                oos.close();
                bos.close();
            }
        } else
            out.writeNull();
    }
}
 
The other class we need is the Deserializer:
public class ThrowableDeserializer extends AbstractDeserializer {

    @Override
    public Class getType() {
        return Throwable.class;
    }

    @Override
    public Object readMap(AbstractHessianInput in) throws IOException {
        int ref = in.addRef(null);
        byte[] initValue = null;
        while (!in.isEnd()) {
            String key = in.readString();

            if (key.equals("value"))
                initValue = in.readBytes();
            else
                in.readString();
        }

        in.readMapEnd();
        ByteArrayInputStream bis = new ByteArrayInputStream(initValue);
        ObjectInputStream ois = new ObjectInputStream(bis);
        try {
            Object value = ois.readObject();
            in.setRef(ref, value);
            return value;
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            ois.close();
            bis.close();
        }
    }

    @Override
    public Object readObject(AbstractHessianInput in, Object[] fieldNames)
            throws IOException {
        int ref = in.addRef(null);
        byte[] initValue = null;
        for (Object o : fieldNames) {
            if (o instanceof String) {
                final String key = (String) o;
                if (key.equals("value"))
                    initValue = in.readBytes();
                else
                    in.readObject();
            }
        }
        ByteArrayInputStream bis = new ByteArrayInputStream(initValue);
        ObjectInputStream ois = new ObjectInputStream(bis);
        try {
            Object value = ois.readObject();
            in.setRef(ref, value);
            return value;
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            ois.close();
            bis.close();
        }
    }
}
I'm not sure if the readMap part is actually needed since I rearranged this code from another example. Also, a ThrowableSerializerFactory is needed:
public class ThrowableSerializerFactory extends AbstractSerializerFactory {
    @Override
    public Serializer getSerializer(Class cl) throws HessianProtocolException {
        if (Throwable.class.isAssignableFrom(cl)) {
            return new ThrowableSerializer();
        }
        return null;
    }

    @Override
    public Deserializer getDeserializer(Class cl) throws HessianProtocolException {
        if (Throwable.class.isAssignableFrom(cl)) {
            return new ThrowableDeserializer();
        }
        return null;
    }
}
What this code essentially does is take a Throwable (which implements Serializable), serializes it to a byte[] and pushes it over to the other side. This serialization does not use the problematic setAccessible method (like com.caucho.hessian.io.ThrowableSerializer) and works correctly on App engine (I tested it). The only part left to do is to plug all this into the servlet and the client. Here is how to do it on the servlet:
public class Service extends HessianServlet implements IService {
    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        getSerializerFactory().addFactory(new ThrowableSerializerFactory());
    }
    // implement IService methods...
}

And here is how to do it on the client:
String url = "http://whatever.appspot.com/service"; [^]
HessianProxyFactory factory = new HessianProxyFactory();
factory.getSerializerFactory().addFactory(new ThrowableSerializerFactory());
IService service = (IService) factory.create(IService.class, url);

If you have any additional questions do not hesitate to ask. Thank you for your time. The attachment contains an example project in which the bug is reproduced.
Additional Information This bug report obsoletes this bug: http://bugs.caucho.com/view.php?id=4061 [^]
Some forums where this bug was discussed:

http://groups.google.com/group/google-appengine-java/browse_thread/thread/0ccb9d0ff6b88545/ [^]
http://forum.caucho.com/showthread.php?t=9999 [^]
Attached Files  testHessian.zip [^] (21,521 bytes) 06-16-10 01:12

- Relationships

There are no notes attached to this issue.

- Issue History
Date Modified Username Field Change
06-16-10 01:12 dilbert New Issue
06-16-10 01:12 dilbert File Added: testHessian.zip
06-16-10 01:29 dilbert Issue Monitored: dilbert


Mantis 1.0.0rc3[^]
Copyright © 2000 - 2005 Mantis Group
28 total queries executed.
24 unique queries executed.
Powered by Mantis Bugtracker