封装 JDK 自带的 HttpServer
JDK 内置的HttpServer
- HttpServer 是在 JDK 1.6 版本中被引入的,它提供了一个基于 HTTP 协议的轻量级 HTTP 服务器实现,可以用于创建和部署 Web 应用程序。
- 在 JDK 9 中,HttpServer 支持 HTTP/2 协议,提供了对 WebSockets 和 SSL/TLS 加密的支持,并提供了更好的异常处理机制。
- 在 JDK 11 中,HttpServer 进一步增强了对 HTTP/2 的支持,并增加了对 WebSocket 的异步处理支持。
- 需要注意的是,虽然 HttpServer 提供了一种轻量级的 HTTP 服务器实现,但它通常不适合用于生产环境中的大型 Web 应用程序,因为它缺少一些高级特性,例如支持 Servlet 规范、JSP 和 EJB 等技术。对于生产环境中的大型 Web 应用程序,通常需要使用专业的 Web 服务器,例如 Apache、Nginx 或 Tomcat 等。
// 创建 HTTP 服务器并绑定到本地 8080 端口。并允许 50 的等待队列
HttpServer httpServer = HttpServer.create(new InetSocketAddress(8080), 50);
// 创建'/'路径的路由
httpServer.createContext("/", exchange -> {
// 响应内容
String response = "Hello World";
byte[] responseBytes = response.getBytes(StandardCharsets.UTF_8);
// 设置响应头
exchange.getResponseHeaders().set("Content-Type", "text/html; charset=UTF-8");
// 发送响应状态码和内容长度
exchange.sendResponseHeaders(200, responseBytes.length);
// 写入响应内容
try (OutputStream os = exchange.getResponseBody()) {
os.write(responseBytes);
} catch (IOException e) {
e.printStackTrace();
exchange.close();
});
// 启动 HTTP 服务
httpServer.start();
有了基础的至少后,那么我们就可以对其进行封装了
- 封装 Request 和 Response
将其封后,后面可以更加方便的使用 exchange
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import github.zimoyin.httpserver.SimpleConsoleFormatter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Logger;
/**
* 请求
*/
public final class Request {
private final HttpExchange exchange;
private byte[] bytes = null;
private final Logger logger = SimpleConsoleFormatter.installFormatter(Logger.getLogger(Response.class.getTypeName()));
public Request(HttpExchange exchange) {
this.exchange = exchange;
parseGetParameters();
// 打印请求头
// Headers headers = getHeaders();
// if (headers.size() > 0) logger.info("Request Headers Start");
// else logger.info("Request Headers is empty");
// headers.forEach((s, strings) -> logger.info(s + " : " + strings));
// if (headers.size() > 0) logger.info("Request Headers End\n");
}
public String getMethod() {
return exchange.getRequestMethod();
}
public String getPath(){
return exchange.getRequestURI().getSchemeSpecificPart().trim();
}
public HttpExchange getExchange() {
return exchange;
}
public Headers getHeaders() {
return exchange.getRequestHeaders();
}
public URI getURI() {
return exchange.getRequestURI();
}
/**
* 获取 body 内容
*
* @return
*/
public InputStream getBodyByInputStream() {
return exchange.getRequestBody();
}
public byte[] getBody() throws IOException {
InputStream stream = getBodyByInputStream();
if (bytes == null) bytes =stream.readAllBytes();
return bytes;
}
//TODO: 只能解析GET的参数,无法解析 x-www-form-urlencoded 的参数,请重新设计,但是要注意不要关闭 body 流
private final HashMap<String, String> parameters = new HashMap<String, String>();
/**
* 服务器解析请求用的字符集
*/
private Charset charset = StandardCharsets.UTF_8;
/**
* 解析GET的参数
*/
private void parseGetParameters() {
String query = getURI().getQuery();
if (!getMethod().equalsIgnoreCase("GET")) return;
parseUrlParameters(query);
}
/**
* Post 参数类型解析: x-www-form-urlencoded
* @param body 参数体
*/
@Deprecated
private void parseUrlencodedParameters(String body) throws IOException {
parseUrlParameters(body);
}
/**
* 通用URL参数解析
*
* @param body 参数列表
*/
private void parseUrlParameters(String body) {
if (body == null || body.isEmpty()) return;
for (String str : body.split("&")) {
if (str == null || str.isEmpty()) continue;
String[] vars = str.split("=", 2);
String key;
String value = null;
if (vars.length >= 2) {
key = vars[0];
value = vars[1];
} else {
key = vars[0];
}
parameters.put(key, value);
}
}
public String getParameter(String key) {
return parameters.get(key);
}
public HashMap<String, String> getParameters() {
return parameters;
}
public void setCharset(Charset charset) {
this.charset = charset;
}
}
/**
* 响应
*/
public final class Response {
private final HttpExchange exchange;
private final ByteArrayOutputStream ByteArray = new ByteArrayOutputStream();
private boolean isEventSource = false;
private int code = 200;
private boolean closed = false;
public Response(HttpExchange exchange) {
this.exchange = exchange;
getHeaders().set("Content-Type", "text/html; charset=utf-8");
}
private OutputStream getResponseBody() throws IOException {
return exchange.getResponseBody();
}
public void write(int body) throws IOException {
if (isClosed()) throw new IOException("Response stream is closed!");
ByteArray.write(body);
}
public void write(byte[] body) throws IOException {
if (isClosed()) throw new IOException("Response stream is closed!");
ByteArray.write(body);
}
public void write(byte[] body, int off, int len) throws IOException {
if (isClosed()) throw new IOException("Response stream is closed!");
ByteArray.write(body, off, len);
}
public void write(String body) throws IOException {
if (isClosed()) throw new IOException("Response stream is closed!");
ByteArray.write(body.getBytes(StandardCharsets.UTF_8));
}
public void write(String body, Charset charset) throws IOException {
if (isClosed()) throw new IOException("Response stream is closed!");
ByteArray.write(body.getBytes(charset));
}
public void flush() throws IOException {
if (isClosed()) throw new IOException("Response stream is closed!");
ByteArray.flush();
getResponseBody().flush();
}
/**
* 发送信息,并刷新流。如果该响应是 EventSource 则发送单次事件。注意该发送方法与write/end 是分离的
*/
@Deprecated
public void send(byte[] body) throws IOException {
if (isClosed()) throw new IOException("Response stream is closed!");
if (!isEventSource) throw new IOException("Response stream not is a EventSource");
getResponseBody().write(body);
getResponseBody().flush();
}
/**
* 发送信息,并刷新流。如果该响应是 EventSource 则发送单次事件。
* 注意该发送方法与write/end 是分离的,请不要使用write 为 该方法写入数据该方法不会发送的
*/
@Deprecated
public void send(byte[] body, int off, int len) throws IOException {
if (isClosed()) throw new IOException("Response stream is closed!");
if (!isEventSource) throw new IOException("Response stream not is a EventSource");
getResponseBody().write(body, off, len);
getResponseBody().flush();
}
/**
* 发送信息,并刷新流。如果该响应是 EventSource 则发送单次事件。
* 注意该发送方法与write/end 是分离的,请不要使用write 为 该方法写入数据该方法不会发送的
*/
public void send(String body, Charset charset) throws IOException {
if (isClosed()) throw new IOException("Response stream is closed!");
if (!isEventSource) throw new IOException("Response stream not is a EventSource");
getResponseBody().write(("data:" + body + "\n\n").getBytes(charset));
getResponseBody().flush();
}
/**
* 发送信息,并刷新流。如果该响应是 EventSource 则发送单次事件。
* 注意该发送方法与write/end 是分离的,请不要使用write 为 该方法写入数据该方法不会发送的
*/
public void send(String body) throws IOException {
if (isClosed()) throw new IOException("Response stream is closed!");
if (!isEventSource) throw new IOException("Response stream not is a EventSource");
PrintWriter out = new PrintWriter(getResponseBody());
out.write("data:" + body + "\n\n");
out.flush();
}
/**
* 设置服务器为事件源
*/
public void setEventSource() throws IOException {
if (isClosed()) throw new IOException("Response stream is closed!");
if (isEventSource) throw new IOException("Response EventSource is set repeatedly");
getHeaders().set("Content-Type", "text/event-stream");
getHeaders().set("Cache-Control", "no-cache");
exchange.sendResponseHeaders(code, 0);
isEventSource = true;
}
/**
* 启用跨域支持
*/
public void setCrossDomain() {
if (isEventSource || isClosed())
try {
throw new IOException("The HTTP request header has already been sent and cannot add a new request header");
} catch (IOException e) {
throw new RuntimeException(e);
}
// exchange.getResponseHeaders().add("Access-Control-Allow-Origin", "*");
addHeader("Access-Control-Allow-Origin", "*");
addHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
addHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
}
public void end() throws IOException {
if (isClosed()) throw new IOException("Response stream is closed!");
closed = true;
//响应长度,响应体为0个字节则设置长度为-1 否则浏览器会尝试重发
long responseLength = ByteArray.size() == 0 ? -1 : ByteArray.size();
//发送响应头
try {
if (!isEventSource) exchange.sendResponseHeaders(code, responseLength);
} catch (IOException e) {
throw new IllegalStateException("The response header has been sent");
}
//如果存在长度则响应内容
if (responseLength>=0){
getResponseBody().write(ByteArray.toByteArray());
getResponseBody().flush();
}
ByteArray.close();
}
/**
* 重定向
*/
public void redirect(String path) throws IOException {
exchange.getResponseHeaders().add("Location",path);
exchange.sendResponseHeaders(302, 0);
}
public Headers getHeaders() {
return exchange.getResponseHeaders();
}
public Headers addHeader(String key, String value) {
if (isEventSource || isClosed())
try {
throw new IOException("The HTTP request header has already been sent and cannot add a new request header");
} catch (IOException e) {
throw new RuntimeException(e);
}
getHeaders().add(key, value);
return getHeaders();
}
public void setCode(int code) {
this.code = code;
}
public int getCode() {
return code;
}
public boolean isClosed() {
return closed;
}
}
- 封装一个处理器基类
封装后,可以更方便分离不同请求,并将 exchange 拆分为 Response、Request
/**
* 基层 Http 处理器
* 如果想要对http 做出响应需要继承此类
* 并且你还需要在服务器启动前注册进服务器的路由里.
* eg:
* SimpleHttpServer server = new SimpleHttpServer("/");
* server.addRoute(new MyHttpHandler());
*/
public abstract class AbsHttpHandler implements HttpHandler {
protected final Logger logger = SimpleConsoleFormatter.installFormatter(Logger.getLogger(this.getClass().getTypeName()));
private final String route;
private final boolean debugLog = false;
/**
* 服务器ID
*/
private UUID ID;
public AbsHttpHandler(String route) {
this.route = route.trim().toLowerCase();
if (route.length() == 0) logger.warning(route + " : 路由是个空路径 ");
if (!"/".equals(route.substring(0, 1))) logger.warning(route + " : 路由没有路径符号 '/' ");
}
@Override
public final void handle(HttpExchange exchange) throws IOException {
try {
if (debugLog) logger.info("Http handler route: " + route);
if (getID() == null) throw new NullPointerException("HTTP Server ID is null");
//根据根据请求方法来分发请求
doMethod(exchange.getRequestMethod(), exchange);
} catch (Exception e) {
doError(e, exchange);
}
}
private void doMethod(String methodName, HttpExchange exchange) throws IOException {
Response response = new Response(exchange);
Request request = new Request(exchange);
boolean error = false;
//过滤器
boolean accept = true;
for (AbsFilter filter : FilterManager.getInstance().findFilters(getID(), request.getPath())) {
accept = filter.filter(request, response) && accept;
}
if (accept) switch (methodName.toUpperCase()) {
case "GET" -> {
if (debugLog) logger.info("Get Path: " + exchange.getRequestURI().getSchemeSpecificPart());
try {
doGet(request, response);
} catch (Exception e) {
error = true;
doError(e, exchange);
}
}
case "POST" -> {
if (debugLog) logger.info("Post Path: " + exchange.getRequestURI().getSchemeSpecificPart());
try {
doPost(request, response);
} catch (Exception e) {
error = true;
doError(e, exchange);
}
}
case "OPTIONS" -> {
if (debugLog) logger.info("OPTIONS Path: " + exchange.getRequestURI().getSchemeSpecificPart());
try {
doOptions(request, response);
} catch (Exception e) {
error = true;
doError(e, exchange);
}
}
default -> doRequest(request, response);
}
//如果响应是因为异常被关闭的则截拦状态异常不进行返回,如果不是则抛出
try {
close(response, exchange);
} catch (IllegalStateException e) {
if (debugLog) if (error) logger.log(Level.INFO, "Please ignore this exception", e);
if (!error) throw e;
}
}
protected final void close(Response response, HttpExchange exchange) throws IOException {
if (!response.isClosed()) response.end();
exchange.close();
}
/**
* 将 Exception 转化为 String
*/
protected final String getExceptionToString(Throwable e) {
if (e == null) {
return "";
}
StringWriter stringWriter = new StringWriter();
e.printStackTrace(new PrintWriter(stringWriter));
return stringWriter.toString();
}
/**
* 处理路由中的错误
*/
protected void doError(Exception e, HttpExchange exchange) throws IOException {
//发送响应标头。必须在之前调用 下一步。
int length = ("Server Error: \n\n" + getExceptionToString(e)).length();
try {
exchange.sendResponseHeaders(500, length);
exchange.getResponseBody().write(("Server Error: \n\n" + getExceptionToString(e)).getBytes());
logger.log(Level.WARNING, "Server Error, with the error path being: " + exchange.getRequestURI().getSchemeSpecificPart(), e);
} catch (IOException e2) {
e2.initCause(e);
logger.log(Level.WARNING, "An incorrect write exception occurred, with the error path being: " + exchange.getRequestURI().getSchemeSpecificPart(), e2);
}
}
/**
* 默认处理器
* 如果请求不是Get 或者 Post 等任意已写的方法处理器 的话他会执行此方法
*/
protected void doRequest(Request request, Response response) throws IOException {
// logger.info("/" + request.getMethod() + " Path: " + request.getPath());
logger.warning("/" + request.getMethod() + " Path: " + request.getPath() + " -> 无法解析的 Method :" + request.getMethod());
response.setCode(-405);
response.write("This Request Method not support.");
}
protected void doGet(Request request, Response response) throws IOException {
response.setCode(-405);
response.write("This Request Method not support.");
}
protected void doPost(Request request, Response response) throws IOException {
response.setCode(-405);
response.write("This Request Method not support.");
}
protected void doOptions(Request request, Response response) throws IOException {
response.setCrossDomain();
response.setCode(204);
}
public String getRoute() {
return route;
}
/**
* 重定向
*/
public void redirectTo(HttpExchange exchange, String path) throws IOException {
exchange.getResponseHeaders().add("Location", path);
exchange.sendResponseHeaders(302, 0);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof AbsHttpHandler that)) return false;
return route.equals(that.route);
}
@Override
public int hashCode() {
return route.hashCode();
}
/**
* 服务器ID
*/
public final UUID getID() {
return ID;
}
/**
* 服务器ID
*/
public final AbsHttpHandler setID(UUID ID) {
if (this.ID != null)
throw new IllegalArgumentException("The HTTP Server ID is final and cannot be changed. And ID can only be set by the server during server startup");
this.ID = ID;
return this;
}
}
- 封装 HttpServer
/**
* Http Server
*/
public final class SimpleHttpServer {
public static boolean isLoggingEnabled = true;
private int port = 8080;
private Executor executor = null;
private final HashSet<AbsHttpHandler> routes = new HashSet<>();
private final String RootPath;
private boolean isRunning = false;
/**
* 队列数量
*/
private int requestQueue = 50;
private final Logger logger = SimpleConsoleFormatter.installFormatter(Logger.getLogger(SimpleHttpServer.class.getTypeName()));
private HttpServer server;
/**
* 服务器ID
*/
private final UUID ID;
public SimpleHttpServer() {
ID = UUID.randomUUID();
RootPath = "/";
this.executor = Executors.newFixedThreadPool(getRequestQueue());
}
/**
* 设置 WEB 的根路径,默认为 /
*/
public SimpleHttpServer(String rootPath) {
ID = UUID.randomUUID();
RootPath = rootPath;
this.executor = Executors.newFixedThreadPool(getRequestQueue());
}
/**
* 启动服务器
*/
public HttpServer start() throws IOException {
if (isRunning) throw new IllegalStateException("HttpServer is already running");
isRunning = true;
try {
// 绑定地址,端口,请求队列
server = HttpServer.create(new InetSocketAddress(port), requestQueue);
logger.info("HttpServer request queue size: " + requestQueue);
} catch (BindException e) {
logger.log(Level.WARNING, "Address already in use: " + port);
throw e;
}
logger.info("Loading route...");
//注册路由
try {
for (AbsHttpHandler route : routes) server.createContext(route.getRoute(), route.setID(ID));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Do not set the server ID for routes outside of the server instance, as this may prevent routes from being loaded properly", e);
}
//注册静态资源路由与根路由
server.createContext(RootPath, new StaticResourcesRoute(routes, RootPath).setID(ID));
//路由日志
logger.info("Loaded routes finished successfully: " + (routes.size() + 1));
if (routes.size() == 0) logger.warning("No routes found for this application (port: " + port + ")");
// 配置HttpServer请求处理的线程池,没有配置则使用默认的线程池;
if (executor != null) server.setExecutor(executor);
logger.info("Loading executor : " + executor);
server.start();
//日志
if (port > 0) logger.info("Server have been started. Listen to port: " + port);
else logger.info("Server have been started. Listen to port: " + port + " -> " + server.getAddress().getPort());
return getServer();
}
/**
* 添加路由
*/
public void addRoute(AbsHttpHandler route) {
routes.add(route);
}
public void addRouteAll(AbsHttpHandler... routes) {
this.routes.addAll(Arrays.asList(routes));
}
/**
* 添加过滤器
*/
public void addFilter(AbsFilter filter) {
FilterManager.getInstance().add(ID, filter);
}
public void addFilterAll(AbsFilter... filters) {
FilterManager.getInstance().addAll(ID, Arrays.asList(filters));
}
/**
* 移除路由
*/
public void removeRoute(AbsHttpHandler route) {
routes.remove(route);
}
/**
* 停止WEB服务器
*
* @param delay 在多少秒内停止
*/
public void stop(int delay) {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (this.server == null) throw new NullPointerException("this http server is null or is stopped");
server.stop(0);
logger.info("Server have been stopped");
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public Executor getExecutor() {
return executor;
}
/**
* 设置处理的线程池
*/
public void setExecutor(Executor executor) {
this.executor = executor;
}
public HashSet<AbsHttpHandler> getRoutes() {
return routes;
}
public boolean isRunning() {
return isRunning;
}
public int getRequestQueue() {
return requestQueue;
}
/**
* 设置访问队列大小
*/
public void setRequestQueue(int requestQueue) {
this.requestQueue = requestQueue;
}
private HttpServer getServer() {
return server;
}
public String getRootPath() {
return RootPath;
}
/**
* 禁用日志输出
*/
public static synchronized void disableLogging() {
Logger.getLogger("github.zimoyin.httpserver").setLevel(Level.OFF);
Logger.getLogger("github.zimoyin.httpserver.core").setLevel(Level.OFF);
isLoggingEnabled = false;
}
}
- 实现一个静态资源处理器
/**
* 静态资源加载器
* 不支持运行时变更根路径
*/
public final class StaticResources {
/**
* 静态资源路径的位置,他可以是一个相对位置或者绝对位置。
* 还可以是一个 resources 的路径,注意,如果是resources 路径需要在 路径前加 ’resources:‘ 以此来标明他是来自于resources下的路径
*
* 默认路径为 resources 下的 static 文件夹
*/
public static String staticResourcePath = "resources:static";
/**
* 主页的默认路径
*/
public static final String indexPath = "/index.html";
public static InputStream getStaticResourceAsStream(String path) throws IOException {
//如果资源在 Resources 目录下
if (staticResourcePath.startsWith("resources:")) {
return getResourceAsStream(staticResourcePath.substring("resources:".length()) + path);
} else {
if (new File(staticResourcePath + path).exists()) return new FileInputStream(staticResourcePath + path);
}
return null;
}
private static InputStream getResourceAsStream(String path) throws IOException {
// return Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
return StaticResources.class.getClassLoader().getResourceAsStream(path);
}
public static String defaultPath() {
return "resources:static";
}
}
/**
* 全局静态资源路由
*/
public final class StaticResourcesRoute extends AbsHttpHandler {
private final HashSet<AbsHttpHandler> Routes;
public StaticResourcesRoute(HashSet<AbsHttpHandler> routes, String rootPath) {
super(rootPath);
this.Routes = routes;
if (!rootPath.endsWith("/")) {
throw new IllegalArgumentException("The RootPath must end with '/'");
}
}
@Override
protected void doGet(Request request, Response response) throws IOException {
//浏览器访问的获取路由
String path = request.getURI().getPath();
//如果访问根路径,则重定向到 Index 页面
if (Objects.equals(path, "/")) path = StaticResources.indexPath;
//判断是不是一个已经注册的路由
String finalPath = path;
boolean isRoute = Routes.stream().anyMatch(absHttpHandler -> absHttpHandler.getRoute().equals(finalPath));
if (isRoute) logger.warning("Error: 静态资源与注册路由路径一致");
// logger.info("客户端请求静态资源: "+path);
//是否存在静态资源,存在和获取,不存在则 404
InputStream stream = StaticResources.getStaticResourceAsStream(path);
if (stream == null) {
notFound(request, response);
return;
}
//返回这个静态资源
ByteArrayOutputStream arrayOutputStream = null;
try {
arrayOutputStream = new ByteArrayOutputStream();
byte[] bytes = readStaticResource(stream, arrayOutputStream);
response.write(bytes);
response.end();
} catch (Exception e) {
logger.log(Level.WARNING, "Static resource read failed with path: " + finalPath, e);
} finally {
if (arrayOutputStream != null) arrayOutputStream.close();
stream.close();
}
if (!response.isClosed()) response.write("Error: 未能加载的静态资源");
if (!response.isClosed())
logger.warning("Error: 未能加载的静态资源: " + StaticResources.staticResourcePath + path);
}
private void notFound(Request request, Response response) throws IOException {
AbsHttpHandler router = Routes.stream().filter(absHttpHandler -> absHttpHandler.getRoute().equals("/*")).findFirst().orElse(null);
if (router == null) {
logger.warning("客户端请求了一个不存在的路径: " + request.getURI());
response.setCode(404);
response.write("Not Found");
} else {
router.doRequest(request, response);
}
}
private byte[] readStaticResource(InputStream stream, ByteArrayOutputStream arrayOutputStream) throws IOException {
int len;
byte[] bytes = new byte[1024];
while ((len = stream.read(bytes)) != -1) {
arrayOutputStream.write(bytes, 0, len);
}
return arrayOutputStream.toByteArray();
}
@Override
protected void doPost(Request request, Response response) throws IOException {
notFound(request, response);
}
}
- 实现过滤器
public abstract class AbsFilter {
private final String route;
protected final Logger logger = SimpleConsoleFormatter.installFormatter(Logger.getLogger(this.getClass().getTypeName()));
public AbsFilter(String route) {
this.route = route.trim().toLowerCase();
if (route.length() == 0)logger.warning(route + " : 路由是个空路径 ");
if (!"/".equals(route.substring(0,1))) logger.warning(route + " : 路由没有路径符号 '/' ");
}
public abstract boolean filter(Request request, Response response);
public final String getRoute() {
return route;
}
}
public final class FilterManager extends HashMap<UUID, ArrayList<AbsFilter>> {
private volatile static FilterManager INSTANCE;
private FilterManager() {
}
public static FilterManager getInstance() {
if (INSTANCE == null) synchronized (FilterManager.class) {
if (INSTANCE == null) INSTANCE = new FilterManager();
}
return INSTANCE;
}
public AbsFilter[] findFilters(UUID ID, String route) {
ArrayList<AbsFilter> filters = this.get(ID);
if (filters == null) return new AbsFilter[0];
return filters.stream().filter(filter -> isFilter(filter, route)).toArray(AbsFilter[]::new);
}
private boolean isFilter(AbsFilter filter, String route) {
boolean ignoreCase = filter.getRoute().equalsIgnoreCase(route);
return ignoreCase || matchRoute(route, filter.getRoute());
}
public void add(UUID ID, AbsFilter filter) {
FilterManager.getInstance().computeIfAbsent(ID, k -> new ArrayList<>()).add(filter);
}
public static boolean matchRoute(String route1, String route2) {
String[] parts1 = route1.split("/");
String[] parts2 = route2.split("/");
if (parts1.length < parts2.length) return false;
if (parts1.length > parts2.length && !route2.contains("*")) return false;
boolean b = false;
//长度一致
//从第二路由开始判断
for (int i = 0; i < parts2.length; i++) {
String item1 = parts1[i];
String item2 = parts2[i];
//判断路径
if ("*".equals(item2)) {
b = true;
// break;
} else if (item1.equals(item2)) {
b = true;
} else {
b = false;
break;
}
}
return b;
}
public void addAll(UUID id, List<AbsFilter> list) {
FilterManager.getInstance().computeIfAbsent(id, k -> new ArrayList<>()).addAll(list);
}
}
- 封装日志
/**
* 为 {@link java.util.logging.Logger}实现自定义的日志输出,可以输出IDE(eclipse)自动识别源码位置的日志格式。方便调试
*
* @author guyadong
* @since 2.7.0
*/
public class SimpleConsoleFormatter extends Formatter {
@Override
public String format(LogRecord record) {
String message = formatMessage(record);
String throwable = "";
if (record.getThrown() != null) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
pw.println();
record.getThrown().printStackTrace(pw);
pw.close();
throwable = "\n" + sw.toString();
}
Thread currentThread = Thread.currentThread();
StackTraceElement stackTrace = currentThread.getStackTrace()[8];
SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss.SSSS");
return String.format("%s [%s] [%s] [%s:%d] %s%s\n",
formatter.format(new Date(System.currentTimeMillis())),
record.getLevel(),
Thread.currentThread().getName(),
stackTrace.getClassName(),
stackTrace.getLineNumber(),
message,
throwable
);
}
/**
* 将{@link SimpleConsoleFormatter}实例指定为{@link Logger}的输出格式
*
* @param logger
* @return always logger
*/
public static Logger installFormatter(Logger logger) {
if (null != logger) {
/* 禁用原输出handler,否则会输出两次 */
logger.setUseParentHandlers(false);
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setFormatter(new SimpleConsoleFormatter());
logger.addHandler(consoleHandler);
}
return logger;
}
}
至此已经将其进行了简单的封装,下面是测试并使用他们
public class Main {
public static void main(String[] args) throws IOException {
SimpleHttpServer server = new SimpleHttpServer("/");
server.setPort(8080);
server.addRoute(new ParameterReceiver());
server.addRoute(new NotFoundRouter());
server.addFilter(new FilterTest());
server.start();
}
}
public class FilterTest extends AbsFilter {
public FilterTest() {
super("/test");
}
@Override
public boolean filter(Request request, Response response) {
System.out.println("Filter test");
return true;
}
}
public class NotFoundRouter extends AbsHttpHandler {
public NotFoundRouter() {
super("/*");
}
@Override
protected void doRequest(Request request, Response response) throws IOException {
response.write("没有这个资源QAQ");
}
}
public class ParameterReceiver extends AbsHttpHandler {
public ParameterReceiver() {
super("/test");
}
@Override
protected void doGet(Request request, Response response) throws IOException {
response.setEventSource();
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
response.send(String.valueOf(i));
// System.out.println(i);
}
response.send(": end");
}
@Override
protected void doPost(Request request, Response response) throws IOException {
// request.getHeaders().forEach((s, strings) -> System.out.println(s+": "+strings));
System.out.println(new String(request.getBody()));
response.send(new String(request.getBody()));
}
@Override
protected void doError(Exception e, HttpExchange exchange) throws IOException {
this.redirectTo(exchange,"/");//重定向
super.doError(e, exchange);
}
}