Hi all,
First off a warning that this is a long email
In doing the virtual service work (GSIP 44) i have yet again have run up against a problem we have had in GeoServer ever since we moved to spring. The problem being that we have to register all servlet mappings in web.xml.
To describe the problem in more detail the following is how a request is routed/dispatched in GeoServer today. In web.xml we register a single servlet (DispatcherServlet) which is the spring dispatcher servlet. This servlet essentially handles all requests that come into geoserver. But it can only do so if the proper servlet mappings are registered web.xml. For example:
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/web/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/wms/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/wfs/*</url-pattern>
</servlet-mapping>
With these mappings a request that comes in of the form "/geoserver/wfs" for example, will get routed to our one servlet to rule them all, the spring dispatcher.
When handling a request the spring dispatcher takes the request and does its down routing. It does this by looking up mappings in the spring applicationContext.xml. For example the wfs mappings:
<bean id="wfsURLMapping" ...>
<property name="alwaysUseFullPath" value="true"/>
<property name="mappings">
<props>
<prop key="/wfs">dispatcher</prop>
<prop key="/wfs/*">dispatcher</prop>
</props>
</property>
</bean>
Which basically says map all urls of the form "/wfs*" to the ows dispatcher.
Now there are a couple of problems with this approach:
1) It is redundant in that we have to maintain two sets of mappings.
2) New services are not truly pluggable because any time one adds a new path prefix web.xml has to be updated.
3) Mapping in web.xml is quite limited compared to the corresponding spring mappings
(3) is where my problem lies with regard to the virtual services stuff. To sum up with virtual services the path of a wfs request changes to:
/geoserver/<workspace>/wfs?
Which of course does not have a mapping in web.xml therefore the request does not even make it to the spring dispatcher.
So the obvious solution (one that we have tried) would be to create a single mapping in web.xml that matches all requests to the spring dispatcher. The syntax is quite simple:
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
But it does not work. The reason being that when this mapping is applied some attributes of the resulting HttpServletRequest change. Namely the "servletPath" property.
With the existing mappings when a request comes in the servlet path gets set to the part of the path that matched. For example "web", "rest", "wfs", etc... But with the "catch all" mapping the servlet path becomes an empty string. I should also point out that this is the behavior in Jetty and Tomcat, i have yet to try out other containers.
And this causes problem. Many of the libraries/servets we use depend on the servlet path being set. Wicket and restlet pretty much fail outright. So.... the evil plan to fix. I tried a variety fo things but the following is only one i have had any success with.
Basically the idea is simple. Wrap the HttpServletRequest object in one that fakes the servlet path. Since all the existing mappings are simple in that they match a single patch component the wrapper class simply expects the entire request uri and uses the first component of the path (that occurs after /geoserver) and uses that for the servlet path.
So for wicket and restlet things go on working the same as the servlet paths become "web" and "rest" the way they were before. And same goes for the ows services.
Things get interesting when we get to the virtual services use case. Since the path is (for example):
/geoserver/<workspace>/wfs
The servlet path becomes "<workspace>". To amend this the "second level mappings" in the application context have to change a bit:
<bean id="wfsURLMapping" ...>
<property name="alwaysUseFullPath" value="true"/>
<property name="mappings">
<props>
<prop key="/wfs">dispatcher</prop>
<prop key="/wfs/*">dispatcher</prop>
<prop key="/*/wfs">dispatcher</prop>
<prop key="/*/wfs/*">dispatcher</prop>
</props>
</property>
</bean>
And with that it all works. So what do we do. I will be the first to admit that this approach is a bit hackish. And I plan to do a great deal of more testing in various environments to ensure it is actually even viable. But if it is what do people think about it?
If the idea does get some uptake I was thinking we could gradually introduce it as we have some of the other recent functionality improvements such as advanced rendering functionality, etc... Basically add a parameter (settable as system prop, web.xml context variable, etc..) called "ADVANCED_DISPATCH" or something that would enable the above. We could enable it on trunk and give it time to mature but disable it by default on 2.0.x, making it available only by explicitly setting the parameter.
Thoughts?
-Justin
--
Justin Deoliveira
OpenGeo - http://opengeo.org
Enterprise support for open source geospatial.