com.logicbus.backend.message.SSEMessage Maven / Gradle / Ivy
package com.logicbus.backend.message;
import com.alogic.xscript.*;
import com.alogic.xscript.doc.XsObject;
import com.alogic.xscript.log.LogInfo;
import com.alogic.xscript.plugins.Segment;
import com.anysoft.util.IOTools;
import com.anysoft.util.Properties;
import com.anysoft.util.PropertiesConstants;
import com.anysoft.util.Settings;
import com.logicbus.backend.Context;
import com.logicbus.backend.message.tools.ContentDigest;
import com.logicbus.backend.message.tools.GzipTool;
import com.logicbus.backend.message.tools.JsonFactory;
import com.logicbus.backend.server.http.HttpContext;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Server-Sent Events 消息
*
* @since 1.6.13.32 [20210128 duanyy]
*
* @version 1.6.13.33 [20210203 duanyy]
* - 支持客户端关闭连接的检测;
*
* @version 1.6.15.2 [20211103 duanyy]
* - 增加Json文档输入
*/
public class SSEMessage implements Message{
protected static final Logger LOG = LoggerFactory.getLogger(SSEMessage.class);
public final static String ID = "$sse";
public static final String END_OF_MSG = "\n\n";
public static final String END_OF_LINE = "\n";
public static final String TYPE_CMD = "cmd";
public static final String TYPE_MESSAGE = "message";
public static final String TYPE_DATA = "data";
public static final String TYPE_RAW = "raw";
public static final String TYPE_ERROR = "error";
public static final String CMD_QUIT = "quit";
public static final String FIELD_DATA = "data";
public static final String FIELD_EVENT = "event";
public static final String FIELD_SEPARATOR = ": ";
public static final String FIELD_ID = "id";
protected static String formContentType = "application/x-www-form-urlencoded";
protected static JsonFactory jsonFactory = Settings.getToolkit(JsonFactory.class);
protected static GzipTool gzipTool = Settings.getToolkit(GzipTool.class);
protected static ContentDigest contentDigest = Settings.getToolkit(ContentDigest.class);
static {
formContentType = PropertiesConstants.getString(Settings.get(),
"http.formContentType", "application/x-www-form-urlencoded");
}
/**
* Json结构的根节点
*/
protected Map root = null;
protected OutputStream out = null;
protected String contentType = "text/event-stream;charset=utf-8";
protected String encoding = "utf-8";
public Map getRoot(){
return root;
}
@Override
public String toString(){
return jsonFactory.toJsonString(root);
}
@Override
public void init(Context ctx) {
boolean gzipEnable = gzipTool.isRequestGzipEnable(ctx);
String data = null;
{
byte [] inputData = ctx.getRequestRaw();
if (inputData != null){
try {
if (gzipEnable){
inputData = gzipTool.decompress(ctx,inputData);
}
data = new String(inputData,ctx.getEncoding());
}catch (Exception ex){
}
}
}
if (data == null){
//当客户端通过form来post的时候,Message不去读取输入流。
String _contentType = ctx.getRequestContentType();
if (_contentType == null || !_contentType.startsWith(formContentType)){
InputStream in = null;
try {
in = ctx.getInputStream();
if (gzipEnable){
in = gzipTool.getInputStream(ctx,in);
}
data = Context.readFromInputStream(in, ctx.getEncoding());
}catch (Exception ex){
LOG.error("Error when reading data from inputstream",ex);
}finally{
IOTools.close(in);
}
}
}
if (contentDigest != null) {
contentDigest.digest(ctx, data);
}
if (StringUtils.isNotEmpty(data)){
Object rootObj = jsonFactory.fromJsonString(data);
if (rootObj instanceof Map){
root = (Map)rootObj;
}
}
if (root == null){
root = new HashMap();
}
contentType = "text/event-stream;charset=" + ctx.getEncoding();
encoding = ctx.getEncoding();
HttpServletResponse httpResponse = ((HttpContext)ctx).getResponse();
httpResponse.setStatus(HttpServletResponse.SC_OK);
httpResponse.setHeader("Content-Type",getContentType());
httpResponse.setHeader("Cache-Control","no-cache");
httpResponse.setHeader("Connection","keep-alive");
try {
out = httpResponse.getOutputStream();
out.flush();
}catch (Exception ex){
}
}
@Override
public void finish(Context ctx, boolean closeStream) {
writeCommand(CMD_QUIT,false);
IOTools.close(out);
}
@Override
public String getContentType() {
return contentType;
}
@Override
public long getContentLength() {
return 0;
}
public boolean writeMessage(String data,boolean flush){
StringBuffer line = new StringBuffer(FIELD_DATA);
line.append(FIELD_SEPARATOR).append(escapeCRLF(data)).append(END_OF_MSG);
return writeRaw(line.toString(),flush);
}
public boolean writeCommand(String cmd,boolean flush){
return writeEvent(TYPE_CMD,cmd,flush);
}
public boolean writeEvent(String id,String event,String data,boolean flush){
StringBuffer line = new StringBuffer();
line.append(FIELD_ID).append(FIELD_SEPARATOR).append(id).append(END_OF_LINE);
line.append(FIELD_EVENT).append(FIELD_SEPARATOR).append(event).append(END_OF_LINE);
line.append(FIELD_DATA).append(FIELD_SEPARATOR).append(escapeCRLF(data)).append(END_OF_MSG);
return writeRaw(line.toString(),flush);
}
public boolean writeEvent(String event,String data,boolean flush){
StringBuffer line = new StringBuffer();
line.append(FIELD_EVENT).append(FIELD_SEPARATOR).append(event).append(END_OF_LINE);
line.append(FIELD_DATA).append(FIELD_SEPARATOR).append(escapeCRLF(data)).append(END_OF_MSG);
return writeRaw(line.toString(),flush);
}
public boolean writeRaw(String raw,boolean flush){
try {
out.write(raw.getBytes(encoding));
if (flush) {
out.flush();
}
}catch (Exception ex){
return false;
}
return true;
}
protected static String escapeCRLF(String value){
return value.replaceAll("\r|\n", "");
}
/**
* 写消息插件
*/
public static class Writer extends AbstractLogiclet{
private String pid = ID;
private String $type = TYPE_MESSAGE;
private String $data;
private boolean ref = false;
private boolean raw = true;
private String $flush = "true";
private String id;
public Writer(String tag, Logiclet p) {
super(tag, p);
}
@Override
public void configure(Properties p){
pid = PropertiesConstants.getString(p,"pid",pid,true);
$type = PropertiesConstants.getRaw(p,"type",$type);
$data = PropertiesConstants.getRaw(p,"data",$data);
ref = PropertiesConstants.getBoolean(p,"ref",ref);
id = PropertiesConstants.getString(p,"id","$" + this.getXmlTag());
raw = PropertiesConstants.getBoolean(p,"raw",raw);
$flush = PropertiesConstants.getRaw(p,"flush",$flush);
}
@Override
protected void onExecute(XsObject root, XsObject current, LogicletContext ctx, ExecuteWatcher watcher) {
SSEMessage msg = ctx.getObject(pid);
if (msg != null){
String type = PropertiesConstants.transform(ctx,$type,TYPE_MESSAGE);
String data = PropertiesConstants.transform(ctx,$data,"");
if (ref){
if (StringUtils.isNotEmpty(data)){
data = raw?PropertiesConstants.getRaw(ctx,data,"")
:PropertiesConstants.getString(ctx,data,"");
}
}
if (StringUtils.isNotEmpty(data)){
boolean flush = PropertiesConstants.transform(ctx, $flush, true);
switch(type){
case TYPE_MESSAGE:
case TYPE_DATA:
ctx.SetValue(id,
BooleanUtils.toStringTrueFalse(
msg.writeMessage(data,flush)));
break;
case TYPE_CMD:
ctx.SetValue(id,
BooleanUtils.toStringTrueFalse(
msg.writeCommand(data, flush)));
break;
case TYPE_RAW:
ctx.SetValue(id,
BooleanUtils.toStringTrueFalse(
msg.writeRaw(data, flush)));
break;
case TYPE_ERROR:
default:
ctx.SetValue(id,
BooleanUtils.toStringTrueFalse(
msg.writeEvent(type, data, flush)));
}
}
}else{
String type = PropertiesConstants.transform(ctx,$type,TYPE_MESSAGE);
String data = PropertiesConstants.transform(ctx,$data,"");
if (ref){
if (StringUtils.isNotEmpty(data)){
data = raw?PropertiesConstants.getRaw(ctx,data,"")
:PropertiesConstants.getString(ctx,data,"");
}
}
log(data,ctx);
}
}
}
/**
* 写消息插件
*/
public static class Log extends Segment {
private String pid = ID;
private boolean flush = true;
public Log(String tag, Logiclet p) {
super(tag, p);
}
@Override
public void configure(Properties p){
pid = PropertiesConstants.getString(p,"pid",pid,true);
flush = PropertiesConstants.getBoolean(p,"flush",flush,true);
}
@Override
public void log(LogInfo logInfo,Properties ctx){
if (ctx instanceof LogicletContext){
LogicletContext logicletContext = (LogicletContext)ctx;
SSEMessage msg = logicletContext.getObject(pid);
if (msg != null) {
msg.writeMessage(logInfo.message(), flush);
}else{
log(logInfo.message(),logInfo.level(),logInfo.activity());
}
}else{
log(logInfo.message(),logInfo.level(),logInfo.activity());
}
}
@Override
protected void onExecute(XsObject root,XsObject current, LogicletContext ctx, ExecuteWatcher watcher) {
LogHandler oldHandler = ctx.getObject(LOG_ID);
try {
ctx.setObject(LOG_ID,this);
super.onExecute(root,current,ctx,watcher);
}finally {
ctx.removeObject(LOG_ID);
if (oldHandler != null){
ctx.setObject(LOG_ID,oldHandler);
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy