/** * Copyright (C) Zhang,Yuexiang (xfeep) * */ package nginx.clojure.embed; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.Socket; import java.net.URL; import java.net.URLConnection; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; import nginx.clojure.DiscoverJvm; import nginx.clojure.MiniConstants; import nginx.clojure.NginxClojureRT; import nginx.clojure.java.ArrayMap; import nginx.clojure.java.NginxJavaRequest; import nginx.clojure.java.NginxJavaRingHandler; public class NginxEmbedServer { protected final static int EMBED_OK = 0; protected final static int EMBED_ERR = -1; protected final static int EMBED_PASS_CONF = 1; protected final static int EMBED_PASS_ALL = 2; protected final static NginxEmbedServer instance; protected boolean started = false; protected String workDir; protected CountDownLatch stopCountDownLatch; static BlockingQueue<EmbedStartEvent> embedStarteEventQueue = new LinkedBlockingQueue<EmbedStartEvent>(); static File tmpRoot; static { loadDynamicLibrary(); if (register() != 0) { throw new RuntimeException("NginxEmbedServer can not register native methods!"); } instance = new NginxEmbedServer(); } private NginxEmbedServer() { try { workDir = new File(System.getProperty("user.dir")).getCanonicalPath(); } catch (IOException e) { throw new RuntimeException("can not getCanonicalPath() for setting workDir of nginx embed server", e); } } private static void loadDynamicLibrary() { tmpRoot = new File(System.getProperty("java.io.tmpdir") + "/nginx-clojure-embed-" + NginxClojureRT.processId); tmpRoot.mkdirs(); tmpRoot.deleteOnExit(); if (System.getProperty("nginx.clojure.embed.sopath") != null) { System.load(System.getProperty("nginx.clojure.embed.sopath")); return; } String soname = "nginx-clojure-embed-" + DiscoverJvm.detectOSArchExt(); URL url = NginxEmbedServer.class.getClassLoader().getResource("slib/" + soname); if (url == null) { String error = "can not get slib/" + soname +" at class path"; NginxClojureRT.getLog().error(error); throw new RuntimeException(error); } InputStream in = null; try { URLConnection conn = url.openConnection(); in = conn.getInputStream(); File soRoot = new File(tmpRoot.getParentFile(), "nginx-clojure-embed-so-" + MiniConstants.NGINX_CLOJURE_RT_VER); soRoot.mkdir(); File sofile = new File(soRoot, soname); if (!sofile.exists() || conn.getLastModified() > sofile.lastModified()) { FileOutputStream out = new FileOutputStream(sofile); byte[] buffer = new byte[4096]; int c = 0; while ( (c = in.read(buffer)) != -1) { out.write(buffer, 0, c); } out.close(); if (DiscoverJvm.detectOSArchExt().startsWith("linux")) { /** * fix SELinux Enabled issue ,e.g. * Exception in thread "main" java.lang.UnsatisfiedLinkError: * nginx-clojure-embed-linux-xxx.so: cannot restore segment prot after reloc: Permission denied */ Process p = Runtime.getRuntime().exec(new String[] {"/bin/sh", "-c", "chcon -u system_u -r object_r -t textrel_shlib_t " + sofile.getAbsolutePath()}); try { if (p.waitFor() != 0) { NginxClojureRT.getLog().debug("fail to change security of nginx clojure embed shared library, just ignore it."); } } catch (InterruptedException e) { NginxClojureRT.getLog().error("can not write so file " + tmpRoot.getAbsoluteFile() + "/" + soname , e); } } } System.load(sofile.getAbsolutePath()); } catch (IOException e) { NginxClojureRT.getLog().error("can not write so file " + tmpRoot.getAbsoluteFile() + "/" + soname , e); }finally{ try { if (in != null) { in.close(); } } catch (IOException e) { NginxClojureRT.getLog().error(e); } } } public static NginxEmbedServer getServer() { return instance; } private native static long register(); public static class EmbedStartEvent { int type; String message; public EmbedStartEvent() { } public EmbedStartEvent(int type, String message) { this.type = type; this.message = message; } } private static void notifyFromNative(int type, String message) { if (type == EMBED_PASS_ALL) { NginxJavaRequest.fixDefaultRequestArray(); try{ nginx.clojure.clj.LazyRequestMap.fixDefaultRequestArray(); }catch(Throwable e) {}; } embedStarteEventQueue.add(new EmbedStartEvent(type, message)); } private native static long innerStart(String cmd); private native static void innerStop(); public void setWorkDir(String workDir) { this.workDir = workDir; } public static int pickFreePort() { Socket s = new Socket(); try { s.bind(null); int port = s.getLocalPort(); s.setReuseAddress(true); s.close(); return port; } catch (IOException e) { throw new RuntimeException("can not pickFreePort", e); } } /** * @param handler class name of a instance of NginxJavaRingHandler * @param options default options are * <pre> * "error-log", "logs/error.log", * "max-connections", "1024", * "access-log", "off", * "keepalive-timeout", "65", * "max-threads", "8", * "host", "0.0.0.0", * "port", "8080", * "jvm-init-handler-type", "java" * "jvm-init-handler-name", "nginx.clojure.embed.NginxEmbedServer$DefaultJvmInitHandler" * //user defined zone * "global-user-defined", "", * "http-user-defined", "", * "types-user-defined", "", * "server-user-defined", "", * "location-user-defined", "" * </pre> * @return the listening port */ public int start(String handler, final Map<String, String> options) { Map<String, String> moptions = ArrayMap.create( "error-log", "logs/error.log", "max-connections", "1024", "access-log", "off", "keepalive-timeout", "65", "max-threads", "8", "host", "0.0.0.0", "port", "8080", "jvm_handler_type", "java" , "jvm-init-handler-name", "nginx.clojure.embed.NginxEmbedServer$DefaultJvmInitHandler", "content-handler-type", "java", "content-handler-name", handler, //user defined zone "global-user-defined", "", "http-user-defined", "", "types-user-defined", "", "server-user-defined", "", "location-user-defined", "" ); for (Map.Entry<String, String> entry : options.entrySet()) { if (entry.getKey().equals("port") && entry.getValue().equals("0")) { entry.setValue(pickFreePort()+""); } if (moptions.containsKey(entry.getKey())) { moptions.put(entry.getKey(), entry.getValue()); }else { NginxClojureRT.getLog().warn("[nginx-clojure embed] skipping unsupported option: %s", entry.getKey()); } } BufferedReader r = new BufferedReader(new InputStreamReader(this.getClass().getResourceAsStream("cftmpl"), MiniConstants.DEFAULT_ENCODING)); BufferedWriter w; File cfg = new File(tmpRoot, "embed.conf"); tmpRoot.deleteOnExit(); try { w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(cfg), MiniConstants.DEFAULT_ENCODING)); } catch (FileNotFoundException e) { throw new RuntimeException("can not create tmp conf file for nginx-clojure embed server!", e); } int c; int state = 0; // 1 meet #, 2 meet #{, 0 normal StringBuilder var = new StringBuilder(); try{ while ( (c = r.read()) != -1) { switch (c) { case '#': if (state == 1) { w.write('#'); state = 0; } state = 1; break; case '{' : if (state == 1) { state = 2; }else { w.write('{'); } break; case '}' : if (state == 2) { state = 0; String val = moptions.get(var.toString()); if (val != null) { w.write(val); } var.delete(0, var.length()); }else { w.write('}'); } break; default: if (state == 2) { var.append((char)c); }else if (state == 1) { w.write('#'); w.write((char)c); state = 0; }else { w.write((char)c); } break; } } }catch(IOException e) { throw new RuntimeException("can not create tmp conf file for nginx-clojure embed server!", e); }finally { try { r.close(); w.close(); } catch (IOException e) { throw new RuntimeException("can not close tmp conf file for nginx-clojure embed server!", e); } } cfg.deleteOnExit(); try { start(cfg.getCanonicalPath()); NginxClojureRT.getLog().info("[nginx-clojure embed] server listening at port %s.", moptions.get("port")); } catch (IOException e) { throw new RuntimeException("can not getCanonicalPath of conf file for nginx-clojure embed server!", e); } return Integer.parseInt(moptions.get("port")); } public void start(final String workDir, final String cfg) { this.workDir = workDir; start(cfg); } public synchronized void start(final String cfg) { if (started) { throw new IllegalStateException("server has already started!"); } new File(workDir, "logs").mkdir(); new File(workDir, "temp").mkdir(); NginxClojureRT.getLog().info("[nginx-clojure embed] starting......"); stopCountDownLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { try{ started = true; StringBuilder cmdsb = new StringBuilder(); cmdsb.append("java\n").append("-p\n").append(workDir).append("\n") .append("-c\n").append(cfg).append("\n") .append("-g\n").append("working_directory ").append(workDir).append(";"); innerStart(cmdsb.toString()); }finally{ started = false; stopCountDownLatch.countDown(); } } }); t.setName("nginx-clojure-embed"); t.start(); do { try { EmbedStartEvent event = embedStarteEventQueue.take(); switch (event.type) { case EMBED_ERR : started = false; throw new RuntimeException("start error:"+ event.message); case EMBED_PASS_CONF: NginxClojureRT.getLog().info("[nginx-clojure embed] finish configuration check"); break; case EMBED_PASS_ALL: NginxClojureRT.getLog().info("[nginx-clojure embed] server started!"); return; } } catch (InterruptedException e) { NginxClojureRT.getLog().warn("Interrupted!", e); break; } }while(true); } public synchronized void stop() { if (!started) { throw new IllegalStateException("server not started!"); } NginxClojureRT.getLog().info("[nginx-clojure embed] server stopping....."); innerStop(); try { stopCountDownLatch.await(); NginxClojureRT.getLog().info("[nginx-clojure embed] server stopped"); } catch (InterruptedException e) { NginxClojureRT.getLog().warn("stop Interrupted!", e); } } public static class DefaultJvmInitHandler implements NginxJavaRingHandler { @Override public Object[] invoke(Map<String, Object> request) throws IOException { return null; } } }