是否有一种方法可以使用Java SE API在Java中创建一个非常基本的HTTP服务器(只支持GET/POST),而不需要编写代码手动解析HTTP请求和手动格式化HTTP响应?Java SE API在HttpURLConnection中很好地封装了HTTP客户机功能,但是是否有类似的HTTP服务器功能呢?
需要明确的是,我在网上看到的许多ServerSocket示例的问题是,它们自己进行请求解析/响应格式化和错误处理,这很乏味,容易出错,而且不太全面,出于这些原因,我试图避免使用它。
是否有一种方法可以使用Java SE API在Java中创建一个非常基本的HTTP服务器(只支持GET/POST),而不需要编写代码手动解析HTTP请求和手动格式化HTTP响应?Java SE API在HttpURLConnection中很好地封装了HTTP客户机功能,但是是否有类似的HTTP服务器功能呢?
需要明确的是,我在网上看到的许多ServerSocket示例的问题是,它们自己进行请求解析/响应格式化和错误处理,这很乏味,容易出错,而且不太全面,出于这些原因,我试图避免使用它。
当前回答
我玩得很开心,我玩转了一下,拼凑出了这个。我希望这对你有所帮助。 你需要安装Gradle或者使用Maven插件。
build.gradle
plugins {
id 'application'
}
group 'foo.bar'
version '1.0'
repositories {
mavenCentral()
}
application{
mainClass.set("foo.FooServer")
}
dependencies {}
FooServer 主入口点,你的主类。
package foo;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FooServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(7654);
serverSocket.setPerformancePreferences(0, 1, 2);
/* the higher the numbers, the better the concurrent performance, ha!
we found that a 3:7 ratio to be optimal
3 partitioned executors to 7 network executors */
ExecutorService executors = Executors.newFixedThreadPool(3);
executors.execute(new PartitionedExecutor(serverSocket));
}
public static class PartitionedExecutor implements Runnable {
ServerSocket serverSocket;
public PartitionedExecutor(ServerSocket serverSocket) {
this.serverSocket = serverSocket;
}
@Override
public void run() {
ExecutorService executors = Executors.newFixedThreadPool(30);
executors.execute(new NetworkRequestExecutor(serverSocket, executors));
}
}
public static class NetworkRequestExecutor implements Runnable{
String IGNORE_CHROME = "/favicon.ico";
String BREAK = "\r\n";
String DOUBLEBREAK = "\r\n\r\n";
Integer REQUEST_METHOD = 0;
Integer REQUEST_PATH = 1;
Integer REQUEST_VERSION = 2;
String RENDERER;
Socket socketClient;
ExecutorService executors;
ServerSocket serverSocket;
public NetworkRequestExecutor(ServerSocket serverSocket, ExecutorService executors){
this.serverSocket = serverSocket;
this.executors = executors;
}
@Override
public void run() {
try {
socketClient = serverSocket.accept();
Thread.sleep(19);//do this for safari, its a hack but safari requires something like this.
InputStream requestInputStream = socketClient.getInputStream();
OutputStream clientOutput = socketClient.getOutputStream();
if (requestInputStream.available() == 0) {
requestInputStream.close();
clientOutput.flush();
clientOutput.close();
executors.execute(new NetworkRequestExecutor(serverSocket, executors));
return;
}
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int bytesRead;
while ((bytesRead = requestInputStream.read(byteBuffer.array())) != -1) {
byteArrayOutputStream.write(byteBuffer.array(), 0, bytesRead);
if (requestInputStream.available() == 0) break;
}
String completeRequestContent = byteArrayOutputStream.toString();
String[] requestBlocks = completeRequestContent.split(DOUBLEBREAK, 2);
String headerComponent = requestBlocks[0];
String[] methodPathComponentsLookup = headerComponent.split(BREAK);
String methodPathComponent = methodPathComponentsLookup[0];
String[] methodPathVersionComponents = methodPathComponent.split("\\s");
String requestVerb = methodPathVersionComponents[REQUEST_METHOD];
String requestPath = methodPathVersionComponents[REQUEST_PATH];
String requestVersion = methodPathVersionComponents[REQUEST_VERSION];
if (requestPath.equals(IGNORE_CHROME)) {
requestInputStream.close();
clientOutput.flush();
clientOutput.close();
executors.execute(new NetworkRequestExecutor(serverSocket, executors));
return;
}
ConcurrentMap<String, String> headers = new ConcurrentHashMap<>();
String[] headerComponents = headerComponent.split(BREAK);
for (String headerLine : headerComponents) {
String[] headerLineComponents = headerLine.split(":");
if (headerLineComponents.length == 2) {
String fieldKey = headerLineComponents[0].trim();
String content = headerLineComponents[1].trim();
headers.put(fieldKey.toLowerCase(), content);
}
}
clientOutput.write("HTTP/1.1 200 OK".getBytes());
clientOutput.write(BREAK.getBytes());
Integer bytesLength = "hi".length();
String contentLengthBytes = "Content-Length:" + bytesLength;
clientOutput.write(contentLengthBytes.getBytes());
clientOutput.write(BREAK.getBytes());
clientOutput.write("Server: foo server".getBytes());
clientOutput.write(BREAK.getBytes());
clientOutput.write("Content-Type: text/html".getBytes());
clientOutput.write(DOUBLEBREAK.getBytes());
clientOutput.write("hi".getBytes());
clientOutput.close();
socketClient.close();
executors.execute(new NetworkRequestExecutor(serverSocket, executors));
} catch (IOException ex) {
ex.printStackTrace();
} catch (InterruptedException ioException) {
ioException.printStackTrace();
}
}
}
}
运行该程序:
gradle run
浏览:
http://localhost:7654/
其他回答
从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。
看看“Jetty”web服务器Jetty。一流的开源软件,似乎可以满足您的所有要求。
如果你坚持要创建自己的类,那么可以看看“httpMessage”类。
检查拍摄。查看https://github.com/yegor256/takes获取快速信息
曾经有一段时间,我正在寻找类似的东西——一个轻量级但功能齐全的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.
我希望其他人会觉得有用:-)
我玩得很开心,我玩转了一下,拼凑出了这个。我希望这对你有所帮助。 你需要安装Gradle或者使用Maven插件。
build.gradle
plugins {
id 'application'
}
group 'foo.bar'
version '1.0'
repositories {
mavenCentral()
}
application{
mainClass.set("foo.FooServer")
}
dependencies {}
FooServer 主入口点,你的主类。
package foo;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FooServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(7654);
serverSocket.setPerformancePreferences(0, 1, 2);
/* the higher the numbers, the better the concurrent performance, ha!
we found that a 3:7 ratio to be optimal
3 partitioned executors to 7 network executors */
ExecutorService executors = Executors.newFixedThreadPool(3);
executors.execute(new PartitionedExecutor(serverSocket));
}
public static class PartitionedExecutor implements Runnable {
ServerSocket serverSocket;
public PartitionedExecutor(ServerSocket serverSocket) {
this.serverSocket = serverSocket;
}
@Override
public void run() {
ExecutorService executors = Executors.newFixedThreadPool(30);
executors.execute(new NetworkRequestExecutor(serverSocket, executors));
}
}
public static class NetworkRequestExecutor implements Runnable{
String IGNORE_CHROME = "/favicon.ico";
String BREAK = "\r\n";
String DOUBLEBREAK = "\r\n\r\n";
Integer REQUEST_METHOD = 0;
Integer REQUEST_PATH = 1;
Integer REQUEST_VERSION = 2;
String RENDERER;
Socket socketClient;
ExecutorService executors;
ServerSocket serverSocket;
public NetworkRequestExecutor(ServerSocket serverSocket, ExecutorService executors){
this.serverSocket = serverSocket;
this.executors = executors;
}
@Override
public void run() {
try {
socketClient = serverSocket.accept();
Thread.sleep(19);//do this for safari, its a hack but safari requires something like this.
InputStream requestInputStream = socketClient.getInputStream();
OutputStream clientOutput = socketClient.getOutputStream();
if (requestInputStream.available() == 0) {
requestInputStream.close();
clientOutput.flush();
clientOutput.close();
executors.execute(new NetworkRequestExecutor(serverSocket, executors));
return;
}
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int bytesRead;
while ((bytesRead = requestInputStream.read(byteBuffer.array())) != -1) {
byteArrayOutputStream.write(byteBuffer.array(), 0, bytesRead);
if (requestInputStream.available() == 0) break;
}
String completeRequestContent = byteArrayOutputStream.toString();
String[] requestBlocks = completeRequestContent.split(DOUBLEBREAK, 2);
String headerComponent = requestBlocks[0];
String[] methodPathComponentsLookup = headerComponent.split(BREAK);
String methodPathComponent = methodPathComponentsLookup[0];
String[] methodPathVersionComponents = methodPathComponent.split("\\s");
String requestVerb = methodPathVersionComponents[REQUEST_METHOD];
String requestPath = methodPathVersionComponents[REQUEST_PATH];
String requestVersion = methodPathVersionComponents[REQUEST_VERSION];
if (requestPath.equals(IGNORE_CHROME)) {
requestInputStream.close();
clientOutput.flush();
clientOutput.close();
executors.execute(new NetworkRequestExecutor(serverSocket, executors));
return;
}
ConcurrentMap<String, String> headers = new ConcurrentHashMap<>();
String[] headerComponents = headerComponent.split(BREAK);
for (String headerLine : headerComponents) {
String[] headerLineComponents = headerLine.split(":");
if (headerLineComponents.length == 2) {
String fieldKey = headerLineComponents[0].trim();
String content = headerLineComponents[1].trim();
headers.put(fieldKey.toLowerCase(), content);
}
}
clientOutput.write("HTTP/1.1 200 OK".getBytes());
clientOutput.write(BREAK.getBytes());
Integer bytesLength = "hi".length();
String contentLengthBytes = "Content-Length:" + bytesLength;
clientOutput.write(contentLengthBytes.getBytes());
clientOutput.write(BREAK.getBytes());
clientOutput.write("Server: foo server".getBytes());
clientOutput.write(BREAK.getBytes());
clientOutput.write("Content-Type: text/html".getBytes());
clientOutput.write(DOUBLEBREAK.getBytes());
clientOutput.write("hi".getBytes());
clientOutput.close();
socketClient.close();
executors.execute(new NetworkRequestExecutor(serverSocket, executors));
} catch (IOException ex) {
ex.printStackTrace();
} catch (InterruptedException ioException) {
ioException.printStackTrace();
}
}
}
}
运行该程序:
gradle run
浏览:
http://localhost:7654/