Статьи

Добавление «облегченной» Groovy веб-консоли в войну Grails

Предположим, у вас есть приложение Grails, развернутое на сервере — как бы вы узнали, как было настроено приложение? Если у вас есть исходный код, вы можете просмотреть Config.groovy , BuildConfig.groovy и т. Д. (В данном случае я говорю о приложении Grails 2, но эти идеи можно обобщить до Grails 3+), но этого часто недостаточно.

Grails 2 поддерживает внешние файлы конфигурации, которые могут находиться в разных местах и ​​объединяться в окончательную конфигурацию. Но просто иметь то, что вы считаете правильным источником, и конфигурационные файлы недостаточно, так как могли быть внесены изменения, которые бы не превратили его в систему контроля версий. И вы не можете легко получить информацию из этих файлов в WAR, так как они скомпилированы в классы.

Я предпочитаю копаться в работающем приложении Grails — это консольный плагин, но для его использования вам нужно добавить его в BuildConfig.groovy и создать и развернуть новый WAR, но опять-таки, он не обязательно будет иметь ту же конфигурацию, что и предыдущий развертывание.

У меня такая ситуация на работе, поэтому я придумал легкий способ добавить веб-консоль, похожую на плагин консоли, в WAR. Первоначально это был сервлет, который генерировал HTML-код для простой формы, содержащей текстовую область для кода Groovy и кнопку отправки для публикации кода, запускаемого на сервере, и логику (в основном заимствованную из плагина консоли) для выполнения кода и вернуть результаты в браузер. Я скомпилировал его в том же проекте, из которого была построена WAR, чтобы убедиться, что он совместим с версиями Groovy, Grails, Spring и т. Д., И скопировал файл .class в WEB-INF/classes в разобранном каталоге в папке webapps Tomcat. и вручную отредактировал WEB-APP/web.xml чтобы добавить необходимые элементы <servlet> и <servlet-mapping> , и все отлично работало в моем небольшом тестовом приложении.

Но когда я попробовал это в реальном приложении, я не смог получить к нему доступ из-за Spring Security. В этом конкретном случае я мог бы обойти это, потому что приложение хранит экземпляры Requestmap в базе данных, но я не хотел вносить изменения, которые я мог бы забыть отменить, и есть проблема курицы и яйца, которую я не делаю обязательно знать, какие настройки базы данных для этого развертывания. Поэтому вместо этого я преобразовал сервлет в фильтр сервлетов и убедился, что добавлял фильтр перед цепочкой фильтров Spring Security в web.xml и он работал должным образом после перезапуска сервера.

Я внес изменения в развернутый каталог war, но также можно внести изменения и в сам файл WAR. Поскольку файлы WAR являются файлами ZIP, вы можете разархивировать WAR, внести изменения и повторно сжать.

Вот источник для фильтра:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
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>
"""
   }
}

и это соответствующие элементы <filter> и <filter-mapping> для web.xml :

1
2
3
4
5
6
7
8
<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>

Для доступа к консоли перейдите по адресу http: // server: port / contextPath / hack. Как и в консольном плагине, вы можете запускать произвольный код Groovy (включая вызовы сервисных методов, работу с классами домена и т. Д.), И вы можете использовать несколько объектов в Binding — config , ctx , grailsApplication , out , request и session

Чтобы изменить URI из / hack на что-то другое, обязательно обновите <url-pattern> в web.xml и атрибут action в сгенерированной форме в классе фильтра.

Предположим, у вас есть приложение Grails, развернутое на сервере — как бы вы узнали, как было настроено приложение? Если у вас есть источник, то вы …

Эта запись была опубликована в четверг, 7 декабря 2017 года в 8:23 и подана в рублях , Groovy , Java , безопасности . Вы можете следить за любыми ответами на эту запись через канал RSS 2.0 . Вы можете оставить отзыв (комментарии модерируются) или трекбек со своего сайта.

Смотрите оригинальную статью здесь: Добавление «облегченной» веб-консоли Groovy в войну Grails

Мнения, высказанные участниками Java Code Geeks, являются их собственными.