当前位置: 首页 > article >正文

封装 JDK 自带的 HttpServer

JDK 内置的HttpServer
  1. HttpServer 是在 JDK 1.6 版本中被引入的,它提供了一个基于 HTTP 协议的轻量级 HTTP 服务器实现,可以用于创建和部署 Web 应用程序。
  2. 在 JDK 9 中,HttpServer 支持 HTTP/2 协议,提供了对 WebSockets 和 SSL/TLS 加密的支持,并提供了更好的异常处理机制。
  3. 在 JDK 11 中,HttpServer 进一步增强了对 HTTP/2 的支持,并增加了对 WebSocket 的异步处理支持。
  4. 需要注意的是,虽然 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();
有了基础的至少后,那么我们就可以对其进行封装了
  1. 封装 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;
    }
}

  1. 封装一个处理器基类
    封装后,可以更方便分离不同请求,并将 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;
    }
}

  1. 封装 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;
    }
}

  1. 实现一个静态资源处理器

/**
 * 静态资源加载器
 * 不支持运行时变更根路径
 */
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);
    }
}

  1. 实现过滤器
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);
    }
}

  1. 封装日志

/**
 * 为 {@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);
    }
}


http://www.kler.cn/a/529478.html

相关文章:

  • 【LLM-agent】(task4)搜索引擎Agent
  • 负载均衡器高可用部署
  • Python闭包:解锁函数式编程的隐藏力量
  • 实现网站内容快速被搜索引擎收录的方法
  • 自定义数据集 使用pytorch框架实现逻辑回归并保存模型,然后保存模型后再加载模型进行预测,对预测结果计算精确度和召回率及F1分数
  • 《Kotlin核心编程》下篇
  • 笔记:电机系统性能标定测试怎么进行?
  • 【Go - 小心! Go中slice的传递陷阱 】
  • 第七篇:数据库备份与恢复
  • UE5 蓝图学习计划 - Day 10:UI 系统(HUD 与 Widget)
  • Web - CSS3基础语法与盒模型
  • python爬虫从0到1 - Scrapy框架的实战应用
  • 蓝桥杯备考:模拟算法之字符串展开
  • ubuntu22.04防火墙策略
  • VSCode设置颜色主题
  • 实体类实现Serializable接口
  • PyCharm中使用Ollama安装和应用Deepseek R1模型:完整指南
  • Vue.js组件开发-实现全屏图片文字缩放切换特效
  • SuccessFactors OData OAuth with SAP IAS-generated SAML assertion
  • gesp(C++六级)(10)洛谷:P10722:[GESP202406 六级] 二叉树
  • 深入解析Python机器学习库Scikit-Learn的应用实例
  • pandas(三)Series使用
  • SpringBoot 整合 Mybatis:提升你的Java项目开发效率
  • 游戏引擎 Unity - Unity 设置为简体中文、Unity 创建项目
  • 数据分析系列--[11] RapidMiner,K-Means聚类分析(含数据集)
  • 洛谷P1403 [AHOI2005] 约数研究