Re: Decoded URL set on asynchronous request
On Sun, Jun 15, 2014 at 8:01 AM, Konstantin Kolinko knst.koli...@gmail.com wrote: 2014-06-09 17:38 GMT+04:00 Konstantin Kolinko knst.koli...@gmail.com: (...) 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. To correct myself here, several notes 1. getRequestURI() returns just what was sent by the client. It is just raw bytes. There is no saying that it is url-encoded. It is just that clients do url-encode it to pass it on the wire. 2. In case of RequestDispatcher.forward(..) the javadoc for getRequestURL() says that it must reflect the path used to obtain the RequestDispatcher. The history section of Servlet 3.1 spec (section A.7.3 page 225/240) mentions when that method was clarified by adding that requirement. There is no such requirement for getRequestURI() method. Should its value be updated on forwards? The implementation in Tomcat does update requestURI on forwards. 3. If getRequestURL() has to perform an url-encoding of forwarded path, it should be noted that url-encoding procedure yields different results depending on the character encoding used to convert chars to their byte codes. The usual convention nowadays is to use UTF-8, though I would say that it would be more correct to ask the Connector for its URIEncoding setting. (So that when browser follows a redirect and sends its next request, the URL has to be correctly parsed by the Connector. I mean that getRequestURL() method is documented as for creating redirect messages.) In the OP case the trouble was with space characters (%20). Just to make it clear and validate my understanding: the space encoding is part of the URI encoding specification and not related to UTF-8 or other character encodings. The percentage encoding is a way to escape characters in the URI. The space characters encoding such as %20 could itself be encoded in UTF-8 or ASCII. Does that sound right? Regards, Jimmy Royer 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 - 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
Re: Decoded URL set on asynchronous request
Is there any information that I should add to this to get some help? Or should I just go straight and create a bug report for this (as this might not be a message targeting the users mailing list but Tomcat's developers)? Thanks, Jimmy On Thu, Jun 5, 2014 at 2:04 PM, Jimmy Royer jimlero...@gmail.com wrote: 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
Re: Decoded URL set on asynchronous request
On Mon, Jun 9, 2014 at 9:00 AM, Daniel Mikusa dmik...@gopivotal.com wrote: On Mon, Jun 9, 2014 at 8:46 AM, Jimmy Royer jimlero...@gmail.com wrote: Is there any information that I should add to this to get some help? Any chance you could put together a sample app or unit test to replicate the problem? Ideally without third party components like CXF. It would make it easier someone on the list to recreate the problem and thus easier for someone to help. I've setup a project and I can reproduce something where we see that the URL is non-encoded when I make use of the AsyncContext#dispatch method, but the overall workflow of my sample does not make sense because I don't know when this method should used or why. Or should I just go straight and create a bug report for this (as this might not be a message targeting the users mailing list but Tomcat's developers)? The users list is always a good first place to start. As an FYI, don't top post. Reply inline like this, or just at the bottom. It's the convention followed on the list. Okay got it, I'll follow the standard, thanks for the reply! Dan On Thu, Jun 5, 2014 at 2:04 PM, Jimmy Royer jimlero...@gmail.com wrote: 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
Re: Decoded URL set on asynchronous request
On Mon, Jun 9, 2014 at 9:07 AM, Mark Thomas ma...@apache.org wrote: On 09/06/2014 13:46, Jimmy Royer wrote: Is there any information that I should add to this to get some help? Right now, no. There may be further questions as folks dig into this. Or should I just go straight and create a bug report for this (as this might not be a message targeting the users mailing list but Tomcat's developers)? No. That is likely to get closed as INVALID since - at the moment - there is not a clearly identified bug in Tomcat. There are lots of moving parts here and while there is obviously a bug somewhere, it isn't yet clear where that bug is. I suspect that most folks took a look at this and decided it was just too big / complicated to get into. Given the complexity of the situation, the effort you have taken to make the issue as clear as it is, is very much appreciated. To be honest, I doubt anyone could have written a better question given the problem. Give me a little time to dig into it and I'll get back to you with either an answer or - more likely - some specific questions or things to test. Mark I understand. I can submit what I started as a sample project to target this bug which includes a minimum of dependencies along with a Maven configuration. I can push it to github and that would be very easy for you to get it. What do you prefer? My sample project does not exactly shows the problem, as a call to AsyncContext#dispatch is necessary and I'm not sure how and why it's used. But that could save you the project setup time at least. Jimmy Thanks, Jimmy On Thu, Jun 5, 2014 at 2:04 PM, Jimmy Royer jimlero...@gmail.com wrote: 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
Re: Decoded URL set on asynchronous request
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
Decoded URL set on asynchronous request
, 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()). 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