gu.dtalk.engine.DtalkHttpServer Maven / Gradle / Ivy
package gu.dtalk.engine;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static gu.dtalk.CommonConstant.DEFAULT_IDLE_TIME_MILLS;
import static gu.dtalk.engine.DeviceUtils.DEVINFO_PROVIDER;
import static org.nanohttpd.protocols.http.response.Response.newFixedLengthResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicReference;
import org.nanohttpd.protocols.http.IHTTPSession;
import org.nanohttpd.protocols.http.NanoHTTPD;
import org.nanohttpd.protocols.http.request.Method;
import org.nanohttpd.protocols.http.response.Response;
import org.nanohttpd.protocols.http.response.Status;
import org.nanohttpd.protocols.websockets.CloseCode;
import org.nanohttpd.protocols.websockets.NanoWSD;
import org.nanohttpd.protocols.websockets.WebSocket;
import org.nanohttpd.protocols.websockets.WebSocketFrame;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.gitee.l0km.com4j.base.BinaryUtils;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import gu.dtalk.Ack;
import gu.dtalk.MenuItem;
import gu.simplemq.exceptions.SmqUnsubscribeException;
import gu.simplemq.json.BaseJsonEncoder;
import static gu.dtalk.CommonConstant.*;
import static gu.dtalk.Version.*;
/**
* dtalk http 服务
* @author guyadong
*
*/
public class DtalkHttpServer extends NanoWSD {
private static final Logger logger = LoggerFactory.getLogger(DtalkHttpServer.class);
/**
* Standard HTTP header names.
*/
public static final class HeaderNames {
/**
* {@code "Accept"}
*/
public static final String ACCEPT = "Accept";
/**
* {@code "Accept-Charset"}
*/
public static final String ACCEPT_CHARSET = "Accept-Charset";
/**
* {@code "Accept-Encoding"}
*/
public static final String ACCEPT_ENCODING = "Accept-Encoding";
/**
* {@code "Accept-Language"}
*/
public static final String ACCEPT_LANGUAGE = "Accept-Language";
/**
* {@code "Accept-Ranges"}
*/
public static final String ACCEPT_RANGES = "Accept-Ranges";
/**
* {@code "Accept-Patch"}
*/
public static final String ACCEPT_PATCH = "Accept-Patch";
/**
* {@code "Access-Control-Allow-Credentials"}
*/
public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
/**
* {@code "Access-Control-Allow-Headers"}
*/
public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
/**
* {@code "Access-Control-Allow-Methods"}
*/
public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
/**
* {@code "Access-Control-Allow-Origin"}
*/
public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
/**
* {@code "Access-Control-Expose-Headers"}
*/
public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
/**
* {@code "Access-Control-Max-Age"}
*/
public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
/**
* {@code "Access-Control-Request-Headers"}
*/
public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
/**
* {@code "Access-Control-Request-Method"}
*/
public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
/**
* {@code "Age"}
*/
public static final String AGE = "Age";
/**
* {@code "Allow"}
*/
public static final String ALLOW = "Allow";
/**
* {@code "Authorization"}
*/
public static final String AUTHORIZATION = "Authorization";
/**
* {@code "Cache-Control"}
*/
public static final String CACHE_CONTROL = "Cache-Control";
/**
* {@code "Connection"}
*/
public static final String CONNECTION = "Connection";
/**
* {@code "Content-Base"}
*/
public static final String CONTENT_BASE = "Content-Base";
/**
* {@code "Content-Encoding"}
*/
public static final String CONTENT_ENCODING = "Content-Encoding";
/**
* {@code "Content-Language"}
*/
public static final String CONTENT_LANGUAGE = "Content-Language";
/**
* {@code "Content-Length"}
*/
public static final String CONTENT_LENGTH = "Content-Length";
/**
* {@code "Content-Location"}
*/
public static final String CONTENT_LOCATION = "Content-Location";
/**
* {@code "Content-Transfer-Encoding"}
*/
public static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
/**
* {@code "Content-MD5"}
*/
public static final String CONTENT_MD5 = "Content-MD5";
/**
* {@code "Content-Range"}
*/
public static final String CONTENT_RANGE = "Content-Range";
/**
* {@code "Content-Type"}
*/
public static final String CONTENT_TYPE = "Content-Type";
/**
* {@code "Cookie"}
*/
public static final String COOKIE = "Cookie";
/**
* {@code "Date"}
*/
public static final String DATE = "Date";
/**
* {@code "ETag"}
*/
public static final String ETAG = "ETag";
/**
* {@code "Expect"}
*/
public static final String EXPECT = "Expect";
/**
* {@code "Expires"}
*/
public static final String EXPIRES = "Expires";
/**
* {@code "From"}
*/
public static final String FROM = "From";
/**
* {@code "Host"}
*/
public static final String HOST = "Host";
/**
* {@code "If-Match"}
*/
public static final String IF_MATCH = "If-Match";
/**
* {@code "If-Modified-Since"}
*/
public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
/**
* {@code "If-None-Match"}
*/
public static final String IF_NONE_MATCH = "If-None-Match";
/**
* {@code "If-Range"}
*/
public static final String IF_RANGE = "If-Range";
/**
* {@code "If-Unmodified-Since"}
*/
public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
/**
* {@code "Last-Modified"}
*/
public static final String LAST_MODIFIED = "Last-Modified";
/**
* {@code "Location"}
*/
public static final String LOCATION = "Location";
/**
* {@code "Max-Forwards"}
*/
public static final String MAX_FORWARDS = "Max-Forwards";
/**
* {@code "Origin"}
*/
public static final String ORIGIN = "Origin";
/**
* {@code "Pragma"}
*/
public static final String PRAGMA = "Pragma";
/**
* {@code "Proxy-Authenticate"}
*/
public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate";
/**
* {@code "Proxy-Authorization"}
*/
public static final String PROXY_AUTHORIZATION = "Proxy-Authorization";
/**
* {@code "Range"}
*/
public static final String RANGE = "Range";
/**
* {@code "Referer"}
*/
public static final String REFERER = "Referer";
/**
* {@code "Retry-After"}
*/
public static final String RETRY_AFTER = "Retry-After";
/**
* {@code "Sec-WebSocket-Key1"}
*/
public static final String SEC_WEBSOCKET_KEY1 = "Sec-WebSocket-Key1";
/**
* {@code "Sec-WebSocket-Key2"}
*/
public static final String SEC_WEBSOCKET_KEY2 = "Sec-WebSocket-Key2";
/**
* {@code "Sec-WebSocket-Location"}
*/
public static final String SEC_WEBSOCKET_LOCATION = "Sec-WebSocket-Location";
/**
* {@code "Sec-WebSocket-Origin"}
*/
public static final String SEC_WEBSOCKET_ORIGIN = "Sec-WebSocket-Origin";
/**
* {@code "Sec-WebSocket-Protocol"}
*/
public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol";
/**
* {@code "Sec-WebSocket-Version"}
*/
public static final String SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version";
/**
* {@code "Sec-WebSocket-Key"}
*/
public static final String SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key";
/**
* {@code "Sec-WebSocket-Accept"}
*/
public static final String SEC_WEBSOCKET_ACCEPT = "Sec-WebSocket-Accept";
/**
* {@code "Server"}
*/
public static final String SERVER = "Server";
/**
* {@code "Set-Cookie"}
*/
public static final String SET_COOKIE = "Set-Cookie";
/**
* {@code "Set-Cookie2"}
*/
public static final String SET_COOKIE2 = "Set-Cookie2";
/**
* {@code "TE"}
*/
public static final String TE = "TE";
/**
* {@code "Trailer"}
*/
public static final String TRAILER = "Trailer";
/**
* {@code "Transfer-Encoding"}
*/
public static final String TRANSFER_ENCODING = "Transfer-Encoding";
/**
* {@code "Upgrade"}
*/
public static final String UPGRADE = "Upgrade";
/**
* {@code "User-Agent"}
*/
public static final String USER_AGENT = "User-Agent";
/**
* {@code "Vary"}
*/
public static final String VARY = "Vary";
/**
* {@code "Via"}
*/
public static final String VIA = "Via";
/**
* {@code "Warning"}
*/
public static final String WARNING = "Warning";
/**
* {@code "WebSocket-Location"}
*/
public static final String WEBSOCKET_LOCATION = "WebSocket-Location";
/**
* {@code "WebSocket-Origin"}
*/
public static final String WEBSOCKET_ORIGIN = "WebSocket-Origin";
/**
* {@code "WebSocket-Protocol"}
*/
public static final String WEBSOCKET_PROTOCOL = "WebSocket-Protocol";
/**
* {@code "WWW-Authenticate"}
*/
public static final String WWW_AUTHENTICATE = "WWW-Authenticate";
private HeaderNames() {
}
}
/**
* Standard HTTP header values.
*/
public static final class HeaderValues {
/**
* {@code "application/x-www-form-urlencoded"}
*/
public static final String APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded";
/**
* {@code "base64"}
*/
public static final String BASE64 = "base64";
/**
* {@code "binary"}
*/
public static final String BINARY = "binary";
/**
* {@code "boundary"}
*/
public static final String BOUNDARY = "boundary";
/**
* {@code "bytes"}
*/
public static final String BYTES = "bytes";
/**
* {@code "charset"}
*/
public static final String CHARSET = "charset";
/**
* {@code "chunked"}
*/
public static final String CHUNKED = "chunked";
/**
* {@code "close"}
*/
public static final String CLOSE = "close";
/**
* {@code "compress"}
*/
public static final String COMPRESS = "compress";
/**
* {@code "100-continue"}
*/
public static final String CONTINUE = "100-continue";
/**
* {@code "deflate"}
*/
public static final String DEFLATE = "deflate";
/**
* {@code "gzip"}
*/
public static final String GZIP = "gzip";
/**
* {@code "identity"}
*/
public static final String IDENTITY = "identity";
/**
* {@code "keep-alive"}
*/
public static final String KEEP_ALIVE = "keep-alive";
/**
* {@code "max-age"}
*/
public static final String MAX_AGE = "max-age";
/**
* {@code "max-stale"}
*/
public static final String MAX_STALE = "max-stale";
/**
* {@code "min-fresh"}
*/
public static final String MIN_FRESH = "min-fresh";
/**
* {@code "multipart/form-data"}
*/
public static final String MULTIPART_FORM_DATA = "multipart/form-data";
/**
* {@code "must-revalidate"}
*/
public static final String MUST_REVALIDATE = "must-revalidate";
/**
* {@code "no-cache"}
*/
public static final String NO_CACHE = "no-cache";
/**
* {@code "no-store"}
*/
public static final String NO_STORE = "no-store";
/**
* {@code "no-transform"}
*/
public static final String NO_TRANSFORM = "no-transform";
/**
* {@code "none"}
*/
public static final String NONE = "none";
/**
* {@code "only-if-cached"}
*/
public static final String ONLY_IF_CACHED = "only-if-cached";
/**
* {@code "private"}
*/
public static final String PRIVATE = "private";
/**
* {@code "proxy-revalidate"}
*/
public static final String PROXY_REVALIDATE = "proxy-revalidate";
/**
* {@code "public"}
*/
public static final String PUBLIC = "public";
/**
* {@code "quoted-printable"}
*/
public static final String QUOTED_PRINTABLE = "quoted-printable";
/**
* {@code "s-maxage"}
*/
public static final String S_MAXAGE = "s-maxage";
/**
* {@code "trailers"}
*/
public static final String TRAILERS = "trailers";
/**
* {@code "Upgrade"}
*/
public static final String UPGRADE = "Upgrade";
/**
* {@code "WebSocket"}
*/
public static final String WEBSOCKET = "WebSocket";
private HeaderValues() {
}
}
private static final String DTALK_SESSION="dtalk-session";
public static final String APPICATION_JSON="application/json";
private static final String UNAUTH_SESSION="UNAUTHORIZATION SESSION";
private static final String AUTH_OK="AUTHORIZATION OK";
private static final String CLIENT_LOCKED="ANOTHER CLIENT LOCKED";
private static final String INVALID_PWD="INVALID REQUEST PASSWORD";
private static final String POST_DATA="postData";
private static final String DTALK_PREFIX="/dtalk";
private static final String STATIC_PAGE_PREFIX="/web";
private static final String ALLOW_METHODS = Joiner.on(',').join(Arrays.asList(Method.POST,Method.GET,Method.PUT,Method.DELETE));
private static final String ALLOW_METHODS_CORS = ALLOW_METHODS + "," + Method.OPTIONS ;
private static final String DEFAULT_ALLOW_HEADERS = Joiner.on(',').join(Arrays.asList(HeaderNames.CONTENT_TYPE));
public static final URL DEFAULT_HOME_PAGE = DtalkHttpServer.class.getResource(STATIC_PAGE_PREFIX + "/index.html");
private static final MapMIME_OF_SUFFIX = ImmutableMap.builder()
.put(".jpeg", "image/jpeg")
.put(".jpg", "image/jpeg")
.put(".png", "image/png")
.put(".gif", "image/gif")
.put(".htm","text/html")
.put(".html","text/html")
.put(".txt","text/plain")
.put(".css","text/css")
.put(".csv","text/csv")
.put(".json","application/json")
.put(".js","application/javascript")
.put(".xml","application/xml")
.put(".zip","application/zip")
.put(".pdf","application/pdf")
.put(".sql","application/sql")
.put(".doc","application/msword")
.build();
private static final Set SUPPORTED_MIME = ImmutableSet.copyOf(MIME_OF_SUFFIX.values());
private static class SingletonTimer{
private static final Timer instnace = new Timer(true);
}
private Timer timer;
private Timer getTimer(){
// 懒加载
if(timer == null){
timer = SingletonTimer.instnace;
}
return timer;
}
private long idleTimeLimit = DEFAULT_IDLE_TIME_MILLS;
private long timerPeriod = 2000;
private String selfMac;
/**
* 当前对话ID
*/
private String dtalkSession;
/**
* 当前websocket 连接
*/
private final AtomicReference wsReference = new AtomicReference<>();
private final Supplier> webSocketSupplier = new Supplier>() {
@Override
public AtomicReference get() {
return wsReference;
}
};
private ItemEngineHttpImpl engine = new ItemEngineHttpImpl().setSupplier(webSocketSupplier);
private boolean debug = false;
/**
* 不做安全验证
*/
private boolean noAuth = false;
/**
* 不支持跨域请求(CORS)
*/
private boolean noCORS = false;
/**
* 首页内容
*/
private String homePageContent;
/**
* 处理扩展http请求的实例
*/
private final Map> extServes =
Collections.synchronizedMap(Maps.>newLinkedHashMap());
public DtalkHttpServer() {
this(DEFAULT_HTTP_PORT);
}
public DtalkHttpServer(int port) {
super(port);
this.selfMac = BinaryUtils.toHex(DeviceUtils.DEVINFO_PROVIDER.getMac());
// 定时检查引擎工作状态,当空闲超时,则中止连接
getTimer().schedule(new TimerTask() {
@Override
public void run() {
try{
if(null != dtalkSession && DtalkHttpServer.this.isAlive()){
long lasthit = engine.lastHitTime();
if(System.currentTimeMillis() - lasthit > idleTimeLimit){
resetSession();
}
}
}catch (Throwable e) {
logger.error(e.getMessage());
}
}
}, 0, timerPeriod);
try {
setHomePage(DEFAULT_HOME_PAGE);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private boolean isAuthorizationSession(IHTTPSession session){
return dtalkSession != null && dtalkSession.equals(session.getCookies().read(DTALK_SESSION));
}
private void checkAuthorizationSession(IHTTPSession session) throws ResponseException{
if(noAuth || isAuthorizationSession(session)){
return;
}
throw new ResponseException(Status.UNAUTHORIZED, ackError(UNAUTH_SESSION));
}
private Response responseAck(Ack ack){
String json=BaseJsonEncoder.getEncoder().toJsonString(ack);
return newFixedLengthResponse(
Ack.Status.OK.equals(ack.getStatus()) ? Status.OK: Status.INTERNAL_ERROR,
APPICATION_JSON,
json);
}
private String ackMessage(Ack.Status status,String message){
Ack
© 2015 - 2025 Weber Informatics LLC | Privacy Policy