On Mon, Jun 9, 2014 at 9:38 AM, Konstantin Kolinko <knst.koli...@gmail.com> wrote: > 2014-06-05 22:04 GMT+04:00 Jimmy Royer <jimlero...@gmail.com>: >> Hello, >> >> I am using this combo of components: >> >> * Apache Tomcat 8.0.8 >> * Apache CXF 2.7.11 >> * Servlet 3.0 >> * JAX-RS 2.0 >> * JDK 1.7.0_45 >> * Windows 7 >> * Chrome browser with the Advanced REST Client plug-in >> >> I developed some web services using REST that leverages CXF ability to >> do asynchronous methods, and under the hood, that uses Apache Tomcat. >> >> This is working fine overall, the setup and configuration are all >> good. There is one exception though. This is when I make a request to >> an async web service that uses a space in the URL, encoded to a %20. >> >> The encoding itself works fine, but internally, when Tomcat resumes >> the Servlet 3 continuation, it passes to some class the previously >> decoded path and sets it on the request URL. The request is then >> passed to the CXF layer, that expects a valid URL with no space and >> tries to instantiate a URL object from it, and fails. Here is the >> stack trace I got: >> >> >> >> """ >> 05-Jun-2014 12:33:37.426 SEVERE [http-apr-8080-exec-10] >> org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() >> for servlet [CXFServlet] in context with path [] threw exception >> [java.lang.RuntimeException: java.lang.IllegalArgumentException: >> Illegal character in path at index 134: >> http://127.0.0.1:8080/qlikview11/bi-service/c4aeb78f-109a-49a3-9716-10d83272a845/folders/13e5f0b4-90e2-4d11-bc5f-4f688e53bed2/Software >> Division/documents] with root cause >> java.net.URISyntaxException: Illegal character in path at index 134: >> http://127.0.0.1:8080/qlikview11/bi-service/c4aeb78f-109a-49a3-9716-10d83272a845/folders/13e5f0b4-90e2-4d11-bc5f-4f688e53bed2/Software >> Division/documents >> at java.net.URI$Parser.fail(URI.java:2829) >> at java.net.URI$Parser.checkChars(URI.java:3002) >> at java.net.URI$Parser.parseHierarchical(URI.java:3086) >> at java.net.URI$Parser.parse(URI.java:3034) >> at java.net.URI.<init>(URI.java:595) >> at java.net.URI.create(URI.java:857) >> at >> org.apache.cxf.transport.servlet.BaseUrlHelper.getBaseURL(BaseUrlHelper.java:49) >> at >> org.apache.cxf.transport.servlet.ServletController.getBaseURL(ServletController.java:78) >> at >> org.apache.cxf.transport.servlet.ServletController.updateDestination(ServletController.java:87) >> at >> org.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:200) >> at >> org.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:153) >> at >> org.apache.cxf.transport.servlet.CXFNonSpringServlet.invoke(CXFNonSpringServlet.java:171) >> at >> org.apache.cxf.transport.servlet.AbstractHTTPServlet.handleRequest(AbstractHTTPServlet.java:286) >> at >> org.apache.cxf.transport.servlet.AbstractHTTPServlet.doGet(AbstractHTTPServlet.java:211) >> at javax.servlet.http.HttpServlet.service(HttpServlet.java:618) >> at >> org.apache.cxf.transport.servlet.AbstractHTTPServlet.service(AbstractHTTPServlet.java:262) >> at >> org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:301) >> at >> org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) >> at >> org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:721) >> at >> org.apache.catalina.core.ApplicationDispatcher.doDispatch(ApplicationDispatcher.java:639) >> at >> org.apache.catalina.core.ApplicationDispatcher.dispatch(ApplicationDispatcher.java:605) >> at org.apache.catalina.core.AsyncContextImpl$1.run(AsyncContextImpl.java:208) >> at >> org.apache.catalina.core.AsyncContextImpl.doInternalDispatch(AsyncContextImpl.java:363) >> at >> org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:214) >> at >> org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106) >> at >> org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:503) >> at >> org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:136) >> at >> org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:78) >> at >> org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610) >> at >> org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88) >> at >> org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:405) >> at >> org.apache.coyote.http11.AbstractHttp11Processor.asyncDispatch(AbstractHttp11Processor.java:1636) >> at >> org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:646) >> at >> org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:277) >> at >> org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2451) >> at >> org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2440) >> at >> java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) >> at >> java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) >> at >> org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) >> at java.lang.Thread.run(Thread.java:744) >> """ >> >> >> >> The URL is perfectly encoded when it gets in the Tomcat machinery, but >> it gets decoded along the way, and the information is not re-encoded >> along the way. It's hard for me to say what should be the proper logic >> as I am not familiar with the Tomcat code base, but here is the >> workflow of the key classes, methods and URI values that got to this >> situation: >> >>> AsyncContextImpl#dispatch: The request path info used to be dispatched. >>> This path was previously decoded during the previous operations. >> >> --> ApplicationContext#getRequestDispatcher: The decoded path is >> eventually sent to this method. The path is normalized and appended to >> a variable uriCC meant to represent an URI. The value of this variable >> is never re-encoded nor validated to a valid URI. A new >> ApplicationDispatcher is returned that contains a non-encoded URI. >> >> --> ApplicationDispatcher#doDispatch: The previously created >> application dispatcher now has to dispatch the request. It overwrites >> the request URL from the incoming request (which is properly encoded) >> with the previously computed path that is non-encoded. >> >> --> BaseUrlHelper#getBaseURL: This CXF method eventually gets >> called with a request that contains a non-valid URI. The code that >> triggers the exception is equivalent to: >> URI.create(request.getRequestURL().toString()). >> > > The CXF source code: > http://cxf.apache.org/source-repository.html > https://git-wip-us.apache.org/repos/asf?p=cxf.git > > The code of BaseUrlHelper is in "2.7.x-fixes" branch. > https://git-wip-us.apache.org/repos/asf?p=cxf.git;a=tree;h=refs/heads/2.7.x-fixes;hb=2.7.x-fixes > > https://git-wip-us.apache.org/repos/asf?p=cxf.git;a=blob;f=rt/transports/http/src/main/java/org/apache/cxf/transport/servlet/BaseUrlHelper.java;h=db80a075c464da83637e2399c19dbdb42fc24aeb;hb=2.7.x-fixes > > [[[ > 33 /** > 34 * Returns base URL which includes scheme, host, port, Servlet > context and servlet paths > 35 * @param request current HttpServletRequest > 36 * @return base URL > 37 */ > 38 public static String getBaseURL(HttpServletRequest request) { > 39 String reqPrefix = request.getRequestURL().toString(); > 40 String pathInfo = request.getPathInfo() == null ? "" : > request.getPathInfo(); > 41 //fix for CXF-898 > 42 if (!"/".equals(pathInfo) || reqPrefix.endsWith("/")) { > 43 StringBuilder sb = new StringBuilder(); > 44 // request.getScheme(), request.getLocalName() and > request.getLocalPort() > 45 // should be marginally cheaper - provided > request.getLocalName() does > 46 // return the actual name used in request URI as > opposed to localhost > 47 // consistently across the Servlet stacks > 48 > 49 URI uri = URI.create(reqPrefix); > 50 > sb.append(uri.getScheme()).append("://").append(uri.getRawAuthority()); > 51 > sb.append(request.getContextPath()).append(request.getServletPath()); > 52 > 53 reqPrefix = sb.toString(); > 54 } > 55 return reqPrefix; > 56 } > ]]] > > Servlet API javadocs: > http://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequest.html > > It does not mention explicitly whether getRequestURL() returns a > non-decoded or decoded URL, > > but as our implementation in o.a.c.connector.Request does > "url.append(getRequestURI());" I would say that it is expected to > return a non-decoded String. > > > On the other hand I think ServletContext.getRequestDispatcher() > expects a decoded URI, as it expects a "resource path", not a > %-encoded URI. > > I think it is OK to file an issue into Bugzilla. > > Best regards, > Konstantin Kolinko
Thanks for your insights Konstantin. My understanding here is that there is an ambiguous case if URLs should contain decoded spaces or not. The URI (and inherently URL) specification forbids the direct usage of spaces (http://www.ietf.org/rfc/rfc3986.txt, Appendix A) as well as the URI Java object (which trigger the problem I have) but the Java API for Servlet does not directly use any URI objects: all of them are either String or StringBuffer objects. It complicates the API I think as implementations might use strings internally to build the URIs but those are not enforced when communicating with other code. I'll wait a bit for opening an issue as Mark Thomas said he'd dig a bit, but if you tell me to go ahead I'll do it! Jimmy >> I came across a somewhat similar bug in the CXF Jira (where the cause >> was different). The CXF folks really expect the URL to be properly >> encoded. In my case, it seems that this might not be properly handed >> by Tomcat to CXF. >> >> As I said, I'm not familiar with the code base of both Tomcat and CXF, >> so please tell me if that could be something wrong with my setup, or >> if that is a bug. >> >> To reproduce this behavior, I guess that these steps would do it: >> >> 1- Develop an async web method for Tomcat (I'm using Apache CXF JAX-RS >> async support for that). >> 2- Send a request to this web method that contains an encoded %20 >> space, make sure that async support is in and servlet 3 continuations >> are used. >> >> That would be the bare minimum I guess, and not the exact setup I have! >> >> Best regards, >> Jimmy Royer >> >> --------------------------------------------------------------------- >> To unsubscribe, e-mail: users-unsubscr...@tomcat.apache.org >> For additional commands, e-mail: users-h...@tomcat.apache.org >> > > --------------------------------------------------------------------- > To unsubscribe, e-mail: users-unsubscr...@tomcat.apache.org > For additional commands, e-mail: users-h...@tomcat.apache.org > --------------------------------------------------------------------- To unsubscribe, e-mail: users-unsubscr...@tomcat.apache.org For additional commands, e-mail: users-h...@tomcat.apache.org