Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON views from namespaced controller are not resolved when running from WAR file #200

Open
delfianto opened this issue Dec 29, 2018 · 7 comments

Comments

@delfianto
Copy link

delfianto commented Dec 29, 2018

This probably related with the closed issue #186

Environment
Linux amd64, OpenJDK 1.8.0_192, Grails 3.3.9, Grails View 1.2.9

Steps to reproduce

  1. Clone the sample project at https://github.com/delfianto/grails-json-view-test
  2. Run the grails app with run-app
  3. Test the endpoint /message?text=hello and /v1/message?text=hello (they work)
  4. Build war file using grails assemble
  5. Run the war file using java -jar grails-json-view-test-0.1.war
  6. The endpoint with namespace will throw an exception while the endpoint without namespace works

Description
When the grails app is started by run-app command, JSON view for namespaced controllers works fine. If the application is started from a war archive somehow the view does not resolve properly, resulting in the exception below:

2018-12-29 14:25:45.670 DEBUG --- [0.0-9090-exec-2] g.views.ResolvableGroovyTemplateEngine   : No template found for path [v1/message/index.gson] and locale [en_US]
2018-12-29 14:25:45.670 DEBUG --- [0.0-9090-exec-2] g.views.ResolvableGroovyTemplateEngine   : No template found for path [v1/v1/message/index.gson] and locale [en_US]
2018-12-29 14:25:45.670 DEBUG --- [0.0-9090-exec-2] g.views.ResolvableGroovyTemplateEngine   : No template found for path [index.gson] and locale [en_US]
2018-12-29 14:25:45.671 ERROR --- [0.0-9090-exec-2] .a.c.c.C.[.[.[.[grailsDispatcherServlet] : Servlet.service() for servlet [grailsDispatcherServlet] in context with path [] threw exception [Could not resolve view with name 'index' in servlet with name 'grailsDispatcherServlet'] with root cause

javax.servlet.ServletException: Could not resolve view with name 'index' in servlet with name 'grailsDispatcherServlet'
        at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1266)
        at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1041)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:984)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:111)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:77)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:67)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:96)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:103)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:493)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:800)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:806)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1498)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:748)
@jeffscottbrown
Copy link
Member

I cloned the project and run ./gradlew assemble && java -jar build/libs/grails-json-view-test-0.1.war. Then I get the following responses:

 $ curl http://localhost:8080/message?text=hello
{"message":"hello"}
 $ 
 $ 
 $ curl http://localhost:8080/v1/message?text=hello
{"message":"hello"}
 $ 

@jeffscottbrown
Copy link
Member

btw... I don't think your responseFormats property has any impact on the app's behavior.

@delfianto
Copy link
Author

delfianto commented Jan 1, 2019

Found a new interesting result from my testing, if I run the war file using java -jar build/libs/grails-json-view-test-0.1.war from the project directory it worked just as your post above.

If I move the war file somewhere else, let's say my home directory, or ~/Downloads then the namespaced controller will throw exception. Actually I found this issue initially when deploying war file into a Google App Engine instance and noticing that all of my namespaced controller keeps throwing exception.

@jeffscottbrown
Copy link
Member

Thanks for the feedback.

@delfianto
Copy link
Author

delfianto commented Jan 22, 2019

Apparently this bug affects all deployment in production environment, noticed this when trying to deploy grails app to our staging server (not running war but using traditional deployment with Jetty).

Here's some log on a non-namespaced controller:

2019-01-22 13:39:14.801 DEBUG --- [44-32] grails.views.ResolvableGroovyTemplateEngine        : Found template class [emvazo_core_app_index_gson] for path [/app/index.gson]
2019-01-22 13:39:14.814 TRACE --- [44-32] grails.views.mvc.GenericGroovyTemplateView         : Rendering view with name 'null' with model {pluginManager=grails.plugins.DefaultGrailsPluginManager@58a68fca, grailsApplication=grails.core.DefaultGrailsApplication@658255aa} and static attributes {}
2019-01-22 13:39:14.815 DEBUG --- [44-32] grails.views.ResolvableGroovyTemplateEngine        : Found cached template for path [/app/index.gson] and locale [en_US]

And here's some log from a namespaced controller, notice v1 becomes 1

2019-01-22 13:44:37.925 TRACE --- [53-24] ricGroovyTemplateResolver : Attempting to load class [emvazo_core_1_country_show_en_hal_gson] for template [v1/country/show_en_hal.gson]
2019-01-22 13:44:37.926 TRACE --- [53-24] ricGroovyTemplateResolver : Attempting to load class [1_country_show_en_hal_gson] for template [v1/country/show_en_hal.gson]
2019-01-22 13:44:37.926 TRACE --- [53-24] ricGroovyTemplateResolver : Attempting to load class [i18n_1_country_show_en_hal_gson] for template [v1/country/show_en_hal.gson]
...

After debugging and setting some breakpoints in the classes, I found that this issue may have originated from resolveTemplateName(String scope, String path) method in GenericGroovyTemplateResolver.groovy class.

    static String resolveTemplateName(String scope, String path) {
        path = path.substring(1) // remove leading slash '/'
        path = path.replace(File.separatorChar, UNDERSCORE_CHAR)
        path = path.replace(SLASH_CHAR, UNDERSCORE_CHAR)
        path = path.replace(DOT_CHAR, UNDERSCORE_CHAR)
        if(scope) {
            scope = scope.replaceAll(/[\W\s]/, String.valueOf(UNDERSCORE_CHAR))
            path = "${scope}_${path}"
        }
        return path
    }

The first line will always remove a single character from the path even if they does not contains any leading forward slash character. This can make the classloader to never found the classname because somehow the path parameter that comes from a namespaced controller does not contains a leading forward slash, thus v1 becomes 1 or system becomes ystem.

Perhaps a better solution for removing the leading slash character is to use regex like:
path.replaceAll('^/+', '')

@davydotcom
Copy link
Contributor

pushed a fix to check if path starts with '/' to 2.0.x branch.

@puneetbehl
Copy link
Contributor

@davydotcom Could you please verify if this is fixed in one of the recent releases, if yes? please attach the milestone and close this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants