Details

    • Type: Sub-task Sub-task
    • Status: Closed
    • Priority: Minor Minor
    • Resolution: Fixed
    • Affects Version/s: 0.6, 1.0.3
    • Fix Version/s: 1.1-beta3
    • Component/s: None
    • Labels:
      None

      Description

      Trying to use RequestDispatcher to include another controller & action fails, despite the efforts of GRAILS-304. I troubleshooted why this is the case and made things work in my application, with a few changes.

      1. The UrlMappings servlet filter needs to execute for this request. There are two reasons why it won't and they need to get resolved:
      a. In the web.xml, the filter-mapping element needs a <dispatcher>REQUEST</dispatcher> and a <dispatcher>INCLUDE</dispatcher> Due to the following issue I was forced to modify grails to make this change:
      http://www.nabble.com/Deferred-output-of-web.xml-past-doWithWebDescriptor()-makes-some-mutations-impossible.-td18719230.html
      b. The UrlMappingsFilter is a Spring OncePerRequestFilter which blocks it from working on the include. So at the point I needed to use the request dispatcher, I removed 'urlMapping.FILTERED' from the request attributes, and add it after the include.

      2. The url to get the request dispatcher must start with a '/' and thus be web-app-context relative because for whatever reason if there is no leading slash, the current request looks like "/grails/controller/action" whereas "/grails" is NOT the name of the application or a controller; it appears to be an internal servlet name, however. UrlMappingsFilter gets confused at this. My workaround was simply to make sure my URLs are webapp relative.

      3. The requestDispatcher deals directly with the response, even if there might be buffered text in the output stream already ("out"). So I was forced to invoke flush() beforehand.

      In the comments on GRAILS-304 I asked it to be re-openned. Since it wasn't, you're looking at another bug report instead of a comment on that one

        Issue Links

          Activity

          Hide
          Graeme Rocher added a comment -

          Patches welcome

          Show
          Graeme Rocher added a comment - Patches welcome
          Hide
          David Smiley added a comment -

          I spent an inordinate amount of time digging deeper into this and I want to add some information. My "description" in JIRA isn't sufficient to get things working. I have a snippet of code in a taglib that looks like this:

                //url looks like '/myyappname/person/dataTable'
                url = url.replaceFirst("^/[^/]+",'')
                //url now looks like: (for example)   '/person/dataTable'
          
                def dispatcher = request.getRequestDispatcher(url)
                def attrNames = ['urlMapping.FILTERED'] //TODO temp hack until grails fixes
                def oldAttrVals = attrNames.collect { request.getAttribute(it) }
                def webRequest = WebUtils.retrieveGrailsWebRequest()
                def origOut = webRequest.getOut()
                try {
                   attrNames.each { request.removeAttribute(it) }
                   def newResponse = new RDInclusionWrappedResponse(response,out)
                   log.debug("BEFORE INCLUDE to $url")
                   dispatcher.include(request,newResponse)
                } catch (Exception e) {
                   includeError(out,url,e)
                } finally {
                   log.debug("AFTER INCLUDE of $url")
                   attrNames.eachWithIndex {name,i -> request.setAttribute(name,oldAttrVals[i])}
                   webRequest.setOut(origOut)
                }
          

          And the RDInclusionWrapperResponse looks like this:

          public class RDInclusionWrappedResponse extends HttpServletResponseWrapper {
          
             private PrintWriter out;
          
             public RDInclusionWrappedResponse(HttpServletResponse httpServletResponse,
                                               PrintWriter out) {
                super(httpServletResponse);
                this.out = new PrintWriter(out) {
                   public void close() {
                      flush();
                      //no close
                   }
                };
             }
          
             public PrintWriter getWriter() throws IOException {
                return out;
             }
          
             public ServletOutputStream getOutputStream() throws IOException { //this is actually needed, though you won't see the exception
                throw new IllegalStateException("Expected to get the writer");
             }
          }
          

          And finally to activate UrlMappingsFilter for includes, I basically copied the code at the end of this:
          http://amorproximi.blogspot.com/2008/07/more-on-grails-declarative-error.html
          into an Events.groovy in the scripts/ but change the code to apply to the "urlMapping" named filter.

          However, doing all this isn't quite perfect but it was good enough for my purposes. I also think a more thorough solution would also activate GrailsWebRequestFilter for includes, and there is code in that filter and also in UrlMappingsFilter that manipulate thread-locals and/or attributes in the request that are in-effect like a global (and globals are of course evil). Although it wasn't necessary to get my app working, I also manipulated those to grails classes to save off the old globals and then restore them when the filter is done.

          Show
          David Smiley added a comment - I spent an inordinate amount of time digging deeper into this and I want to add some information. My "description" in JIRA isn't sufficient to get things working. I have a snippet of code in a taglib that looks like this: //url looks like '/myyappname/person/dataTable' url = url.replaceFirst( "^/[^/]+" ,'') //url now looks like: ( for example) '/person/dataTable' def dispatcher = request.getRequestDispatcher(url) def attrNames = ['urlMapping.FILTERED'] //TODO temp hack until grails fixes def oldAttrVals = attrNames.collect { request.getAttribute(it) } def webRequest = WebUtils.retrieveGrailsWebRequest() def origOut = webRequest.getOut() try { attrNames.each { request.removeAttribute(it) } def newResponse = new RDInclusionWrappedResponse(response,out) log.debug( "BEFORE INCLUDE to $url" ) dispatcher.include(request,newResponse) } catch (Exception e) { includeError(out,url,e) } finally { log.debug( "AFTER INCLUDE of $url" ) attrNames.eachWithIndex {name,i -> request.setAttribute(name,oldAttrVals[i])} webRequest.setOut(origOut) } And the RDInclusionWrapperResponse looks like this: public class RDInclusionWrappedResponse extends HttpServletResponseWrapper { private PrintWriter out; public RDInclusionWrappedResponse(HttpServletResponse httpServletResponse, PrintWriter out) { super (httpServletResponse); this .out = new PrintWriter(out) { public void close() { flush(); //no close } }; } public PrintWriter getWriter() throws IOException { return out; } public ServletOutputStream getOutputStream() throws IOException { // this is actually needed, though you won't see the exception throw new IllegalStateException( "Expected to get the writer" ); } } And finally to activate UrlMappingsFilter for includes, I basically copied the code at the end of this: http://amorproximi.blogspot.com/2008/07/more-on-grails-declarative-error.html into an Events.groovy in the scripts/ but change the code to apply to the "urlMapping" named filter. However, doing all this isn't quite perfect but it was good enough for my purposes. I also think a more thorough solution would also activate GrailsWebRequestFilter for includes, and there is code in that filter and also in UrlMappingsFilter that manipulate thread-locals and/or attributes in the request that are in-effect like a global (and globals are of course evil). Although it wasn't necessary to get my app working, I also manipulated those to grails classes to save off the old globals and then restore them when the filter is done.
          Hide
          Graeme Rocher added a comment -

          Reduced priority of non critical issues which have current workarounds

          Show
          Graeme Rocher added a comment - Reduced priority of non critical issues which have current workarounds
          Hide
          Graeme Rocher added a comment -

          You can now use the new include tag to do this from a view:

          <g:include controller="foo" action="test"></g:include>
          

          Or from a controller:

          def content = include(controller:"foo", action:"bar")
          
          Show
          Graeme Rocher added a comment - You can now use the new include tag to do this from a view: <g:include controller= "foo" action= "test" ></g:include> Or from a controller: def content = include(controller: "foo" , action: "bar" )

            People

            • Assignee:
              Graeme Rocher
              Reporter:
              David Smiley
            • Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:

                Development