4

I have developed a Spring application that serves Spring MVC REST services. Now I want to implement Angularjs in to the webapp directory of my Spring application. The problem is that I don't know how to correctly configure this.

What I want to achieve:

  • /api/... = URL that serves REST services over Spring MVC such as localhost:8080/api/user/1
  • /... = URL that contains static html pages such as localhost:8080/index.html

Also I would like that index.html will be loaded by default.

Currently I'm using following configuration which works but it doesn't load index.html by default. But I don't know if this is really the way I should configure static pages within the Spring Container:

web.xml

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
    <servlet-name>spring</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>dispatchOptionsRequest</param-name>
        <param-value>true</param-value>
    </init-param>
</servlet>

<servlet-mapping>
    <servlet-name>spring</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

applicationContext.xml

<context:property-placeholder properties-ref="deployProperties" />

<!-- Activates various annotations to be detected in bean classes -->
<context:annotation-config />

<!-- Scans the classpath for annotated components that will be auto-registered as Spring beans.
For example @Controller and @Service. Make sure to set the correct base-package -->
<context:component-scan base-package="com.eerra" />

<!-- Configures the annotation-driven Spring MVC Controller programming model.
Note that, with Spring 3.0, this tag works in Servlet MVC only! -->
<mvc:annotation-driven/>

<!-- Tell Spring what to treat as resources -->
<mvc:resources mapping="/" location="/resources/ang2/app/"/>

With the current configuration everything is working but when I try to point my browser to localhost:8080/ then a 404 not found appears instead of loading the index.html file. When i try with localhost:8080/index.html then everything works.

Can somebody point me in to the right direction how to configure such a setup?

Below you can see my debug.log. Problem here seems to be following:

Did not find handler method for [/]

So I guess that the DispatcherServlet is trying to map the path to a controller. But I don't know how I can avoid this.

21:55:04.896 [qtp581501261-25] DEBUG org.eclipse.jetty.http.HttpParser - filled 314/314
21:55:04.897 [qtp581501261-25 - /] DEBUG org.eclipse.jetty.server.Server - REQUEST / on AsyncHttpConnection@f908897,g=HttpGenerator{s=0,h=-1,b=-1,c=-1},p=HttpParser{s=-5,l=9,c=0},r=1
21:55:04.897 [qtp581501261-25 - /] DEBUG o.e.j.server.handler.ContextHandler - scope null||/ @ o.m.j.p.JettyWebAppContext{/,file:/C:/Users/charms/Documents/Intellij%20Projects/cardkeeper/src/main/webapp/},file:/C:/Users/charms/Documents/Intellij%20Projects/cardkeeper/src/main/webapp/
21:55:04.897 [qtp581501261-25 - /] DEBUG o.e.j.server.handler.ContextHandler - context=||/ @ o.m.j.p.JettyWebAppContext{/,file:/C:/Users/charms/Documents/Intellij%20Projects/cardkeeper/src/main/webapp/},file:/C:/Users/charms/Documents/Intellij%20Projects/cardkeeper/src/main/webapp/
21:55:04.897 [qtp581501261-25 - /] DEBUG org.eclipse.jetty.server.session - sessionManager=org.eclipse.jetty.server.session.HashSessionManager@70799896
21:55:04.897 [qtp581501261-25 - /] DEBUG org.eclipse.jetty.server.session - session=null
21:55:04.897 [qtp581501261-25 - /] DEBUG o.e.jetty.servlet.ServletHandler - servlet |/|null -> spring
21:55:04.897 [qtp581501261-25 - /] DEBUG o.e.jetty.servlet.ServletHandler - chain=springSecurityFilterChain->spring
21:55:04.897 [qtp581501261-25 - /] DEBUG o.e.jetty.servlet.ServletHandler - call filter springSecurityFilterChain
21:55:04.897 [qtp581501261-25 - /] DEBUG o.s.s.web.util.AntPathRequestMatcher - Checking match of request : '/'; against '/favicon.ico*'
21:55:04.897 [qtp581501261-25 - /] DEBUG o.s.s.web.util.AntPathRequestMatcher - Checking match of request : '/'; against '/resources/**'
21:55:04.897 [qtp581501261-25 - /] DEBUG o.s.security.web.FilterChainProxy - / at position 1 of 9 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
21:55:04.898 [qtp581501261-25 - /] DEBUG o.s.security.web.FilterChainProxy - / at position 2 of 9 in additional filter chain; firing Filter: 'LogoutFilter'
21:55:04.898 [qtp581501261-25 - /] DEBUG o.s.security.web.FilterChainProxy - / at position 3 of 9 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
21:55:04.898 [qtp581501261-25 - /] DEBUG o.s.security.web.FilterChainProxy - / at position 4 of 9 in additional filter chain; firing Filter: 'DefaultLoginPageGeneratingFilter'
21:55:04.898 [qtp581501261-25 - /] DEBUG o.s.security.web.FilterChainProxy - / at position 5 of 9 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
21:55:04.898 [qtp581501261-25 - /] DEBUG o.s.security.web.FilterChainProxy - / at position 6 of 9 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
21:55:04.898 [qtp581501261-25 - /] DEBUG o.s.security.web.FilterChainProxy - / at position 7 of 9 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
21:55:04.898 [qtp581501261-25 - /] DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@9055c2bc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS'
21:55:04.898 [qtp581501261-25 - /] DEBUG o.s.security.web.FilterChainProxy - / at position 8 of 9 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
21:55:04.898 [qtp581501261-25 - /] DEBUG o.s.security.web.FilterChainProxy - / at position 9 of 9 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
21:55:04.898 [qtp581501261-25 - /] DEBUG o.s.s.web.util.AntPathRequestMatcher - Checking match of request : '/'; against '/api/**'
21:55:04.899 [qtp581501261-25 - /] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Public object - authentication not attempted
21:55:04.899 [qtp581501261-25 - /] TRACE o.s.w.c.s.XmlWebApplicationContext - Publishing event in Root WebApplicationContext: org.springframework.security.access.event.PublicInvocationEvent[source=FilterInvocation: URL: /]
21:55:04.899 [qtp581501261-25 - /] DEBUG o.s.security.web.FilterChainProxy - / reached end of additional filter chain; proceeding with original chain
21:55:04.899 [qtp581501261-25 - /] DEBUG o.e.jetty.servlet.ServletHandler - call servlet spring
21:55:04.899 [qtp581501261-25 - /] TRACE o.s.web.servlet.DispatcherServlet - Bound request context to thread: SecurityContextHolderAwareRequestWrapper[ FirewalledRequest[ (GET /)@604241542 org.eclipse.jetty.server.Request@2403fe86]]
21:55:04.899 [qtp581501261-25 - /] DEBUG o.s.web.servlet.DispatcherServlet - DispatcherServlet with name 'spring' processing GET request for [/]
21:55:04.899 [qtp581501261-25 - /] TRACE o.s.web.servlet.DispatcherServlet - Testing handler map [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping@12440d38] in DispatcherServlet with name 'spring'
21:55:04.899 [qtp581501261-25 - /] DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping - Looking up handler method for path /
21:55:04.912 [qtp581501261-25 - /] DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping - Did not find handler method for [/]
21:55:04.912 [qtp581501261-25 - /] TRACE o.s.web.servlet.DispatcherServlet - Testing handler map [org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping@c518734] in DispatcherServlet with name 'spring'
21:55:04.912 [qtp581501261-25 - /] TRACE o.s.w.s.h.BeanNameUrlHandlerMapping - No handler mapping found for [/]
21:55:04.912 [qtp581501261-25 - /] TRACE o.s.web.servlet.DispatcherServlet - Testing handler map [org.springframework.web.servlet.handler.SimpleUrlHandlerMapping@3c836d3d] in DispatcherServlet with name 'spring'
21:55:04.912 [qtp581501261-25 - /] DEBUG o.s.w.s.h.SimpleUrlHandlerMapping - Matching patterns for request [/] are [/**]
21:55:04.912 [qtp581501261-25 - /] DEBUG o.s.w.s.h.SimpleUrlHandlerMapping - URI Template variables for request [/] are {}
21:55:04.912 [qtp581501261-25 - /] DEBUG o.s.w.s.h.SimpleUrlHandlerMapping - Mapping [/] to HandlerExecutionChain with handler [org.springframework.web.servlet.resource.ResourceHttpRequestHandler@6abe6713] and 1 interceptor
21:55:04.912 [qtp581501261-25 - /] TRACE o.s.web.servlet.DispatcherServlet - Testing handler adapter [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter@7a614724]
21:55:04.912 [qtp581501261-25 - /] TRACE o.s.web.servlet.DispatcherServlet - Testing handler adapter [org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter@885cb41]
21:55:04.912 [qtp581501261-25 - /] DEBUG o.s.web.servlet.DispatcherServlet - Last-Modified value for [/] is: -1
21:55:04.912 [qtp581501261-25 - /] DEBUG o.s.w.s.r.ResourceHttpRequestHandler - Ignoring invalid resource path []
21:55:04.913 [qtp581501261-25 - /] DEBUG o.s.w.s.r.ResourceHttpRequestHandler - No matching resource found - returning 404
21:55:04.913 [qtp581501261-25 - /] DEBUG o.s.web.servlet.DispatcherServlet - Null ModelAndView returned to DispatcherServlet with name 'spring': assuming HandlerAdapter completed request handling
21:55:04.913 [qtp581501261-25 - /] TRACE o.s.web.servlet.DispatcherServlet - Cleared thread-bound request context: SecurityContextHolderAwareRequestWrapper[ FirewalledRequest[ [GET /]@604241542 org.eclipse.jetty.server.Request@2403fe86]]
21:55:04.914 [qtp581501261-25 - /] DEBUG o.s.web.servlet.DispatcherServlet - Successfully completed request
21:55:04.914 [qtp581501261-25 - /] TRACE o.s.w.c.s.XmlWebApplicationContext - Publishing event in WebApplicationContext for namespace 'spring-servlet': ServletRequestHandledEvent: url=[/]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[spring]; session=[null]; user=[null]; time=[15ms]; status=[OK]
21:55:04.914 [qtp581501261-25 - /] TRACE o.s.w.c.s.XmlWebApplicationContext - Publishing event in Root WebApplicationContext: ServletRequestHandledEvent: url=[/]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[spring]; session=[null]; user=[null]; time=[15ms]; status=[OK]
21:55:04.914 [qtp581501261-25 - /] DEBUG o.s.s.w.a.ExceptionTranslationFilter - Chain processed normally
21:55:04.914 [qtp581501261-25 - /] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
21:55:04.914 [qtp581501261-25 - /] DEBUG org.eclipse.jetty.server.Server - RESPONSE /  404 handled=true
21:55:04.915 [qtp581501261-25] DEBUG o.e.jetty.server.AsyncHttpConnection - Enabled read interest SCEP@7cee85c5{l(/0:0:0:0:0:0:0:1:51001)<->r(/0:0:0:0:0:0:0:1:8080),d=true,open=true,ishut=false,oshut=false,rb=false,wb=false,w=true,i=0r}-{AsyncHttpConnection@f908897,g=HttpGenerator{s=4,h=0,b=0,c=-1},p=HttpParser{s=0,l=9,c=0},r=1}
21:55:04.915 [qtp581501261-25] DEBUG org.eclipse.jetty.http.HttpParser - filled 0/0

4 Answers 4

1

Have you defined a welcome file in your web.xml?

For example

<welcome-file-list>
    <welcome-file>index.html</welcome-file>
</welcome-file-list>

where index.html is a file in your project root folder.

Sign up to request clarification or add additional context in comments.

1 Comment

Yes, but this didn't do the trick. Exactly the same thing happened.
1

I had the same problem and I solved, as you advised, prefixing the rest api pattern managed by Spring.

The only difference is that I didn't used the url rewriter because all my angular files are deployed in the root of the web app, so directly accessible.

My Angular project is actually in a different folder, as a different module and is added during the war build with this configuration:

<plugin>
     <artifactId>maven-war-plugin</artifactId>
     <version>2.3</version>
     <configuration>
         <webResources>
             <resource>
                 <directory>${project.parent.basedir}/ANGULAR-PROJECT-NAME
             </resource>
         </webResources>
     </configuration>
</plugin>

like explained here. Hope this helps, regards.

Comments

1

The main problem is that ResourceHttpRequestHandler which is registered by <mvc:resource> doesnt resolve to foo/index.html when requesting foo/

A parameter that would enable this behavior would be nice.

Comments

0

I have solved my problem the following way.

  • First I have changed the path from the servlet-mapping to /api/*. This resultet that I had to adjust the path in the MVC Controllers from @RequestMapping("/api/role") to @RequestMapping("/role"). Otherwise the URL would have responded to /api/api/role.
  • Then I have integrated urlrewrite http://tuckey.org/urlrewrite/. I'm using Maven so I added it to the pom.xml. I did this because the mvc:resource mapping directive seems only to work when the DispatcherServlet is used. And this was what I wanted to disable for my static html pages.
  • Then I have added the filter to the web.xml file and created a urlrewrite.xml file in the WEB-INF directory as seen below.
  • With urlrewrite I could set a condition that excludes /api/** (directory with my Spring MVC based REST Controllers) but rewrites anything else from /** to /resources/ang2/app directory (my angularjs directory).
  • Also I have deleted the mvc:resource directive from applicationContext.xml.

web.xml

<!-- Setup urlrewrite filter -->
<filter>
    <filter-name>UrlRewriteFilter</filter-name>
    <filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
    <init-param>
        <param-name>logLevel</param-name>
        <param-value>WARN</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>UrlRewriteFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<!-- Spring related section -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
    <servlet-name>spring</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    <param-name>dispatchOptionsRequest</param-name>
    <param-value>true</param-value>
    </init-param>
</servlet>

<servlet-mapping>
    <servlet-name>spring</servlet-name>
    <url-pattern>/api/*</url-pattern>
</servlet-mapping>

urlrewrite.xml

<!-- Use urlrewritefilter to redirect / requests to angularjs directory -->
<urlrewrite default-match-type="wildcard">
<rule>
    <!-- exclude everything in /api/** directory -->
    <condition type="request-uri" operator="notequal">/api/**</condition>
    <!-- everything else from / is redirected to the angularjs directory -->
    <from>/**</from>
    <to>/resources/ang2/app/$1</to>
</rule>
</urlrewrite>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.