Adding a “lite” Groovy web console to a Grails war
Suppose you have a Grails application deployed to a server – how would you go about finding out how the application was configured? If you have the source then you can view Config.groovy
, BuildConfig.groovy
, etc. (in this case I’m talking about a Grails 2 app but these ideas are generalizable to Grails 3+) but that’s often not enough.
Grails 2 supports external configuration files, which can be in various places and get merged into the final configuration. But just having what you think is the correct source and configuration files isn’t enough since changes could have been made that didn’t make it into source control. And you can’t easily get information from those files in a WAR since they’re compiled into classes.
My preference for digging into a running Grails application is the console plugin, but to use that you would need to add it to BuildConfig.groovy
and build and deploy a new WAR, but again that’s not necessarily going to have the same configuration as the previous deployment.
I have a situation like this at work,so I came up with a lightweight way to add a web-based console similar to the console plugin to a WAR. Originally it was a servlet which generated the HTML for a simple form containing a textarea for Groovy code and a submit button to post the code to be run on the server, and the logic (mostly borrowed from the console plugin) to execute the code and return the results to the browser. I compiled it in the same project that the WAR was built from to ensure that it’s compatible with the versions of Groovy, Grails, Spring, etc. and copied the .class file to WEB-INF/classes
in the exploded directory in Tomcat’s webapps
folder, and manually edited WEB-APP/web.xml
to add the required <servlet>
and <servlet-mapping>
elements, and everything worked great in my small test app.
But when I tried it in the real application I couldn’t access it because of Spring Security. In this particular case I could have worked around that because the app stores Requestmap
instances in the database, but I didn’t want to make changes that I might forget to undo, and there’s the chicken-and-egg problem that I don’t necessarily know what the database settings are for this deployment. So instead I converted the servlet to a servlet filter, and made sure to add the filter before the Spring Security filter chain in web.xml
and it worked as expected after restarting the server.
I made the changes in the exploded war directory, but it’s also possible to make the changes in the WAR file itself. Since WAR files are ZIP files, you can unzip the WAR, make the changes, and re-zip.
Here’s the source for the filter:
package com.burtbeckwith.hack import groovy.transform.CompileStatic import groovy.util.logging.Slf4j import org.codehaus.groovy.grails.commons.GrailsApplication import org.springframework.context.ApplicationContext import org.springframework.web.context.support.WebApplicationContextUtils 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 @CompileStatic @Slf4j class HackFilter implements Filter { private ApplicationContext applicationContext private GrailsApplication grailsApplication void init(FilterConfig fc) throws ServletException { applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(fc.servletContext) grailsApplication = applicationContext.getBean(GrailsApplication) } void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req HttpServletResponse response = (HttpServletResponse) res if ('GET' == request.method) { doGet request, response } else { // assume POST doPost request, response } } void destroy() {} private void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.writer.write html(request.contextPath) } private void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis() String code = request.getParameter('code') ByteArrayOutputStream baos = new ByteArrayOutputStream() PrintStream out = new PrintStream(baos) PrintStream systemOut = System.out Throwable e String result = '' try { System.out = out result = new GroovyShell(grailsApplication.classLoader, new Binding( config: grailsApplication.config, ctx: applicationContext, grailsApplication: grailsApplication, out: out, request: request, session: request.session)).evaluate(code) } catch (Throwable t) { e = t } finally { System.out = systemOut } if (e) { StringWriter sw = new StringWriter() e.printStackTrace new PrintWriter(sw) result = sw.toString().replace('\t', ' ').replace(System.getProperty('line.separator'), '<br/>\n') } response.writer << html(request.contextPath, code, """\ Total time: ${System.currentTimeMillis() - startTime}ms Stdout: ${baos.toString('UTF8')} ${e ? 'Exception' : 'Result'}: $result""") } private String html(String contextPath, String code = '', String results = '') { """\ <html> <head> <title>Hack</title> </head> <body> <form action="$contextPath/hack" method="POST"> <span>Code: (binding vars include <i>config</i>, <i>ctx</i>, <i>grailsApplication</i>, <i>out</i>, <i>request</i>, <i>session</i>)</span><br/> <textarea name="code" cols="120" rows="25">$code</textarea><br/> <input type="submit" value="Execute" name="execute" /><br/> <span>Results:</span><br/> <textarea name="results" cols="120" rows="25" disabled="disabled">$results</textarea> </form> </body> </html> """ } }
and these are the corresponding <filter>
and <filter-mapping> elements for web.xml
:
<filter> <filter-name>hack</filter-name> <filter-class>com.burtbeckwith.hack.HackFilter</filter-class> </filter> <filter-mapping> <filter-name>hack</filter-name> <url-pattern>/hack</url-pattern> </filter-mapping>
To access the console, navigate to http://server:port/contextPath/hack. As in the console plugin you can run arbitrary Groovy code (including service method calls, working with domain classes, etc.), and there are several objects in the Binding that you can use – config
, ctx
, grailsApplication
, out
, request
, and session
.
To change the uri from /hack to something else, be sure to update both the <url-pattern>
tag in web.xml
and the action
attribute in the generated form in the filter class.
This entry was posted on Thursday, December 07th, 2017 at 8:23am and is filed under grails, groovy, java, security. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response (comments are moderated) or trackback from your own site.
Published on Java Code Geeks with permission by Burt Beckwith, partner at our JCG program. See the original article here: Adding a “lite” Groovy web console to a Grails war Opinions expressed by Java Code Geeks contributors are their own. |