sobota 25. února 2012

GAE doesn't support HTTP OPTIONS method.

1.March update: There is new issue at GAE tracker  related directly to OPTIONS. Feel free to star it if you want that feature to be implemented.

During work at my java project RESTfulChecker, I reached a point where I needed to send OPTIONS HTTP method from RESTfulChecker to the remote API. OPTIONS method is defined in rfc2616 along with other HTTP methods such as GET, POST, PUT etc and is vital for REST. RESTfulChecker is running at Google App Engine, which is Google's cloud based solution for hosting (not only) Java apps. However upon building OPTIONS request I ran into following exception occurring when executed on Google App Engine:

java.net.ProtocolException: OPTIONS is not one of the supported http methods: [GET, POST, HEAD, PUT, DELETE]
    at com.google.apphosting.utils.security.urlfetch.URLFetchServiceStreamHandler$Connection.setRequestMethod(URLFetchServiceStreamHandler.java:142)
    at tk.ludva.ConnectionTestServlet.doGet(ConnectionTestServlet.java:18)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
    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:102)
    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:249)
    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$RequestRunnable.run(JavaRuntime.java:422)
    at com.google.tracing.TraceContext$TraceContextRunnable.runInContext(TraceContext.java:449)
    at com.google.tracing.TraceContext$TraceContextRunnable$1.run(TraceContext.java:455)
    at com.google.tracing.TraceContext.runInContext(TraceContext.java:695)
    at com.google.tracing.TraceContext$AbstractTraceContextCallback.runInInheritedContextNoUnref(TraceContext.java:333)
    at com.google.tracing.TraceContext$AbstractTraceContextCallback.runInInheritedContext(TraceContext.java:325)
    at com.google.tracing.TraceContext$TraceContextRunnable.run(TraceContext.java:453)
    at com.google.apphosting.runtime.ThreadGroupPool$PoolEntry.run(ThreadGroupPool.java:251)
    at java.lang.Thread.run(Thread.java:679)



The exception is caused by this part of my code:

URL remoteUrl = new URL(apiUrl);
HttpURLConnection conn = (HttpURLConnection) remoteUrl.openConnection();
conn.setRequestMethod("OPTIONS");


Namely by conn.setRequestMethod("OPTIONS"); We can see from Stack Trace that the exception is raised by Google's URLFetchService. As far as I know, every request sent from your GAE application have to pass through that URLFetchService so unless it supports OPTIONS we are unable to send that request from our GAE apps.
We can check googleappengine's source and this part of urlfetch can confirm us about supported methods.

I found a posts related to this problem at GAE group but at the time of writing this blogpost, it's without reply:
Limited HTTP method on urlfetch API - need OPTIONS, PROPFIND ... by Wingi.
People want WebDAV suppport, there is even ticket in GAE's bugtracker for it.
Because mentioned post is about WebDAV's methods, I added my own dedicated to OPTIONS method only: OPTIONS is not one of the supported http methods?

If you know the solution for sending OPTIONS from GAE, kindly let me know or discus in threads mentioned.

Žádné komentáře:

Okomentovat