All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.nutz.web.WebServer Maven / Gradle / Ivy

package org.nutz.web;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.URL;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.websocket.server.ServerContainer;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.nutz.http.Http;
import org.nutz.http.Response;
import org.nutz.lang.Each;
import org.nutz.lang.Files;
import org.nutz.lang.Lang;
import org.nutz.lang.Streams;
import org.nutz.lang.Strings;
import org.nutz.lang.socket.SocketAction;
import org.nutz.lang.socket.SocketContext;
import org.nutz.lang.socket.Sockets;
import org.nutz.log.Log;
import org.nutz.log.Logs;

/**
 * 这个类将调用 Jetty 的类启动一个 HTTP 服务,并提供关闭这个服务的 Socket 端口
 * 
 * @author zozoh([email protected])
 */
public class WebServer {

    private static final Log log = Logs.get();

    protected WebConfig dc;

    protected Server server;
    
    boolean websocketEnable = false;
    
    WebAppContext wac;

    public WebServer(WebConfig config) {
        this.dc = config;

        // 保存到静态变量中
        Webs.setProp(config);
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    protected void prepare() throws IOException {
        if (dc.getAppPort() <= 0) {
            dc.set(WebConfig.APP_PORT, "80");
        }
        if (!dc.has(WebConfig.BIND_ADDRESS))
            dc.set(WebConfig.BIND_ADDRESS, "0.0.0.0");
        server = new Server(InetSocketAddress.createUnresolved(dc.get(WebConfig.BIND_ADDRESS), dc.getAppPort()));
        // 设置应用上下文
        String warUrlString = null;
        if (dc.has("war")) {
            warUrlString = dc.get("war");
        } else {
            String rootPath = dc.getAppRoot();
            File root = Files.findFile(rootPath);
            if (root == null || !root.exists()) {
                log.warnf("root: '%s' not exist!", dc.get(WebConfig.APP_ROOT));
                warUrlString = Lang.runRootPath();
            } else {
                warUrlString = root.toURI().toURL().toExternalForm();
            }
        }
        log.debugf("++war path : %s", warUrlString);
        wac = new WebAppContext(warUrlString, dc.getAppContextPath());
        if (warUrlString.endsWith(".war") || dc.has("war")) {
            wac.setExtractWAR(false);
            wac.setCopyWebInf(true);
            wac.setProtectedTargets(new String[]{"/java", "/javax", "/org", "/net", "/WEB-INF", "/META-INF"});
            wac.setTempDirectory(new File("./tmp").getAbsoluteFile());
            wac.setServerClasses(new String[] { "org.objectweb.asm.", // hide asm used by jetty
                                                    "org.eclipse.jdt.", // hide jdt used by jetty
                                                    "org.nutz" // hide nutz classes
                                            });
            InputStream ins = getClass().getClassLoader().getResourceAsStream("web.allows");
            if (ins != null) {
                log.info("found web.allows");
                final Set allowPaths = new HashSet();
                Streams.eachLine(new InputStreamReader(ins), new Each() {
                    public void invoke(int index, String ele, int length) {
                        allowPaths.add(ele);
                    }
                });
                wac.addFilter(new FilterHolder(new WebAllowFilter(allowPaths)), "/*", EnumSet.of(DispatcherType.REQUEST));
            }
        } else {
            if (dc.hasAppDefaultsDescriptor()) {
                wac.setDefaultsDescriptor(dc.getAppDefaultsDescriptor());
            }
            wac.setClassLoader(getClass().getClassLoader());
        }
        wac.setExtraClasspath(dc.getAppClasspath());
        wac.setConfigurationDiscovered(true);
        if (System.getProperty("os.name").toLowerCase().contains("windows")) {
            wac.setInitParameter("org.eclipse.jetty.servlet.Default.useFileMappedBuffer", "false");
        }
        server.setHandler(wac);
        try {
            Class _klass = Class.forName("org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer", false, getClass().getClassLoader());
            Class.forName("javax.annotation.security.RunAs", false, getClass().getClassLoader());
            List list = Configuration.ClassList.serverDefault(server);
            list.add("org.eclipse.jetty.annotations.AnnotationConfiguration");
            wac.setConfigurationClasses(list);
            if (dc.has("war"))
                _klass.getMethod("configureContext", ServletContextHandler.class).invoke(null, wac);
            log.info("init websocket context success");
            websocketEnable = true;
        } catch (Exception e) {
            log.info("miss some websocket class, skip websocket init", e);
        }
        
        
    }

    public void run() {
        try {
            // 准备 ..
            prepare();

            // 启动
            server.start();
            

            if (websocketEnable) {
                List websockets = dc.getList("websockets");
                try {
                    ServerContainer sc = (ServerContainer) wac.getAttribute(ServerContainer.class.getName());
                    if (websockets != null) {
                        for (String className : websockets) {
                            sc.addEndpoint(Class.forName(className));
                        }
                    }
                }
                catch (Exception e) {
                    log.warn("enable websocket fail", e);
                }
            }

            // 添加更多的 JSP 寻找路径
            if (dc.has("app-jsp-extpath")) {
                // 基础的 app-root 作为寻找列表的第一项
                WebAppContext wac = (WebAppContext) server.getHandler();
                List rs = new ArrayList();
                rs.add(wac.getBaseResource());
                String[] ss = Strings.splitIgnoreBlank(dc.trim("app-jsp-extpath"), "[,\n]");
                for (String s : ss) {
                    File d = Files.findFile(s);
                    if (null != d) {
                        Resource r = Resource.newResource(d.getCanonicalFile().toURI());
                        if (r.exists()) {
                        	log.debug("app-jsp-extpath OK >> " + s);
                            rs.add(r);
                            continue;
                        }
                    }
                    log.debug("app-jsp-extpath FAIL >> " + s);
                }
                // 设置进上下文
                wac.setBaseResource(new ResourceCollection(rs.toArray(new Resource[rs.size()])));
            }

            // 自省一下,判断自己是否能否正常访问
            Response resp = Http.get("http://127.0.0.1:" + dc.getAppPort());
            if (resp == null || resp.getStatus() >= 500) {
                log.error("Self-Testing fail !!Server start fail?!!");
                server.stop();
                return;
            }

            if (log.isInfoEnabled())
                log.info("Server is up!");

            // 管理
            if (log.isInfoEnabled())
                log.infof("Create admin port at %d", dc.getAdminPort());
            Sockets.localListenOne(dc.getAdminPort(), "stop", new SocketAction() {
                public void run(SocketContext context) {
                    if (null != server)
                        try {
                            server.stop();
                        }
                        catch (Exception e4stop) {
                            if (log.isErrorEnabled())
                                log.error("Fail to stop!", e4stop);
                        }
                    Sockets.close();
                }
            });

        }
        catch (Throwable e) {
            if (log.isWarnEnabled())
                log.warn("Unknow error", e);
        }

    }

    @Override
    protected void finalize() throws Throwable {
        if (null != server)
            try {
                server.stop();
            }
            catch (Throwable e) {
                if (log.isErrorEnabled())
                    log.error("Fail to stop!", e);
                throw e;
            }
        super.finalize();
    }

    public static class WebAllowFilter implements Filter {
        
        protected Set allowPaths;
        
        public WebAllowFilter(Set allowPaths) {
            this.allowPaths = allowPaths;
        }

        public void init(FilterConfig filterConfig){
        }
        
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            HttpServletRequest req = (HttpServletRequest)request;
            String uri = req.getRequestURI();
            if (uri != null && uri.length() > 1) {
                uri = uri.substring(1);
                URL u = getClass().getClassLoader().getResource(uri);
                if (u != null && !allowPaths.contains(uri)) {
                    ((HttpServletResponse)response).setStatus(404);
                    return;
                }
                //log.debug("Pass : " + uri);
            }
            chain.doFilter(request, response);
        }
        
        public void destroy() {
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy