第一次提交
This commit is contained in:
39
fastbee-server/http-server/pom.xml
Normal file
39
fastbee-server/http-server/pom.xml
Normal file
@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.fastbee</groupId>
|
||||
<artifactId>fastbee-server</artifactId>
|
||||
<version>3.8.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>http-server</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.fastbee</groupId>
|
||||
<artifactId>iot-server-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session-data-redis</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session</artifactId>
|
||||
<version>1.3.5.RELEASE</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,55 @@
|
||||
package com.fastbee.http.auth;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.*;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class BasicAuth {
|
||||
@Value("${server.http.auth.user.name)")
|
||||
private String user;
|
||||
@Value("${server.http.auth.user.password)")
|
||||
private String password;
|
||||
private static final String REALM = "FastbeeBasic";
|
||||
|
||||
public boolean auth(ChannelHandlerContext ctx, String authHeader) {
|
||||
if (authHeader != null && authHeader.startsWith("Basic ")) {
|
||||
String encodedCredentials = authHeader.substring("Basic ".length());
|
||||
String credentials = new String(Base64.getDecoder().decode(encodedCredentials), StandardCharsets.UTF_8);
|
||||
String[] parts = credentials.split(":", 2);
|
||||
if (parts.length == 2 && validateUser(parts[0], parts[1])) {
|
||||
// 用户名和密码验证通过,继续处理请求
|
||||
return true;
|
||||
} else {
|
||||
// 发送401 Unauthorized响应
|
||||
sendUnauthorizedResponse(ctx);
|
||||
}
|
||||
} else {
|
||||
// 发送401 Unauthorized响应
|
||||
sendUnauthorizedResponse(ctx);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean validateUser(String username, String password) {
|
||||
// 实现用户验证逻辑
|
||||
return username.equals(user) && password.equals(this.password);
|
||||
}
|
||||
|
||||
public void sendUnauthorizedResponse(ChannelHandlerContext ctx) {
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(
|
||||
HttpVersion.HTTP_1_1, HttpResponseStatus.UNAUTHORIZED,
|
||||
Unpooled.copiedBuffer("Unauthorized\r\n", CharsetUtil.UTF_8));
|
||||
response.headers().set(HttpHeaderNames.WWW_AUTHENTICATE, "Basic realm=\"" + REALM + "\"");
|
||||
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.fastbee.http.auth;
|
||||
|
||||
import com.fastbee.http.utils.DigestAuthUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.*;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DigestAuth {
|
||||
|
||||
private static final String REALM = "FastbeeDigest";
|
||||
private static final String NONCE = UUID.randomUUID().toString();
|
||||
@Value("${server.http.auth.user.name)")
|
||||
private String user;
|
||||
@Value("${server.http.auth.user.password)")
|
||||
private String password;
|
||||
|
||||
public boolean auth(ChannelHandlerContext ctx, FullHttpRequest request) throws NoSuchAlgorithmException {
|
||||
if (new DigestAuthUtil().doAuthenticatePlainTextPassword(request,
|
||||
password)) {
|
||||
return true;
|
||||
} else {
|
||||
// 发送401 Unauthorized响应
|
||||
sendUnauthorizedResponse(ctx);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void sendUnauthorizedResponse(ChannelHandlerContext ctx) {
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(
|
||||
HttpVersion.HTTP_1_1, HttpResponseStatus.UNAUTHORIZED,
|
||||
Unpooled.copiedBuffer("Unauthorized\r\n", CharsetUtil.UTF_8));
|
||||
response.headers().set(HttpHeaderNames.WWW_AUTHENTICATE,
|
||||
"Digest realm=\"" + REALM + "\", nonce=\"" + NONCE + "\"");
|
||||
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.fastbee.http.handler;
|
||||
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
public interface IHttpReqHandler {
|
||||
public void processMsg(FullHttpRequest req, HttpSession session);
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.fastbee.http.handler;
|
||||
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
public interface IHttpResHandler {
|
||||
public void processMsg(FullHttpResponse req) throws ParseException;
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.fastbee.http.handler.req;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem;
|
||||
import com.fastbee.http.service.IHttpMqttService;
|
||||
import com.fastbee.http.handler.IHttpReqHandler;
|
||||
import com.fastbee.http.server.HttpListener;
|
||||
import com.fastbee.iot.domain.Device;
|
||||
import com.fastbee.iot.service.IDeviceService;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class EventHttpReqHandler implements InitializingBean, IHttpReqHandler {
|
||||
@Autowired
|
||||
private HttpListener httpListener;
|
||||
|
||||
@Autowired
|
||||
private IHttpMqttService mqttService;
|
||||
|
||||
@Autowired
|
||||
private IDeviceService deviceService;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
String uri = "/event/post";
|
||||
httpListener.addRequestProcessor(uri, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processMsg(FullHttpRequest req, HttpSession session) {
|
||||
String serialNumber = (String) session.getAttribute("SerialNumber");
|
||||
Device device = deviceService.selectDeviceBySerialNumber(serialNumber);
|
||||
if (device != null) {
|
||||
List<ThingsModelSimpleItem> thingsModelSimpleItems = JSON.parseArray(req.content().toString(StandardCharsets.UTF_8), ThingsModelSimpleItem.class);
|
||||
mqttService.publishEvent(device,thingsModelSimpleItems);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package com.fastbee.http.handler.req;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.fastbee.common.enums.DeviceStatus;
|
||||
import com.fastbee.common.utils.DateUtils;
|
||||
import com.fastbee.http.service.IHttpMqttService;
|
||||
import com.fastbee.http.handler.IHttpReqHandler;
|
||||
import com.fastbee.http.manager.HttpSessionManager;
|
||||
import com.fastbee.http.server.HttpListener;
|
||||
import com.fastbee.iot.domain.Device;
|
||||
import com.fastbee.iot.service.IDeviceService;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class InfoHttpReqHandler implements InitializingBean, IHttpReqHandler {
|
||||
@Autowired
|
||||
private HttpListener httpListener;
|
||||
|
||||
@Autowired
|
||||
private HttpSessionManager sessionManager;
|
||||
|
||||
@Autowired
|
||||
private IHttpMqttService mqttService;
|
||||
|
||||
@Autowired
|
||||
private IDeviceService deviceService;
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
String uri = "/info/post";
|
||||
httpListener.addRequestProcessor(uri, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processMsg(FullHttpRequest req, HttpSession session) {
|
||||
//设备上报基本信息后保存到会话中
|
||||
Device device = JSON.parseObject(req.content().toString(StandardCharsets.UTF_8), Device.class);
|
||||
session.setAttribute("SerialNumber", device.getSerialNumber());
|
||||
session.setAttribute("productId", device.getProductId());
|
||||
device.setActiveTime(DateUtils.getNowDate());
|
||||
device.setStatus(DeviceStatus.ONLINE.getType());
|
||||
sessionManager.saveSession(session.getId(), session);
|
||||
//更新设备信息
|
||||
deviceService.updateDevice(device);
|
||||
//发布到mqtt
|
||||
mqttService.publishInfo(device);
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.fastbee.http.handler.req;
|
||||
|
||||
import com.fastbee.common.utils.DateUtils;
|
||||
import com.fastbee.http.handler.IHttpReqHandler;
|
||||
import com.fastbee.http.server.HttpListener;
|
||||
import com.fastbee.iot.domain.Device;
|
||||
import com.fastbee.iot.service.IDeviceService;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class KeepaliveHttpReqHandler implements InitializingBean, IHttpReqHandler {
|
||||
@Autowired
|
||||
private HttpListener httpListener;
|
||||
|
||||
@Autowired
|
||||
private IDeviceService deviceService;
|
||||
|
||||
@Override
|
||||
public void processMsg(FullHttpRequest req, HttpSession session) {
|
||||
String serialNumber = (String) session.getAttribute("SerialNumber");
|
||||
Device device = deviceService.selectDeviceBySerialNumber(serialNumber);
|
||||
if (device != null) {
|
||||
device.setActiveTime(DateUtils.getNowDate());
|
||||
deviceService.updateDevice(device);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
String uri = "/keepalive";
|
||||
httpListener.addRequestProcessor(uri, this);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package com.fastbee.http.handler.req;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem;
|
||||
import com.fastbee.http.service.IHttpMqttService;
|
||||
import com.fastbee.http.handler.IHttpReqHandler;
|
||||
import com.fastbee.http.server.HttpListener;
|
||||
import com.fastbee.iot.domain.Device;
|
||||
import com.fastbee.iot.service.IDeviceService;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class MonitorHttpReqHandler implements InitializingBean, IHttpReqHandler {
|
||||
@Autowired
|
||||
private HttpListener httpListener;
|
||||
|
||||
@Autowired
|
||||
private IHttpMqttService mqttService;
|
||||
|
||||
@Autowired
|
||||
private IDeviceService deviceService;
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
String uri = "/monitor/post";
|
||||
httpListener.addRequestProcessor(uri, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processMsg(FullHttpRequest req, HttpSession session) {
|
||||
String serialNumber = (String) session.getAttribute("SerialNumber");
|
||||
Device device = deviceService.selectDeviceBySerialNumber(serialNumber);
|
||||
if (device != null) {
|
||||
List<ThingsModelSimpleItem> thingsModelSimpleItems = JSON.parseArray(req.content().toString(StandardCharsets.UTF_8), ThingsModelSimpleItem.class);
|
||||
mqttService.publishMonitor(device,thingsModelSimpleItems);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.fastbee.http.handler.req;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem;
|
||||
import com.fastbee.http.service.IHttpMqttService;
|
||||
import com.fastbee.http.handler.IHttpReqHandler;
|
||||
import com.fastbee.http.server.HttpListener;
|
||||
import com.fastbee.iot.domain.Device;
|
||||
import com.fastbee.iot.service.IDeviceService;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class PropertyHttpReqHandler implements InitializingBean, IHttpReqHandler {
|
||||
@Autowired
|
||||
private HttpListener httpListener;
|
||||
|
||||
@Autowired
|
||||
private IHttpMqttService mqttService;
|
||||
|
||||
@Autowired
|
||||
private IDeviceService deviceService;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
String uri = "/property/post";
|
||||
httpListener.addRequestProcessor(uri, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processMsg(FullHttpRequest req, HttpSession session) {
|
||||
String serialNumber = (String) session.getAttribute("SerialNumber");
|
||||
Device device = deviceService.selectDeviceBySerialNumber(serialNumber);
|
||||
if (device != null) {
|
||||
List<ThingsModelSimpleItem> thingsModelSimpleItems = JSON.parseArray(req.content().toString(StandardCharsets.UTF_8), ThingsModelSimpleItem.class);
|
||||
mqttService.publishProperty(device, thingsModelSimpleItems, 0);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package com.fastbee.http.manager;
|
||||
|
||||
import com.fastbee.common.core.redis.RedisCache;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class HttpSessionManager {
|
||||
@Autowired
|
||||
private RedisCache sessionStore;
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
public HttpSessionManager() {
|
||||
}
|
||||
|
||||
public String createSession() {
|
||||
String sessionId = generateSessionId();
|
||||
saveSession(sessionId, new NettyHttpSession(sessionId));
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public HttpSession getSession(String sessionId) {
|
||||
String sessionData = sessionStore.getCacheObject("session:" + sessionId);
|
||||
if (sessionData != null) {
|
||||
try {
|
||||
return new NettyHttpSession(sessionId, objectMapper.readValue(sessionData, Map.class));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void saveSession(String sessionId, HttpSession session) {
|
||||
try {
|
||||
String sessionData = objectMapper.writeValueAsString(((NettyHttpSession) session).getAttributeMap());
|
||||
sessionStore.setCacheObject("session:" + sessionId, sessionData);
|
||||
sessionStore.expire("session:" + sessionId, 30 * 60);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String generateSessionId() {
|
||||
return Long.toHexString(Double.doubleToLongBits(Math.random()));
|
||||
}
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
package com.fastbee.http.manager;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import javax.servlet.http.HttpSessionContext;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class NettyHttpSession implements HttpSession {
|
||||
private final String id;
|
||||
private final Map<String, Object> attributes;
|
||||
|
||||
public NettyHttpSession(String id) {
|
||||
this(id, new HashMap<>());
|
||||
}
|
||||
|
||||
public NettyHttpSession(String id, Map<String, Object> attributes) {
|
||||
this.id = id;
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCreationTime() {
|
||||
// Implement as needed
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastAccessedTime() {
|
||||
// Implement as needed
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public javax.servlet.ServletContext getServletContext() {
|
||||
// Implement as needed
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxInactiveInterval(int interval) {
|
||||
// Implement as needed
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxInactiveInterval() {
|
||||
// Implement as needed
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpSessionContext getSessionContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
// Implement as needed
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNew() {
|
||||
// Implement as needed
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name) {
|
||||
return attributes.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(String name) {
|
||||
return attributes.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getAttributeNames() {
|
||||
// Implement as needed
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getValueNames() {
|
||||
// Implement as needed
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, Object value) {
|
||||
attributes.put(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putValue(String name, Object value) {
|
||||
attributes.put(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
attributes.remove(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeValue(String name) {
|
||||
attributes.remove(name);
|
||||
}
|
||||
|
||||
public Map<String, Object> getAttributeMap() {
|
||||
return attributes;
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package com.fastbee.http.server;
|
||||
|
||||
import com.fastbee.http.handler.IHttpReqHandler;
|
||||
import com.fastbee.http.handler.IHttpResHandler;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class HttpListener {
|
||||
private static final Map<String, IHttpReqHandler> requestProcessorMap = new ConcurrentHashMap<>();
|
||||
private static final Map<String, IHttpResHandler> responseProcessorMap = new ConcurrentHashMap<>();
|
||||
|
||||
public void addRequestProcessor(String method, IHttpReqHandler processor) {
|
||||
requestProcessorMap.put(method, processor);
|
||||
}
|
||||
|
||||
public void addResponseProcessor(String method, IHttpResHandler processor) {
|
||||
responseProcessorMap.put(method, processor);
|
||||
}
|
||||
|
||||
@Async("taskExecutor")
|
||||
public void processRequest(FullHttpRequest req, HttpSession session) {
|
||||
String uri = req.uri();
|
||||
IHttpReqHandler sipRequestProcessor = requestProcessorMap.get(uri);
|
||||
if (sipRequestProcessor == null) {
|
||||
log.warn("不支持的uri:{}", uri);
|
||||
return;
|
||||
}
|
||||
requestProcessorMap.get(uri).processMsg(req, session);
|
||||
}
|
||||
|
||||
@Async("taskExecutor")
|
||||
public void processResponse(FullHttpResponse response) {
|
||||
HttpResponseStatus status = response.status();
|
||||
// 响应成功
|
||||
if ((status.code() >= HttpResponseStatus.OK.code()) && (status.code() < HttpResponseStatus.MULTIPLE_CHOICES.code())) {
|
||||
log.info("response:{},", response.content());
|
||||
log.info("接收response响应!status:{}", status);
|
||||
|
||||
} else if ((status.code() >= HttpResponseStatus.CONTINUE.code()) && (status.code() < HttpResponseStatus.OK.code())) {
|
||||
log.info("接收response响应!status:{}", status);
|
||||
} else {
|
||||
log.warn("接收到失败的response响应!status:{}", status);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package com.fastbee.http.server;
|
||||
|
||||
import com.fastbee.server.Server;
|
||||
import io.netty.bootstrap.AbstractBootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioChannelOption;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpServerCodec;
|
||||
import io.netty.util.concurrent.DefaultThreadFactory;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class HttpServer extends Server {
|
||||
|
||||
@Autowired
|
||||
private HttpServerHandler httpServerHandler;
|
||||
|
||||
@Override
|
||||
protected AbstractBootstrap initialize() {
|
||||
bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory(config.name, Thread.MAX_PRIORITY));
|
||||
workerGroup = new NioEventLoopGroup(config.workerCore, new DefaultThreadFactory(config.name, Thread.MAX_PRIORITY));
|
||||
if (config.businessCore > 0) {
|
||||
businessService = new ThreadPoolExecutor(config.businessCore, config.businessCore, 1L,
|
||||
TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new DefaultThreadFactory(config.name + "-B", true, Thread.NORM_PRIORITY));
|
||||
}
|
||||
return new ServerBootstrap()
|
||||
.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.option(NioChannelOption.SO_REUSEADDR, true)
|
||||
.option(NioChannelOption.SO_BACKLOG, 1024)
|
||||
.childOption(NioChannelOption.SO_KEEPALIVE, true)
|
||||
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) throws Exception {
|
||||
ChannelPipeline p = ch.pipeline();
|
||||
p.addLast(new HttpServerCodec());
|
||||
p.addLast(new HttpObjectAggregator(65536));
|
||||
p.addLast(httpServerHandler);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package com.fastbee.http.server;
|
||||
|
||||
import com.fastbee.http.auth.BasicAuth;
|
||||
import com.fastbee.http.auth.DigestAuth;
|
||||
import com.fastbee.http.manager.NettyHttpSession;
|
||||
import com.fastbee.http.manager.HttpSessionManager;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.cookie.Cookie;
|
||||
import io.netty.handler.codec.http.cookie.DefaultCookie;
|
||||
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
|
||||
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.util.Set;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class HttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
|
||||
@Autowired
|
||||
private HttpSessionManager sessionManager;
|
||||
|
||||
@Autowired
|
||||
private HttpListener httpListener;
|
||||
|
||||
@Autowired
|
||||
private BasicAuth basicAuth;
|
||||
|
||||
@Autowired
|
||||
private DigestAuth digestAuth;
|
||||
|
||||
@Value("${server.http.auth.type)")
|
||||
private String authtype;
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {
|
||||
String sessionId = null;
|
||||
HttpSession session;
|
||||
String cookieHeader = req.headers().get(HttpHeaderNames.COOKIE);
|
||||
// 使用ServerCookieDecoder解码Cookie字符串
|
||||
Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode(cookieHeader);
|
||||
// 遍历Cookies
|
||||
for (Cookie cookie : cookies) {
|
||||
if ("JSESSIONID".equals(cookie.name())) {
|
||||
sessionId = cookie.value();
|
||||
}
|
||||
}
|
||||
// 未认证
|
||||
if (sessionId == null) {
|
||||
String authHeader = req.headers().get(HttpHeaderNames.AUTHORIZATION);
|
||||
boolean check = false;
|
||||
if (authHeader != null && authHeader.startsWith("Basic ")) {
|
||||
if (basicAuth.auth(ctx, authHeader)) {
|
||||
check = true;
|
||||
}
|
||||
} else if (authHeader != null && authHeader.startsWith("Digest ")) {
|
||||
if (digestAuth.auth(ctx, req)) {
|
||||
check = true;
|
||||
}
|
||||
}
|
||||
if (check) {
|
||||
sessionId = sessionManager.createSession();
|
||||
session = sessionManager.getSession(sessionId);
|
||||
((NettyHttpSession) session).setAttribute("user", "John Doe");
|
||||
// 创建一个Cookie
|
||||
Cookie sessionCookie = new DefaultCookie("JSESSIONID", sessionId);
|
||||
// 设置一些属性,比如路径和最大年龄
|
||||
sessionCookie.setPath("/");
|
||||
sessionCookie.setMaxAge(30 * 60); // 30分钟
|
||||
// 编码Cookie并添加到响应头中
|
||||
FullHttpResponse res = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
|
||||
res.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(sessionCookie));
|
||||
ctx.writeAndFlush(res);
|
||||
} else {
|
||||
if ("Basic".equals(authtype)) {
|
||||
basicAuth.sendUnauthorizedResponse(ctx);
|
||||
} else {
|
||||
digestAuth.sendUnauthorizedResponse(ctx);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 已经认证
|
||||
session = sessionManager.getSession(sessionId);
|
||||
// http 路由处理函数
|
||||
httpListener.processRequest(req, session);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.fastbee.http.service;
|
||||
|
||||
import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem;
|
||||
import com.fastbee.iot.domain.Device;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IHttpMqttService {
|
||||
void publishInfo(Device device);
|
||||
|
||||
void publishStatus(Device device, int deviceStatus);
|
||||
|
||||
void publishEvent(Device device, List<ThingsModelSimpleItem> thingsList);
|
||||
|
||||
void publishProperty(Device device, List<ThingsModelSimpleItem> thingsList, int delay);
|
||||
|
||||
void publishMonitor(Device device, List<ThingsModelSimpleItem> thingsList);
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package com.fastbee.http.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem;
|
||||
import com.fastbee.common.enums.TopicType;
|
||||
import com.fastbee.common.utils.gateway.mq.TopicsUtils;
|
||||
import com.fastbee.http.service.IHttpMqttService;
|
||||
import com.fastbee.iot.domain.Device;
|
||||
import com.fastbee.mqttclient.PubMqttClient;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class HttpMqttServiceImpl implements IHttpMqttService {
|
||||
@Resource
|
||||
private PubMqttClient mqttClient;
|
||||
@Resource
|
||||
private TopicsUtils topicsUtils;
|
||||
|
||||
@Override
|
||||
public void publishInfo(Device device) {
|
||||
device.setRssi(0);
|
||||
device.setStatus(3);
|
||||
device.setFirmwareVersion(BigDecimal.valueOf(1.0));
|
||||
String topic = topicsUtils.buildTopic(device.getProductId(), device.getSerialNumber(), TopicType.DEV_INFO_POST);
|
||||
mqttClient.publish(1, false, topic, JSON.toJSONString(device));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishStatus(Device device, int deviceStatus) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishEvent(Device device, List<ThingsModelSimpleItem> thingsList) {
|
||||
String topic = topicsUtils.buildTopic(device.getProductId(), device.getSerialNumber(), TopicType.DEV_EVENT_POST);
|
||||
if (thingsList == null) {
|
||||
mqttClient.publish(1, false, topic, "");
|
||||
} else {
|
||||
mqttClient.publish(1, false, topic, JSON.toJSONString(thingsList));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishProperty(Device device, List<ThingsModelSimpleItem> thingsList, int delay) {
|
||||
String pre = "";
|
||||
if (delay > 0) {
|
||||
pre = "$delayed/" + String.valueOf(delay) + "/";
|
||||
}
|
||||
String topic = topicsUtils.buildTopic(device.getProductId(), device.getSerialNumber(), TopicType.DEV_PROPERTY_POST);
|
||||
if (thingsList == null) {
|
||||
mqttClient.publish(1, false, topic, "");
|
||||
} else {
|
||||
mqttClient.publish(1, false, topic, JSON.toJSONString(thingsList));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishMonitor(Device device, List<ThingsModelSimpleItem> thingsList) {
|
||||
String topic = topicsUtils.buildTopic(device.getProductId(), device.getSerialNumber(), TopicType.DEV_PROPERTY_POST);
|
||||
if (thingsList == null) {
|
||||
mqttClient.publish(1, false, topic, "");
|
||||
} else {
|
||||
mqttClient.publish(1, false, topic, JSON.toJSONString(thingsList));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,197 @@
|
||||
package com.fastbee.http.utils;
|
||||
|
||||
import gov.nist.core.InternalErrorHandler;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.handler.codec.http.*;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Implements the HTTP digest authentication method server side functionality.
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
public class DigestAuthUtil {
|
||||
|
||||
private final MessageDigest messageDigest;
|
||||
public static final String DEFAULT_ALGORITHM = "MD5";
|
||||
public static final String DEFAULT_SCHEME = "Digest";
|
||||
/** to hex converter */
|
||||
private static final char[] toHex = { '0', '1', '2', '3', '4', '5', '6',
|
||||
'7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
public DigestAuthUtil()
|
||||
throws NoSuchAlgorithmException {
|
||||
messageDigest = MessageDigest.getInstance(DEFAULT_ALGORITHM);
|
||||
}
|
||||
|
||||
public static String toHexString(byte b[]) {
|
||||
int pos = 0;
|
||||
char[] c = new char[b.length * 2];
|
||||
for (byte value : b) {
|
||||
c[pos++] = toHex[(value >> 4) & 0x0F];
|
||||
c[pos++] = toHex[value & 0x0f];
|
||||
}
|
||||
return new String(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the challenge string.
|
||||
*
|
||||
* @return a generated nonce.
|
||||
*/
|
||||
private String generateNonce() {
|
||||
// Get the time of day and run MD5 over it.
|
||||
Date date = new Date();
|
||||
long time = date.getTime();
|
||||
Random rand = new Random();
|
||||
long pad = rand.nextLong();
|
||||
String nonceString = (new Long(time)).toString()
|
||||
+ (new Long(pad)).toString();
|
||||
byte[] mdbytes = messageDigest.digest(nonceString.getBytes());
|
||||
// Convert the mdbytes array into a hex string.
|
||||
return toHexString(mdbytes);
|
||||
}
|
||||
|
||||
public FullHttpResponse generateChallenge(String realm) {
|
||||
try {
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(
|
||||
HttpVersion.HTTP_1_1, HttpResponseStatus.UNAUTHORIZED,
|
||||
Unpooled.copiedBuffer("Unauthorized\r\n", CharsetUtil.UTF_8));
|
||||
response.headers().set(HttpHeaderNames.WWW_AUTHENTICATE,
|
||||
"Digest realm=\"" + realm + "\", nonce=\"" + generateNonce()
|
||||
+ "\", opaque=\"\", stale=\"FALSE\", algorithm=\"" + DEFAULT_ALGORITHM + "\"");
|
||||
return response;
|
||||
} catch (Exception ex) {
|
||||
InternalErrorHandler.handleException(ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Authenticate the inbound request.
|
||||
*
|
||||
* @param request - the request to authenticate.
|
||||
* @param hashedPassword -- the MD5 hashed string of username:realm:plaintext password.
|
||||
*
|
||||
* @return true if authentication succeded and false otherwise.
|
||||
*/
|
||||
public boolean doAuthenticateHashedPassword(FullHttpRequest request, String hashedPassword) {
|
||||
String authHeader = request.headers().get(HttpHeaderNames.AUTHORIZATION);
|
||||
if ( authHeader == null ) return false;
|
||||
Map<String, String> params = parseDigestParameters(authHeader);
|
||||
String realm = params.get("realm");
|
||||
String username = params.get("username");
|
||||
|
||||
if ( username == null || realm == null ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String nonce = params.get("nonce");
|
||||
String uri = params.get("uri");
|
||||
if (uri == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String A2 = request.method() + ":" + uri;
|
||||
|
||||
byte[] mdbytes = messageDigest.digest(A2.getBytes());
|
||||
String HA2 = toHexString(mdbytes);
|
||||
|
||||
String cnonce = params.get("cnonce");
|
||||
String KD = hashedPassword + ":" + nonce;
|
||||
if (cnonce != null) {
|
||||
KD += ":" + cnonce;
|
||||
}
|
||||
KD += ":" + HA2;
|
||||
mdbytes = messageDigest.digest(KD.getBytes());
|
||||
String mdString = toHexString(mdbytes);
|
||||
String response = params.get("response");
|
||||
|
||||
|
||||
return mdString.equals(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the inbound request given plain text password.
|
||||
*
|
||||
* @param request - the request to authenticate.
|
||||
* @param pass -- the plain text password.
|
||||
*
|
||||
* @return true if authentication succeded and false otherwise.
|
||||
*/
|
||||
public boolean doAuthenticatePlainTextPassword(FullHttpRequest request, String pass) {
|
||||
|
||||
String authHeader = request.headers().get(HttpHeaderNames.AUTHORIZATION);
|
||||
if ( authHeader == null ) return false;
|
||||
Map<String, String> params = parseDigestParameters(authHeader);
|
||||
String realm = params.get("realm");
|
||||
String username = params.get("username");
|
||||
|
||||
String nonce = params.get("nonce");
|
||||
String uri = params.get("uri");
|
||||
if (uri == null) {
|
||||
return false;
|
||||
}
|
||||
// qop 保护质量 包含auth(默认的)和auth-int(增加了报文完整性检测)两种策略
|
||||
String qop = params.get("qop");
|
||||
|
||||
// nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量
|
||||
int nc = Integer.parseInt(params.get("nc"));
|
||||
String ncStr = new DecimalFormat("00000000").format(nc);
|
||||
|
||||
String A1 = username + ":" + realm + ":" + pass;
|
||||
String A2 = request.method() + ":" + uri;
|
||||
byte[] mdbytes = messageDigest.digest(A1.getBytes());
|
||||
String HA1 = toHexString(mdbytes);
|
||||
|
||||
|
||||
mdbytes = messageDigest.digest(A2.getBytes());
|
||||
String HA2 = toHexString(mdbytes);
|
||||
String cnonce = params.get("cnonce");
|
||||
String KD = HA1 + ":" + nonce;
|
||||
|
||||
if (qop != null && qop.equals("auth") ) {
|
||||
if (nc != -1) {
|
||||
KD += ":" + ncStr;
|
||||
}
|
||||
if (cnonce != null) {
|
||||
KD += ":" + cnonce;
|
||||
}
|
||||
KD += ":" + qop;
|
||||
}
|
||||
KD += ":" + HA2;
|
||||
mdbytes = messageDigest.digest(KD.getBytes());
|
||||
String mdString = toHexString(mdbytes);
|
||||
String response = params.get("response");
|
||||
return mdString.equals(response);
|
||||
|
||||
}
|
||||
|
||||
private Map<String, String> parseDigestParameters(String authHeader) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
Pattern pattern = Pattern.compile("(\\w+)=(?:\"([^\"]*)\"|([^,]+))");
|
||||
Matcher matcher = pattern.matcher(authHeader);
|
||||
|
||||
while (matcher.find()) {
|
||||
String key = matcher.group(1);
|
||||
String value = matcher.group(2) != null ? matcher.group(2) : matcher.group(3);
|
||||
params.put(key, value);
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user