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) > } > } >