Zhiqim Httpd即知启蒙WEB容器,是Zhiqim Framework面向WEB开发的多例服务,提供更简洁配置、积木式组件模块和天然的模型模板设计。

森中灵 最后提交于1月前 增加RedirectContext方便配置HTTP:80跳转到HTTPS:443
HttpServer.java9KB
/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙,邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_httpd.htm
 *
 * Zhiqim Httpd is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.httpd;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

import org.zhiqim.httpd.bio.HttpBioListener;
import org.zhiqim.httpd.bio.HttpBioSslListener;
import org.zhiqim.httpd.nio.HttpNioListener;
import org.zhiqim.httpd.nio.HttpNioSslListener;
import org.zhiqim.kernel.Z;
import org.zhiqim.kernel.config.Group;
import org.zhiqim.kernel.logging.Log;
import org.zhiqim.kernel.logging.LogFactory;
import org.zhiqim.kernel.service.Servicer;
import org.zhiqim.kernel.util.Arrays;
import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Classes;
import org.zhiqim.kernel.util.Lists;
import org.zhiqim.kernel.util.Validates;

/**
 * HTTP多例服务,负责启动和关闭: <br><br>
 * 1、初始化AIO/BIO/NIO监听服务<br>
 * 2、加载和配置上下文环境 <br>
 *
 * @version v1.0.0 @author zouzhigang 2014-3-21 新建与整理
 */
public class HttpServer extends Servicer implements HttpdConstants
{
    private static final Log log = LogFactory.getLog(HttpServer.class);
    
    private volatile boolean isRunning;            //是否运行
    private HttpListener listener;                   //HTTP监听器
    private final List<HttpContext> contextList;    //HTTP上下文环境表
    
    /*********************************************************************************/
    //配置创建HTTP服务
    /*********************************************************************************/
    
    public HttpServer()
    {
        this.contextList = new ArrayList<>();
    }
    
    public boolean isRunning()
    {
        return isRunning;
    }
    
    @Override
    public boolean create() throws Exception
    {
        //1.诊断参数
        Group group = Z.conf().group(id);
        Asserts.as(group != null?null:"HTTP服务[%s]未找到整个配置组", id);
        log.info("初始化[HTTP服务:%s]开始...", id);
        
        //2、初始化上下文环境
        String contexts = group.getString(_SERVER_CONTEXT_);
        Asserts.as(Validates.isNotEmptyBlank(contexts)?null:"HTTP服务["+id+"]未找到[context]配置项");
        
        //3、初始化监听器
        String scheme = group.getString(_LISTEN_SCHEME_, _HTTP_);
        String io = group.getString(_LISTEN_IO_, _BIO_);
        if (_HTTPS_.equalsIgnoreCase(scheme))
        {
            if (_NIO_.equalsIgnoreCase(io))
                listener = new HttpNioSslListener(this);
            else
                listener = new HttpBioSslListener(this);
        }
        else
        {
            if (_NIO_.equalsIgnoreCase(io))
                listener = new HttpNioListener(this);
            else
                listener = new HttpBioListener(this);
        }
        
        listener.setGroup(group);
        if (!listener.open())
        {
            return false;
        }
        
        //4、初始化上下文环境
        String[] contextArr = Arrays.toStringArray(contexts);
        for (String contextId : contextArr)
        {
            Group cGroup = Z.conf().group(contextId);
            Asserts.as(cGroup != null?null:"HTTP服务["+id+"]未找到context["+contextId+"]配置项");
            
            String contextClass = cGroup.getString(_CONTEXT_CLASS_, _HTTP_CONTEXT_CLASS_);
            Object obj = Classes.newInstance(contextClass);
            Asserts.as((obj instanceof HttpContext)?null:"HTTP服务["+id+"]context["+contextId+"]的[class]配置项未实现HttpContext接口");
            
            HttpContext context = (HttpContext)obj;
            context.setServer(this);
            if (!context.create(cGroup))
                return false;
            
            addContext(context);
        }
        
        isRunning = true;
        
        log.info("初始化[HTTP服务:%s]"+this.listener.toString()+"完成!!!%s", id, _BR_);
        return true;
    }

    @Override
    public void destroy() throws Exception
    {
        isRunning = false;
        if (listener != null)
        {
            listener.close();
            listener = null;
        }
        
        synchronized (contextList)
        {//删除全部
            for (HttpContext context : contextList)
            {
                if (context.isRunning())
                    context.destroy();
            }
            contextList.clear();
        }
    }
    
    /***************************************************************************************************/
    //手动创建服务 设置三个参数 domain & host & encoding 和 设置 listen & context 再setRunning()
    /***************************************************************************************************/
    
    public void setRunning()
    {
        this.isRunning = true;
    }
    
    /** 增加上下文环境 */
    public void addContext(HttpContext context)
    {
        //检查是否有相同的配置
        context.setServer(this);
        chkContextDomainPath(context.getContextDomains(), context.getContextPath());
        
        //通过后放置到列表中
        synchronized (contextList)
        {
            contextList.add(context);
        }
    }
    
    /** 移除上下文环境 */
    public void removeContext(HttpContext context)
    {
        try
        {
            context.destroy();
        }
        catch (Exception e)
        {
            log.error("移除上下文环境时异常", e);
        }
        finally
        {
            synchronized (contextList)
            {//从列表中删除
                contextList.remove(context);
            }
        }
    }
    
    public void removeContext(String contextId)
    {
        HttpContext context = getContext(contextId);
        if (context == null)
            return;
        
        removeContext(context);
    }
    
    /** 检查域名和路径是否存在相同的配置 */
    public void chkContextDomainPath(HashSet<String> domains, String path)
    {
        List<HttpContext> list = Lists.copy(contextList, true);
        if (domains.isEmpty())
        {//未配置域名
            for (HttpContext ctx : list)
            {
                if (!ctx.getContextDomains().isEmpty())
                    continue;
                
                if (!ctx.getContextPath().equals(path))
                    continue;
                
                throw Asserts.exception("存在相同的上下文环境配置[%s]", ctx.getContextPath());
            }
        }
        else
        {//有配置域名
            for (HttpContext ctx : list)
            {
                HashSet<String> ds = ctx.getContextDomains();
                if (ds.isEmpty())
                    continue;
                
                if (!ctx.getContextPath().equals(path))
                    continue;
                
                for (String d : domains)
                {
                    if (!ds.contains(d))
                        continue;
                    
                    //只要有一个成立则表示有相同
                    throw Asserts.exception("存在相同的上下文环境配置[%s][%s]", d, ctx.getContextPath());
                }
            }
        }
    }
    
    /*********************************************************************************/
    //获取 & 配对 listen, context, scheme, port, domain, host, encoding
    /*********************************************************************************/
    
    public HttpListener getListener()
    {
        return listener;
    }
    
    public List<HttpContext> getContextList()
    {
        return contextList;
    }
    
    public HttpContext getContext(String contextId)
    {
        Asserts.as(contextId != null?null:"上下文环境路径不能为空");
        
        for (HttpContext context : contextList)
        {
            if (contextId.equals(context.getId()))
                return context;
        }
        
        return null;
    }
    
    public HttpContext getContext(String contextDomain, String contextPath)
    {
        Asserts.as(contextPath != null?null:"上下文环境路径不能为空");
        
        if (!Validates.isEmptyBlank(contextDomain))
        {//1.域名不为空,优先找匹配上的
            for (HttpContext context : contextList)
            {
                HashSet<String> domains = context.getContextDomains();
                if (domains.isEmpty())
                    continue;
                
                if (domains.contains(contextDomain) && contextPath.equals(context.getContextPath()))
                    return context;
            }
        }
        
        //2.再找缺省未配置域名的,只比较上下文环境路径
        for (HttpContext context : contextList)
        {
            if (!context.getContextDomains().isEmpty())
                continue;
            
            if (contextPath.equals(context.getContextPath()))
                return context;
        }
        
        return null;
    }
    
    public int getPort()
    {
        return listener.getPort();
    }
    
    public String getScheme()
    {
        return listener.getScheme();
    }
}