Announcement Announcement Module
Collapse
No announcement yet.
JstlView, extensionless URLs, /* matching and loops Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • JstlView, extensionless URLs, /* matching and loops

    Hi,

    I would like to have URLs that are clean/clear to users. So I'd like
    to have URLs like [root]/user/andrew or [root]/help/faq without file
    extensions etc.

    I'd also like to do this without Apache rewrite (ie just within
    Tomcat). So I defined my servlet-mapping to match the url pattern /*.

    However, I get stuck in a loop when I use JstlView to foward a request
    to a particular JSP page like WEB-INF/jsp/root/index.jsp because that
    is itself matches the /* url pattern of the servlet (at least, I
    believe this is what happens).

    In the examples I've found, this is avoided by using using /*.html as
    a map and providing implementations in .jsp files. However, as I
    said, I'd like to avoid that if possible.

    Is there a solutuion without using apache and mod_rewrite? If not, is
    there anything I ought to take care of now, while developing with
    Tomcat, so that mod_rewrite is easy to add later?

    Thanks,
    Andrew

  • #2
    In search of extension-less URLs

    Hey Andrew, did you figure out a solution to this problem? I too am searching for extension-less URLs, and we're not the only ones:

    "URL mapping, why don't I get it?"
    http://forum.springframework.org/showthread.php?t=18004

    "directory like URL, no extension?"
    http://forum.springframework.org/showthread.php?t=18755
    Last edited by robyn; May 14th, 2006, 09:08 PM.

    Comment


    • #3
      what i've done so far is:
      - develop everything with ".html" as external url extensions
      - use ".jsp" as the internal extension
      - use the servlet name in the url path
      (that's all "as normal")

      what i intend to do is:
      - generate urls to my own pages via a custom tag that will allow me to strip the servlet name and extension (maybe c:url allows this - i haven't looked yet).
      - place the production implementation in apache
      - use apache rewrite rules to add ".html" (and servlet name, although i guess that will be via the virtual host config rather than a rewrite rule) on incoming requests
      - configure the custom tag to generate links without extensions (and without servlet name)

      i think that should work.

      Comment


      • #4
        an alternate approach - multiple servlet-mapping elements

        I think I'm probably just going to use multiple servlet-mapping elements in conjunction with the dispatcher handler's AlwaysUseFullPath property. Using your original example URLs...

        In web.xml it would look something like this:
        Code:
        	<servlet>
        		<servlet-name>dispatcher</servlet-name>
        		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        		<load-on-startup>1</load-on-startup>
        	</servlet>
        	<servlet-mapping>
        		<servlet-name>dispatcher</servlet-name>
        		<url-pattern>/user/*</url-pattern>
        	</servlet-mapping>   
        	<servlet-mapping>
        		<servlet-name>dispatcher</servlet-name>
        		<url-pattern>/help/*</url-pattern>
        	</servlet-mapping>
        In dispatcher-servlet.xml, something like this:
        Code:
            <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
                <property name="alwaysUseFullPath" value="true" />
                <property name="mappings">
                    <props>
                        <prop key="/users/*">usersController</prop>
                        <prop key="/help/faq">faqController</prop>
                        <prop key="/help/*">generalHelpController</prop>
                    </props>
                </property>
            </bean>
        That seems to me like it gives the desired flexibility. The cost is that you have to maintain potentially many servlet-mapping elements in web.xml, which also must not conflict with any file directories you want to serve.

        Ideally the dispatcher would just catch all requests, handle only the ones for which it has a mapping, and then pass the rest through to the standard file/directory handler.

        It makes me wonder how much the Spring people actually use their own framework; after all, this is a PHP forum...

        Comment


        • #5
          thanks for the comments. i've not yet got round to this, but will try what you suggest (for some reason i thought i tried something similar and got stuck in a loop with redirects, but i can't remember the details, and it was early on in the project - i understand a bit more now...)

          Comment


          • #6
            My final solution

            Someone just sent me an email asking what I finally did, so I thought I should post here. I don't think I tried everything above (in fact I may have asked in a different thread, because I thought the solution I am using came from here, but didn't see it glancing quickly through this thread), so it is probably not the only way.

            Anyway, in my web.xml I have:

            <filter>
            <filter-name>UrlRewriteFilter</filter-name>
            <filter-class>org.tuckey.web.filters.urlrewrite.UrlRewrite Filter</filter-class>
            <init-param>
            <param-name>logLevel</param-name>
            <param-value>DEBUG</param-value>
            </init-param>
            </filter>
            <filter-mapping>
            <filter-name>UrlRewriteFilter</filter-name>
            <url-pattern>/*</url-pattern>
            </filter-mapping>

            In urlrewrite.xml (also in WEB-INF) I have

            <?xml version="1.0" encoding="utf-8"?>
            <!DOCTYPE urlrewrite PUBLIC "-//tuckey.org//DTD UrlRewrite 2.6//EN"
            "http://tuckey.org/res/dtds/urlrewrite2.6.dtd">

            <urlrewrite>
            <rule>
            <note>any url without a final file name goes to index</note>
            <from>^([^?]*)/(\?.*)?$</from>
            <to last="false">$1/index$2</to>
            </rule>
            <rule>
            <note>any url without a final file extension goes to .dynamic</note>
            <from>^([^?]*)/([^?/\.]+)(\?.*)?$</from>
            <to last="false">$1/$2.dynamic$3</to>
            </rule>
            </urlrewrite>

            In my appname-servlet.xml I have:

            <bean id="urlMapping"
            class="org.springframework.web.servlet.handler.Sim pleUrlHandlerMapping">
            <property name="order" value="1"/>
            <property name="alwaysUseFullPath" value="true"/>
            <property name="interceptors">
            <list>
            <ref bean="localeChangeInterceptor"/>
            <ref bean="themeChangeInterceptor"/>
            <ref bean="tzChangeInterceptor"/>
            <ref bean="initialUrlInterceptor"/>
            <ref bean="taglineInterceptor"/>
            </list>
            </property>
            <property name="mappings">
            <props>
            <prop key="/new-member.dynamic">newUserController</prop>
            <prop key="/new-member-email.dynamic">newUserEmailController</prop>
            <prop key="/new-member-thanks.dynamic">rootController</prop>
            <prop key="/help/*.dynamic">helpController</prop>
            <prop key="/index.dynamic">rootController</prop>
            <prop key="/communities.dynamic">communityIndexController</prop>
            <prop key="/members.dynamic">memberIndexController</prop>
            </props>
            </property>
            </bean>

            etc.

            Hope that makes sense. I can't remember in detail what it all means, but basically I map incoming URLs to add a ".dynamic" and then match that. Internally I use ".jsp" for jsp pages.

            Comment


            • #7
              Different solution

              I was the one who asked... after sending the note, I kept working and came up with a different Tomcat-dependent solution. I'm still testing and thinking through potential problems. But here it is in its nascent form.

              In web.xml catch /* and send it to Spring.


              In your servlet context file you need catchall mappings. Spring provides a convenient class for this:
              <!-- JSP files are compiled and run -->
              <bean name="/**/*.jsp" class="org.springframework.web.servlet.mvc.Servlet ForwardingController">
              <property name="servletName" value="jsp"/>
              </bean>

              <!-- All other resources are served up plain -->
              <bean name="/**/*" class="org.springframework.web.servlet.mvc.Servlet ForwardingController">
              <property name="servletName" value="default"/>
              </bean>

              The disadvantage of this is that everything is running through Spring, but I don't see that as a problem for us at this time. If your usage of the default mappings is more complicated, though, you'd have to redirect back to the container for other resources. I'll reply if I change course...
              Last edited by ndp; Nov 29th, 2006, 11:10 AM.

              Comment


              • #8
                My Solution

                Hi guys,

                I had the same requirements about having clean URLs and I really believe it's an issue about spring URL handler since mapping other web framework servlet to /* and call URLs like /user/25 /listusers and so on works like a charm.
                My solution is surely not the best nor the cleanest but it's application- server independent and so far it's working with no overload.
                So here it is:

                web.xml:
                <!-- Spring Filter -->
                <filter>
                <filter-name>Spring Filter</filter-name>
                <filter-class>com.mycompany.web.SpringFilter</filter-class>
                <init-param>
                <param-name>springMapping</param-name>
                <param-value>/spring</param-value>
                </init-param>
                <init-param>
                <param-name>resourceExtensions</param-name>
                <param-value>jpg,gif,png,css,js</param-value>
                </init-param>
                </filter>

                <filter-mapping>
                <filter-name>Spring Filter</filter-name>
                <url-pattern>/*</url-pattern>
                </filter-mapping>

                <!-- Spring Servlet -->
                <servlet>
                <servlet-name>Spring Servlet</servlet-name>
                <servlet-class>
                org.springframework.web.servlet.DispatcherServlet
                </servlet-class>
                <load-on-startup>1</load-on-startup>
                </servlet>

                <servlet-mapping>
                <servlet-name>Spring Servlet</servlet-name>
                <url-pattern>/spring/*</url-pattern>
                </servlet-mapping>

                SpringFilter.java:
                package com.mycompany.web;

                import javax.servlet.Filter;
                import javax.servlet.FilterChain;
                import javax.servlet.FilterConfig;
                import javax.servlet.ServletException;
                import javax.servlet.ServletRequest;
                import javax.servlet.ServletResponse;
                import javax.servlet.http.HttpServletRequest;
                import javax.servlet.http.HttpServletResponse;

                import org.apache.log4j.Logger;

                import java.io.IOException;
                import java.util.ArrayList;
                import java.util.Iterator;
                import java.util.List;
                import java.util.StringTokenizer;


                public class SpringFilter implements Filter {


                private FilterConfig filterConfig;
                private static String springMapping;
                private static List resourceExtensions;

                private final Logger logger = Logger.getLogger(getClass().getName());


                public void init(FilterConfig filterConfig) throws ServletException {

                this.filterConfig = filterConfig;
                springMapping = filterConfig.getInitParameter("springMapping");
                resourceExtensions = getResourceExtensions(filterConfig.getInitParamete r("resourceExtensions"));
                }

                public void doFilter(final ServletRequest request, final ServletResponse response, FilterChain chain) {

                HttpServletResponse httpServletResponse = (HttpServletResponse)response;
                HttpServletRequest httpServletRequest = (HttpServletRequest)request;
                String requestUri = httpServletRequest.getRequestURI();

                try {

                if (requestUri.indexOf(springMapping) == -1 && isNotAResource(requestUri)) {

                String forwardUrl = springMapping + requestUri.substring(httpServletRequest.getContext Path().length());
                }
                logger.debug("forwarding to " + forwardUrl);
                filterConfig.getServletContext().getRequestDispatc her(forwardUrl).forward(httpServletRequest, httpServletResponse);

                } else {

                logger.debug("no need to forward url " + requestUri);
                chain.doFilter(request, response);
                }

                } catch (ServletException e) {

                e.printStackTrace();

                } catch (IOException e) {

                e.printStackTrace();
                }


                }

                private static boolean isNotAResource (String requestUri) {

                Iterator iterator = resourceExtensions.iterator();
                while (iterator.hasNext()) {

                if (requestUri.endsWith(iterator.next().toString())) {

                return false;
                }

                }

                return true;
                }

                private List getResourceExtensions (String extensions) {

                StringTokenizer stringTokenizer = new StringTokenizer(extensions, ",");
                List list = new ArrayList(stringTokenizer.countTokens());
                while (stringTokenizer.hasMoreTokens()) {

                list.add(stringTokenizer.nextToken());
                }

                return list;
                }

                public void destroy() {

                this.filterConfig = null;
                }


                }

                That's it. When you call a url like home the filter will turn it into /spring/home within the context path but if the url it's an image or css or js file there's no need change it.

                Cheers,

                Gianmaria

                Comment

                Working...
                X