是否有一种方法可以使用Java SE API在Java中创建一个非常基本的HTTP服务器(只支持GET/POST),而不需要编写代码手动解析HTTP请求和手动格式化HTTP响应?Java SE API在HttpURLConnection中很好地封装了HTTP客户机功能,但是是否有类似的HTTP服务器功能呢?

需要明确的是,我在网上看到的许多ServerSocket示例的问题是,它们自己进行请求解析/响应格式化和错误处理,这很乏味,容易出错,而且不太全面,出于这些原因,我试图避免使用它。


当前回答

曾经有一段时间,我正在寻找类似的东西——一个轻量级但功能齐全的HTTP服务器,我可以轻松地嵌入和定制。我发现了两种可能的解决方案:

不是那么轻量级或简单的完整服务器(对于轻量级的极端定义)。 真正的轻量级服务器不是HTTP服务器,而是华丽的ServerSocket示例,甚至不远程兼容rfc,不支持通常需要的基本功能。

所以…我开始编写JLHTTP——Java轻量级HTTP服务器。

您可以将它作为单个(如果相当长)源文件嵌入到任何项目中,或者作为一个~50K的jar (~35K剥离),没有依赖关系。它努力与rfc兼容,包括大量的文档和许多有用的特性,同时将膨胀保持在最低限度。

Features include: virtual hosts, file serving from disk, mime type mappings via standard mime.types file, directory index generation, welcome files, support for all HTTP methods, conditional ETags and If-* header support, chunked transfer encoding, gzip/deflate compression, basic HTTPS (as provided by the JVM), partial content (download continuation), multipart/form-data handling for file uploads, multiple context handlers via API or annotations, parameter parsing (query string or x-www-form-urlencoded body), etc.

我希望其他人会觉得有用:-)

其他回答

Apache Commons HttpCore项目怎么样?

来自网站:…… HttpCore目标

实现最基本的HTTP传输方面 良好的表现和清晰的表达之间的平衡 API 小(可预测的)内存占用 自包含库(没有JRE以外的外部依赖)

从Java 18开始,你可以用Java标准库创建简单的web服务器:

class Main {
    public static void main(String[] args) {
        var port = 8000;
        var rootDirectory = Path.of("C:/Users/Mahozad/Desktop/");
        var outputLevel = OutputLevel.VERBOSE;
        var server = SimpleFileServer.createFileServer(
                new InetSocketAddress(port),
                rootDirectory,
                outputLevel
        );
        server.start();
    }
}

默认情况下,这将显示指定根目录的目录列表。您可以将index.html文件(以及CSS和JS文件等其他资产)放在该目录中来显示它们。

示例(我把这些放在桌面上,上面指定为我的根目录):

index . html:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Java 18 Simple Web Server</title>
  <link rel="stylesheet" href="styles.css">
  <style>h1 { color: blue; }</style>
  <script src="scripts.js" defer>
    let element = document.getElementsByTagName("h1")[0];
    element.style.fontSize = "48px";
  </script>
</head>
<body>
  <h1>I'm <i>index.html</i> in the root directory.</h1>
</body>
</html>

旁注

对于Java标准库HTTP客户端,请参阅Java 11新HTTP客户端API。

曾经有一段时间,我正在寻找类似的东西——一个轻量级但功能齐全的HTTP服务器,我可以轻松地嵌入和定制。我发现了两种可能的解决方案:

不是那么轻量级或简单的完整服务器(对于轻量级的极端定义)。 真正的轻量级服务器不是HTTP服务器,而是华丽的ServerSocket示例,甚至不远程兼容rfc,不支持通常需要的基本功能。

所以…我开始编写JLHTTP——Java轻量级HTTP服务器。

您可以将它作为单个(如果相当长)源文件嵌入到任何项目中,或者作为一个~50K的jar (~35K剥离),没有依赖关系。它努力与rfc兼容,包括大量的文档和许多有用的特性,同时将膨胀保持在最低限度。

Features include: virtual hosts, file serving from disk, mime type mappings via standard mime.types file, directory index generation, welcome files, support for all HTTP methods, conditional ETags and If-* header support, chunked transfer encoding, gzip/deflate compression, basic HTTPS (as provided by the JVM), partial content (download continuation), multipart/form-data handling for file uploads, multiple context handlers via API or annotations, parameter parsing (query string or x-www-form-urlencoded body), etc.

我希望其他人会觉得有用:-)

从Java SE 6开始,在Sun Oracle JRE中有一个内置的HTTP服务器。Java 9模块名称为jdk.httpserver。httpserver包摘要概述了涉及的类并包含示例。

这里有一个从他们的文档复制粘贴的启动示例。你可以复制,粘贴,然后在Java 6+上运行。 (尽管如此,所有试图编辑它的人,因为它是一段丑陋的代码,请不要,这是一个复制粘贴,不是我的,此外,你不应该编辑引文,除非它们在原始来源中发生了变化)

package com.stackoverflow.q3732109; import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; public class Test { public static void main(String[] args) throws Exception { HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0); server.createContext("/test", new MyHandler()); server.setExecutor(null); // creates a default executor server.start(); } static class MyHandler implements HttpHandler { @Override public void handle(HttpExchange t) throws IOException { String response = "This is the response"; t.sendResponseHeaders(200, response.length()); OutputStream os = t.getResponseBody(); os.write(response.getBytes()); os.close(); } } }

应该注意的是,他们示例中的response.length()部分是坏的,它应该是response.getBytes().length。即使这样,getBytes()方法也必须显式地指定在响应头中指定的字符集。唉,尽管对初学者有误导,但毕竟这只是一个基本的启动示例。

执行它并访问http://localhost:8000/test,你将看到以下响应:

这是反应


As to using com.sun.* classes, do note that this is, in contrary to what some developers think, absolutely not forbidden by the well known FAQ Why Developers Should Not Write Programs That Call 'sun' Packages. That FAQ concerns the sun.* package (such as sun.misc.BASE64Encoder) for internal usage by the Oracle JRE (which would thus kill your application when you run it on a different JRE), not the com.sun.* package. Sun/Oracle also just develop software on top of the Java SE API themselves like as every other company such as Apache and so on. Moreover, this specific HttpServer must be present in every JDK so there is absolutely no means of "portability" issue like as would happen with sun.* package. Using com.sun.* classes is only discouraged (but not forbidden) when it concerns an implementation of a certain Java API, such as GlassFish (Java EE impl), Mojarra (JSF impl), Jersey (JAX-RS impl), etc.

以上所有的回答关于单主线程请求处理器的细节。

设置:

 server.setExecutor(java.util.concurrent.Executors.newCachedThreadPool());

允许多个请求服务通过多个线程使用执行器服务。

因此,结束代码将如下所示:

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
public class App {
    public static void main(String[] args) throws Exception {
        HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
        server.createContext("/test", new MyHandler());
        //Thread control is given to executor service.
        server.setExecutor(java.util.concurrent.Executors.newCachedThreadPool());
        server.start();
    }
    static class MyHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange t) throws IOException {
            String response = "This is the response";
            long threadId = Thread.currentThread().getId();
            System.out.println("I am thread " + threadId );
            response = response + "Thread Id = "+threadId;
            t.sendResponseHeaders(200, response.length());
            OutputStream os = t.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }
}