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

java18学习笔记-Simple Web Server

408:Simple Web Server

Python、Ruby、PHP、Erlang 和许多其他平台提供从命令行运行的开箱即用服务器。这种现有的替代方案表明了对此类工具的公认需求。

提供一个命令行工具来启动仅提供静态文件的最小web服务器。没有CGI或类似servlet的功能可用。该工具将用于原型设计、即席编码和测试目的,特别是在教育背景下。

Simple Web Server是一个用于服务单个目录层次结构的最小HTTP服务器。它基于自2006年以来JDK中包含的com.sun.net.httpserver包中的web服务器实现。该包得到了官方支持,我们用API对其进行了扩展,以简化服务器创建并增强请求处理。Simple Web Server可以通过专用命令行工具jwebserver使用,也可以通过其API以编程方式使用。

以下命令启动简单Web服务器

通过jwebserver运行

jwebserver

然后在提示serving的目录下放一张图片asd.jpg,然后请求结果如下

注意仅支持 HTTP/1.1。不支持 HTTPS。(但是测试了几次HTTP/2.0是可以访问到的)

命令的几个参数也很简单

Options:-h or -? or --helpPrints the help message and exits.-b addr or --bind-address addrSpecifies the address to bind to.  Default: 127.0.0.1 or ::1 (loopback).  Forall interfaces use -b 0.0.0.0 or -b ::.-d dir or --directory dirSpecifies the directory to serve.  Default: current directory.-o level or --output levelSpecifies the output format.  none | info | verbose.  Default: info.-p port or --port portSpecifies the port to listen on.  Default: 8000.-version or --versionPrints the version information and exits.To stop the server, press Ctrl + C.

通过JSHELL运行

在Jshell中导入会报错 sun.net.httpserver.simpleserver.FileServerHandler

import sun.net.httpserver.simpleserver.FileServerHandler;
|  错误:
|  程序包 sun.net.httpserver.simpleserver 不可见
|    (程序包 sun.net.httpserver.simpleserver 已在模块 jdk.httpserver 中声明, 但该模块未导出它)
|  import sun.net.httpserver.simpleserver.FileServerHandler;
|         ^-----------------------------^

所以可以自己复制一个一模一样的FileServerHandler 

同样的sun.net.httpserver.simpleserver.ResourceBundleHelper也复制一个

ResourceBundleHelper

import java.text.MessageFormat;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;class ResourceBundleHelper {static final ResourceBundle bundle;static {try {bundle = ResourceBundle.getBundle("sun.net.httpserver.simpleserver.resources.simpleserver");} catch (MissingResourceException e) {throw new InternalError("Cannot find simpleserver resource bundle for locale " + Locale.getDefault());}}static String getMessage(String key, Object... args) {try {return MessageFormat.format(bundle.getString(key), args);} catch (MissingResourceException e) {throw new InternalError("Missing message: " + key);}}
}

复制到Jshell执行

FileServerHandler

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.lang.System.Logger;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.function.UnaryOperator;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpHandlers;
import static java.nio.charset.StandardCharsets.UTF_8;/*** A basic HTTP file server handler for static content.** <p> Must be given an absolute pathname to the directory to be served.* Supports only HEAD and GET requests. Directory listings and files can be* served, content types are supported on a best-guess basis.*/
public final class FileServerHandler implements HttpHandler {private static final List<String> SUPPORTED_METHODS = List.of("HEAD", "GET");private static final List<String> UNSUPPORTED_METHODS =List.of("CONNECT", "DELETE", "OPTIONS", "PATCH", "POST", "PUT", "TRACE");private final Path root;private final UnaryOperator<String> mimeTable;private final Logger logger;private FileServerHandler(Path root, UnaryOperator<String> mimeTable) {root = root.normalize();@SuppressWarnings("removal")var securityManager = System.getSecurityManager();if (securityManager != null)securityManager.checkRead(pathForSecurityCheck(root.toString()));if (!Files.exists(root))throw new IllegalArgumentException("Path does not exist: " + root);if (!root.isAbsolute())throw new IllegalArgumentException("Path is not absolute: " + root);if (!Files.isDirectory(root))throw new IllegalArgumentException("Path is not a directory: " + root);if (!Files.isReadable(root))throw new IllegalArgumentException("Path is not readable: " + root);this.root = root;this.mimeTable = mimeTable;this.logger = System.getLogger("com.sun.net.httpserver");}private static String pathForSecurityCheck(String path) {var separator = String.valueOf(File.separatorChar);return path.endsWith(separator) ? (path + "-") : (path + separator + "-");}private static final HttpHandler NOT_IMPLEMENTED_HANDLER =HttpHandlers.of(501, Headers.of(), "");private static final HttpHandler METHOD_NOT_ALLOWED_HANDLER =HttpHandlers.of(405, Headers.of("Allow", "HEAD, GET"), "");public static HttpHandler create(Path root, UnaryOperator<String> mimeTable) {var fallbackHandler = HttpHandlers.handleOrElse(r -> UNSUPPORTED_METHODS.contains(r.getRequestMethod()),METHOD_NOT_ALLOWED_HANDLER,NOT_IMPLEMENTED_HANDLER);return HttpHandlers.handleOrElse(r -> SUPPORTED_METHODS.contains(r.getRequestMethod()),new FileServerHandler(root, mimeTable), fallbackHandler);}private void handleHEAD(HttpExchange exchange, Path path) throws IOException {handleSupportedMethod(exchange, path, false);}private void handleGET(HttpExchange exchange, Path path) throws IOException {handleSupportedMethod(exchange, path, true);}private void handleSupportedMethod(HttpExchange exchange, Path path, boolean writeBody)throws IOException {if (Files.isDirectory(path)) {if (missingSlash(exchange)) {handleMovedPermanently(exchange);return;}if (indexFile(path) != null) {serveFile(exchange, indexFile(path), writeBody);} else {listFiles(exchange, path, writeBody);}} else {serveFile(exchange, path, writeBody);}}private void handleMovedPermanently(HttpExchange exchange) throws IOException {exchange.getResponseHeaders().set("Location", getRedirectURI(exchange.getRequestURI()));exchange.sendResponseHeaders(301, -1);}private void handleForbidden(HttpExchange exchange) throws IOException {exchange.sendResponseHeaders(403, -1);}private void handleNotFound(HttpExchange exchange) throws IOException {String fileNotFound = ResourceBundleHelper.getMessage("html.not.found");var bytes = (openHTML+ "<h1>" + fileNotFound + "</h1>\n"+ "<p>" + sanitize.apply(exchange.getRequestURI().getPath()) + "</p>\n"+ closeHTML).getBytes(UTF_8);exchange.getResponseHeaders().set("Content-Type", "text/html; charset=UTF-8");if (exchange.getRequestMethod().equals("HEAD")) {exchange.getResponseHeaders().set("Content-Length", Integer.toString(bytes.length));exchange.sendResponseHeaders(404, -1);} else {exchange.sendResponseHeaders(404, bytes.length);try (OutputStream os = exchange.getResponseBody()) {os.write(bytes);}}}private static void discardRequestBody(HttpExchange exchange) throws IOException {try (InputStream is = exchange.getRequestBody()) {is.readAllBytes();}}private String getRedirectURI(URI uri) {String query = uri.getRawQuery();String redirectPath = uri.getRawPath() + "/";return query == null ? redirectPath : redirectPath + "?" + query;}private static boolean missingSlash(HttpExchange exchange) {return !exchange.getRequestURI().getPath().endsWith("/");}private static String contextPath(HttpExchange exchange) {String context = exchange.getHttpContext().getPath();if (!context.startsWith("/")) {throw new IllegalArgumentException("Context path invalid: " + context);}return context;}private static String requestPath(HttpExchange exchange) {String request = exchange.getRequestURI().getPath();if (!request.startsWith("/")) {throw new IllegalArgumentException("Request path invalid: " + request);}return request;}// Checks that the request does not escape context.private static void checkRequestWithinContext(String requestPath,String contextPath) {if (requestPath.equals(contextPath)) {return;  // context path requested, e.g. context /foo, request /foo}String contextPathWithTrailingSlash = contextPath.endsWith("/")? contextPath : contextPath + "/";if (!requestPath.startsWith(contextPathWithTrailingSlash)) {throw new IllegalArgumentException("Request not in context: " + contextPath);}}// Checks that path is, or is within, the root.private static Path checkPathWithinRoot(Path path, Path root) {if (!path.startsWith(root)) {throw new IllegalArgumentException("Request not in root");}return path;}// Returns the request URI path relative to the context.private static String relativeRequestPath(HttpExchange exchange) {String context = contextPath(exchange);String request = requestPath(exchange);checkRequestWithinContext(request, context);return request.substring(context.length());}private Path mapToPath(HttpExchange exchange, Path root) {try {assert root.isAbsolute() && Files.isDirectory(root);  // checked during creationString uriPath = relativeRequestPath(exchange);String[] pathSegment = uriPath.split("/");// resolve each path segment against the rootPath path = root;for (var segment : pathSegment) {path = path.resolve(segment);if (!Files.isReadable(path) || isHiddenOrSymLink(path)) {return null;  // stop resolution, null results in 404 response}}path = path.normalize();return checkPathWithinRoot(path, root);} catch (Exception e) {logger.log(System.Logger.Level.TRACE,"FileServerHandler: request URI path resolution failed", e);return null;  // could not resolve request URI path}}private static Path indexFile(Path path) {Path html = path.resolve("index.html");Path htm = path.resolve("index.htm");return Files.exists(html) ? html : Files.exists(htm) ? htm : null;}private void serveFile(HttpExchange exchange, Path path, boolean writeBody)throws IOException{var respHdrs = exchange.getResponseHeaders();respHdrs.set("Content-Type", mediaType(path.toString()));respHdrs.set("Last-Modified", getLastModified(path));if (writeBody) {exchange.sendResponseHeaders(200, Files.size(path));try (InputStream fis = Files.newInputStream(path);OutputStream os = exchange.getResponseBody()) {fis.transferTo(os);}} else {respHdrs.set("Content-Length", Long.toString(Files.size(path)));exchange.sendResponseHeaders(200, -1);}}private void listFiles(HttpExchange exchange, Path path, boolean writeBody)throws IOException{var respHdrs = exchange.getResponseHeaders();respHdrs.set("Content-Type", "text/html; charset=UTF-8");respHdrs.set("Last-Modified", getLastModified(path));var bodyBytes = dirListing(exchange, path).getBytes(UTF_8);if (writeBody) {exchange.sendResponseHeaders(200, bodyBytes.length);try (OutputStream os = exchange.getResponseBody()) {os.write(bodyBytes);}} else {respHdrs.set("Content-Length", Integer.toString(bodyBytes.length));exchange.sendResponseHeaders(200, -1);}}private static final String openHTML = """<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>""";private static final String closeHTML = """</body></html>""";private static final String hrefListItemTemplate = """<li><a href="%s">%s</a></li>""";private static String hrefListItemFor(URI uri) {return hrefListItemTemplate.formatted(uri.toASCIIString(), sanitize.apply(uri.getPath()));}private static String dirListing(HttpExchange exchange, Path path) throws IOException {String dirListing = ResourceBundleHelper.getMessage("html.dir.list");var sb = new StringBuilder(openHTML+ "<h1>" + dirListing + " "+ sanitize.apply(exchange.getRequestURI().getPath())+ "</h1>\n"+ "<ul>\n");try (var paths = Files.list(path)) {paths.filter(p -> Files.isReadable(p) && !isHiddenOrSymLink(p)).map(p -> path.toUri().relativize(p.toUri())).forEach(uri -> sb.append(hrefListItemFor(uri)));}sb.append("</ul>\n");sb.append(closeHTML);return sb.toString();}private static String getLastModified(Path path) throws IOException {var fileTime = Files.getLastModifiedTime(path);return fileTime.toInstant().atZone(ZoneId.of("GMT")).format(DateTimeFormatter.RFC_1123_DATE_TIME);}private static boolean isHiddenOrSymLink(Path path) {try {return Files.isHidden(path) || Files.isSymbolicLink(path);} catch (IOException e) {throw new UncheckedIOException(e);}}// Default for unknown content types, as per RFC 2046private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";private String mediaType(String file) {String type = mimeTable.apply(file);return type != null ? type : DEFAULT_CONTENT_TYPE;}// A non-exhaustive map of reserved-HTML and special characters to their// equivalent entity.private static final Map<Integer,String> RESERVED_CHARS = Map.of((int) '&'  , "&amp;"   ,(int) '<'  , "&lt;"    ,(int) '>'  , "&gt;"    ,(int) '"'  , "&quot;"  ,(int) '\'' , "&#x27;"  ,(int) '/'  , "&#x2F;"  );// A function that takes a string and returns a sanitized version of that// string with the reserved-HTML and special characters replaced with their// equivalent entity.private static final UnaryOperator<String> sanitize =file -> file.chars().collect(StringBuilder::new,(sb, c) -> sb.append(RESERVED_CHARS.getOrDefault(c, Character.toString(c))),StringBuilder::append).toString();@Overridepublic void handle(HttpExchange exchange) throws IOException {assert List.of("GET", "HEAD").contains(exchange.getRequestMethod());try (exchange) {discardRequestBody(exchange);Path path = mapToPath(exchange, root);if (path != null) {exchange.setAttribute("request-path", path.toString());  // store for OutputFilterif (!Files.exists(path) || !Files.isReadable(path) || isHiddenOrSymLink(path)) {handleNotFound(exchange);} else if (exchange.getRequestMethod().equals("HEAD")) {handleHEAD(exchange, path);} else {handleGET(exchange, path);}} else {exchange.setAttribute("request-path", "could not resolve request URI path");handleNotFound(exchange);}}}
}

复制到Jshell执行

创建简单的服务

import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.file.Path;
import java.util.function.UnaryOperator;UnaryOperator<String> identity = UnaryOperator.identity();var server = HttpServer.create(new InetSocketAddress(9000), 10, "/img/",FileServerHandler.create(Path.of("D:\\Program Files"), identity));server.start();

由于在idea中执行放在jshell中执行之后报端口被占用异常,关了idea中的就好了

另外jshell中运行的需要手动自己去找服务停止。

参数解释

9000  端口号

10 最大并发数量  <1的话默认会设置成50

/img/  访问链接前缀

D:\\Program Files 代理到的目标文件,此文件夹下的文件都可以通过http://127.0.0.1:+端口9000+访问前缀/img/ +文件夹下的文件名(带后缀)如下

http://127.0.0.1:9000/img/asd.jpg

http://www.dtcms.com/a/343396.html

相关文章:

  • 美国联邦调查局警告俄罗斯针对思科设备的网络间谍活动
  • 残差神经网络(ResNet)
  • 矫平机与纵剪:一条钢卷“变身”的全过程
  • 【UE5-Airsim】Windows10下安装UE5-Airsim的仿真环境
  • leetcode 1658 将x减到0的最小操作数
  • 同题异构解决leetcode第3646题下一个特殊回文数
  • Linux网络socket套接字(上)
  • linux 之virtio 的驱动框架
  • Motocycle 智能仪表盘
  • 白光干涉测量系统的复合相移三维重建和多视场形貌拼接的复现
  • 【自然语言处理与大模型】微调与RAG的区别
  • JavaScript基础语法five
  • 【Protues仿真】基于AT89C52单片机的数码管驱动事例
  • 力扣905:按奇偶排序数组
  • 2025-08-21 Python进阶4——错误和异常
  • 开发者中使用——控制台打印数据
  • 爬虫基础学习-基本原理和GET请求
  • JavaScript 基本语法
  • 智慧城市SaaS平台/市政设施运行监测系统之空气质量监测系统、VOC气体监测系统、污水水质监测系统及环卫车辆定位调度系统架构内容
  • 学习嵌入式之驱动
  • 3.2.6 混凝土基础施工
  • Chrome 内置扩展 vs WebUI:浏览器内核开发中的选择与实践
  • C++入门自学Day16-- STL容器类型总结
  • Git标准化开发流程
  • iOS 应用上架多环境实战,Windows、Linux 与 Mac 的不同路径
  • 详解开源关键信息提取方案PP-ChatOCRv4的设计与实现
  • 哈尔滨云前沿服务器租用类型
  • IoTDB如何解决海量数据存储难题?
  • 多模态大模型研究每日简报【2025-08-21】
  • Python学习-- 数据库和MySQL入门