Nice work but my suspicion is that enhancements like this are best not
bundled into parrot but just made a separate PR. There is no reason
this can't be evaluated for inclusion into 2.5 independent of when
parrot is merged. WDYT?

Cheers, Paul.

On Wed, Dec 14, 2016 at 10:06 PM,  <sun...@apache.org> wrote:
> Repository: groovy
> Updated Branches:
>   refs/heads/parrot 3a035b977 -> 1c94654ca
>
>
> Add option -lh to launch SimpleHTTPServer
>
>
> Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
> Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/1c94654c
> Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/1c94654c
> Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/1c94654c
>
> Branch: refs/heads/parrot
> Commit: 1c94654ca153d28395a7607211ea3d2f78d4412f
> Parents: 3a035b9
> Author: Daniel Sun <sun...@apache.org>
> Authored: Wed Dec 14 20:05:51 2016 +0800
> Committer: Daniel Sun <sun...@apache.org>
> Committed: Wed Dec 14 20:05:51 2016 +0800
>
> ----------------------------------------------------------------------
>  src/main/groovy/ui/GroovyMain.java       | 178 ++++++++++++++++++++++++--
>  src/test/groovy/ui/GroovyMainTest.groovy |   2 +-
>  2 files changed, 168 insertions(+), 12 deletions(-)
> ----------------------------------------------------------------------
>
>
> http://git-wip-us.apache.org/repos/asf/groovy/blob/1c94654c/src/main/groovy/ui/GroovyMain.java
> ----------------------------------------------------------------------
> diff --git a/src/main/groovy/ui/GroovyMain.java 
> b/src/main/groovy/ui/GroovyMain.java
> index 6f44e67..0cb7d45 100644
> --- a/src/main/groovy/ui/GroovyMain.java
> +++ b/src/main/groovy/ui/GroovyMain.java
> @@ -18,6 +18,9 @@
>   */
>  package groovy.ui;
>
> +import com.sun.net.httpserver.HttpExchange;
> +import com.sun.net.httpserver.HttpHandler;
> +import com.sun.net.httpserver.HttpServer;
>  import groovy.lang.Binding;
>  import groovy.lang.GroovyCodeSource;
>  import groovy.lang.GroovyRuntimeException;
> @@ -34,13 +37,27 @@ import org.apache.commons.cli.ParseException;
>  import org.codehaus.groovy.control.CompilationFailedException;
>  import org.codehaus.groovy.control.CompilerConfiguration;
>  import org.codehaus.groovy.control.customizers.ImportCustomizer;
> +import org.codehaus.groovy.runtime.IOGroovyMethods;
>  import org.codehaus.groovy.runtime.InvokerHelper;
>  import org.codehaus.groovy.runtime.InvokerInvocationException;
>  import org.codehaus.groovy.runtime.ResourceGroovyMethods;
>  import org.codehaus.groovy.runtime.StackTraceUtils;
>
> -import java.io.*;
> +import java.io.BufferedInputStream;
> +import java.io.BufferedOutputStream;
> +import java.io.BufferedReader;
> +import java.io.File;
> +import java.io.FileInputStream;
> +import java.io.FileNotFoundException;
> +import java.io.FileReader;
> +import java.io.FileWriter;
> +import java.io.IOException;
> +import java.io.InputStreamReader;
> +import java.io.PrintStream;
> +import java.io.PrintWriter;
>  import java.math.BigInteger;
> +import java.net.HttpURLConnection;
> +import java.net.InetSocketAddress;
>  import java.net.URI;
>  import java.net.URISyntaxException;
>  import java.net.URL;
> @@ -48,7 +65,10 @@ import java.security.AccessController;
>  import java.security.PrivilegedAction;
>  import java.util.Iterator;
>  import java.util.List;
> +import java.util.concurrent.Executors;
>  import java.util.regex.Pattern;
> +import java.util.zip.ZipEntry;
> +import java.util.zip.ZipFile;
>
>  import static org.apache.commons.cli.Option.builder;
>
> @@ -91,6 +111,12 @@ public class GroovyMain {
>      // port to listen on when processing sockets
>      private int port;
>
> +    // provide http service
> +    private boolean provideHttpService;
> +
> +    // port to listen on when providing http service
> +    private int httpServerPort;
> +
>      // backup input files with extension
>      private String backupExtension;
>
> @@ -196,6 +222,7 @@ public class GroovyMain {
>                  .addOption(builder("p").hasArg(false).desc("process files 
> line by line and print result (see also -n)").build())
>                  .addOption(builder("pa").hasArg(false).desc("Generate 
> metadata for reflection on method parameter names (jdk8+ 
> only)").longOpt("parameters").build())
>                  
> .addOption(builder("l").argName("port").optionalArg(true).desc("listen on a 
> port and process inbound lines (default: 1960)").build())
> +                
> .addOption(builder("lh").argName("httpServerPort").hasArg().desc("listen on a 
> port and provide http service").build())
>                  
> .addOption(builder("a").argName("splitPattern").optionalArg(true).desc("split 
> lines using splitPattern (default '\\s') using implicit 'split' 
> variable").longOpt("autosplit").build())
>                  .addOption(builder().longOpt("indy").desc("enables 
> compilation using invokedynamic").build())
>                  
> .addOption(builder().longOpt("configscript").hasArg().desc("A script for 
> tweaking the configuration options").build())
> @@ -258,22 +285,29 @@ public class GroovyMain {
>          if (sp != null)
>              main.splitPattern = sp;
>
> -        if (main.isScriptFile) {
> -            if (args.isEmpty())
> -                throw new ParseException("neither -e or filename provided");
> -
> -            main.script = (String) args.remove(0);
> -            if (main.script.endsWith(".java"))
> -                throw new ParseException("error: cannot compile file with 
> .java extension: " + main.script);
> -        } else {
> -            main.script = line.getOptionValue('e');
> -        }
> +         main.provideHttpService = line.hasOption("lh");
> +         if (main.provideHttpService) {
> +             String p = line.getOptionValue("lh");
> +             main.httpServerPort = Integer.parseInt(p);
> +         } else {
> +             if (main.isScriptFile) {
> +                 if (args.isEmpty())
> +                     throw new ParseException("neither -e or filename 
> provided");
> +
> +                 main.script = (String) args.remove(0);
> +                 if (main.script.endsWith(".java"))
> +                     throw new ParseException("error: cannot compile file 
> with .java extension: " + main.script);
> +             } else {
> +                 main.script = line.getOptionValue('e');
> +             }
> +         }
>
>          main.processSockets = line.hasOption('l');
>          if (main.processSockets) {
>              String p = line.getOptionValue('l', "1960"); // default port to 
> listen to
>              main.port = Integer.parseInt(p);
>          }
> +
>
>          // we use "," as default, because then split will create
>          // an empty array if no option is set
> @@ -320,6 +354,8 @@ public class GroovyMain {
>          try {
>              if (processSockets) {
>                  processSockets();
> +            } else if (provideHttpService) {
> +                provideHttpService();
>              } else if (processFiles) {
>                  processFiles();
>              } else {
> @@ -352,6 +388,23 @@ public class GroovyMain {
>      }
>
>      /**
> +     * Provide http service
> +     */
> +    private void provideHttpService() throws IOException {
> +        int argsSize = args.size();
> +
> +        if (0 == argsSize) {
> +            new SimpleHttpServer(httpServerPort).start();
> +        } else if (1 == argsSize) {
> +            new SimpleHttpServer(httpServerPort, "/", new File((String) 
> args.get(0))).start();
> +        } else if (2 == argsSize) {
> +            new SimpleHttpServer(httpServerPort, (String) args.get(1), new 
> File((String) args.get(0))).start();
> +        } else {
> +            throw new IllegalArgumentException("Too many arguments: " + args 
> + ", usage: -lh <httpServerPort> [base directory] [context root]");
> +        }
> +    }
> +
> +    /**
>       * Get the text of the Groovy script at the given location.
>       * If the location is a file path and it does not exist as given,
>       * then {@link GroovyMain#huntForTheScriptFile(String)} is called to try
> @@ -594,4 +647,107 @@ public class GroovyMain {
>          setupContextClassLoader(groovy);
>          groovy.run(getScriptSource(isScriptFile, script), args);
>      }
> +
> +}
> +
> +/*
> + *   Failed to put SimpleHttpServer in a separate 
> file(groovy/ui/SimpleHttpServer.java), because of the following compilation 
> error:
> + *
> + *   
> /home/travis/build/danielsun1106/groovy/src/main/groovy/ui/GroovyMain.java:397:
>  error: cannot find symbol
> + *           new SimpleHttpServer(httpServerPort, "/", (String) 
> args.get(0)).start();
> + *               ^
> + *   symbol:   class SimpleHttpServer
> + *   location: class GroovyMain
> + *
> + *   but it can be compiled successfully in the IntelliJ IDEA 2016.3.1, 
> weird... so put it here for the time being
> + */
> +
> +/**
> + * SimpleHTTPServer for Groovy, inspired by Python's SimpleHTTPServer
> + */
> +class SimpleHttpServer {
> +    private HttpServer server;
> +    private int port;
> +    private String contextRoot;
> +    private File docBase;
> +
> +    public SimpleHttpServer(final int port) throws IOException {
> +        this(port, "/", new File("."));
> +    }
> +
> +    public SimpleHttpServer(final int port, final String contextRoot, final 
> File docBase) throws IOException {
> +        this.port = port;
> +        this.contextRoot = contextRoot.startsWith("/") ? contextRoot : ("/" 
> + contextRoot);
> +        this.docBase = docBase;
> +
> +        server = HttpServer.create(new InetSocketAddress(port), 0);
> +        server.setExecutor(Executors.newCachedThreadPool());
> +        server.createContext(this.contextRoot, new HttpHandler() {
> +            @Override
> +            public void handle(HttpExchange exchg) throws IOException {
> +                BufferedOutputStream bos = new 
> BufferedOutputStream(exchg.getResponseBody());
> +                byte[] content = null;
> +
> +                try {
> +                    String uri = exchg.getRequestURI().getPath();
> +                    String path =
> +                            !"/".equals(SimpleHttpServer.this.contextRoot) 
> && uri.startsWith(SimpleHttpServer.this.contextRoot) ? 
> uri.substring(SimpleHttpServer.this.contextRoot.length()) : uri;
> +
> +                    content = readContent(docBase, path);
> +                    exchg.sendResponseHeaders(HttpURLConnection.HTTP_OK, 
> content.length);
> +                    bos.write(content);
> +                } catch (Exception e) {
> +                    content = e.getMessage().getBytes();
> +                    
> exchg.sendResponseHeaders(HttpURLConnection.HTTP_INTERNAL_ERROR, 
> content.length);
> +                    bos.write(content);
> +                } finally {
> +                    bos.close();
> +                    exchg.close();
> +                }
> +            }
> +        });
> +    }
> +
> +    private byte[] readContent(File docBase, String path) throws IOException 
> {
> +        if ("/".equals(path)) {
> +            return "Groovy SimpleHTTPServer is running".getBytes();
> +        } else {
> +            if (docBase.isDirectory()) {
> +                return readFile(docBase, path);
> +            } else {
> +                return readZipEntry(docBase, path);
> +            }
> +        }
> +    }
> +
> +    private byte[] readFile(File docBase, String path) throws IOException {
> +        File file = new File((docBase.getCanonicalPath() + File.separator + 
> path).trim());
> +
> +        if (file.isDirectory()) {
> +            return ("Accessing the directory[" + file.getCanonicalPath() + 
> "] is forbidden").getBytes();
> +        } else {
> +            return IOGroovyMethods.getBytes(
> +                    new BufferedInputStream(
> +                            new FileInputStream(file)));
> +        }
> +    }
> +
> +    private byte[] readZipEntry(File docBase, String entryName) throws 
> IOException {
> +        entryName = entryName.startsWith("/") ? entryName.substring(1) : 
> entryName;
> +
> +        try(ZipFile zf = new ZipFile(docBase);
> +            BufferedInputStream bis =
> +                    new BufferedInputStream(
> +                            zf.getInputStream(
> +                                    new ZipEntry(entryName)))) {
> +
> +            return IOGroovyMethods.getBytes(bis);
> +        }
> +    }
> +
> +    public void start() {
> +        server.start();
> +        System.out.println("HTTP Server started up, visit http://localhost:"; 
> + this.port + this.contextRoot + " to access the files in the " + 
> this.docBase);
> +    }
> +
>  }
>
> http://git-wip-us.apache.org/repos/asf/groovy/blob/1c94654c/src/test/groovy/ui/GroovyMainTest.groovy
> ----------------------------------------------------------------------
> diff --git a/src/test/groovy/ui/GroovyMainTest.groovy 
> b/src/test/groovy/ui/GroovyMainTest.groovy
> index 6c9396e..825a2cd 100644
> --- a/src/test/groovy/ui/GroovyMainTest.groovy
> +++ b/src/test/groovy/ui/GroovyMainTest.groovy
> @@ -27,7 +27,7 @@ class GroovyMainTest extends GroovyTestCase {
>          GroovyMain.processArgs(args, ps)
>          def out = baos.toString()
>          assert out.contains('usage: groovy')
> -        ['-a', '-c', '-d', '-e', '-h', '-i', '-l', '-n', '-p', '-v'].each{
> +        ['-a', '-c', '-d', '-e', '-h', '-i', '-l', '-lh', '-n', '-p', 
> '-v'].each{
>              assert out.contains(it)
>          }
>      }
>

Reply via email to