Warning - "You are probably missing the ObjectifyFilter" - in deployed application only

706 views
Skip to first unread message

Arpit

unread,
Jul 28, 2015, 1:49:22 AM7/28/15
to objectify-appengine
All APIs of my service work fine except the one which produces this (only when deployed, never in Local Dev Server).
API actually completes the job, and almost returns the response too but then something kicks in causing this error. From GAE logs:

    I 17:49:17.892 org.glassfish.jersey.filter.LoggingFilter log: 6 * Server responded with a response on thread Request 59F369C8
      6 < 204
    I 17:49:17.900 com.google.appengine.tools.appstats.AppstatsFilter doFilter: Appstats available: /appstats/details?time=1438044555552

W17:49:17.901 java.lang.IllegalStateException: You have not started an Objectify context. You are probably missing the ObjectifyFilter. If you are not running in the context of an http request, see the ObjectifyService.run() method. at com.googlecode.objectify.ObjectifyService.ofy(ObjectifyService.java:44) at com.googlecode.objectify.impl.ref.LiveRef.ofy(LiveRef.java:64) at com.googlecode.objectify.impl.ref.LiveRef.isLoaded(LiveRef.java:55) at com.googlecode.objectify.Ref.getValue(Ref.java:83) at com.googlecode.objectify.impl.ref.LiveRef.writeReplace(LiveRef.java:74) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
.......
.......
.......
at com.google.tracing.TraceContext$AbstractTraceContextCallback.runInInheritedContext(TraceContext.java:300) at com.google.tracing.TraceContext$TraceContextRunnable.run(TraceContext.java:441) at com.google.apphosting.runtime.ThreadGroupPool$PoolEntry.run(ThreadGroupPool.java:234) at java.lang.Thread.run(Thread.java:745)

Any help is appreciated.

Thanks,
Arpit

Jeff Schnitzer

unread,
Jul 28, 2015, 10:58:10 AM7/28/15
to objectify...@googlegroups.com
Can you post the whole stacktrace? Oddly enough, it's the middle parts that are interesting here.

Jeff

--
You received this message because you are subscribed to the Google Groups "objectify-appengine" group.
To unsubscribe from this group and stop receiving emails from it, send an email to objectify-appen...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Arpit

unread,
Jul 28, 2015, 2:42:08 PM7/28/15
to objectify-appengine, je...@infohazard.org
Here is the full trace:

Uncaught exception from servlet java.lang.IllegalStateException: You have not started an Objectify context. You are probably missing the ObjectifyFilter. If you are not running in the context of an http request, see the ObjectifyService.run() method. at com.googlecode.objectify.ObjectifyService.ofy(ObjectifyService.java:44) at com.googlecode.objectify.impl.ref.LiveRef.ofy(LiveRef.java:64) at com.googlecode.objectify.impl.ref.LiveRef.isLoaded(LiveRef.java:55) at com.googlecode.objectify.Ref.getValue(Ref.java:83) at com.googlecode.objectify.impl.ref.LiveRef.writeReplace(LiveRef.java:74) 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:606) at java.io.ObjectStreamClass.invokeWriteReplace(ObjectStreamClass.java:1075) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1154) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:365) at java.util.HashMap.writeObject(HashMap.java:1131) at sun.reflect.GeneratedMethodAccessor20.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:988) at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1515) at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1451) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1197) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:365) at com.google.apphosting.runtime.SessionManagerUtil.serialize(SessionManagerUtil.java:28) at com.google.apphosting.runtime.DatastoreSessionStore.createEntityForSession(DatastoreSessionStore.java:73) at com.google.apphosting.runtime.DatastoreSessionStore.saveSession(DatastoreSessionStore.java:112) at com.google.apphosting.runtime.jetty.SessionManager$AppEngineSession.save(SessionManager.java:159) at com.google.apphosting.runtime.jetty.SaveSessionFilter.doFilter(SaveSessionFilter.java:41) at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157) at com.google.apphosting.utils.servlet.JdbcMySqlConnectionCleanupFilter.doFilter(JdbcMySqlConnectionCleanupFilter.java:60) 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:257) 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:146) at com.google.apphosting.runtime.JavaRuntime$RequestRunnable.run(JavaRuntime.java:482) at com.google.tracing.TraceContext$TraceContextRunnable.runInContext(TraceContext.java:437) at com.google.tracing.TraceContext$TraceContextRunnable$1.run(TraceContext.java:444) at com.google.tracing.CurrentContext.runInContext(CurrentContext.java:230) at com.google.tracing.TraceContext$AbstractTraceContextCallback.runInInheritedContextNoUnref(TraceContext.java:308) at com.google.tracing.TraceContext$AbstractTraceContextCallback.runInInheritedContext(TraceContext.java:300) at com.google.tracing.TraceContext$TraceContextRunnable.run(TraceContext.java:441) at com.google.apphosting.runtime.ThreadGroupPool$PoolEntry.run(ThreadGroupPool.java:234) at java.lang.Thread.run(Thread.java:745)

Jeff Schnitzer

unread,
Jul 28, 2015, 3:25:10 PM7/28/15
to objectify...@googlegroups.com
Interesting.

You have put an entity object in the servlet httpsession. The container is trying to serialize the contents of this session somewhere (probably the datastore). This serialization happens outside user-defined filters, and therefore outside the ObjectifyFilter.

Objectify tries to be clever about serialization - if you have loaded an entity, any Ref<?>s that point to that entity get serialized into a DeadRef which holds an actual link to that entity. Thus you can serialize whole entity graphs. However, this requires a "live" Objectify session - which has already been closed at this point.

I'm not quite sure what to do about this. 

The "right" answer is that you should not use http sessions - they are a horrid misfeature of the servlet API and a legacy of the early 2000s way of building web apps. Make your server stateless.

The secondmost "right" answer is that you should not store entity objects in the http session. Entity objects are things you store and retrieve from the datastore, not sessions (or other persistent stores). If you absolutely must store something in an httpsession, store a the Key<?> to an entity so you can look it up in your code. Keys serialize just fine.

Getting into the neighborhood of somewhat wrong answers, we could modify Objectify to serialize a DeadRef with a null value if the serialization takes place outside of a live objectify session (eg, ObjectifyFilter). I'm not sure what the real value is here since the entity instance is going to be somewhat broken - full of DeadRefs instead of LiveRefs. Calling Ref<?>.get() will not work. Ref<?> serialization behavior is intended only to make client-server apps easier; it's not intended to survive roundtrips. I'm not particularly inclined to implement this feature because it sounds like a patch for bad architecture, but if you think you have a really compelling use case, tell your story and we'll see.

Jeff

Arpit

unread,
Jul 28, 2015, 6:50:43 PM7/28/15
to objectify-appengine, je...@infohazard.org
What an amazing explanation! Thanks Jeff. 
That's exactly what was happening. GAE persists session in datastore **after servlet responds** which goes outside ObjectifyFilter purview. I've replaced Ref<?> with Key<?> and it's working fine now.

Regarding not using sessions - how else should I authenticate user on every request? 
I'm reading up spring security and other alternatives. If you have a recommended solution, please share.

Regards,
Arpit

Jeff Schnitzer

unread,
Jul 28, 2015, 7:05:56 PM7/28/15
to objectify...@googlegroups.com
Pass a signed token with every request. If you have a single page app, use a custom header. If you have a traditional app, use a cookie, but watch out for XSS (you have to do this with session cookies too).

My latest app uses Google login via Google's javascript auth library. It provides a signed token that can be verified with a toolkit they provide. The toolkit downloads, caches, and rotates Google's public certificates. Past apps of mine have used various combination of this, Microsoft's auth, and Facebook's auth. They all work the same way - verify a signed token - although MS and FB use a secret key instead of a public/private key scheme.

BTW, if you want to make your own signed certificates (say, for your own u/p scheme), GAE comes with a little toolkit that makes this easy:


It maintains and rotates a set of private keys for you. Pretty nifty. You might also want to read up on JWT (Json Web Tokens).

Jeff


Arpit

unread,
Aug 2, 2015, 10:46:38 AM8/2/15
to objectify-appengine, je...@infohazard.org
Hey Jeff,

Do you have any example that uses AppIdentityService? From documentation, it's clear how to sign any random bytes but it doesn't explain how to verify them. 
Also, to use JWT, I was following this example - https://bitbucket.org/b_c/jose4j/wiki/JWT%20Examples - but I need to maintain my own key somewhere in app-engine. What's the recommended way to keep secrets in GAE app?

Thanks.
Arpit
To unsubscribe from this group and stop receiving emails from it, send an email to objectify-appengine+unsub...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "objectify-appengine" group.
To unsubscribe from this group and stop receiving emails from it, send an email to objectify-appengine+unsub...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "objectify-appengine" group.
To unsubscribe from this group and stop receiving emails from it, send an email to objectify-appengine+unsub...@googlegroups.com.

Jeff Schnitzer

unread,
Aug 3, 2015, 2:43:19 AM8/3/15
to objectify...@googlegroups.com
Wow, yeah, the documentation is terrible. I haven't needed the AppIdentityService since my identity tokens are generated by IdP client javascript. That said, if I had to figure out how to validate signForApp() myself, I would look closely at the signature verification step of the GoogleIdTokenVerifier class of the google api client library. Also, you might get results from stackoverflow.

If you're just looking for an auth solution, have you looked at the new Google Identity Toolkit? There should be pretty reasonable docs around for verifying the (JWT) identity token.

Jeff

Arpit
To unsubscribe from this group and stop receiving emails from it, send an email to objectify-appen...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "objectify-appengine" group.
To unsubscribe from this group and stop receiving emails from it, send an email to objectify-appen...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "objectify-appengine" group.
To unsubscribe from this group and stop receiving emails from it, send an email to objectify-appen...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "objectify-appengine" group.
To unsubscribe from this group and stop receiving emails from it, send an email to objectify-appen...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages