跳至主要內容

Java 18 新特性:简单Web服务器

会敲代码的程序猿原创JavaJava Features大约 5 分钟

Java 18 新特性:简单Web服务器

Java 18 引入了简单Web服务器(Simple Web Server),一个专为教育或非正式任务设计的最小HTTP服务器,为单个目录层次结构提供服务。 基于JDK中的com.sun.net.httpserver包实现,旨在简化服务器的创建和请求处理过程。

主要特点:

  • 不能替代成熟的商业服务器,如JettyNginxApache Tomcat
  • 不提供身份验证、访问控制或加密等安全功能
  • 仅支持HTTP/1.1,不支持HTTPS
  • 仅支持GET、HEAD请求,否则返回 501 Not Implemented 或 405 Not Allowed

命令行工具

为了开始使用简单Web服务器,您需要准备一个index.html文件,并执行以下步骤:

  1. 打开终端。
  2. 输入命令:jwebserver
$ jwebserver

默认情况下,服务器将绑定到本地回环地址,并在端口8000上提供服务。看到类似以下的输出:

Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::".
Serving /cwd and its subdirectories on 127.0.0.1 port 8000
URL: http://127.0.0.1:8000/

尝试访问一下 http://127.0.0.1:8000/ ,就可以获得之前准备的HTML内容了。

支持的命令行选项

选项:
       -h 或 -? 或 --help
              打印帮助信息并退出.

       -b addr 或 --bind-address addr
              指定绑定的地址。默认:127.0.0.1或::1(回环地址)。要使用所有接口,请使用 -b 0.0.0.0 或 -b ::.

       -d dir--directory dir
              指定要提供服务的目录。默认:当前目录.

       -o level 或 --output level
              指定输出格式。none | info | verbose。默认:info.

       -p port 或 --port port
              指定要监听的端口。默认:8000.

       -version--version
              打印版本信息并退出。

       要停止服务器,请按 Ctrl + C.

API编程方式

尽管命令行工具提供了便利,但为了更灵活地定制处理程序的行为,与现有代码集成,提高代码的可读性和可维护性,我们引入了新的API。

新的API中引入了三个新的类是SimpleFileServerHttpHandlersRequest, 每个类都构建在com.sun.net.httpserver包中的现有类和接口上:HttpServerHttpHandlerFilterHttpExchange

简单文件服务器(SimpleFileServer)

SimpleFileServer支持创建文件服务器、文件服务器处理程序和输出过滤器。

package com.sun.net.httpserver;

public final class SimpleFileServer {
    // 创建文件服务器实例
    public static 1 createFileServer(
        InetSocketAddress addr, Path rootDirectory, OutputLevel outputLevel) {...}          
    
    // 创建文件服务器处理程序
    public static HttpHandler createFileHandler(
        Path rootDirectory) {...}
    
    // 创建输出过滤器
    public static Filter createOutputFilter(
        OutputStream out, OutputLevel outputLevel) {...}
    ...
}

有了这个类,在jshell中只需几行代码,就可以启动一个最小但定制的文件服务器

jshell> var server = SimpleFileServer.createFileServer(new InetSocketAddress(8080),
   ...> Path.of("/some/path"), SimpleFileServer.OutputLevel.INFO);
jshell> server.start()

相当于命令行模式的:

jwebserver -p 8080 -d /some/path -o info

自定义处理程序和过滤器

将自定义的文件服务器处理程序添加到现有服务器:

jshell> var handler = SimpleFileServer.createFileHandler(Path.of("/some/path"));
jshell> var server = HttpServer.create(new InetSocketAddress(8080),
   ...> 10, "/store/", new SomePutHandler());
jshell> server.createContext("/browse/", handler);
jshell> server.start();

将自定义的输出过滤器在创建过程中添加到服务器:

jshell> var filter = SimpleFileServer.createOutputFilter(System.out,
   ...> OutputLevel.INFO);
jshell> var server = HttpServer.create(new InetSocketAddress(8080),
   ...> 10, "/store/", new SomePutHandler(), filter);
jshell> server.start();

两个例子是由HttpServerHttpsServer类中的新重载create方法启用的:

public static HttpServer create(InetSocketAddress addr,
                                int backlog,
                                String root,
                                HttpHandler handler,
                                Filter... filters) throws IOException {...}

增强的请求处理(HttpHandlers)

简单Web服务器的核心功能是处理程序。为了与现有代码兼容,我们引入了HttpHandlers类, 提供两个静态方法用于创建自定义处理程序,还有Filter类中的新方法用于适配请求:

package com.sun.net.httpserver;

public final class HttpHandlers {
    
    // handleOrElse方法补充条件处理程序
    public static HttpHandler handleOrElse(Predicate<Request> handlerTest,
                                           HttpHandler handler,
                                           HttpHandler fallbackHandler) {...}
    
    // of方法创建具有预设响应状态的处理程序
    public static HttpHandler of(int statusCode, Headers headers, String body) {...}
    {...}
}
public abstract class Filter {
    // adaptRequest方法获取预处理过滤器,用于在处理请求之前检查和调整请求的某些属性
    public static Filter adaptRequest(
        String description, UnaryOperator<Request> requestOperator) {...}
    {...}
}

这些方法的用例包括基于请求方法委托交换,创建总是返回特定响应的“canned response”处理程序,或向所有传入请求添加标头。

请求(Request)

现有API中,使用HttpExchange类来表达HTTP请求-响应对,描述了请求-响应交换的完整可变状态。 然而,并非所有这状态对于处理程序的定制和适配都是必要的。 因此,我们引入了更简单的Request接口,提供请求的不可变状态的有限视图。

public interface Request {
    URI getRequestURI();         // 获取请求的URI
    String getRequestMethod();   // 获取请求的方法
    Headers getRequestHeaders(); // 获取请求的标头
    // 用于修改请求头部信息
    default Request with(String headerName, List<String> headerValues)
    {...}
}

这使得可以直接定制现有的处理程序,例如:

// 创建一个处理程序,根据请求方法选择对应的处理器,如果请求方法为 PUT,则使用 SomePutHandler,否则使用 SomeHandler
var h = HttpHandlers.handleOrElse(r -> 
    r.getRequestMethod().equals("PUT"), new SomePutHandler(), new SomeHandler());
// 创建一个过滤器,用于修改请求头部信息,在请求中添加名为 "Foo" 的头部,值为 "Bar"
var f = Filter.adaptRequest("Add Foo header", r -> r.with("Foo", List.of("Bar")));
// 创建一个 HTTP 服务器,并指定端口为 8080,最大连接数为 10,根路径为 "/",处理程序为 h,过滤器为 f
var s = HttpServer.create(new InetSocketAddress(8080), 10, "/", h, f);
// 启动服务器
s.start();

替代品

命令行工具的替代方案:

  • 最初,使用 java -m jdk.httpserver 命令运行 Simple Web Server,没有专门的命令行工具
  • 为了提高便利性和可访问性,我们引入了一个专门的工具 jwebserver
    • 实际上 jwebserver 在后台使用了 java -m ... 命令

API编程方式替代方案:

  • 新的 DelegatingHandler 类:捆绑定制方法在一个单独的类中,但由于引入了新类型并未添加更多功能,我们放弃了这个选项。
  • HttpHandler 作为服务:将 HttpHandler 转换为服务,并提供内部文件服务器处理程序实现。然而,这种方法对于我们要提供的功能来说过于复杂。
  • 使用过滤器而不是 HttpHandler:仅使用过滤器来处理请求,但这样做不符合直觉,并且过滤器的方法会更难找到。