groovy git commit: Add option -lh to launch SimpleHTTPServer

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

groovy git commit: Add option -lh to launch SimpleHTTPServer

Daniel.Sun
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 <[hidden email]>
Authored: Wed Dec 14 20:05:51 2016 +0800
Committer: Daniel Sun <[hidden email]>
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)
         }
     }

Daniel Sun
Apache Groovy committer

Blog: http://blog.sunlan.me
Twitter: @daniel_sun