Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Gr8conf EU 2013 Speed up your development: GroovyServ and Grails Improx Plugin

Gr8conf EU 2013 Speed up your development: GroovyServ and Grails Improx Plugin

Yasuharu Nakano

March 25, 2023
Tweet

More Decks by Yasuharu Nakano

Other Decks in Programming

Transcript

  1. $  time  groovy  -­‐e  “println  ‘Hello,  Groovy’” Hello,  Groovy real

           0m0.823s user        0m1.016s sys          0m0.096s But, It takes 1 sec!
  2. Why so slow? • JVM initialization • Loading Many Many

    Classes • .... from Disk http://en.wikipedia.org/wiki/Java_performance#Startup_time “ It seems that much of the startup time is due to IO- bound operations rather than JVM initialization or class loading (the rt.jar class data file alone is 40 MB and the JVM must seek a lot of data in this huge file).
  3. groovyConsole • Handy for trying Java/Groovy APIs, etc. • But

    features as editor aren’t so rich: • No code completion • No syntax checking • No auto import • etc. • I want to use my favorite IDE/editor!
  4. GroovyServ “GroovyServ makes Groovy’s startup time much faster, by pre-invoking

    Groovy as a server.” http://kobo.github.io/groovyserv/
  5. $  time  groovyclient  -­‐e  “println  ‘Hello,  GroovyServ’” Hello,  GroovyServ real

           0m0.031s user        0m0.001s sys          0m0.002s Normal  Groovy: real        0m0.823s user        0m1.016s sys          0m0.096s
  6. Normal Groovy User Groovy Script Shell Environment Java VM File

    System C LASSPATH cm d line args stdin stdout stderr Ctrl-C other EN Vs exit status System .in System .out System .err groovy User Groovy Script
  7. Shell Environment Java VM File System User Groovy Script C

    LASSPATH cm d line args stdin stdout stderr Ctrl-C other EN Vs ≪file≫ Authtoken exit status TCP/IP System .in System .out System .err groovyserver groovyclient GroovyServ
  8. GroovyServ Way • Write code with your favorite IDE/editor •

    Run the code by using GroovyServ as External Tool • You can also see the result on a console of IDE
  9. GroovyServ “GroovyServ makes Groovy’s startup time much faster, by pre-invoking

    Groovy as a server.” http://kobo.github.io/groovyserv/
  10. How to Install • Downloadable Binaries for • Mac OS

    X (built at Mac OS X 10.8 (x86_64)) • Windows • bundled in Groovy Windows Installer • (built at WindowsXP (32bit) (x86)) • (built at Windows7 (64bit) (AMD64)) • Linux (i386) (linked with glibc 2.9) • Self-build (required Gradle) • Homebrew (only Mac) • $  brew  install  groovyserv • http://kobo.github.io/groovyserv/howtoinstall.html
  11. No setting required • If there is a binary set,

    you can use it: • $  /xxx/groovyserv/bin/groovyclient • Adding to PATH, you can easily use it: • $  groovyclient
  12. groovyclient • Invokes a groovy file or one-liner code, by

    sending a request to server • Also invokes groovyserver process automatically if not exists
  13. groovyserver • Invokes a JVM process as a server. •

    Should be used only when you want to specify special options. • e.g. • -­‐v                        verbose  option • -­‐-­‐allow-­‐from    for  remote  feature
  14. Jython $  alias  gythonserver="env  CLASSPATH=/your/jython.jar  groovyserver" $  alias  gython="groovyclient  -­‐e

     'import  org.python.util.jython;   jython.main(args)'  -­‐-­‐" $  gythonserver  -­‐r ... $  time  gython  -­‐c  "print('Hello')" Hello real        0m0.057s user        0m0.001s sys          0m0.004s
  15. Clojure $  alias  glojureserver="env  CLASSPATH=/your/clojure.jar  groovyserver" $  alias  glojure="groovyclient  -­‐e

     'import  clojure.main;main.main(args)'  -­‐-­‐" $  glojureserver  -­‐r ... $  time  glojure  -­‐e  "(println  'Hello)" Hello real        0m0.053s user        0m0.001s sys          0m0.004s main  method  of closure.main  class
  16. • A script to get schedule data from groupware •

    As editor’s macro • As mail filter • etc. Using at Runtime
  17. Other Features • Access Control • Propagation of CLASSPATH •

    Propagation of Environment Variables • Handling System#exit() • Dynamic CWD
  18. How to run a specified test fast • Run only

    a unit test • test-­‐app  unit:  <FQCN  of  TestClass> • Run only a integration test • test-­‐app  integration:  <FQCN  of  TestClass> • Run only a functional test • test-­‐app  functional:  <FQCN  of  TestClass>
  19. If test phase not specified when run unit test, integration

    phase is also run. $  cat  test/unit/test/app/sample/FooTests.groovy .... class  FooTests  {        void  testSomething()  {                //  do  nothing        } } $  time  grails  test-­‐app  FooTests |  Packaging  Grails  application..... |  Tests  PASSED  -­‐  view  reports  in  /xxx/test-­‐reports real        0m15.375s user        0m35.119s sys          0m1.858s Oops!Integrationphaserunning!?
  20. $  time  grails  test-­‐app  unit:  FooTests |  Completed  1  unit

     test,  0  failed  in  546ms |  Tests  PASSED  -­‐  view  reports  in  /xxx/test-­‐reports real        0m7.303s user        0m18.068s sys          0m0.909s Lessthanahalfofprevioussample! Specifying a phase can avoid running an extra phase Without  a  phase: real        0m15.375s user        0m35.119s sys          0m1.858s
  21. Required to run test-app • FQCN of target test class

    • Test phase of target test class
  22. How do you run a test while writing it? •

    (A) By IDE support (GGTS, IntelliJ, etc.) • (B) By “grails test-app” as standalone • (C) By “grails test-app” via Interactive Mode • (D) Others
  23. Interactive Mode • Very very useful • Makes command execution

    faster because the JVM doesn't have to be restarted for each command • $  grails • TAB completion is available • Since Grails 2.0
  24. But still there is trouble • It needs frequent switching

    of windows for use while writing code in IDE/editor • To run a test, you must prepare: • FQCN of target test class • Test phase of target test class
  25. Improx Plugin Provides the way of using interactive mode from

    other process via TCP. http://grails.org/plugin/improx
  26. Improx Plugin • You can run a test • Faster

    than general IDE supports because using interactive mode • Directly from IDE/editor without complicated operations
  27. Grails Improx Shell Environment ≪script≫ improxClient TCP/IP ≪script≫ improxSmartInvoker ≪class≫

    InteractiveMode ≪class≫ GrailsConsole ≪command≫ improx-start ≪command≫ improx-stop ≪command≫ improx-install-resources other clients ≪class≫ GrailsScriptRunner echo back delegate run script start stop grails command expand client scripts use
  28. • As same as interactive mode • install-­‐plugin • uninstall-­‐plugin

    • Special built-in commands • create-­‐app • quit • stop-­‐app • exit • !(shell  command) • Using threads complexly • run-­‐app Unsupported Commands
  29. Install Client Scripts $  grails  improx-­‐install-­‐resources |  Improx  resources  installed

     successfully:  /xxx/improx-­‐resources $  chmod  +x  improx-­‐resources/scripts/*.sh $  mv  improx-­‐resources  $HOME/.improx-­‐resources Ifonceyoustorethemtoglobal path,youdon'tneedthepreparation forotherprojects.
  30. improx-­‐start  /  improx-­‐stop • improx-­‐start • Starts an interactive mode

    proxy server • improx-­‐stop • Stops the interactive mode proxy server which is running
  31. improx-­‐install-­‐resources • Installs “improx-resources” directory which has some client scripts

    into your application project improx-­‐resources/  !""  scripts            #""  improxClient.groovy            #""  improxClient.sh            #""  improxSmartInvoker.groovy            !""  improxSmartInvoker.sh
  32. $  improxClient.sh  help Executing  'help'  via  interactive  mode  proxy... ......

    Usage  (optionals  marked  with  *): grails  [environment]*  [options]*  [target]   [arguments]* ...(snipped)... test-­‐app tomcat uninstall-­‐plugin upgrade war wrapper $
  33. All you have to do is to set improxSmartInvoker as

    External Tool of IDE/editor http://kobo.github.io/grails-improx/guide/integrationWithEditors.html
  34. TCP • Request: COMMAND • Whole response is result of

    the command • You can also use as client • e.g. telnet, nc
  35. $  telnet  localhost  8096 Trying  ::1... Connected  to  localhost. Escape

     character  is  '^]'. help ...... Usage  (optionals  marked  with  *): grails  [environment]*  [options]*  [target]   [arguments]* ...(snipped)... uninstall-­‐plugin upgrade war wrapper $
  36. HTTP • Request GET  /COMMAND  HTTP/[0-­‐9.]+ • Whole response is

    result of the command • You can also use as client • e.g. Web browser, wget, curl
  37. • GroovyServ • http://kobo.github.io/groovyserv/index.html • https://github.com/kobo/groovyserv • Improx - Interactive

    Mode Proxy • http://kobo.github.io/grails-improx/guide/ • http://grails.org/plugin/improx • https://github.com/kobo/grails-improx • Demo code • https://github.com/nobeans/gr8confeu2013-demo
  38. Under the hood • Client-Server Communication • Access Control •

    Propagation of CLASSPATH • Propagation of Environment Variables • Handling System#exit() • Dynamic CWD
  39. groovyclient ClientConnection OutputStream ≪script≫ println “Hello, GroovyServ!” ≪System.out≫ DynamicDelegatedPrintStream writes

    data converted based on a protocol into output stream of socket Hello, GroovyServ! if Channel header == ”err” To STDERR if Channel header == ”out” To STDOUT Client console
  40. ClientConnection PipedOutputStream PipedInputStream groovyclient ≪thread≫ StreamRequestHandler ≪script≫ System.in.eachLine { ...

    } Connected rawData = Socket.inputStream.read() bodyData = convert(rawData) pipedOutputStream.write(bodyData) ≪System.in≫ DynamicDelegatedInputStream
  41. InvocationRequest 'Cwd:' <cwd> LF 'Arg:' <arg1> LF 'Arg:' <arg2> LF

    : 'Env:' <env1>=<value1> LF 'Env:' <env2>=<value2> LF : 'Cp:' <classpath> LF 'AuthToken:' <authToken> LF LF where: <cwd> is current working directory. <arg1>,<arg2>.. are commandline arguments which must be encoded by Base64. (optional) <env1>,<env2>.. are environment variable names which sent to the server. (optional) <value1>,<valeu2>.. are environment variable values which sent to the server. (optional) <classpath> is the value of environment variable CLASSPATH. (optional) <authToken> is authentication value which certify client is the user who invoked the server. LF is line feed (0x0a, '\n').
  42. StreamRequest 'Size:' <size> LF LF <body from STDIN> where: <size>

    is the size of body to send to server. <size>==-1 means client exited. <body from STDIN> is byte sequence from standard input.
  43. StreamResponse 'Channel:' <id> LF 'Size:' <size> LF LF <body for

    STDERR/STDOUT> where: <id> is 'out' or 'err', where 'out' means standard output of the program. 'err' means standard error of the program. <size> is the size of chunk. <body from STDERR/STDOUT> is byte sequence from standard output/error.
  44. Client Address Checking / Authtoken • Request only from loopback

    address is allowed. • Authtoken is generated and stored into a file when server process starts. • $HOME/.groovy/groovyserver/authtoken-­‐<PORT> • Authtoken always is used for checking that the request is from valid owner user.
  45. -­‐-­‐allow-­‐from • By default, A request only from loop-back address

    is available. • You can allow specified addresses. $  groovyserver  -­‐-­‐allow-­‐from  192.168.1.1 SECURITY RISK: Be careful when using this option
  46. -­‐-­‐authtoken • By default, authtoken is generated automatically. • You

    can specify any string. SECURITY RISK: Be careful when using this option $  groovyserver  -­‐-­‐authtoken  MY_AUTHTOKEN
  47. ≪process≫ groovyserver ≪process≫ groovyclient CLASSPATH= /my/classpath/foo.jar I don’t know its

    class! org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed: script_from_command_line: 1: unable to resolve class foo.Foo @ line 1, column 1. new foo.Foo().execute() ^ 1 error Please execute this! new foo.Foo().execute()
  48. ≪process≫ groovyserver ≪process≫ groovyclient CLASSPATH= /my/classpath/foo.jar Please execute this! new

    foo.Foo().execute() CLASSPATH= /my/classpath/foo.jar new foo.Foo().execute()
  49. ≪process≫ groovyserver ≪process≫ groovyclient CLASSPATH= /my/classpath/foo.jar You got it! Please

    execute this! new foo.Foo().execute() CLASSPATH= /my/classpath/foo.jar
  50. -classpath / -cp / --classpath $  groovyclient      

       -­‐cp  /my/classpath/foo.jar            -­‐e  “new  foo.Foo().execute()”
  51. ≪process≫ groovyserver ≪process≫ groovyclient CLASSPATH= /my/classpath/foo.jar Please execute this! new

    foo.Foo().execute() CLASSPATH= /my/classpath/foo.jar I already know foo.jar!
  52. ≪process≫ groovyserver ≪process≫ groovyclient CLASSPATH= /my/classpath/bar.jar Please execute this! new

    bar.Bar().execute() CLASSPATH= /my/classpath/bar.jar OK CLASSPATH= /my/classpath/foo.jar
  53. ≪process≫ groovyserver ≪process≫ groovyclient CLASSPATH= /my/classpath/xxx.jar How to propagate env

    KEY_A_1=VALUE_A_1 KEY_A_2=VALUE_A_2 KEY_B=VALUE_B KEY_A_1=VALUE_A_1 KEY_B=VALUE_B KEY_A_2=VALUE_A_2
  54. groovyclient options • -­‐Cenv  <substr> • Passes environment variables of

    which a name includes specified substr • -­‐Cenv-­‐all • pass all environment variables • -­‐Cenv-­‐exclude  <substr> • don't pass environment variables of which a name includes specified substr
  55. SecurityManager public  class  NoExitSecurityManager2  extends  NoExitSecurityManager  {      

     public  void  checkExit(final  int  code)  {                throw  new  SystemExitException(code,                                            "called  System.exit("  +  code  +  ")");        } }
  56. GroovyMain private  boolean  run()  {        try  {

                   //...                processArgs(...)                //...        }  catch  (SystemExitException  e)                throw  e;        }  catch  (Throwable  e)  {                //...        } } static  void  processArgs(...)  {        //...        if  (!process(cmd,  classpath))  {                //  Disabled  because  this  causes  a  secondary  disaster                //System.exit(1);        }        //... }
  57. $ cd /tmp $ groovyserver -r $ cd /home/nobeans $

    cat test.txt Can you read me? $ groovyclient -e ‘println(new File(“test.txt”).text)’ Caught: java.io.FileNotFoundException: test.txt (No such file or directory) ...SNIP... ≪process≫ groovyserver CWD=/tmp I wanna use “test.txt” of current directory. Is it “/tmp/test.txt” ? Hmm, such a file not found.
  58. Solution • Setting to “user.dir” system property • It affects

    File#getAbsolutePath() • Changing CWD of native JVM process • Many classes don’t use “user.dir” to complete an absolute path. • But, JNI is troublesome...
  59. JNA is easy way to use native API UnixLibC.groovy: interface

     UnixLibC  extends  com.sun.jna.Library.Library  {        int  chdir(String  dir)        int  setenv(String  envVarName,  String  envVarValue,  int  overwrite) } pom.xml:        <dependency>            <groupId>net.java.dev.jna</groupId>            <artifactId>jna</artifactId>            <version>3.2.2</version>        </dependency> Usage: def  libc  =  com.sun.jna.Native.loadLibrary("c",  UnixLibC.class) libc.chdir(“/tmp/foo/bar”)
  60. $ cd /tmp $ groovyserver -r $ cd /home/nobeans $

    cat test.txt Can you read me? $ groovyclient -e ‘println(new File(“test.txt”).text)’ Can you read me? ≪process≫ groovyserver CWD=/tmp I wanna use “test.txt” of current directory. ≪process≫ groovyserver CWD=/home/nobeans Is it “/home/nobeans/test.txt” ? OK, I got it. Yes!