Zhiqim Httpd即知启蒙WEB容器,是Zhiqim Framework面向WEB开发的多例服务,提供更简洁配置、积木式组件模块和天然的模型模板设计。
HttpSenderImpl.java14KB
/*
* 版权所有 (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.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map.Entry;
import org.zhiqim.httpd.constants.HttpStatus;
import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.DateTimes;
import org.zhiqim.kernel.util.Strings;
import org.zhiqim.kernel.util.Urls;
import org.zhiqim.kernel.util.Validates;
/**
* HTTP发送器
*
* @version v1.0.0 @author zouzhigang 2018-9-11 新建与整理
*/
public class HttpSenderImpl implements HttpSender
{
private HttpHeaderAbs header;
private HttpOutputStream output;
private String version = _HTTP_1_1_;
private int status = _200_OK_;
private String reason = _200_DESC_;
private String characterEncoding = _UTF_8_;
//消息头
private HashMap<String, String> headers;
private StringBuilder headerBuffer;
public HttpSenderImpl(HttpHeaderAbs header, boolean autoClose)
{
this.header = header;
this.output = header.getOutputStream();
this.output.setSender(this);
this.headers = new HashMap<>(5);
this.headers.put(_SERVER_, _ZHTTPD_);
if (autoClose)
{//是否执行完自动关闭
this.headers.put(_CONNECTION_, _CLOSE_);
}
this.headerBuffer = new StringBuilder();
}
/** 是否已提交 */
public boolean isCommitted()
{
return header.isCommitted();
}
/** 是否可编辑 */
public boolean isEditable()
{
return header.isEditable();
}
/** 是否支持响应GZIP */
public boolean isResponseGZip()
{
return header.isResponseGZip();
}
/** 提交流 */
public void commit() throws IOException
{
if (header.isCommitted())
return;
header.setStep(_11_COMMITTED_);
output.commit();
if (isClose())
{//是否提交后关闭连接
header.close();
}
}
/***********************************************************************/
// 状态&版本&编码
/***********************************************************************/
public void setStatus(int code)
{
this.status = code;
this.reason = HttpStatus.getStatusMsg(code);
}
public int getStatus()
{
return status;
}
public void setVersion(String version)
{
this.version = version;
}
public String getReason()
{
return reason;
}
public String getCharacterEncoding()
{
return characterEncoding;
}
public long getFlushLength()
{
return output.getOutputLength();
}
/***********************************************************************/
// 设置头部信息
/***********************************************************************/
/** 设置头部域 */
public void setHeader(String key, Object value)
{
if (value == null)
return;
headers.put(key, Strings.valueOf(value));
}
/** 获取头部域 */
public String getHeader(String key)
{
return headers.get(key);
}
/** 判断是否有头部域 */
public boolean hasHeader(String key)
{
return headers.containsKey(key);
}
/** 是否关闭连接(没有连接属性和指明关闭的) */
public boolean isClose()
{
String connection = getHeader(_CONNECTION_);
return Validates.isEmptyBlank(connection) || _CLOSE_.equalsIgnoreCase(connection);
}
/** 设置头部日期格式域 */
public void setDateHeader(String key, long value)
{
String date = DateTimes.getDateTimeHttp(value);
setHeader(key, date);
}
/** 设置头部域,增加一个属性 */
public void addHeader(String key, Object value)
{
if (value == null)
return;
String v = getHeader(key);
if (v == null)
setHeader(key, value);
else
setHeader(key, v + "," + Strings.valueOf(value));
}
/** 设置头部日期格式域,增加一个日期类型的属性 */
public void addDateHeader(String key, long value)
{
String date = DateTimes.getDateTimeHttp(value);
String v = getHeader(key);
if (v == null)
setHeader(key, date);
else
setHeader(key, v + "," + value);
}
/** 删除头部域 */
public void removeHeader(String key)
{
headers.remove(key);
}
/** 添加复杂的头信息,如cookie等 */
public void addMultiHeader(String key, String value)
{
headerBuffer.append(key).append(_COLON_).append(value).append(_BR_);
}
/**
* 设置编码格式
*
* @param encoding 编码格式
*/
public void setCharacterEncoding(String encoding)
{
Asserts.assertNotEmptyBlank(encoding, "编码格式不允许为空白");
this.characterEncoding = encoding;
}
/**
* 设置内容类型,会增加默认的字符集到消息头中
*
* @param contentType 内容类型
*/
public void setContentType(String contentType)
{
Asserts.assertNotEmptyBlank(contentType, "内容类型不允许为空白");
//在contentType中查找encoding,格式为:text/html; charset=UTF-8
String mimeType = null;
int i0 = contentType.indexOf(';');
if (i0 == -1)
mimeType = contentType;
else
{
mimeType = contentType.substring(0, i0).trim();
int i1 = contentType.indexOf("charset=", i0);
if (i1>=0)
{//有设置编码则修改为该编码
characterEncoding = contentType.substring(i1 + 8);
}
}
if (Validates.isEmptyBlank(characterEncoding))
setHeader(_CONTENT_TYPE_, mimeType);
else
setHeader(_CONTENT_TYPE_, mimeType + "; charset="+characterEncoding);
}
/** 设置头部域中内容类型 */
public void setContentTypeNoCharset(String contentType)
{
Asserts.assertNotEmptyBlank(contentType, "内容类型不允许为空白");
Asserts.as(contentType.indexOf(";") == -1?null:"内容类型含字符集请调用setContentType方法");
setHeader(_CONTENT_TYPE_, contentType);
}
/***********************************************************************/
// 发送错误&内容
/***********************************************************************/
/**
* 发送错误信息
*
* @param code 编码
* @throws IOException 异常
*/
public void sendError(int code) throws IOException
{
sendError(code, null);
}
/**
* 发送错误信息
*
* @param code 编码
* @param reason 原因
* @throws IOException 异常
*/
public void sendError(int code, String reason) throws IOException
{
Asserts.asState(isEditable()?null:"已提交不允许再提交");
this.status = code;
if (Validates.isEmptyBlank(reason))
this.reason = HttpStatus.getStatusMsg(code);
else
this.reason = reason;
this.clear();
this.print(this.reason);
this.commit();
}
/**
* 发送错误信息,内容为HTML格式
*
* @param code 响应码
* @throws IOException 可能的异常
*/
public void sendErrorHTML(int code) throws IOException
{
sendErrorHTML(code, null);
}
/**
* 发送错误信息,内容为HTML格式
*
* @param code 响应码
* @param reason 响应原因
* @throws IOException 可能的异常
*/
public void sendErrorHTML(int code, String reason) throws IOException
{
Asserts.asState(isEditable()?null:"已提交不允许再提交");
this.status = code;
if (Validates.isEmptyBlank(reason))
this.reason = HttpStatus.getStatusMsg(code);
else
this.reason = reason;
//content
StringBuilder strb = new StringBuilder();
strb.append(_HTML_5_TYPE_).append(_BR_);
strb.append(_HTML).append(_BR_);
strb.append(_HEAD).append("<title>Error ").append(code).append("</title>").append(_HEAD).append(_BR_);
strb.append(_BODY).append("<h2>Error ").append(reason).append("</h2>").append(BODY_).append(_BR_);
strb.append(HTML_).append(_BR_);
this.clear();
this.print(this.reason);
this.commit();
}
/**
* 发送消息内容,如200,201等,含内容
*
* @param code 响应码,可以是200表示成功
* @param content 响应内容
* @throws IOException 可能的异常
*/
public void sendContent(int code, String content) throws IOException
{
Asserts.asState(isEditable()?null:"已提交不允许再提交");
this.status = code;
this.reason = HttpStatus.getStatusMsg(code);
this.print(content);
this.commit();
}
/**
* 发送重定向信息
*
* @param url 重定向URL
* @throws IOException 异常
*/
public void sendRedirect(String url) throws IOException
{
Asserts.asState(!isCommitted()?null:"已提交不允许再提交");
this.status = _302_FOUND_;
this.reason = _302_DESC_;
this.setHeader(_LOCATION_, url);
this.setHeader(_PROXY_CONNECTION_, _CLOSE_);
this.commit();
}
/**
* 只返回消息头,如304等,会清空内容
*
* @param code 响应码,可以是200表示成功
* @throws IOException 可能的异常
*/
public void sendHeader(int code) throws IOException
{
Asserts.asState(isEditable()?null:"有提交数据时不允许更新提交内容");
this.status = code;
this.reason = HttpStatus.getStatusMsg(code);
this.clear();
this.commit();
}
/***********************************************************************/
// write & print & commit
/***********************************************************************/
/** 获取输出流 */
public OutputStream getOutputStream()
{
return output;
}
/** 写内容字节方式 */
public void write(byte[] b) throws IOException
{
output.write(b);
}
/** 写内容加回车换行 */
public void println(String str) throws IOException
{
output.write(str.getBytes(characterEncoding));
output.write(_CRLF_);
}
/** 写回车换行 */
public void println() throws IOException
{
output.write(_CRLF_);
}
/** 写内容,无回车换行 */
public void print(String str) throws IOException
{
output.write(str.getBytes(characterEncoding));
}
/** 清理内容 */
public void clear()
{
output.reset();
}
/** 刷新流(分块) */
public void flush() throws IOException
{
if (isCommitted())
return;
output.flush();
}
public byte[] buildChunkedHeader(boolean chunked)
{
//1.1 写入标志
if (chunked)
{//分块
setHeader(_TRANSFER_ENCODING_, _CHUNKED_);
}
else
{//整块
if (isResponseGZip() && output.getContentLength() > 256)
{//如果支持gzip且内容长度大于256,则尝试压缩,压缩成功设置gzip头
if (output.processGZipCompress())
addHeader(_CONTENT_ENCODING_, _ENCODING_GZIP_);
}
//整块的不管用户是否设置了内容长度,统一重设
setHeader(_CONTENT_LENGTH_, output.getContentLength());
}
if (!hasHeader(_CONTENT_TYPE_))
{//1.2 没有contentType设置成默认格式
setContentType(_TEXT_HTML_UTF_8_);
}
//2.1 准备消息头
StringBuilder strb = new StringBuilder();
strb.append(version).append(" ").append(status).append(" ").append(Urls.encodeUTF8(reason)).append(_BR_);
for (Entry<String, String> entry : headers.entrySet())
{//2.2 响应消息头
strb.append(entry.getKey()).append(_COLON_).append(entry.getValue()).append(_BR_);
}
//2.3 响应复杂的消息头
strb.append(headerBuffer);
//2.4 增加时间和结束标志
strb.append(_DATE_).append(_COLON_).append(DateTimes.getDateTimeHttp()).append(_BR_);
strb.append(_BR_);//头部结束标志
return strb.toString().getBytes(_UTF_8_C_);
}
/***********************************************************************/
// toString & destroy
/***********************************************************************/
public String toString()
{
StringBuilder strb = new StringBuilder();
strb.append(version).append(" ").append(status).append(" ").append(Urls.encode(reason, characterEncoding)).append(_BR_);
if (headers != null)
{
for (Entry<String, String> entry : headers.entrySet())
{
strb.append(entry.getKey()).append(_COLON_).append(entry.getValue()).append(_BR_);
}
}
if (headerBuffer != null)
{
strb.append(headerBuffer.toString());
}
strb.append(_BR_);
return strb.toString();
}
/** 销毁 */
public void destroy()
{
if (headers != null)
{
headers.clear();
headers = null;
}
if (headerBuffer != null)
{
headerBuffer.setLength(0);
headerBuffer = null;
}
//引用置空
output = null;
header = null;
}
}