Zhiqim Httpd即知启蒙WEB容器,是Zhiqim Framework面向WEB开发的多例服务,提供更简洁配置、积木式组件模块和天然的模型模板设计。
HttpHeaderAbs.java31KB
/*
* 版权所有 (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.io.EOFException;
import java.io.IOException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map.Entry;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLHandshakeException;
import org.zhiqim.httpd.constants.HttpStep;
import org.zhiqim.httpd.context.RedirectContext;
import org.zhiqim.httpd.model.HttpDomainPortPath;
import org.zhiqim.kernel.logging.Log;
import org.zhiqim.kernel.logging.LogFactory;
import org.zhiqim.kernel.model.HttpLine;
import org.zhiqim.kernel.model.codes.URI;
import org.zhiqim.kernel.model.maps.HashMapSS;
import org.zhiqim.kernel.model.seqs.Sequence;
import org.zhiqim.kernel.util.Ints;
import org.zhiqim.kernel.util.Strings;
import org.zhiqim.kernel.util.Validates;
/**
* HTTP头部抽象类
*
* @version v1.0.0 @author zouzhigang 2018-9-11 新建与整理
*/
public abstract class HttpHeaderAbs extends HttpHeaderInner implements HttpHeader
{
private static final Log log = LogFactory.getLog("http.request");
private static final Sequence sequence = new Sequence(6);
//连接&监听&输入输出流&发送器
private HttpConnection conn;
private HttpListener listener;
private HttpServer server;
private HttpInputStream input;
private HttpOutputStream output;
private HttpSenderImpl sender;
//编号&步骤&时间
private String requestId;
private int requestStep;
private long requestTimeMillis;
//状态
private String statusLine;
private String method;
private URI uri;
private String version;
//属性
private HashMapSS headerMap;
private String x_Forwarded_For;
private String mimeType;
private String characterEncoding;
private X509Certificate[] certs;
//关联对象
private HttpContext context;
//请求&响应&处理器
private HttpRequestAbs request;
private HttpResponseImpl response;
private HttpHandler handler;
private HttpDomainPortPath domainPortPath;
//临时路径值
private transient String pathInContext;
private transient String pathInRMI;
private transient String pathOnResource;
//解释头
private HttpLine liner;
/** 构造函数 */
public HttpHeaderAbs (HttpConnection conn, HttpInputStream input, SSLEngine sslEngine)
{
this.conn = conn;
this.listener = conn.getListener();
this.server = listener.getServer();
this.input = input;
this.output = new HttpOutputStream(conn);
this.sender = newSender();
this.requestId = conn.getId() + sequence.nextString();
this.requestStep = _01_CREATE_;
this.requestTimeMillis = System.currentTimeMillis();
this.liner = new HttpLine();
this.headerMap = new HashMapSS();
}
/*****************************************************************************/
//子类必须实现的抽象方法
/*****************************************************************************/
/** 创建发送器 */
public abstract HttpSenderImpl newSender();
/** 创建请求 */
public abstract HttpRequestAbs newRequest();
/** 是否BIO */
public abstract boolean isBio();
/*****************************************************************************/
//子类必须实现的抽象方法
/*****************************************************************************/
public HttpServer getServer()
{
return server;
}
public HttpContext getContext()
{
return context;
}
public HttpInputStream getInputStream()
{
return input;
}
public HttpOutputStream getOutputStream()
{
return output;
}
public String getId()
{
return requestId;
}
public long getReceiveTimeMillis()
{
return requestTimeMillis;
}
public HttpListener getListener()
{
return listener;
}
public HttpResponse getResponse()
{
return response;
}
@Override /** 获取连接中的日志对象 */
public Log getLog()
{
return log;
}
@Override /** 获取发送器 */
public HttpSender getSender()
{
return sender;
}
/*****************************************************************************/
//执行请求
/*****************************************************************************/
/**
* 执行缓冲请求
*
* @exception HttpException HTTP异常
* @exception IOException IO异常
*/
public void execute() throws HttpException, IOException
{
try
{
//2.第二步,从流中解析消息头
if (requestStep < _02_PARSE_HEADER_)
{
int code = liner.parse(input);
if (code == _100_CONTINUE_)
{//解析未完成情况,等待下次execute
return;
}
if (code != _0_SUCCESS_)
{//解析出错
throw new HttpException(code);
}
setStep(_02_PARSE_HEADER_);
}
//3.第三步,检查消息头
if (requestStep < _03_CHECK_HEADER_)
{
parseHeader();
sender.setVersion(getVersion());
if (!isBio())
{//非BIO根据请求来判断
sender.setHeader(_CONNECTION_, getHeader(_CONNECTION_));
}
setStep(_03_CHECK_HEADER_);
}
//4.第五步,查询上下文环境
if (requestStep < _04_QUERY_CONTEXT_)
{
String host = getHostOnly();
if (Validates.isEmpty(host))
throw new HttpException(_412_PRECONDITION_FAILED_);
String path = getPath();
//4.1检查完全匹配,如http://www.zhiqim.com/example 对应到/example上下文环境
context = server.getContext(host, path);
if (context != null && !"/".equals(path))
{//如果找到Context,则认为path是虚拟目录/example,重定向到Context的根http://example.zhiqim.com/
sender.sendRedirect(path+"/");
return;
}
if (context == null)
{//4.2检查是否有虚拟目录上下文环境
String maybe = getVirtualDirectory();//虚拟目录
context = server.getContext(host, maybe);
if (context == null)
{//不是虚拟目录上下文环境
context = server.getContext(host, "/");
}
}
if (context == null)
{//4.3未找到则表示未配置该上下文环境,返回未找到
sender.sendError(_404_NOT_FOUND_);
return;
}
if (context instanceof RedirectContext)
{//4.4 重定向上下文环境,直接跳转
sender.sendRedirect(((RedirectContext)context).getResourcePath());
return;
}
domainPortPath = new HttpDomainPortPath().parse(this);
setStep(_04_QUERY_CONTEXT_);
}
//5.第五步,在上下文环境中查找处理器
if (requestStep < _05_QUERY_HANDLER_)
{
parsePathInContext(context);
String pathInContext = getPathInContext();
if ("/".equals(pathInContext))
{//5.1路径为根路径,但没匹配到欢迎页,返回未找到
sender.sendError(_404_NOT_FOUND_);
return;
}
if (context.isFilterPath(pathInContext))
{//5.2路径为过滤地址,返回禁止访问
sender.sendError(_403_FORBIDDEN_);
return;
}
//指定输出块大小
output.setChunkSize(context.getChunkSize());
handler = context.getMatchHandler(pathInContext);
if (handler == null)
{//5.3未找到匹配的处理器
context.handleResource(this, sender);
return;
}
if (isMethodHead())
{//5.4针对HEAD的请求直接返回成功即可
sender.sendHeader(_200_OK_);
return;
}
if (isMethodOptions())
{//5.5针对OPTIONS请求的处理
context.getOptionsHandler().handle(this, sender);
sender.commit();
return;
}
if (handler instanceof HttpEntity)
{//5.6找到是实体处理器
((HttpEntity)handler).handle(this, sender);
sender.commit();
return;
}
setStep(_05_QUERY_HANDLER_);
}
//6.第六步,解析GET,POST COOKIE,SESSION和内容等信息
if (requestStep < _06_PARSE_CONTENT_)
{
if (request == null)
request = newRequest();
if (response == null)
response = new HttpResponseImpl(this.request);
request.parseHeaderByContextOK();
request.parseGetPostCookieSession();
if (!request.parseContent())
return;
setStep(_06_PARSE_CONTENT_);
}
//7.第七步,转入处理器进行处理
if (requestStep < _07_HANDLER_BEGIN_)
{
//BEGIN
setStep(_07_HANDLER_BEGIN_);
HttpExecutor executor = (HttpExecutor)handler;
executor.handle(request, response);
//END
if (requestStep < _10_HANDLER_END_)
setStep(_10_HANDLER_END_);
}
//12.第十二步,提交
if (!isCommitted())
{
if (response != null)
response.commit();
else
sender.commit();
}
if (isWebSocket())
{//13.最后判断是否是WebSocket,在提交后开启WebSocket回调
conn.doWebSocketOpen();
}
}
catch (SSLHandshakeException e)
{//SSL握手异常
close();
}
catch(EOFException e)
{//没读到消息
try{sender.sendError(_400_BAD_REQUEST_);}catch(Exception e2){close();}
}
catch(SocketTimeoutException | SocketException e)
{//SOCKET异常
try{sender.sendError(_408_REQUEST_TIMEOUT_);}catch(Exception e2){close();}
}
catch(HttpException e)
{//HTTP异常
if (e.getCode() == _444_INTERRUPT_)
close();//直接关闭
else
try{sender.sendError(e.getCode());}catch(Exception e2){close();}
}
catch(IOException e)
{//IO异常
close();
}
catch(Throwable e)
{//未知服务端异常
log.error(e);
try{sender.sendError(_500_INTERNAL_SERVER_ERROR_);}catch(Exception e2){close();}
}
finally
{
if (isCommitted())
{//已提交和关闭状态的清理及日志打印
setStep(_12_FINISHED_);
listener.finished(this);
destroy();
if (sender != null){
sender.destroy();
}
if (request != null){
request.destroy();
}
if (response != null){
response.destroy();
}
}
}
}
/** 解析全路径path,生成上下文环境中绝对路径 */
public void parsePathInContext(HttpContext context)
{
this.context = context;
String contextPath = context.getContextPath();
if ("/".equals(contextPath))
pathInContext = uri.getPath();
else
pathInContext = uri.getPath().substring(contextPath.length());
pathInContext = Strings.addStartsWith(pathInContext, "/");
if ("/".equals(pathInContext))
{//如果是根地址,则加上可能的welcomeUrl
if (Validates.isNotEmptyBlank(context.getWelcomeUrl()))
pathInContext = context.getWelcomeUrl();
}
//设置编码格式
if (Validates.isEmptyBlank(characterEncoding))
characterEncoding = context.getDefaultEncoding();
if (Validates.isEmptyBlank(characterEncoding))
characterEncoding = _UTF_8_;
}
/**
* 分析请求行和请求头信息
*
* @param headerList 消息头列表,不为空
* @exception EOFException 流结束异常
* @exception HttpException HTTP请求异常
*/
public void parseHeader() throws EOFException, HttpException
{
List<String> headerList = liner.list();
//第一步,读头部流,得到头部列表
if (headerList.isEmpty())
throw new EOFException();
//第二步,检查首行格式,得到HTTP版本,方法和URL,如GET /index.htm HTTP/1.1
String line = statusLine = headerList.get(0).trim();
if (line.length() > _MAX_LINE_LEN_)
throw new HttpException(_414_REQUEST_URL_TOO_LARGE_);
int ind = line.indexOf(' ');
if (ind == -1)
throw new HttpException(_400_BAD_REQUEST_);
//2.1,判断方法是不是支持的GET,POST和HEAD,PUT,DELETE,OPTIONS
method = line.substring(0, ind);
if (!(_GET_.equalsIgnoreCase(method) || _POST_.equalsIgnoreCase(method) || _HEAD_.equalsIgnoreCase(method) || _PUT_.equalsIgnoreCase(method) || _DELETE_.equalsIgnoreCase(method) || _OPTIONS_.equalsIgnoreCase(method)))
throw new HttpException(_405_METHOD_NOT_ALLOWD_);
method = method.toUpperCase();
line = line.substring(ind+1).trim();
ind = line.indexOf(' ');
if (ind == -1)
throw new HttpException(_400_BAD_REQUEST_);
//2.2,读取URL
String url = line.substring(0, ind);
if (url == null || url.trim().length()<1)
throw new HttpException(_400_BAD_REQUEST_);
if (!url.startsWith("/") || url.indexOf("/..") != -1 || url.indexOf("../") != -1)//如果出现目录相关的,直接拒绝
throw new HttpException(_400_BAD_REQUEST_);
//2.3,解析URL成URI
uri = new URI();
if (!uri.parse(url.trim()).isParsed())
throw new HttpException(_400_BAD_REQUEST_);
//2.4,判断HTTP版本是否是支持的1.0/1.1
version = line.substring(ind+1).trim();
if (!_HTTP_1_1_.equals(version) && !_HTTP_1_0_.equals(version))
throw new HttpException(_505_VERSION_NOT_SUPPORTED_);
//第三步,读取其他头部信息
for (int i=1;i<headerList.size();i++)
{
line = headerList.get(i);
ind = line.indexOf(':');
if (ind == -1)
continue;
String key = Strings.trim(line.substring(0, ind));
String value = Strings.trim(line.substring(ind+1));
setHeader(key, value);
}
//第四步,检查是否是代理模式
//4.1,如果不是代理模式则返结束
x_Forwarded_For = getHeader(_X_FORWARDED_FOR_);
//4.2,把真实HOST放置到_HOST_
String x_Forwarded_Host = getHeader(_X_FORWARDED_HOST_);
if (Validates.isNotEmptyBlank(x_Forwarded_Host))
setHeader(_HOST_, x_Forwarded_Host);
//第五步,验证HOST是否是服务端支持的
String hostPort = getHostPort();
if (hostPort == null)
throw new HttpException(_412_PRECONDITION_FAILED_);
//第六步,获取内容格式和编码,如果不存在内容类型则默认为UTF-8
String contentType= getHeader(_CONTENT_TYPE_);
if (contentType != null)
{
//在contentType中查找encoding,格式为:text/html; charset=UTF-8; boundary=-------123456789
int i0 = contentType.indexOf(';');
if (i0 == -1)
mimeType = contentType.toLowerCase();
else
{
mimeType = contentType.substring(0, i0).trim().toLowerCase();
int i1 = contentType.indexOf("charset=", i0);
if (i1>=0)
{
int i2 = contentType.indexOf(";", i1);
if (i2 == -1)
characterEncoding = contentType.substring(i1 + 8);
else
characterEncoding = contentType.substring(i1 + 8, i2).trim();
}
}
}
}
/*******************************************************************************/
//步骤
/*******************************************************************************/
public void setStep(int step)
{
if (this.requestStep >= step)
return;
this.requestStep = step;
}
public int getStep()
{
return requestStep;
}
public String getStepDesc()
{
return HttpStep.getStatusMsg(requestStep);
}
public boolean isRead()
{//头部已读
return requestStep > _03_CHECK_HEADER_;
}
public boolean isParsed()
{//内部已读
return requestStep > _06_PARSE_CONTENT_;
}
public boolean isHandled()
{//处理完成
return requestStep >= _10_HANDLER_END_;
}
public boolean isCommitted()
{//提交完成
return requestStep >= _11_COMMITTED_;
}
public boolean isEditable()
{//正在提交即不可编辑
return !isCommitted();
}
/** 关闭连接 */
public void close()
{//由sender回调
if (!isCommitted())
setStep(_11_COMMITTED_);
conn.close();
}
/***********************************************************************/
//获取和判断请求行信息,包括协议、方法、版本、URI等
/***********************************************************************/
@Override /** 获取客户端IP地址 */
public String getRemoteAddr()
{
//如果是代理,取代理第一个IP
if (x_Forwarded_For != null)
return x_Forwarded_For.split(",")[0];
else//否则取连接
return conn.getRemoteAddr();
}
/** 获取客户端端口 */
public int getRemotePort()
{
return conn.getRemotePort();
}
@Override
public int getListenerPort()
{
return conn.getListener().getPort();
}
@Override /** 获取请求行 */
public String getHeaderLine()
{
return statusLine;
}
@Override /** 获取请求版本 */
public String getVersion()
{
return version;
}
@Override /** 获取请求方法 */
public String getMethod()
{
return method;
}
@Override /** 是否是HEAD方法 */
public boolean isMethodHead()
{
return _HEAD_.equals(method);
}
@Override /** 是否OPTIONS方法 */
public boolean isMethodOptions()
{
return _OPTIONS_.equals(method);
}
@Override /** 是否PUT方法 */
public boolean isMethodPut()
{
return _PUT_.equals(method);
}
@Override /** 是否DELETE方法 */
public boolean isMethodDelete()
{
return _DELETE_.equals(method);
}
@Override /** 是否是GET方法 */
public boolean isMethodGet()
{
return _GET_.equals(method);
}
@Override /** 是否是POST方法 */
public boolean isMethodPost()
{
return _POST_.equals(method);
}
@Override /** 判断是否需要响应内容 */
public boolean isMethodResponseContent()
{//GET,POST,其他的PUT,DELETE认为是增加和删除,只响应状态
return _GET_.equals(method) || _POST_.equals(method);
}
/** 获取URI信息 */
public URI getUri()
{
return uri;
}
@Override /** 获取URI路径,以/开头,如/test.html,如果没有文件后缀则为'/' */
public String getPath()
{
return (uri == null)?"":uri.getPath();
}
@Override /** 获取查询串 */
public String getQueryString()
{
return uri.getQuery();
}
@Override /** 获取URI虚拟目录信息 */
public String getVirtualDirectory()
{
return uri.getVirtualDirectory();
}
@Override /** 获取头部信息 */
public HashMapSS getHeaderMap()
{
return headerMap;
}
/** 设置头部信息,KEY统一使用小写 */
public void setHeader(String key, String value)
{
headerMap.put(key.toLowerCase(), value);
}
@Override /** 获取请求头属性 */
public String getHeader(String key)
{
return headerMap.get(key.toLowerCase());
}
@Override /** 获取请求头中的编码,如果未设置默认null */
public String getCharacterEncodingHeader()
{
String contentType= getHeader(_CONTENT_TYPE_);
if (Validates.isEmptyBlank(contentType))
return null;
contentType = contentType.toLowerCase();
int i1 = contentType.indexOf("charset=");
if (i1 == -1)
return null;
int i2 = contentType.indexOf(";", i1);
if (i2 == -1)
return contentType.substring(i1 + 8);
else
return contentType.substring(i1 + 8, i2).trim();
}
/** 获取请求内容长度 */
public int getContentLength()
{
String sLen = getHeader(_CONTENT_LENGTH_);
if (!Validates.isInteger(sLen))
return 0;
return Integer.parseInt(sLen);
}
/** 获取请求内容类型 */
public String getContentType()
{
return getHeader(_CONTENT_TYPE_);
}
/** 获取请求要求的类型 */
public String getMimeType()
{
return mimeType;
}
/** 判断是否表单提交 */
public boolean isMimeForm()
{
return _APPLICATION_X_WWW_FORM_.equals(mimeType);
}
/** 判断是否文本请求 */
public boolean isMimeTextPlain()
{
return _TEXT_PLAIN_.equals(mimeType);
}
/** 设置请求的编码格式 */
public void setCharacterEncoding(String characterEncoding)
{
if (Validates.isEmptyBlank(characterEncoding))
return;//不支持设置空白编码格式
this.characterEncoding = characterEncoding;
}
/** 获取请求要求的编码,如果未设置默认UTF-8 */
public String getCharacterEncoding()
{
return characterEncoding;
}
/** 获取协议格式 */
public String getScheme()
{
String proxy = getHeader(_X_FORWARDED_PROTO_);
return (Validates.isEmpty(proxy))?server.getScheme():proxy;
}
/** 获取HOST:PORT */
public String getHostPort()
{
return getHeader(_HOST_);
}
/** 如果有PORT仅取HOST */
public String getHostOnly()
{
String hostPort = getHostPort();
if (Validates.isEmpty(hostPort))
return null;
int ind = hostPort.indexOf(":");
if (ind == -1)
return hostPort;
else//去除:之间的空格
return Strings.trim(hostPort.substring(0, ind));
}
/** 获取端口信息 */
public int getPort()
{
String scheme = getScheme();
int defaultPort = _HTTPS_.equalsIgnoreCase(scheme)?443:80;
String hostPort = getHostPort();
if (Validates.isEmpty(hostPort) || hostPort.indexOf(":") == -1)
return defaultPort;
int ind = hostPort.indexOf(":");
return Ints.toInt(Strings.trim(hostPort.substring(ind+1)), defaultPort);
}
/** 获取域名端口和路径值 */
public HttpDomainPortPath getDomainPortPath()
{
return domainPortPath;
}
/** 获取浏览器代理 */
public String getUserAgent()
{
return getHeader(_USER_AGENT_);
}
/** 是否请求内容GZIP */
public boolean isRequestGZip()
{
String contentEncoding = getHeader(_CONTENT_ENCODING_);
return _ENCODING_GZIP_.equalsIgnoreCase(contentEncoding);
}
/** 是否响应支持GZIP */
public boolean isResponseGZip()
{
String acceptEncoding = getHeader(_ACCEPT_ENCODING_);
if (Validates.isEmpty(acceptEncoding) || acceptEncoding.indexOf(_ENCODING_GZIP_) == -1)
return false;
String userAgent = getUserAgent();
if (Validates.isEmpty(userAgent) || userAgent.toLowerCase().indexOf("msie 6.0") > -1)
return false;//IE6,有部分浏览器有问题可能不支持GZIP
return true;
}
public String getXForwardedFor()
{
return x_Forwarded_For;
}
public void setCertificates(X509Certificate[] certs)
{
this.certs = certs;
}
public X509Certificate[] getCertificates()
{
return certs;
}
/** 是否是Websocket协议 */
public boolean isWebSocket()
{
return _WEBSOCKET_.equalsIgnoreCase(getHeader(_UPGRADE_)) && Strings.contains(getHeader(_CONNECTION_).toLowerCase(), _UPGRADE_.toLowerCase());
}
/** 由转向时设置新的地址 */
public void setPathInContext(String pathInContext)
{
pathInContext = Strings.addStartsWith(pathInContext, "/");
if ("/".equals(pathInContext))
{//如果是根地址,则加上可能的welcomeUrl
if (Validates.isNotEmptyBlank(context.getWelcomeUrl()))
pathInContext = context.getWelcomeUrl();
}
this.pathInContext = pathInContext;
}
/***********************************************************************/
//在找到HttpContext时才有效
/***********************************************************************/
/** 获取HTTP连接 */
public HttpConnection getConnection()
{
return conn;
}
/** 获取在上下文环境下的路径 */
public String getPathInContext()
{
return pathInContext;
}
/***********************************************************************/
//由远程调用处理器指定的权限路径
/***********************************************************************/
/** 获取远程调用的权限路径 */
public String getPathInRMI()
{
return pathInRMI;
}
/** 设置资源文件下绝对路径 ,由context回调 */
public void setPathInRMI(String pathInRMI)
{
this.pathInRMI = pathInRMI;
}
/***********************************************************************/
//由ClassResourceHandler和FileResourceHandler处理的资源临时文件路径
/***********************************************************************/
/** 获取资源文件下绝对路径 */
public String getPathOnResource()
{
return pathOnResource;
}
/** 设置资源文件下绝对路径 ,由context回调 */
public void setPathOnResource(String pathOnResource)
{
this.pathOnResource = pathOnResource;
}
/***********************************************************************/
//toString & destroy
/***********************************************************************/
public String toString()
{
StringBuilder strb = new StringBuilder();
strb.append(statusLine).append(_BR_);
if (headerMap != null)
{
for (Entry<String, String> entry : headerMap.entrySet())
{
strb.append(entry.getKey()).append(_COLON_).append(entry.getValue()).append(_BR_);
}
strb.append(_BR_);
}
return strb.toString();
}
private void destroy()
{
if (headerMap != null)
{
headerMap.clear();
headerMap = null;
}
if (liner != null)
{
liner.clear();
liner = null;
}
//引用置空
statusLine = null;
method = null;
uri = null;
version = null;
x_Forwarded_For = null;
mimeType = null;
characterEncoding = null;
conn = null;
context = null;
pathInContext = null;
pathOnResource = null;
}
}