From e3a60ad93fd9cab389dae3a3a2e42cd0fafd46e6 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 18 Nov 2024 01:53:18 +0800 Subject: [PATCH] add http server engine --- hutool-http/pom.xml | 24 + .../org/dromara/hutool/http/HttpUtil.java | 2 +- .../hutool/http/multipart/UploadFile.java | 34 +- .../http/server/HttpServerResponse.java | 446 ------------------ .../hutool/http/server/ServerConfig.java | 121 +++++ .../hutool/http/server/ServerRequest.java | 328 +++++++++++++ .../hutool/http/server/ServerResponse.java | 334 +++++++++++++ .../server/engine/AbstractServerEngine.java | 54 +++ .../http/server/engine/ServerEngine.java | 58 +++ .../jetty}/package-info.java | 8 +- .../http/server/engine/package-info.java | 23 + .../{ => engine/sun}/HttpExchangeWrapper.java | 14 +- .../server/{ => engine/sun}/SimpleServer.java | 96 ++-- .../engine/sun/SunHttpServerEngine.java | 163 +++++++ .../sun/SunServerBase.java} | 16 +- .../sun/SunServerRequest.java} | 272 ++--------- .../server/engine/sun/SunServerResponse.java | 227 +++++++++ .../sun}/filter/DefaultExceptionFilter.java | 8 +- .../sun}/filter/ExceptionFilter.java | 16 +- .../{ => engine/sun}/filter/HttpFilter.java | 12 +- .../{ => engine/sun}/filter/SimpleFilter.java | 2 +- .../{ => engine/sun}/filter/package-info.java | 2 +- .../http/server/engine/sun/package-info.java | 22 + .../server/engine/tomcat/package-info.java | 22 + .../engine/undertow/UndertowEngine.java | 82 ++++ .../engine/undertow/UndertowExchangeBase.java | 48 ++ .../engine/undertow/UndertowRequest.java | 82 ++++ .../engine/undertow/UndertowResponse.java | 112 +++++ .../server/engine/undertow/package-info.java | 23 + .../http/server/handler/ActionHandler.java | 63 --- .../Action.java => handler/HttpHandler.java} | 20 +- .../RootHandler.java} | 25 +- .../http/server/ExceptionServerTest.java | 7 +- .../hutool/http/server/Issue3343Test.java | 7 +- .../hutool/http/server/Issue3723Test.java | 5 +- .../hutool/http/server/IssueI6Q30XTest.java | 6 +- .../http/server/RedirectServerTest.java | 9 +- .../hutool/http/server/SimpleServerTest.java | 9 +- .../http/server/engine/UndertowTest.java | 33 ++ 39 files changed, 1925 insertions(+), 910 deletions(-) delete mode 100644 hutool-http/src/main/java/org/dromara/hutool/http/server/HttpServerResponse.java create mode 100644 hutool-http/src/main/java/org/dromara/hutool/http/server/ServerConfig.java create mode 100644 hutool-http/src/main/java/org/dromara/hutool/http/server/ServerRequest.java create mode 100644 hutool-http/src/main/java/org/dromara/hutool/http/server/ServerResponse.java create mode 100644 hutool-http/src/main/java/org/dromara/hutool/http/server/engine/AbstractServerEngine.java create mode 100644 hutool-http/src/main/java/org/dromara/hutool/http/server/engine/ServerEngine.java rename hutool-http/src/main/java/org/dromara/hutool/http/server/{action => engine/jetty}/package-info.java (77%) create mode 100644 hutool-http/src/main/java/org/dromara/hutool/http/server/engine/package-info.java rename hutool-http/src/main/java/org/dromara/hutool/http/server/{ => engine/sun}/HttpExchangeWrapper.java (91%) rename hutool-http/src/main/java/org/dromara/hutool/http/server/{ => engine/sun}/SimpleServer.java (71%) create mode 100644 hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/SunHttpServerEngine.java rename hutool-http/src/main/java/org/dromara/hutool/http/server/{HttpServerBase.java => engine/sun/SunServerBase.java} (76%) rename hutool-http/src/main/java/org/dromara/hutool/http/server/{HttpServerRequest.java => engine/sun/SunServerRequest.java} (55%) create mode 100644 hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/SunServerResponse.java rename hutool-http/src/main/java/org/dromara/hutool/http/server/{ => engine/sun}/filter/DefaultExceptionFilter.java (82%) rename hutool-http/src/main/java/org/dromara/hutool/http/server/{ => engine/sun}/filter/ExceptionFilter.java (63%) rename hutool-http/src/main/java/org/dromara/hutool/http/server/{ => engine/sun}/filter/HttpFilter.java (67%) rename hutool-http/src/main/java/org/dromara/hutool/http/server/{ => engine/sun}/filter/SimpleFilter.java (93%) rename hutool-http/src/main/java/org/dromara/hutool/http/server/{ => engine/sun}/filter/package-info.java (92%) create mode 100644 hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/package-info.java create mode 100644 hutool-http/src/main/java/org/dromara/hutool/http/server/engine/tomcat/package-info.java create mode 100644 hutool-http/src/main/java/org/dromara/hutool/http/server/engine/undertow/UndertowEngine.java create mode 100644 hutool-http/src/main/java/org/dromara/hutool/http/server/engine/undertow/UndertowExchangeBase.java create mode 100644 hutool-http/src/main/java/org/dromara/hutool/http/server/engine/undertow/UndertowRequest.java create mode 100644 hutool-http/src/main/java/org/dromara/hutool/http/server/engine/undertow/UndertowResponse.java create mode 100644 hutool-http/src/main/java/org/dromara/hutool/http/server/engine/undertow/package-info.java delete mode 100644 hutool-http/src/main/java/org/dromara/hutool/http/server/handler/ActionHandler.java rename hutool-http/src/main/java/org/dromara/hutool/http/server/{action/Action.java => handler/HttpHandler.java} (56%) rename hutool-http/src/main/java/org/dromara/hutool/http/server/{action/RootAction.java => handler/RootHandler.java} (75%) create mode 100644 hutool-http/src/test/java/org/dromara/hutool/http/server/engine/UndertowTest.java diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 8460f9e96..911e1364b 100755 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -37,6 +37,10 @@ 5.4.1 4.5.14 4.12.0 + + 2.2.36.Final + 12.0.15 + 11.0.1 @@ -100,6 +104,26 @@ provided + + + io.undertow + undertow-core + ${undertow.version} + provided + + + org.eclipse.jetty + jetty-server + ${jetty.version} + provided + + + org.apache.tomcat.embed + tomcat-embed-core + ${tomcat.version} + provided + + org.dromara.hutool diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/HttpUtil.java b/hutool-http/src/main/java/org/dromara/hutool/http/HttpUtil.java index a2e6b4d74..914226137 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/HttpUtil.java @@ -27,7 +27,7 @@ import org.dromara.hutool.http.client.Response; import org.dromara.hutool.http.client.engine.ClientEngine; import org.dromara.hutool.http.client.engine.ClientEngineFactory; import org.dromara.hutool.http.meta.Method; -import org.dromara.hutool.http.server.SimpleServer; +import org.dromara.hutool.http.server.engine.sun.SimpleServer; import java.nio.charset.Charset; import java.util.Collection; diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/multipart/UploadFile.java b/hutool-http/src/main/java/org/dromara/hutool/http/multipart/UploadFile.java index c943deb94..06b31f66d 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/multipart/UploadFile.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/multipart/UploadFile.java @@ -16,17 +16,13 @@ package org.dromara.hutool.http.multipart; -import org.dromara.hutool.core.io.file.FileUtil; +import org.dromara.hutool.core.io.IORuntimeException; import org.dromara.hutool.core.io.IoUtil; import org.dromara.hutool.core.io.file.FileNameUtil; +import org.dromara.hutool.core.io.file.FileUtil; import org.dromara.hutool.core.text.StrUtil; -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.NoSuchFileException; +import java.io.*; /** * 上传的文件对象 @@ -80,9 +76,9 @@ public class UploadFile { * * @param destPath 目标文件路径 * @return 目标文件 - * @throws IOException IO异常 + * @throws IORuntimeException IO异常 */ - public File write(final String destPath) throws IOException { + public File write(final String destPath) throws IORuntimeException { if (data != null || tempFile != null) { return write(FileUtil.file(destPath)); } @@ -95,9 +91,9 @@ public class UploadFile { * * @param destination 目标文件 * @return 目标文件 - * @throws IOException IO异常 + * @throws IORuntimeException IO异常 */ - public File write(File destination) throws IOException { + public File write(File destination) throws IORuntimeException { assertValid(); if (destination.isDirectory()) { @@ -113,7 +109,7 @@ public class UploadFile { throw new NullPointerException("Temp file is null !"); } if(!this.tempFile.exists()){ - throw new NoSuchFileException("Temp file: [" + this.tempFile.getAbsolutePath() + "] not exist!"); + throw new IORuntimeException("Temp file: [" + this.tempFile.getAbsolutePath() + "] not exist!"); } FileUtil.move(tempFile, destination, true); @@ -123,9 +119,9 @@ public class UploadFile { /** * @return 获得文件字节流 - * @throws IOException IO异常 + * @throws IORuntimeException IO异常 */ - public byte[] getFileContent() throws IOException { + public byte[] getFileContent() throws IORuntimeException { assertValid(); if (data != null) { @@ -139,9 +135,9 @@ public class UploadFile { /** * @return 获得文件流 - * @throws IOException IO异常 + * @throws IORuntimeException IO异常 */ - public InputStream getFileInputStream() throws IOException { + public InputStream getFileInputStream() throws IORuntimeException { assertValid(); if (data != null) { @@ -278,11 +274,11 @@ public class UploadFile { /** * 断言是否文件流可用 * - * @throws IOException IO异常 + * @throws IORuntimeException IO异常 */ - private void assertValid() throws IOException { + private void assertValid() throws IORuntimeException { if (!isUploaded()) { - throw new IOException(StrUtil.format("File [{}] upload fail", getFileName())); + throw new IORuntimeException(StrUtil.format("File [{}] upload fail", getFileName())); } } // ---------------------------------------------------------------------------- Private method end diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/HttpServerResponse.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/HttpServerResponse.java deleted file mode 100644 index c57c318db..000000000 --- a/hutool-http/src/main/java/org/dromara/hutool/http/server/HttpServerResponse.java +++ /dev/null @@ -1,446 +0,0 @@ -/* - * Copyright (c) 2013-2024 Hutool Team and hutool.cn - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.dromara.hutool.http.server; - -import org.dromara.hutool.core.io.IORuntimeException; -import org.dromara.hutool.core.io.IoUtil; -import org.dromara.hutool.core.io.file.FileUtil; -import org.dromara.hutool.core.net.url.UrlEncoder; -import org.dromara.hutool.core.text.StrUtil; -import org.dromara.hutool.core.util.ByteUtil; -import org.dromara.hutool.core.util.ObjUtil; -import org.dromara.hutool.http.meta.ContentType; -import org.dromara.hutool.http.meta.HeaderName; -import org.dromara.hutool.http.meta.HttpStatus; -import com.sun.net.httpserver.Headers; -import com.sun.net.httpserver.HttpExchange; - -import java.io.*; -import java.nio.charset.Charset; -import java.util.List; -import java.util.Map; - -/** - * Http响应对象,用于写出数据到客户端 - */ -@SuppressWarnings("resource") -public class HttpServerResponse extends HttpServerBase { - - private Charset charset; - /** - * 是否已经发送了Http状态码,如果没有,提前写出状态码 - */ - private boolean isSendCode; - - /** - * 构造 - * - * @param httpExchange {@link HttpExchange} - */ - public HttpServerResponse(final HttpExchange httpExchange) { - super(httpExchange); - } - - /** - * 发送HTTP状态码,Content-Length为0不定长度,会输出Transfer-encoding: chunked - * - * @param httpStatusCode HTTP状态码,见HttpStatus - * @return this - */ - public HttpServerResponse send(final int httpStatusCode) { - return send(httpStatusCode, 0); - } - - /** - * 发送成功状态码 - * - * @return this - */ - public HttpServerResponse sendOk() { - return send(HttpStatus.HTTP_OK); - } - - /** - * 发送成功状态码 - * - * @param bodyLength 响应体长度,默认0表示不定长度,会输出Transfer-encoding: chunked - * @return this - * @since 5.5.7 - */ - public HttpServerResponse sendOk(final int bodyLength) { - return send(HttpStatus.HTTP_OK, bodyLength); - } - - /** - * 发送404错误页 - * - * @param content 错误页页面内容,默认text/html类型 - * @return this - */ - public HttpServerResponse send404(final String content) { - return sendError(HttpStatus.HTTP_NOT_FOUND, content); - } - - /** - * 发送错误页 - * - * @param errorCode HTTP错误状态码,见HttpStatus - * @param content 错误页页面内容,默认text/html类型 - * @return this - */ - public HttpServerResponse sendError(final int errorCode, final String content) { - send(errorCode); - setContentType(ContentType.TEXT_HTML.toString()); - return write(content); - } - - /** - * 发送HTTP状态码 - * - * @param httpStatusCode HTTP状态码,见HttpStatus - * @param bodyLength 响应体长度,默认0表示不定长度,会输出Transfer-encoding: chunked - * @return this - */ - public HttpServerResponse send(final int httpStatusCode, final long bodyLength) { - if (this.isSendCode) { - throw new IORuntimeException("Http status code has been send!"); - } - - try { - this.httpExchange.sendResponseHeaders(httpStatusCode, bodyLength); - } catch (final IOException e) { - throw new IORuntimeException(e); - } - - this.isSendCode = true; - return this; - } - - /** - * 获得所有响应头,获取后可以添加新的响应头 - * - * @return 响应头 - */ - public Headers getHeaders() { - return this.httpExchange.getResponseHeaders(); - } - - /** - * 添加响应头,如果已经存在,则追加 - * - * @param header 头key - * @param value 值 - * @return this - */ - public HttpServerResponse addHeader(final String header, final String value) { - getHeaders().add(header, value); - return this; - } - - /** - * 设置响应头,如果已经存在,则覆盖 - * - * @param headerName 头key - * @param value 值 - * @return this - */ - public HttpServerResponse setHeader(final HeaderName headerName, final String value) { - return setHeader(headerName.getValue(), value); - } - - /** - * 设置响应头,如果已经存在,则覆盖 - * - * @param header 头key - * @param value 值 - * @return this - */ - public HttpServerResponse setHeader(final String header, final String value) { - getHeaders().set(header, value); - return this; - } - - /** - * 设置响应头,如果已经存在,则覆盖 - * - * @param header 头key - * @param value 值列表 - * @return this - */ - public HttpServerResponse setHeader(final String header, final List value) { - getHeaders().put(header, value); - return this; - } - - /** - * 设置所有响应头,如果已经存在,则覆盖 - * - * @param headers 响应头map - * @return this - */ - public HttpServerResponse setHeaders(final Map> headers) { - getHeaders().putAll(headers); - return this; - } - - /** - * 设置Content-Type头,类似于:text/html;charset=utf-8
- * 如果用户传入的信息无charset信息,自动根据charset补充,charset设置见{@link #setCharset(Charset)} - * - * @param contentType Content-Type头内容 - * @return this - */ - public HttpServerResponse setContentType(String contentType) { - if (null != contentType && null != this.charset) { - if (!contentType.contains(";charset=")) { - contentType = ContentType.build(contentType, this.charset); - } - } - - return setHeader(HeaderName.CONTENT_TYPE, contentType); - } - - /** - * 设置Content-Length头 - * - * @param contentLength Content-Length头内容 - * @return this - */ - public HttpServerResponse setContentLength(final long contentLength) { - return setHeader(HeaderName.CONTENT_LENGTH, String.valueOf(contentLength)); - } - - /** - * 设置响应的编码 - * - * @param charset 编码 - * @return this - */ - public HttpServerResponse setCharset(final Charset charset) { - this.charset = charset; - return this; - } - - /** - * 设置属性 - * - * @param name 属性名 - * @param value 属性值 - * @return this - */ - public HttpServerResponse setAttr(final String name, final Object value) { - this.httpExchange.setAttribute(name, value); - return this; - } - - /** - * 获取响应数据流 - * - * @return 响应数据流 - */ - public OutputStream getOut() { - if (!this.isSendCode) { - sendOk(); - } - return this.httpExchange.getResponseBody(); - } - - /** - * 获取响应数据流 - * - * @return 响应数据流 - */ - public PrintWriter getWriter() { - final Charset charset = ObjUtil.defaultIfNull(this.charset, DEFAULT_CHARSET); - return new PrintWriter(new OutputStreamWriter(getOut(), charset)); - } - - /** - * 写出数据到客户端 - * - * @param data 数据 - * @param contentType Content-Type类型 - * @return this - */ - public HttpServerResponse write(final String data, final String contentType) { - setContentType(contentType); - return write(data); - } - - /** - * 写出数据到客户端 - * - * @param data 数据 - * @return this - */ - public HttpServerResponse write(final String data) { - final Charset charset = ObjUtil.defaultIfNull(this.charset, DEFAULT_CHARSET); - return write(ByteUtil.toBytes(data, charset)); - } - - /** - * 写出数据到客户端 - * - * @param data 数据 - * @param contentType 返回的类型 - * @return this - */ - public HttpServerResponse write(final byte[] data, final String contentType) { - setContentType(contentType); - return write(data); - } - - /** - * 写出数据到客户端 - * - * @param data 数据 - * @return this - */ - public HttpServerResponse write(final byte[] data) { - final ByteArrayInputStream in = new ByteArrayInputStream(data); - return write(in, in.available()); - } - - /** - * 返回数据给客户端 - * - * @param in 需要返回客户端的内容 - * @param contentType 返回的类型 - * @return this - * @since 5.2.6 - */ - public HttpServerResponse write(final InputStream in, final String contentType) { - return write(in, 0, contentType); - } - - /** - * 返回数据给客户端 - * - * @param in 需要返回客户端的内容 - * @param length 内容长度,默认0表示不定长度,会输出Transfer-encoding: chunked - * @param contentType 返回的类型 - * @return this - * @since 5.2.7 - */ - public HttpServerResponse write(final InputStream in, final int length, final String contentType) { - setContentType(contentType); - return write(in, length); - } - - /** - * 写出数据到客户端 - * - * @param in 数据流 - * @return this - */ - public HttpServerResponse write(final InputStream in) { - return write(in, 0); - } - - /** - * 写出数据到客户端 - * - * @param in 数据流 - * @param length 指定响应内容长度,默认0表示不定长度,会输出Transfer-encoding: chunked - * @return this - */ - public HttpServerResponse write(final InputStream in, final int length) { - if (!isSendCode) { - sendOk(Math.max(0, length)); - } - OutputStream out = null; - try { - out = this.httpExchange.getResponseBody(); - IoUtil.copy(in, out); - } finally { - IoUtil.closeQuietly(out); - IoUtil.closeQuietly(in); - } - return this; - } - - /** - * 返回文件给客户端(文件下载) - * - * @param file 写出的文件对象 - * @return this - * @since 5.2.6 - */ - public HttpServerResponse write(final File file) { - return write(file, null); - } - - /** - * 返回文件给客户端(文件下载) - * - * @param file 写出的文件对象 - * @param fileName 文件名 - * @return this - * @since 5.5.8 - */ - public HttpServerResponse write(final File file, String fileName) { - final long fileSize = file.length(); - if(fileSize > Integer.MAX_VALUE){ - throw new IllegalArgumentException("File size is too bigger than " + Integer.MAX_VALUE); - } - - if(StrUtil.isBlank(fileName)){ - fileName = file.getName(); - } - final String contentType = FileUtil.getMimeType(fileName, ContentType.OCTET_STREAM.getValue()); - BufferedInputStream in = null; - try { - in = FileUtil.getInputStream(file); - write(in, (int)fileSize, contentType, fileName); - } finally { - IoUtil.closeQuietly(in); - } - return this; - } - - /** - * 返回文件数据给客户端(文件下载) - * - * @param in 需要返回客户端的内容 - * @param contentType 返回的类型 - * @param fileName 文件名 - * @since 5.2.6 - */ - public void write(final InputStream in, final String contentType, final String fileName) { - write(in, 0, contentType, fileName); - } - - /** - * 返回文件数据给客户端(文件下载) - * - * @param in 需要返回客户端的内容 - * @param length 长度 - * @param contentType 返回的类型 - * @param fileName 文件名 - * @return this - * @since 5.2.7 - */ - public HttpServerResponse write(final InputStream in, final int length, final String contentType, final String fileName) { - final Charset charset = ObjUtil.defaultIfNull(this.charset, DEFAULT_CHARSET); - - if (!contentType.startsWith("text/")) { - // 非文本类型数据直接走下载 - setHeader(HeaderName.CONTENT_DISPOSITION, StrUtil.format("attachment;filename={}", UrlEncoder.encodeAll(fileName, charset))); - } - return write(in, length, contentType); - } -} diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/ServerConfig.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/ServerConfig.java new file mode 100644 index 000000000..0d9641c19 --- /dev/null +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/ServerConfig.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.http.server; + +import javax.net.ssl.SSLContext; + +/** + * 服务器配置 + * + * @author Looly + */ +public class ServerConfig { + + /** + * 创建配置 + * + * @return 配置 + */ + public static ServerConfig of(){ + return new ServerConfig(); + } + + private String host = "localhost"; + private int port = 8888; + private String root; + private SSLContext sslContext; + + /** + * 获取服务器地址,默认127.0.0.1 + * + * @return 服务器地址 + */ + public String getHost() { + return host; + } + + /** + * 设置服务器地址,默认127.0.0.1 + * + * @param host 服务器地址 + * @return this + */ + public ServerConfig setHost(final String host) { + this.host = host; + return this; + } + + /** + * 获取服务器端口 + * + * @return 服务器端口 + */ + public int getPort() { + return port; + } + + /** + * 设置服务器端口 + * + * @param port 服务器端口 + * @return this + */ + public ServerConfig setPort(final int port) { + this.port = port; + return this; + } + + /** + * 获取服务器根目录 + * + * @return 服务器根目录 + */ + public String getRoot() { + return root; + } + + /** + * 设置服务器根目录 + * + * @param root 服务器根目录 + * @return this + */ + public ServerConfig setRoot(final String root) { + this.root = root; + return this; + } + + /** + * 获取SSL上下文 + * + * @return SSL上下文 + */ + public SSLContext getSslContext() { + return sslContext; + } + + /** + * 设置SSL上下文 + * + * @param sslContext SSL上下文 + * @return this + */ + public ServerConfig setSslContext(final SSLContext sslContext) { + this.sslContext = sslContext; + return this; + } +} diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/ServerRequest.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/ServerRequest.java new file mode 100644 index 000000000..785963336 --- /dev/null +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/ServerRequest.java @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.http.server; + +import org.dromara.hutool.core.io.IORuntimeException; +import org.dromara.hutool.core.io.IoUtil; +import org.dromara.hutool.core.map.multi.ListValueMap; +import org.dromara.hutool.core.net.url.UrlQueryUtil; +import org.dromara.hutool.core.text.StrUtil; +import org.dromara.hutool.core.util.CharsetUtil; +import org.dromara.hutool.core.util.ObjUtil; +import org.dromara.hutool.http.meta.ContentTypeUtil; +import org.dromara.hutool.http.meta.HeaderName; +import org.dromara.hutool.http.meta.Method; +import org.dromara.hutool.http.multipart.MultipartFormData; +import org.dromara.hutool.http.multipart.UploadSetting; +import org.dromara.hutool.http.useragent.UserAgent; +import org.dromara.hutool.http.useragent.UserAgentUtil; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.Collection; + +/** + * 服务端请求对象,用于获取请求参数等 + * + * @author looly + * @since 6.0.0 + */ +public interface ServerRequest { + + /** + * 默认编码,用于获取请求头和响应头编码,默认为UTF-8 + */ + Charset DEFAULT_CHARSET = CharsetUtil.UTF_8; + + // region ----- method + + /** + * 获取请求方法 + * + * @return 请求方法 + */ + String getMethod(); + + /** + * 是否为GET请求 + * + * @return 是否为GET请求 + */ + default boolean isGetMethod() { + return Method.GET.name().equalsIgnoreCase(getMethod()); + } + + /** + * 是否为POST请求 + * + * @return 是否为POST请求 + */ + default boolean isPostMethod() { + return Method.POST.name().equalsIgnoreCase(getMethod()); + } + // endregion + + /** + * 获取请求路径,包含请求参数部分 + * + * @return 请求路径,包含请求参数部分 + */ + String getPath(); + + /** + * 获取请求参数,包括pathVariable和queryString + * + * @return 请求参数,包括pathVariable和queryString + */ + String getQuery(); + + // region ----- header + + /** + * 获取请求头 + * + * @param name 请求头名 + * @return 请求头 + */ + String getHeader(final String name); + + /** + * 获得请求header中的信息 + * + * @param headerNameKey 头信息的KEY + * @return header值 + */ + default String getHeader(final HeaderName headerNameKey) { + return getHeader(headerNameKey.toString()); + } + + /** + * 获得请求header中的信息 + * + * @param headerKey 头信息的KEY + * @param charset 字符集 + * @return header值 + */ + default String getHeader(final String headerKey, final Charset charset) { + final String header = getHeader(headerKey); + if (null != header) { + return CharsetUtil.convert(header, CharsetUtil.ISO_8859_1, charset); + } + return null; + } + + /** + * 获取Content-Type头信息 + * + * @return Content-Type头信息 + */ + default String getContentType() { + return getHeader(HeaderName.CONTENT_TYPE); + } + + /** + * 获取Content-Length头信息,单位:字节 + * + * @return Content-Length头信息,单位:字节 + */ + default long getContentLength() { + final String contentLength = getHeader(HeaderName.CONTENT_LENGTH); + return StrUtil.isEmpty(contentLength) ? -1 : Long.parseLong(contentLength); + } + + /** + * 获取编码,获取失败默认使用UTF-8,获取规则如下: + * + *
+	 *     1、从Content-Type头中获取编码,类似于:text/html;charset=utf-8
+	 * 
+ * + * @return 编码,默认UTF-8 + */ + default Charset getCharset() { + final String contentType = getContentType(); + return ObjUtil.defaultIfNull(ContentTypeUtil.getCharset(contentType), DEFAULT_CHARSET); + } + + /** + * 获得User-Agent + * + * @return User-Agent字符串 + */ + default String getUserAgentStr() { + return getHeader(HeaderName.USER_AGENT); + } + + /** + * 获得User-Agent,未识别返回null + * + * @return User-Agent字符串,未识别返回null + */ + default UserAgent getUserAgent() { + return UserAgentUtil.parse(getUserAgentStr()); + } + + /** + * 获得Cookie信息字符串 + * + * @return cookie字符串 + */ + default String getCookiesStr() { + return getHeader(HeaderName.COOKIE); + } + + /** + * 是否为Multipart类型表单,此类型表单用于文件上传 + * + * @return 是否为Multipart类型表单,此类型表单用于文件上传 + */ + default boolean isMultipart() { + if (!isPostMethod()) { + return false; + } + + final String contentType = getContentType(); + if (StrUtil.isBlank(contentType)) { + return false; + } + return contentType.toLowerCase().startsWith("multipart/"); + } + // endregion + + // region ----- body + + /** + * 获取请求体流 + * + * @return 请求体流 + */ + InputStream getBodyStream(); + + /** + * 获取请求体文本,可以是form表单、json、xml等任意内容
+ * 使用{@link #getCharset()}判断编码,判断失败使用UTF-8编码 + * + * @return 请求 + */ + default String getBody() { + return getBody(getCharset()); + } + + /** + * 获取请求体文本,可以是form表单、json、xml等任意内容 + * + * @param charset 编码 + * @return 请求 + */ + default String getBody(final Charset charset) { + return StrUtil.str(getBodyBytes(), charset); + } + + /** + * 获取body的bytes数组 + * + * @return body的bytes数组 + */ + default byte[] getBodyBytes() { + return IoUtil.readBytes(getBodyStream(), true); + } + + /** + * 获得MultiPart表单内容,多用于获得上传的文件 + * + * @return MultipartFormData + * @throws IORuntimeException IO异常 + * @since 5.3.0 + */ + default MultipartFormData getMultipart() throws IORuntimeException { + return parseMultipart(new UploadSetting()); + } + + /** + * 获得multipart/form-data 表单内容
+ * 包括文件和普通表单数据
+ * 在同一次请求中,此方法只能被执行一次! + * + * @param uploadSetting 上传文件的设定,包括最大文件大小、保存在内存的边界大小、临时目录、扩展名限定等 + * @return MultiPart表单 + * @throws IORuntimeException IO异常 + * @since 5.3.0 + */ + default MultipartFormData parseMultipart(final UploadSetting uploadSetting) throws IORuntimeException { + final MultipartFormData formData = new MultipartFormData(uploadSetting); + try { + formData.parseRequestStream(getBodyStream(), getCharset()); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + + return formData; + } + // endregion + + // region ----- param + /** + * 获取指定名称的参数值,取第一个值 + * @param name 参数名 + * @return 参数值 + * @since 5.5.8 + */ + default String getParam(final String name){ + return getParams().getValue(name, 0); + } + + /** + * 获取指定名称的参数值 + * + * @param name 参数名 + * @return 参数值 + * @since 5.5.8 + */ + default Collection getParams(final String name){ + return getParams().get(name); + } + + /** + * 获取参数Map + * + * @return 参数map + */ + default ListValueMap getParams() { + final ListValueMap params = new ListValueMap<>(); + final Charset charset = getCharset(); + + //解析URL中的参数 + final String query = getQuery(); + if(StrUtil.isNotBlank(query)){ + params.putAll(UrlQueryUtil.decodeQueryList(query, charset)); + } + + // 解析multipart中的参数 + if(isMultipart()){ + params.putAll(getMultipart().getParamListMap()); + } else{ + // 解析body中的参数 + final String body = getBody(); + if(StrUtil.isNotBlank(body)){ + params.putAll(UrlQueryUtil.decodeQueryList(body, charset)); + } + } + return params; + } + // endregion +} diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/ServerResponse.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/ServerResponse.java new file mode 100644 index 000000000..5522b7d22 --- /dev/null +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/ServerResponse.java @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.http.server; + +import org.dromara.hutool.core.io.IoUtil; +import org.dromara.hutool.core.io.file.FileUtil; +import org.dromara.hutool.core.net.url.UrlEncoder; +import org.dromara.hutool.core.text.StrUtil; +import org.dromara.hutool.core.util.ByteUtil; +import org.dromara.hutool.core.util.CharsetUtil; +import org.dromara.hutool.core.util.ObjUtil; +import org.dromara.hutool.http.meta.ContentType; +import org.dromara.hutool.http.meta.HeaderName; + +import java.io.*; +import java.nio.charset.Charset; +import java.util.List; + +/** + * 服务端响应接口,用于写出数据 + * + * @author Looly + */ +public interface ServerResponse { + + /** + * 默认编码,用于获取请求头和响应头编码,默认为UTF-8 + */ + Charset DEFAULT_CHARSET = CharsetUtil.UTF_8; + + /** + * 设置状态码 + * + * @param statusCode 状态码 + * @return this + */ + ServerResponse setStatus(int statusCode); + + /** + * 设置编码,默认为UTF-8 + * + * @param charset 编码 + * @return this + */ + ServerResponse setCharset(Charset charset); + + /** + * 获取编码,默认为UTF-8 + * + * @return 编码 + */ + Charset getCharset(); + + /** + * 添加响应头,如果已经存在,则追加 + * + * @param header 头key + * @param value 值 + * @return this + */ + ServerResponse addHeader(final String header, final String value); + + /** + * 设置响应头,如果已经存在,则覆盖 + * + * @param header 头key + * @param value 值 + * @return this + */ + ServerResponse setHeader(final String header, final String value); + + /** + * 设置响应头,如果已经存在,则覆盖 + * + * @param headerName 头key + * @param value 值 + * @return this + */ + default ServerResponse setHeader(final HeaderName headerName, final String value) { + return setHeader(headerName.getValue(), value); + } + + /** + * 设置响应头,如果已经存在,则覆盖 + * + * @param header 头key + * @param value 值列表,如果为空,删除该header + * @return this + */ + default ServerResponse setHeader(final String header, final List value) { + // 去除原有header + setHeader(header, (String) null); + if(null == value){ + return this; + } + + // 加入新的header + for (final String valueItem : value) { + addHeader(header, valueItem); + } + return this; + } + + /** + * 设置Content-Type头,类似于:text/html;charset=utf-8
+ * 如果用户传入的信息无charset信息,自动根据charset补充,charset设置见{@link #setCharset(Charset)} + * + * @param contentType Content-Type头内容 + * @return this + */ + default ServerResponse setContentType(String contentType) { + if (null != contentType) { + final Charset charset = getCharset(); + if (null != charset && !contentType.contains(";charset=")) { + contentType = ContentType.build(contentType, charset); + } + } + + return setHeader(HeaderName.CONTENT_TYPE, contentType); + } + + /** + * 设置Content-Length头,-1表示移除头 + * + * @param contentLength Content-Length头内容 + * @return this + */ + default ServerResponse setContentLength(final long contentLength) { + return setHeader(HeaderName.CONTENT_LENGTH, + contentLength < 0 ? null : String.valueOf(contentLength)); + } + + /** + * 获取输出流,用于写出数据 + * + * @return 输出流 + */ + OutputStream getOutputStream(); + + /** + * 获取响应数据流 + * + * @return 响应数据流 + */ + default PrintWriter getWriter() { + final Charset charset = ObjUtil.defaultIfNull(getCharset(), DEFAULT_CHARSET); + return new PrintWriter(new OutputStreamWriter(getOutputStream(), charset)); + } + + /** + * 写出数据到客户端 + * + * @param data 数据 + * @param contentType Content-Type类型 + * @return this + */ + default ServerResponse write(final String data, final String contentType) { + setContentType(contentType); + return write(data); + } + + /** + * 写出数据到客户端 + * + * @param data 数据 + * @return this + */ + default ServerResponse write(final String data) { + final Charset charset = ObjUtil.defaultIfNull(getCharset(), DEFAULT_CHARSET); + return write(ByteUtil.toBytes(data, charset)); + } + + /** + * 写出数据到客户端 + * + * @param data 数据 + * @param contentType 返回的类型 + * @return this + */ + default ServerResponse write(final byte[] data, final String contentType) { + setContentType(contentType); + return write(data); + } + + /** + * 写出数据到客户端 + * + * @param data 数据 + * @return this + */ + default ServerResponse write(final byte[] data) { + final ByteArrayInputStream in = new ByteArrayInputStream(data); + return write(in, in.available()); + } + + /** + * 返回数据给客户端 + * + * @param in 需要返回客户端的内容 + * @param contentType 返回的类型 + * @return this + * @since 5.2.6 + */ + default ServerResponse write(final InputStream in, final String contentType) { + return write(in, 0, contentType); + } + + /** + * 返回数据给客户端 + * + * @param in 需要返回客户端的内容 + * @param length 内容长度,默认0表示不定长度,会输出Transfer-encoding: chunked + * @param contentType 返回的类型 + * @return this + * @since 5.2.7 + */ + default ServerResponse write(final InputStream in, final int length, final String contentType) { + setContentType(contentType); + return write(in, length); + } + + /** + * 写出数据到客户端 + * + * @param in 数据流 + * @return this + */ + default ServerResponse write(final InputStream in) { + return write(in, 0); + } + + /** + * 写出数据到客户端 + * + * @param in 数据流 + * @param length 指定响应内容长度,默认0表示不定长度,会输出Transfer-encoding: chunked + * @return this + */ + default ServerResponse write(final InputStream in, final int length) { + setContentLength(length); + OutputStream out = null; + try { + out = getOutputStream(); + IoUtil.copy(in, out); + } finally { + IoUtil.closeQuietly(out); + IoUtil.closeQuietly(in); + } + return this; + } + + /** + * 返回文件给客户端(文件下载) + * + * @param file 写出的文件对象 + * @return this + * @since 5.2.6 + */ + default ServerResponse write(final File file) { + return write(file, null); + } + + /** + * 返回文件给客户端(文件下载) + * + * @param file 写出的文件对象 + * @param fileName 文件名 + * @return this + */ + default ServerResponse write(final File file, String fileName) { + final long fileSize = file.length(); + if (fileSize > Integer.MAX_VALUE) { + throw new IllegalArgumentException("File size is too bigger than " + Integer.MAX_VALUE); + } + + if (StrUtil.isBlank(fileName)) { + fileName = file.getName(); + } + final String contentType = FileUtil.getMimeType(fileName, ContentType.OCTET_STREAM.getValue()); + BufferedInputStream in = null; + try { + in = FileUtil.getInputStream(file); + write(in, (int) fileSize, contentType, fileName); + } finally { + IoUtil.closeQuietly(in); + } + return this; + } + + /** + * 返回文件数据给客户端(文件下载) + * + * @param in 需要返回客户端的内容 + * @param contentType 返回的类型 + * @param fileName 文件名 + * @return this + */ + default ServerResponse write(final InputStream in, final String contentType, final String fileName) { + return write(in, 0, contentType, fileName); + } + + /** + * 返回文件数据给客户端(文件下载) + * + * @param in 需要返回客户端的内容 + * @param length 长度 + * @param contentType 返回的类型 + * @param fileName 文件名 + * @return this + */ + default ServerResponse write(final InputStream in, final int length, final String contentType, final String fileName) { + final Charset charset = ObjUtil.defaultIfNull(getCharset(), DEFAULT_CHARSET); + + if (!contentType.startsWith("text/")) { + // 非文本类型数据直接走下载 + setHeader(HeaderName.CONTENT_DISPOSITION, StrUtil.format("attachment;filename={}", UrlEncoder.encodeAll(fileName, charset))); + } + return write(in, length, contentType); + } +} diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/AbstractServerEngine.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/AbstractServerEngine.java new file mode 100644 index 000000000..4568cd0be --- /dev/null +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/AbstractServerEngine.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.http.server.engine; + +import org.dromara.hutool.http.server.ServerConfig; +import org.dromara.hutool.http.server.handler.HttpHandler; + +/** + * 服务端引擎抽象类,实现重置引擎功能 + * + * @author looly + */ +public abstract class AbstractServerEngine implements ServerEngine { + + protected ServerConfig config; + protected HttpHandler handler; + + @Override + public AbstractServerEngine init(final ServerConfig config) { + this.config = config; + reset(); + return this; + } + + @Override + public AbstractServerEngine setHandler(final HttpHandler handler) { + this.handler = handler; + return this; + } + + /** + * 重置引擎 + */ + protected abstract void reset(); + + /** + * 初始化引擎,实现逻辑中如果初始化完成,不再重新初始化 + */ + protected abstract void initEngine(); +} diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/ServerEngine.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/ServerEngine.java new file mode 100644 index 000000000..90c04191b --- /dev/null +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/ServerEngine.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.http.server.engine; + +import org.dromara.hutool.http.server.ServerConfig; +import org.dromara.hutool.http.server.handler.HttpHandler; + +/** + * HTTP服务器引擎 + * + * @author looly + * @since 6.0.0 + */ +public interface ServerEngine { + + /** + * 初始化HTTP服务器 + * + * @param config 配置项 + * @return this + */ + ServerEngine init(ServerConfig config); + + /** + * 设置请求处理器 + * + * @param handler 请求处理器 + * @return this + */ + ServerEngine setHandler(HttpHandler handler); + + /** + * 启动HTTP服务器 + */ + void start(); + + /** + * 获取原始引擎的钩子方法,用于自定义特殊属性,如插件等 + * + * @return 对应HTTP服务器实现的引擎对象 + * @since 6.0.0 + */ + Object getRawEngine(); +} diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/action/package-info.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/jetty/package-info.java similarity index 77% rename from hutool-http/src/main/java/org/dromara/hutool/http/server/action/package-info.java rename to hutool-http/src/main/java/org/dromara/hutool/http/server/engine/jetty/package-info.java index 68eb1ef52..8675fed66 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/server/action/package-info.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/jetty/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2024 Hutool Team and hutool.cn + * Copyright (c) 2024 Hutool Team and hutool.cn * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,8 @@ */ /** - * {@link com.sun.net.httpserver.HttpServer} 封装 + * Jetty引擎实现 * - * @author looly + * @author Looly */ -package org.dromara.hutool.http.server.action; +package org.dromara.hutool.http.server.engine.jetty; diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/package-info.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/package-info.java new file mode 100644 index 000000000..ca7898b93 --- /dev/null +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * HTTP服务器引擎包 + * + * @author Looly + * @since 6.0.0 + */ +package org.dromara.hutool.http.server.engine; diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/HttpExchangeWrapper.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/HttpExchangeWrapper.java similarity index 91% rename from hutool-http/src/main/java/org/dromara/hutool/http/server/HttpExchangeWrapper.java rename to hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/HttpExchangeWrapper.java index eb5383c57..d17f88fac 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/server/HttpExchangeWrapper.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/HttpExchangeWrapper.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.dromara.hutool.http.server; +package org.dromara.hutool.http.server.engine.sun; import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpContext; @@ -36,8 +36,8 @@ import java.net.URI; public class HttpExchangeWrapper extends HttpExchange implements Wrapper { private final HttpExchange raw; - private final HttpServerRequest request; - private final HttpServerResponse response; + private final SunServerRequest request; + private final SunServerResponse response; /** * 构造 @@ -46,8 +46,8 @@ public class HttpExchangeWrapper extends HttpExchange implements Wrapper filters; + private final SunHttpServerEngine engine; /** * 构造 @@ -71,39 +65,23 @@ public class SimpleServer { * @param address 监听地址 */ public SimpleServer(final InetSocketAddress address) { - this(address, (HttpsConfigurator) null); + this(address, null); } /** * 构造 * - * @param address 监听地址 + * @param address 监听地址 * @param sslContext ssl配置 */ public SimpleServer(final InetSocketAddress address, final SSLContext sslContext) { - this(address, new HttpsConfigurator(sslContext)); - } + this.engine = new SunHttpServerEngine(); - /** - * 构造 - * - * @param address 监听地址 - * @param configurator https配置信息,用于使用自定义SSL(TLS)证书等 - */ - public SimpleServer(final InetSocketAddress address, final HttpsConfigurator configurator) { - try { - if(null != configurator){ - final HttpsServer server = HttpsServer.create(address, 0); - server.setHttpsConfigurator(configurator); - this.server = server; - } else{ - this.server = HttpServer.create(address, 0); - } - } catch (final IOException e) { - throw new IORuntimeException(e); - } - setExecutor(GlobalThreadPool.getExecutor()); - filters = new ArrayList<>(); + final ServerConfig serverConfig = ServerConfig.of() + .setHost(address.getHostName()) + .setPort(address.getPort()) + .setSslContext(sslContext); + this.engine.init(serverConfig); } /** @@ -115,7 +93,6 @@ public class SimpleServer { *
  • {@link #setRoot(String)}
  • *
  • {@link #createContext(String, HttpHandler)}
  • *
  • {@link #addHandler(String, HttpHandler)}
  • - *
  • {@link #addAction(String, Action)}
  • * * * @param filter {@link Filter} 请求过滤器 @@ -123,7 +100,7 @@ public class SimpleServer { * @since 5.5.7 */ public SimpleServer addFilter(final Filter filter) { - this.filters.add(filter); + this.engine.addFilter(filter); return this; } @@ -136,7 +113,6 @@ public class SimpleServer { *
  • {@link #setRoot(String)}
  • *
  • {@link #createContext(String, HttpHandler)}
  • *
  • {@link #addHandler(String, HttpHandler)}
  • - *
  • {@link #addAction(String, Action)}
  • * * * @param filter {@link Filter} 请求过滤器 @@ -177,10 +153,22 @@ public class SimpleServer { public HttpContext createContext(String path, final HttpHandler handler) { // 非/开头的路径会报错 path = StrUtil.addPrefixIfNot(path, StrUtil.SLASH); - final HttpContext context = this.server.createContext(path, handler); - // 增加整体过滤器 - context.getFilters().addAll(this.filters); - return context; + return this.engine.createContext(path, handler); + } + + /** + * 增加请求处理规则,使用默认的{@link RootHandler},默认从当前项目根目录读取页面 + * + * @param path 路径,例如:/a/b 或者 a/b + * @param action 处理器,包括请求和响应处理 + * @return this + * @since 6.0.0 + */ + public SimpleServer addAction(final String path, final org.dromara.hutool.http.server.handler.HttpHandler action) { + return addHandler(path, exchange -> { + final HttpExchangeWrapper exchangeWrapper = new HttpExchangeWrapper(exchange); + action.handle(exchangeWrapper.getRequest(), exchangeWrapper.getResponse()); + }); } /** @@ -200,18 +188,8 @@ public class SimpleServer { * @return this */ public SimpleServer setRoot(final File root) { - return addAction("/", new RootAction(root)); - } - - /** - * 增加请求处理规则 - * - * @param path 路径 - * @param action 处理器 - * @return this - */ - public SimpleServer addAction(final String path, final Action action) { - return addHandler(path, new ActionHandler(action)); + this.engine.setHandler(new RootHandler(root)); + return this; } /** @@ -221,7 +199,7 @@ public class SimpleServer { * @return this */ public SimpleServer setExecutor(final Executor executor) { - this.server.setExecutor(executor); + this.engine.setExecutor(executor); return this; } @@ -231,7 +209,7 @@ public class SimpleServer { * @return {@link HttpServer} */ public HttpServer getRawServer() { - return this.server; + return this.engine.getRawEngine(); } /** @@ -240,7 +218,7 @@ public class SimpleServer { * @return {@link InetSocketAddress} */ public InetSocketAddress getAddress() { - return this.server.getAddress(); + return getRawServer().getAddress(); } /** @@ -249,6 +227,6 @@ public class SimpleServer { public void start() { final InetSocketAddress address = getAddress(); Console.log("Hutool Simple Http Server listen on 【{}:{}】", address.getHostName(), address.getPort()); - this.server.start(); + this.engine.start(); } } diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/SunHttpServerEngine.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/SunHttpServerEngine.java new file mode 100644 index 000000000..53ad8fdd9 --- /dev/null +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/SunHttpServerEngine.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.http.server.engine.sun; + +import com.sun.net.httpserver.*; +import org.dromara.hutool.core.io.IORuntimeException; +import org.dromara.hutool.core.text.StrUtil; +import org.dromara.hutool.core.thread.GlobalThreadPool; +import org.dromara.hutool.http.server.engine.AbstractServerEngine; +import org.dromara.hutool.http.server.ServerConfig; +import org.dromara.hutool.http.server.engine.sun.filter.HttpFilter; +import org.dromara.hutool.http.server.engine.sun.filter.SimpleFilter; + +import javax.net.ssl.SSLContext; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * 基于Sun HttpServer的HTTP服务器引擎实现 + * + * @author looly + * @since 6.0.0 + */ +public class SunHttpServerEngine extends AbstractServerEngine { + + private HttpServer server; + private List filters; + + /** + * 构造 + */ + public SunHttpServerEngine() { + } + + @Override + public void start() { + initEngine(); + this.server.start(); + } + + @Override + public HttpServer getRawEngine() { + return this.server; + } + + /** + * 增加请求过滤器,此过滤器对所有请求有效
    + * 此方法需在以下方法前之前调用: + * + *
      + *
    • {@link #createContext(String, HttpHandler)}
    • + *
    + * + * @param filter {@link Filter} 请求过滤器 + * @return this + * @since 5.5.7 + */ + public SunHttpServerEngine addFilter(final Filter filter) { + if (null == this.filters) { + this.filters = new ArrayList<>(); + } + this.filters.add(filter); + return this; + } + + /** + * 增加请求过滤器,此过滤器对所有请求有效
    + * 此方法需在以下方法前之前调用: + * + *
      + *
    • {@link #createContext(String, HttpHandler)}
    • + *
    + * + * @param filter {@link Filter} 请求过滤器 + * @return this + * @since 5.5.7 + */ + public SunHttpServerEngine addFilter(final HttpFilter filter) { + return addFilter(new SimpleFilter() { + @Override + public void doFilter(final HttpExchange httpExchange, final Chain chain) throws IOException { + final HttpExchangeWrapper httpExchangeWrapper = new HttpExchangeWrapper(httpExchange); + filter.doFilter(httpExchangeWrapper.getRequest(), httpExchangeWrapper.getResponse(), chain); + } + }); + } + + /** + * 创建请求映射上下文,创建后,用户访问指定路径可使用{@link HttpHandler} 中的规则进行处理 + * + * @param path 路径,例如:/a/b 或者 a/b + * @param handler 处理器,包括请求和响应处理 + * @return {@link HttpContext} + * @since 5.5.7 + */ + public HttpContext createContext(String path, final HttpHandler handler) { + // 非/开头的路径会报错 + path = StrUtil.addPrefixIfNot(path, StrUtil.SLASH); + final HttpContext context = this.server.createContext(path, handler); + // 增加整体过滤器 + context.getFilters().addAll(this.filters); + return context; + } + + /** + * 设置自定义线程池 + * + * @param executor {@link Executor} + * @return this + */ + public SunHttpServerEngine setExecutor(final Executor executor) { + this.server.setExecutor(executor); + return this; + } + + @Override + protected void reset() { + if (null != this.server) { + this.server.stop(0); + this.server = null; + } + } + + @Override + protected void initEngine() { + final ServerConfig config = this.config; + final InetSocketAddress address = new InetSocketAddress(config.getHost(), config.getPort()); + final SSLContext sslContext = config.getSslContext(); + try { + if (null != sslContext) { + final HttpsServer server = HttpsServer.create(address, 0); + server.setHttpsConfigurator(new HttpsConfigurator(sslContext)); + this.server = server; + } else { + this.server = HttpServer.create(address, 0); + } + } catch (final IOException e) { + throw new IORuntimeException(e); + } + setExecutor(GlobalThreadPool.getExecutor()); + createContext("/", exchange -> SunHttpServerEngine.this.handler.handle( + new SunServerRequest(exchange), + new SunServerResponse(exchange) + )); + } +} diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/HttpServerBase.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/SunServerBase.java similarity index 76% rename from hutool-http/src/main/java/org/dromara/hutool/http/server/HttpServerBase.java rename to hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/SunServerBase.java index 50f6275b9..98e5ab952 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/server/HttpServerBase.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/SunServerBase.java @@ -14,14 +14,12 @@ * limitations under the License. */ -package org.dromara.hutool.http.server; +package org.dromara.hutool.http.server.engine.sun; -import org.dromara.hutool.core.util.CharsetUtil; import com.sun.net.httpserver.HttpContext; import com.sun.net.httpserver.HttpExchange; import java.io.Closeable; -import java.nio.charset.Charset; /** * HttpServer公用对象,提供HttpExchange包装和公用方法 @@ -29,18 +27,16 @@ import java.nio.charset.Charset; * @author looly * @since 5.2.6 */ -public class HttpServerBase implements Closeable { +public class SunServerBase implements Closeable { - final static Charset DEFAULT_CHARSET = CharsetUtil.UTF_8; - - final HttpExchange httpExchange; + protected final HttpExchange httpExchange; /** * 构造 * * @param httpExchange {@link HttpExchange} */ - public HttpServerBase(final HttpExchange httpExchange) { + public SunServerBase(final HttpExchange httpExchange) { this.httpExchange = httpExchange; } @@ -49,7 +45,7 @@ public class HttpServerBase implements Closeable { * * @return {@link HttpExchange}对象 */ - public HttpExchange getHttpExchange() { + public HttpExchange getExchange() { return this.httpExchange; } @@ -60,7 +56,7 @@ public class HttpServerBase implements Closeable { * @since 5.5.7 */ public HttpContext getHttpContext() { - return getHttpExchange().getHttpContext(); + return getExchange().getHttpContext(); } /** diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/HttpServerRequest.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/SunServerRequest.java similarity index 55% rename from hutool-http/src/main/java/org/dromara/hutool/http/server/HttpServerRequest.java rename to hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/SunServerRequest.java index 1906dae7c..c9432d758 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/server/HttpServerRequest.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/SunServerRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2024 Hutool Team and hutool.cn + * Copyright (c) 2024 Hutool Team and hutool.cn * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.dromara.hutool.http.server; +package org.dromara.hutool.http.server.engine.sun; import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; @@ -26,19 +26,15 @@ import org.dromara.hutool.core.map.CaseInsensitiveMap; import org.dromara.hutool.core.map.MapUtil; import org.dromara.hutool.core.map.multi.ListValueMap; import org.dromara.hutool.core.net.NetUtil; -import org.dromara.hutool.http.multipart.MultipartFormData; -import org.dromara.hutool.http.multipart.UploadSetting; import org.dromara.hutool.core.net.url.UrlQueryUtil; import org.dromara.hutool.core.text.StrUtil; -import org.dromara.hutool.core.util.CharsetUtil; import org.dromara.hutool.core.util.ObjUtil; import org.dromara.hutool.http.meta.ContentTypeUtil; import org.dromara.hutool.http.meta.HeaderName; -import org.dromara.hutool.http.meta.Method; -import org.dromara.hutool.http.useragent.UserAgent; -import org.dromara.hutool.http.useragent.UserAgentUtil; +import org.dromara.hutool.http.multipart.MultipartFormData; +import org.dromara.hutool.http.multipart.UploadSetting; +import org.dromara.hutool.http.server.ServerRequest; -import java.io.IOException; import java.io.InputStream; import java.net.HttpCookie; import java.net.URI; @@ -47,12 +43,11 @@ import java.util.Collection; import java.util.Map; /** - * Http请求对象,对{@link HttpExchange}封装 + * Sun Http请求包装 * - * @author looly - * @since 5.2.6 + * @author Looly */ -public class HttpServerRequest extends HttpServerBase { +public class SunServerRequest extends SunServerBase implements ServerRequest { private Map cookieCache; private ListValueMap paramsCache; @@ -65,37 +60,15 @@ public class HttpServerRequest extends HttpServerBase { * * @param httpExchange {@link HttpExchange} */ - public HttpServerRequest(final HttpExchange httpExchange) { + public SunServerRequest(final HttpExchange httpExchange) { super(httpExchange); } - /** - * 获得Http Method - * - * @return Http Method - */ + @Override public String getMethod() { return this.httpExchange.getRequestMethod(); } - /** - * 是否为GET请求 - * - * @return 是否为GET请求 - */ - public boolean isGetMethod() { - return Method.GET.name().equalsIgnoreCase(getMethod()); - } - - /** - * 是否为POST请求 - * - * @return 是否为POST请求 - */ - public boolean isPostMethod() { - return Method.POST.name().equalsIgnoreCase(getMethod()); - } - /** * 获得请求URI * @@ -105,20 +78,12 @@ public class HttpServerRequest extends HttpServerBase { return this.httpExchange.getRequestURI(); } - /** - * 获得请求路径Path - * - * @return 请求路径 - */ + @Override public String getPath() { return getURI().getPath(); } - /** - * 获取请求参数 - * - * @return 参数字符串 - */ + @Override public String getQuery() { return getURI().getQuery(); } @@ -132,59 +97,12 @@ public class HttpServerRequest extends HttpServerBase { return this.httpExchange.getRequestHeaders(); } - /** - * 获得请求header中的信息 - * - * @param headerNameKey 头信息的KEY - * @return header值 - */ - public String getHeader(final HeaderName headerNameKey) { - return getHeader(headerNameKey.toString()); + @Override + public String getHeader(final String name) { + return getHeaders().getFirst(name); } - /** - * 获得请求header中的信息 - * - * @param headerKey 头信息的KEY - * @return header值 - */ - public String getHeader(final String headerKey) { - return getHeaders().getFirst(headerKey); - } - - /** - * 获得请求header中的信息 - * - * @param headerKey 头信息的KEY - * @param charset 字符集 - * @return header值 - */ - public String getHeader(final String headerKey, final Charset charset) { - final String header = getHeader(headerKey); - if (null != header) { - return CharsetUtil.convert(header, CharsetUtil.ISO_8859_1, charset); - } - return null; - } - - /** - * 获取Content-Type头信息 - * - * @return Content-Type头信息 - */ - public String getContentType() { - return getHeader(HeaderName.CONTENT_TYPE); - } - - /** - * 获取编码,获取失败默认使用UTF-8,获取规则如下: - * - *
    -	 *     1、从Content-Type头中获取编码,类似于:text/html;charset=utf-8
    -	 * 
    - * - * @return 编码,默认UTF-8 - */ + @Override public Charset getCharset() { if(null == this.charsetCache){ final String contentType = getContentType(); @@ -194,33 +112,6 @@ public class HttpServerRequest extends HttpServerBase { return this.charsetCache; } - /** - * 获得User-Agent - * - * @return User-Agent字符串 - */ - public String getUserAgentStr() { - return getHeader(HeaderName.USER_AGENT); - } - - /** - * 获得User-Agent,未识别返回null - * - * @return User-Agent字符串,未识别返回null - */ - public UserAgent getUserAgent() { - return UserAgentUtil.parse(getUserAgentStr()); - } - - /** - * 获得Cookie信息字符串 - * - * @return cookie字符串 - */ - public String getCookiesStr() { - return getHeader(HeaderName.COOKIE); - } - /** * 获得Cookie信息列表 * @@ -238,9 +129,9 @@ public class HttpServerRequest extends HttpServerBase { public Map getCookieMap() { if (null == this.cookieCache) { cookieCache = MapUtil.view(MapUtil.putAll( - new CaseInsensitiveMap<>(), - NetUtil.parseCookies(getCookiesStr()), - HttpCookie::getName)); + new CaseInsensitiveMap<>(), + NetUtil.parseCookies(getCookiesStr()), + HttpCookie::getName)); } return cookieCache; } @@ -255,60 +146,7 @@ public class HttpServerRequest extends HttpServerBase { return getCookieMap().get(cookieName); } - /** - * 是否为Multipart类型表单,此类型表单用于文件上传 - * - * @return 是否为Multipart类型表单,此类型表单用于文件上传 - */ - public boolean isMultipart() { - if (!isPostMethod()) { - return false; - } - - final String contentType = getContentType(); - if (StrUtil.isBlank(contentType)) { - return false; - } - return contentType.toLowerCase().startsWith("multipart/"); - } - - /** - * 获取请求体文本,可以是form表单、json、xml等任意内容
    - * 使用{@link #getCharset()}判断编码,判断失败使用UTF-8编码 - * - * @return 请求 - */ - public String getBody() { - return getBody(getCharset()); - } - - /** - * 获取请求体文本,可以是form表单、json、xml等任意内容 - * - * @param charset 编码 - * @return 请求 - */ - public String getBody(final Charset charset) { - return StrUtil.str(getBodyBytes(), charset); - } - - /** - * 获取body的bytes数组 - * - * @return body的bytes数组 - */ - public byte[] getBodyBytes(){ - if(null == this.bodyCache){ - this.bodyCache = IoUtil.readBytes(getBodyStream(), true); - } - return this.bodyCache; - } - - /** - * 获取请求体的流,流中可以读取请求内容,包括请求表单数据或文件上传数据 - * - * @return 流 - */ + @Override public InputStream getBodyStream() { InputStream bodyStream = this.httpExchange.getRequestBody(); @@ -330,32 +168,23 @@ public class HttpServerRequest extends HttpServerBase { return bodyStream; } - /** - * 获取指定名称的参数值,取第一个值 - * @param name 参数名 - * @return 参数值 - * @since 5.5.8 - */ - public String getParam(final String name){ - return getParams().getValue(name, 0); + @Override + public byte[] getBodyBytes(){ + if(null == this.bodyCache){ + this.bodyCache = IoUtil.readBytes(getBodyStream(), true); + } + return this.bodyCache; } - /** - * 获取指定名称的参数值 - * - * @param name 参数名 - * @return 参数值 - * @since 5.5.8 - */ - public Collection getParams(final String name){ - return getParams().get(name); + @Override + public MultipartFormData getMultipart() throws IORuntimeException { + if(null == this.multipartFormDataCache){ + this.multipartFormDataCache = parseMultipart(new UploadSetting()); + } + return this.multipartFormDataCache; } - /** - * 获取参数Map - * - * @return 参数map - */ + @Override public ListValueMap getParams() { if (null == this.paramsCache) { this.paramsCache = new ListValueMap<>(); @@ -436,39 +265,4 @@ public class HttpServerRequest extends HttpServerBase { ip = this.httpExchange.getRemoteAddress().getHostName(); return NetUtil.getMultistageReverseProxyIp(ip); } - - /** - * 获得MultiPart表单内容,多用于获得上传的文件 - * - * @return MultipartFormData - * @throws IORuntimeException IO异常 - * @since 5.3.0 - */ - public MultipartFormData getMultipart() throws IORuntimeException { - if(null == this.multipartFormDataCache){ - this.multipartFormDataCache = parseMultipart(new UploadSetting()); - } - return this.multipartFormDataCache; - } - - /** - * 获得multipart/form-data 表单内容
    - * 包括文件和普通表单数据
    - * 在同一次请求中,此方法只能被执行一次! - * - * @param uploadSetting 上传文件的设定,包括最大文件大小、保存在内存的边界大小、临时目录、扩展名限定等 - * @return MultiPart表单 - * @throws IORuntimeException IO异常 - * @since 5.3.0 - */ - public MultipartFormData parseMultipart(final UploadSetting uploadSetting) throws IORuntimeException { - final MultipartFormData formData = new MultipartFormData(uploadSetting); - try { - formData.parseRequestStream(getBodyStream(), getCharset()); - } catch (final IOException e) { - throw new IORuntimeException(e); - } - - return formData; - } } diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/SunServerResponse.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/SunServerResponse.java new file mode 100644 index 000000000..acec7524a --- /dev/null +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/SunServerResponse.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.http.server.engine.sun; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpExchange; +import org.dromara.hutool.core.io.IORuntimeException; +import org.dromara.hutool.core.io.IoUtil; +import org.dromara.hutool.http.meta.ContentType; +import org.dromara.hutool.http.meta.HttpStatus; +import org.dromara.hutool.http.server.ServerResponse; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; + +/** + * SunHttp服务器响应对象 + * + * @author looly + * @since 6.0.0 + */ +public class SunServerResponse extends SunServerBase implements ServerResponse { + + private Charset charset; + /** + * 是否已经发送了Http状态码,如果没有,提前写出状态码 + */ + private boolean isSendCode; + + /** + * 构造 + * + * @param httpExchange {@link HttpExchange} + */ + public SunServerResponse(final HttpExchange httpExchange) { + super(httpExchange); + } + + @Override + public SunServerResponse setStatus(final int statusCode) { + return send(statusCode, 0); + } + + /** + * 发送成功状态码 + * + * @return this + */ + public SunServerResponse sendOk() { + return setStatus(HttpStatus.HTTP_OK); + } + + /** + * 发送成功状态码 + * + * @param bodyLength 响应体长度,默认0表示不定长度,会输出Transfer-encoding: chunked + * @return this + * @since 5.5.7 + */ + public SunServerResponse sendOk(final int bodyLength) { + return send(HttpStatus.HTTP_OK, bodyLength); + } + + /** + * 发送404错误页 + * + * @param content 错误页页面内容,默认text/html类型 + * @return this + */ + public SunServerResponse send404(final String content) { + return sendError(HttpStatus.HTTP_NOT_FOUND, content); + } + + /** + * 发送错误页 + * + * @param errorCode HTTP错误状态码,见HttpStatus + * @param content 错误页页面内容,默认text/html类型 + * @return this + */ + @SuppressWarnings("resource") + public SunServerResponse sendError(final int errorCode, final String content) { + setStatus(errorCode); + setContentType(ContentType.TEXT_HTML.toString()); + write(content); + return this; + } + + /** + * 发送HTTP状态码 + * + * @param httpStatusCode HTTP状态码,见HttpStatus + * @param bodyLength 响应体长度,默认0表示不定长度,会输出Transfer-encoding: chunked + * @return this + */ + public SunServerResponse send(final int httpStatusCode, final long bodyLength) { + if (this.isSendCode) { + throw new IORuntimeException("Http status code has been send!"); + } + + try { + this.httpExchange.sendResponseHeaders(httpStatusCode, bodyLength); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + + this.isSendCode = true; + return this; + } + + @Override + public SunServerResponse setCharset(final Charset charset) { + this.charset = charset; + return this; + } + + @Override + public Charset getCharset() { + return this.charset; + } + + /** + * 获得所有响应头,获取后可以添加新的响应头 + * + * @return 响应头 + */ + public Headers getHeaders() { + return this.httpExchange.getResponseHeaders(); + } + + @Override + public SunServerResponse addHeader(final String header, final String value) { + getHeaders().add(header, value); + return this; + } + + @Override + public SunServerResponse setHeader(final String header, final String value) { + getHeaders().set(header, value); + return this; + } + + /** + * 设置响应头,如果已经存在,则覆盖 + * + * @param header 头key + * @param value 值列表 + * @return this + */ + public SunServerResponse setHeader(final String header, final List value) { + getHeaders().put(header, value); + return this; + } + + /** + * 设置所有响应头,如果已经存在,则覆盖 + * + * @param headers 响应头map + * @return this + */ + public SunServerResponse setHeaders(final Map> headers) { + getHeaders().putAll(headers); + return this; + } + + /** + * 设置属性 + * + * @param name 属性名 + * @param value 属性值 + * @return this + */ + public SunServerResponse setAttr(final String name, final Object value) { + this.httpExchange.setAttribute(name, value); + return this; + } + + @SuppressWarnings("resource") + @Override + public OutputStream getOutputStream() { + if (!this.isSendCode) { + sendOk(); + } + return this.httpExchange.getResponseBody(); + } + + /** + * 写出数据到客户端 + * + * @param in 数据流 + * @param length 指定响应内容长度,默认0表示不定长度,会输出Transfer-encoding: chunked + * @return this + */ + @SuppressWarnings("resource") + public SunServerResponse write(final InputStream in, final int length) { + if (!isSendCode) { + sendOk(Math.max(0, length)); + } + OutputStream out = null; + try { + out = this.httpExchange.getResponseBody(); + IoUtil.copy(in, out); + } finally { + IoUtil.closeQuietly(out); + IoUtil.closeQuietly(in); + } + return this; + } +} diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/filter/DefaultExceptionFilter.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/filter/DefaultExceptionFilter.java similarity index 82% rename from hutool-http/src/main/java/org/dromara/hutool/http/server/filter/DefaultExceptionFilter.java rename to hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/filter/DefaultExceptionFilter.java index 0fd9f74f9..02e1d3eb0 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/server/filter/DefaultExceptionFilter.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/filter/DefaultExceptionFilter.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package org.dromara.hutool.http.server.filter; +package org.dromara.hutool.http.server.engine.sun.filter; import org.dromara.hutool.core.exception.ExceptionUtil; import org.dromara.hutool.core.text.StrUtil; -import org.dromara.hutool.http.server.HttpServerRequest; -import org.dromara.hutool.http.server.HttpServerResponse; +import org.dromara.hutool.http.server.engine.sun.SunServerRequest; +import org.dromara.hutool.http.server.engine.sun.SunServerResponse; /** * 默认异常处理拦截器 @@ -31,7 +31,7 @@ public class DefaultExceptionFilter extends ExceptionFilter{ private final static String TEMPLATE_ERROR = "Hutool - Error report

    HTTP Status {} - {}


    {}


    Hutool

    "; @Override - public void afterException(final HttpServerRequest req, final HttpServerResponse res, final Throwable e) { + public void afterException(final SunServerRequest req, final SunServerResponse res, final Throwable e) { String content = ExceptionUtil.stacktraceToString(e); content = content.replace("\n", "
    \n"); content = StrUtil.format(TEMPLATE_ERROR, 500, req.getURI(), content); diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/filter/ExceptionFilter.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/filter/ExceptionFilter.java similarity index 63% rename from hutool-http/src/main/java/org/dromara/hutool/http/server/filter/ExceptionFilter.java rename to hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/filter/ExceptionFilter.java index d4a71aa56..967a9ba6a 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/server/filter/ExceptionFilter.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/filter/ExceptionFilter.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package org.dromara.hutool.http.server.filter; +package org.dromara.hutool.http.server.engine.sun.filter; import com.sun.net.httpserver.Filter; -import org.dromara.hutool.http.server.HttpServerRequest; -import org.dromara.hutool.http.server.HttpServerResponse; +import org.dromara.hutool.http.server.engine.sun.SunServerRequest; +import org.dromara.hutool.http.server.engine.sun.SunServerResponse; /** * 异常处理过滤器 @@ -28,9 +28,9 @@ import org.dromara.hutool.http.server.HttpServerResponse; public abstract class ExceptionFilter implements HttpFilter { @Override - public void doFilter(final HttpServerRequest req, final HttpServerResponse res, final Filter.Chain chain) { + public void doFilter(final SunServerRequest req, final SunServerResponse res, final Filter.Chain chain) { try { - chain.doFilter(req.getHttpExchange()); + chain.doFilter(req.getExchange()); } catch (final Throwable e) { afterException(req, res, e); } @@ -39,9 +39,9 @@ public abstract class ExceptionFilter implements HttpFilter { /** * 异常之后的处理逻辑 * - * @param req {@link HttpServerRequest} - * @param res {@link HttpServerResponse} + * @param req {@link SunServerRequest} + * @param res {@link SunServerResponse} * @param e 异常 */ - public abstract void afterException(final HttpServerRequest req, final HttpServerResponse res, final Throwable e); + public abstract void afterException(final SunServerRequest req, final SunServerResponse res, final Throwable e); } diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/filter/HttpFilter.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/filter/HttpFilter.java similarity index 67% rename from hutool-http/src/main/java/org/dromara/hutool/http/server/filter/HttpFilter.java rename to hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/filter/HttpFilter.java index b72cf2640..f2b19b490 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/server/filter/HttpFilter.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/filter/HttpFilter.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package org.dromara.hutool.http.server.filter; +package org.dromara.hutool.http.server.engine.sun.filter; -import org.dromara.hutool.http.server.HttpServerRequest; -import org.dromara.hutool.http.server.HttpServerResponse; import com.sun.net.httpserver.Filter; +import org.dromara.hutool.http.server.engine.sun.SunServerRequest; +import org.dromara.hutool.http.server.engine.sun.SunServerResponse; import java.io.IOException; @@ -33,10 +33,10 @@ public interface HttpFilter { /** * 执行过滤 - * @param req {@link HttpServerRequest} 请求对象,用于获取请求内容 - * @param res {@link HttpServerResponse} 响应对象,用于写出内容 + * @param req {@link SunServerRequest} 请求对象,用于获取请求内容 + * @param res {@link SunServerResponse} 响应对象,用于写出内容 * @param chain {@link Filter.Chain} * @throws IOException IO异常 */ - void doFilter(HttpServerRequest req, HttpServerResponse res, Filter.Chain chain) throws IOException; + void doFilter(SunServerRequest req, SunServerResponse res, Filter.Chain chain) throws IOException; } diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/filter/SimpleFilter.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/filter/SimpleFilter.java similarity index 93% rename from hutool-http/src/main/java/org/dromara/hutool/http/server/filter/SimpleFilter.java rename to hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/filter/SimpleFilter.java index 4227e94f4..8d3397944 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/server/filter/SimpleFilter.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/filter/SimpleFilter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.dromara.hutool.http.server.filter; +package org.dromara.hutool.http.server.engine.sun.filter; import com.sun.net.httpserver.Filter; diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/filter/package-info.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/filter/package-info.java similarity index 92% rename from hutool-http/src/main/java/org/dromara/hutool/http/server/filter/package-info.java rename to hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/filter/package-info.java index 843e34056..6b7878427 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/server/filter/package-info.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/filter/package-info.java @@ -17,4 +17,4 @@ /** * {@link com.sun.net.httpserver.Filter} 实现包装 */ -package org.dromara.hutool.http.server.filter; +package org.dromara.hutool.http.server.engine.sun.filter; diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/package-info.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/package-info.java new file mode 100644 index 000000000..e7ed11d51 --- /dev/null +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * {@link com.sun.net.httpserver.HttpServer}引擎实现包 + * + * @author Looly + */ +package org.dromara.hutool.http.server.engine.sun; diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/tomcat/package-info.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/tomcat/package-info.java new file mode 100644 index 000000000..09d7d4963 --- /dev/null +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/tomcat/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Tomcat引擎实现 + * + * @author Looly + */ +package org.dromara.hutool.http.server.engine.tomcat; diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/undertow/UndertowEngine.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/undertow/UndertowEngine.java new file mode 100644 index 000000000..66ee18676 --- /dev/null +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/undertow/UndertowEngine.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.http.server.engine.undertow; + +import io.undertow.Undertow; +import org.dromara.hutool.core.lang.Assert; +import org.dromara.hutool.http.server.engine.AbstractServerEngine; + +import javax.net.ssl.SSLContext; + +/** + * Undertow引擎实现 + * + * @author looly + */ +public class UndertowEngine extends AbstractServerEngine { + + private Undertow undertow; + + /** + * 构造 + */ + public UndertowEngine() { + // issue#IABWBL JDK8下,在IDEA旗舰版加载Spring boot插件时,启动应用不会检查字段类是否存在 + // 此处构造时调用下这个类,以便触发类是否存在的检查 + Assert.notNull(Undertow.class); + } + + @Override + public void start() { + initEngine(); + undertow.start(); + } + + @Override + public Undertow getRawEngine() { + return this.undertow; + } + + @Override + protected void reset() { + if(null != this.undertow){ + this.undertow.stop(); + this.undertow = null; + } + } + + @Override + protected void initEngine() { + if (null != this.undertow) { + return; + } + final Undertow.Builder builder = Undertow.builder() + .setHandler(exchange -> { + this.handler.handle( + new UndertowRequest(exchange), + new UndertowResponse(exchange)); + }); + + final SSLContext sslContext = this.config.getSslContext(); + if(null != sslContext){ + builder.addHttpsListener(this.config.getPort(), this.config.getHost(), sslContext); + }else{ + builder.addHttpListener(this.config.getPort(), this.config.getHost()); + } + this.undertow = builder.build(); + } +} diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/undertow/UndertowExchangeBase.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/undertow/UndertowExchangeBase.java new file mode 100644 index 000000000..2490b37ea --- /dev/null +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/undertow/UndertowExchangeBase.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.http.server.engine.undertow; + +import io.undertow.server.HttpServerExchange; + +/** + * Undertow请求对象基类,用于获取原始Undertow请求对象 + * + * @author looly + * @since 6.0.0 + */ +public class UndertowExchangeBase { + + final HttpServerExchange exchange; + + /** + * 构造 + * + * @param exchange Undertow请求对象 + */ + public UndertowExchangeBase(final HttpServerExchange exchange) { + this.exchange = exchange; + } + + /** + * 获取原始Undertow请求对象 + * + * @return 原始Undertow请求对象 + */ + public HttpServerExchange getExchange(){ + return this.exchange; + } +} diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/undertow/UndertowRequest.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/undertow/UndertowRequest.java new file mode 100644 index 000000000..88241be4e --- /dev/null +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/undertow/UndertowRequest.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.http.server.engine.undertow; + +import io.undertow.server.HttpServerExchange; +import io.undertow.util.HeaderMap; +import org.dromara.hutool.core.util.CharsetUtil; +import org.dromara.hutool.http.server.ServerRequest; + +import java.io.InputStream; +import java.nio.charset.Charset; + +/** + * Undertow请求对象 + * + * @author looly + * @since 6.0.0 + */ +public class UndertowRequest extends UndertowExchangeBase implements ServerRequest { + + /** + * 构造 + * + * @param exchange Undertow请求对象 + */ + public UndertowRequest(final HttpServerExchange exchange) { + super(exchange); + } + + @Override + public String getMethod() { + return this.exchange.getRequestMethod().toString(); + } + + @Override + public String getPath() { + return this.exchange.getRelativePath(); + } + + @Override + public String getQuery() { + return this.exchange.getQueryString(); + } + + @Override + public String getHeader(final String name) { + return this.exchange.getRequestHeaders().getFirst(name); + } + + /** + * 获取所有请求头 + * + * @return 请求头 + */ + public HeaderMap getHeaders() { + return this.exchange.getRequestHeaders(); + } + + @Override + public Charset getCharset() { + return CharsetUtil.charset(this.exchange.getRequestCharset()); + } + + @Override + public InputStream getBodyStream() { + return this.exchange.getInputStream(); + } +} diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/undertow/UndertowResponse.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/undertow/UndertowResponse.java new file mode 100644 index 000000000..4c660b8d7 --- /dev/null +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/undertow/UndertowResponse.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.http.server.engine.undertow; + +import io.undertow.io.Sender; +import io.undertow.server.HttpServerExchange; +import io.undertow.util.HttpString; +import org.dromara.hutool.http.server.ServerResponse; + +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.List; + +/** + * Undertow响应对象 + * + * @author looly + * @since 6.0.0 + */ +public class UndertowResponse extends UndertowExchangeBase implements ServerResponse { + + private Charset charset; + + /** + * 构造 + * + * @param exchange Undertow exchange + */ + public UndertowResponse(final HttpServerExchange exchange) { + super(exchange); + this.charset = DEFAULT_CHARSET; + } + + /** + * 获取原始的HttpServerExchange对象 + * + * @return HttpServerExchange对象 + */ + public HttpServerExchange getExchange() { + return exchange; + } + + @Override + public ServerResponse setStatus(final int statusCode) { + this.exchange.setStatusCode(statusCode); + return this; + } + + @Override + public ServerResponse setCharset(final Charset charset) { + this.charset = charset; + return this; + } + + @Override + public Charset getCharset() { + return this.charset; + } + + @Override + public ServerResponse addHeader(final String header, final String value) { + this.exchange.getResponseHeaders().add(new HttpString(header), value); + return this; + } + + @Override + public ServerResponse setHeader(final String header, final String value) { + this.exchange.getResponseHeaders().put(new HttpString(header), value); + return this; + } + + @Override + public ServerResponse setHeader(final String header, final List value) { + this.exchange.getResponseHeaders().putAll(new HttpString(header), value); + return this; + } + + /** + * 获取Sender对象,用于发送数据 + * + * @return Sender对象 + */ + public Sender getSender() { + return this.exchange.getResponseSender(); + } + + @Override + public OutputStream getOutputStream() { + return this.exchange.getOutputStream(); + } + + @Override + public ServerResponse write(final byte[] data) { + getSender().send(ByteBuffer.wrap(data)); + return this; + } +} diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/undertow/package-info.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/undertow/package-info.java new file mode 100644 index 000000000..73038ce8c --- /dev/null +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/undertow/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Undertow引擎实现 + * + * @author Looly + * @since 6.0.0 + */ +package org.dromara.hutool.http.server.engine.undertow; diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/handler/ActionHandler.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/handler/ActionHandler.java deleted file mode 100644 index 5e1dbe398..000000000 --- a/hutool-http/src/main/java/org/dromara/hutool/http/server/handler/ActionHandler.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2013-2024 Hutool Team and hutool.cn - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.dromara.hutool.http.server.handler; - -import org.dromara.hutool.http.server.HttpExchangeWrapper; -import org.dromara.hutool.http.server.HttpServerRequest; -import org.dromara.hutool.http.server.HttpServerResponse; -import org.dromara.hutool.http.server.action.Action; -import com.sun.net.httpserver.HttpExchange; -import com.sun.net.httpserver.HttpHandler; - -import java.io.IOException; - -/** - * Action处理器,用于将HttpHandler转换为Action形式 - * - * @author looly - * @since 5.2.6 - */ -public class ActionHandler implements HttpHandler { - - private final Action action; - - /** - * 构造 - * - * @param action Action - */ - public ActionHandler(final Action action) { - this.action = action; - } - - @Override - public void handle(final HttpExchange httpExchange) throws IOException { - final HttpServerRequest request; - final HttpServerResponse response; - if (httpExchange instanceof HttpExchangeWrapper) { - // issue#3343 当使用Filter时,可能读取了请求参数,此时使用共享的req和res,可复用缓存 - final HttpExchangeWrapper wrapper = (HttpExchangeWrapper) httpExchange; - request = wrapper.getRequest(); - response = wrapper.getResponse(); - } else { - request = new HttpServerRequest(httpExchange); - response = new HttpServerResponse(httpExchange); - } - action.doAction(request, response); - httpExchange.close(); - } -} diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/action/Action.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/handler/HttpHandler.java similarity index 56% rename from hutool-http/src/main/java/org/dromara/hutool/http/server/action/Action.java rename to hutool-http/src/main/java/org/dromara/hutool/http/server/handler/HttpHandler.java index 460c1b33e..b24678fff 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/server/action/Action.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/handler/HttpHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2024 Hutool Team and hutool.cn + * Copyright (c) 2024 Hutool Team and hutool.cn * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,29 +14,23 @@ * limitations under the License. */ -package org.dromara.hutool.http.server.action; +package org.dromara.hutool.http.server.handler; -import org.dromara.hutool.http.server.HttpServerRequest; -import org.dromara.hutool.http.server.HttpServerResponse; - -import java.io.IOException; +import org.dromara.hutool.http.server.ServerRequest; +import org.dromara.hutool.http.server.ServerResponse; /** - * 请求处理接口
    - * 当用户请求某个Path,则调用相应Action的doAction方法 + * HTTP请求处理器 * * @author Looly - * @since 5.2.6 */ -@FunctionalInterface -public interface Action { +public interface HttpHandler { /** * 处理请求 * * @param request 请求对象 * @param response 响应对象 - * @throws IOException IO异常 */ - void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException; + void handle(ServerRequest request, ServerResponse response); } diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/action/RootAction.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/handler/RootHandler.java similarity index 75% rename from hutool-http/src/main/java/org/dromara/hutool/http/server/action/RootAction.java rename to hutool-http/src/main/java/org/dromara/hutool/http/server/handler/RootHandler.java index 51b3d05cb..4c2504c27 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/server/action/RootAction.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/handler/RootHandler.java @@ -14,12 +14,13 @@ * limitations under the License. */ -package org.dromara.hutool.http.server.action; +package org.dromara.hutool.http.server.handler; import org.dromara.hutool.core.collection.ListUtil; import org.dromara.hutool.core.io.file.FileUtil; -import org.dromara.hutool.http.server.HttpServerRequest; -import org.dromara.hutool.http.server.HttpServerResponse; +import org.dromara.hutool.http.meta.HttpStatus; +import org.dromara.hutool.http.server.ServerRequest; +import org.dromara.hutool.http.server.ServerResponse; import java.io.File; import java.util.List; @@ -30,8 +31,11 @@ import java.util.List; * @author looly * @since 5.2.6 */ -public class RootAction implements Action { +public class RootHandler implements HttpHandler { + /** + * 默认主页文件名 + */ public static final String DEFAULT_INDEX_FILE_NAME = "index.html"; private final File rootDir; @@ -42,7 +46,7 @@ public class RootAction implements Action { * * @param rootDir 网页根目录 */ - public RootAction(final String rootDir) { + public RootHandler(final String rootDir) { this(new File(rootDir)); } @@ -51,7 +55,7 @@ public class RootAction implements Action { * * @param rootDir 网页根目录 */ - public RootAction(final File rootDir) { + public RootHandler(final File rootDir) { this(rootDir, DEFAULT_INDEX_FILE_NAME); } @@ -61,7 +65,7 @@ public class RootAction implements Action { * @param rootDir 网页根目录 * @param indexFileNames 主页文件名列表 */ - public RootAction(final String rootDir, final String... indexFileNames) { + public RootHandler(final String rootDir, final String... indexFileNames) { this(new File(rootDir), indexFileNames); } @@ -72,13 +76,13 @@ public class RootAction implements Action { * @param indexFileNames 主页文件名列表 * @since 5.4.0 */ - public RootAction(final File rootDir, final String... indexFileNames) { + public RootHandler(final File rootDir, final String... indexFileNames) { this.rootDir = rootDir; this.indexFileNames = ListUtil.of(indexFileNames); } @Override - public void doAction(final HttpServerRequest request, final HttpServerResponse response) { + public void handle(final ServerRequest request, final ServerResponse response) { final String path = request.getPath(); File file = FileUtil.file(rootDir, path); @@ -99,6 +103,7 @@ public class RootAction implements Action { } } - response.send404("404 Not Found !"); + response.setStatus(HttpStatus.HTTP_NOT_FOUND); + response.write("404 Not Found !"); } } diff --git a/hutool-http/src/test/java/org/dromara/hutool/http/server/ExceptionServerTest.java b/hutool-http/src/test/java/org/dromara/hutool/http/server/ExceptionServerTest.java index 2cb694847..d17b0a13a 100644 --- a/hutool-http/src/test/java/org/dromara/hutool/http/server/ExceptionServerTest.java +++ b/hutool-http/src/test/java/org/dromara/hutool/http/server/ExceptionServerTest.java @@ -16,17 +16,16 @@ package org.dromara.hutool.http.server; +import org.dromara.hutool.core.io.IORuntimeException; import org.dromara.hutool.http.HttpUtil; -import org.dromara.hutool.http.server.filter.DefaultExceptionFilter; - -import java.io.IOException; +import org.dromara.hutool.http.server.engine.sun.filter.DefaultExceptionFilter; public class ExceptionServerTest { public static void main(final String[] args) { HttpUtil.createServer(8888) .addFilter(new DefaultExceptionFilter()) .addAction("/", (req, res) -> { - throw new IOException("Test Exception"); + throw new IORuntimeException("Test Exception"); }) .start(); } diff --git a/hutool-http/src/test/java/org/dromara/hutool/http/server/Issue3343Test.java b/hutool-http/src/test/java/org/dromara/hutool/http/server/Issue3343Test.java index 383f4afdf..662bd8f35 100644 --- a/hutool-http/src/test/java/org/dromara/hutool/http/server/Issue3343Test.java +++ b/hutool-http/src/test/java/org/dromara/hutool/http/server/Issue3343Test.java @@ -20,6 +20,7 @@ import org.dromara.hutool.core.date.DateUtil; import org.dromara.hutool.core.lang.Console; import org.dromara.hutool.core.map.multi.ListValueMap; import org.dromara.hutool.http.HttpUtil; +import org.dromara.hutool.http.server.engine.sun.SimpleServer; /** * http://localhost:8888/?name=hutool @@ -33,7 +34,7 @@ public class Issue3343Test { Console.log(" > from : " + req.getClientIP()); // 过滤器中获取请求参数 Console.log(" > params : " + req.getParams()); - chain.doFilter(req.getHttpExchange()); + chain.doFilter(req.getExchange()); }); server.addAction("/", Issue3343Test::index); @@ -41,9 +42,9 @@ public class Issue3343Test { server.start(); } - private static void index(HttpServerRequest request, HttpServerResponse response) { + private static void index(final ServerRequest request, final ServerResponse response) { // 具体逻辑中再次获取请求参数 - ListValueMap params = request.getParams(); + final ListValueMap params = request.getParams(); Console.log("index params: " + params); response.getWriter().write("GOT: " + params); } diff --git a/hutool-http/src/test/java/org/dromara/hutool/http/server/Issue3723Test.java b/hutool-http/src/test/java/org/dromara/hutool/http/server/Issue3723Test.java index 3b26e1801..f87f99d3f 100644 --- a/hutool-http/src/test/java/org/dromara/hutool/http/server/Issue3723Test.java +++ b/hutool-http/src/test/java/org/dromara/hutool/http/server/Issue3723Test.java @@ -19,18 +19,19 @@ package org.dromara.hutool.http.server; import org.dromara.hutool.core.data.id.IdUtil; import org.dromara.hutool.http.HttpUtil; import org.dromara.hutool.http.meta.ContentType; +import org.dromara.hutool.http.server.engine.sun.SimpleServer; public class Issue3723Test { public static void main(final String[] args) { final SimpleServer server = HttpUtil.createServer(8888); server.addFilter((req, res, chain) -> { final String requestId = IdUtil.fastSimpleUUID(); - req.getHttpExchange().setAttribute("requestId", requestId); + req.getExchange().setAttribute("requestId", requestId); res.addHeader("X-Request-Id", requestId); res.write("new Content"); - chain.doFilter(req.getHttpExchange()); + chain.doFilter(req.getExchange()); }); server.addAction("/", (req, res)-> res.write("Hello Hutool Server", ContentType.JSON.getValue())); server.start(); diff --git a/hutool-http/src/test/java/org/dromara/hutool/http/server/IssueI6Q30XTest.java b/hutool-http/src/test/java/org/dromara/hutool/http/server/IssueI6Q30XTest.java index 5d81aca97..bb6d14062 100644 --- a/hutool-http/src/test/java/org/dromara/hutool/http/server/IssueI6Q30XTest.java +++ b/hutool-http/src/test/java/org/dromara/hutool/http/server/IssueI6Q30XTest.java @@ -19,8 +19,10 @@ package org.dromara.hutool.http.server; import org.dromara.hutool.core.date.DateUtil; import org.dromara.hutool.core.date.StopWatch; import org.dromara.hutool.core.lang.Console; -import org.dromara.hutool.http.multipart.MultipartFormData; import org.dromara.hutool.http.HttpUtil; +import org.dromara.hutool.http.multipart.MultipartFormData; +import org.dromara.hutool.http.server.engine.sun.SimpleServer; +import org.dromara.hutool.http.server.engine.sun.SunServerRequest; import java.util.concurrent.TimeUnit; @@ -35,7 +37,7 @@ public class IssueI6Q30XTest { public static void main(String[] args) { final SimpleServer server = HttpUtil.createServer(8888); server.addAction("/file", (request, response) -> { - Console.log(request.getHeaders().entrySet()); + Console.log(((SunServerRequest)request).getHeaders().entrySet()); final StopWatch stopWatch = DateUtil.createStopWatch(); stopWatch.start(); diff --git a/hutool-http/src/test/java/org/dromara/hutool/http/server/RedirectServerTest.java b/hutool-http/src/test/java/org/dromara/hutool/http/server/RedirectServerTest.java index a9859c7e6..5f3e6a816 100644 --- a/hutool-http/src/test/java/org/dromara/hutool/http/server/RedirectServerTest.java +++ b/hutool-http/src/test/java/org/dromara/hutool/http/server/RedirectServerTest.java @@ -19,25 +19,26 @@ package org.dromara.hutool.http.server; import org.dromara.hutool.core.lang.Console; import org.dromara.hutool.http.HttpUtil; import org.dromara.hutool.http.meta.HeaderName; +import org.dromara.hutool.http.meta.HttpStatus; public class RedirectServerTest { public static void main(final String[] args) { HttpUtil.createServer(8888).addAction("/redirect1", (request, response) -> { response.addHeader(HeaderName.LOCATION.getValue(),"http://localhost:8888/redirect2"); response.addHeader(HeaderName.SET_COOKIE.getValue(),"redirect1=1; path=/; HttpOnly"); - response.send(301); + response.setStatus(301); }).addAction("/redirect2", (request, response) -> { response.addHeader(HeaderName.LOCATION.getValue(),"http://localhost:8888/redirect3"); response.addHeader(HeaderName.SET_COOKIE.getValue(), "redirect2=2; path=/; HttpOnly"); - response.send(301); + response.setStatus(301); }).addAction("/redirect3", (request, response) -> { response.addHeader(HeaderName.LOCATION.getValue(),"http://localhost:8888/redirect4"); response.addHeader(HeaderName.SET_COOKIE.getValue(),"redirect3=3; path=/; HttpOnly"); - response.send(301); + response.setStatus(301); }).addAction("/redirect4", (request, response) -> { final String cookie = request.getHeader(HeaderName.COOKIE); Console.log(cookie); - response.sendOk(); + response.setStatus(HttpStatus.HTTP_OK); }).start(); } } diff --git a/hutool-http/src/test/java/org/dromara/hutool/http/server/SimpleServerTest.java b/hutool-http/src/test/java/org/dromara/hutool/http/server/SimpleServerTest.java index 35bd6ddf8..b11fd8148 100644 --- a/hutool-http/src/test/java/org/dromara/hutool/http/server/SimpleServerTest.java +++ b/hutool-http/src/test/java/org/dromara/hutool/http/server/SimpleServerTest.java @@ -19,10 +19,11 @@ package org.dromara.hutool.http.server; import org.dromara.hutool.core.collection.ListUtil; import org.dromara.hutool.core.io.file.FileUtil; import org.dromara.hutool.core.lang.Console; -import org.dromara.hutool.http.multipart.UploadFile; +import org.dromara.hutool.http.HttpUtil; import org.dromara.hutool.http.meta.ContentType; import org.dromara.hutool.http.meta.HeaderName; -import org.dromara.hutool.http.HttpUtil; +import org.dromara.hutool.http.multipart.UploadFile; +import org.dromara.hutool.http.server.engine.sun.SunServerRequest; import org.dromara.hutool.json.JSONUtil; import java.net.HttpCookie; @@ -33,13 +34,13 @@ public class SimpleServerTest { HttpUtil.createServer(8888) .addFilter(((req, res, chain) -> { Console.log("Filter: " + req.getPath()); - chain.doFilter(req.getHttpExchange()); + chain.doFilter(req.getExchange()); })) // 设置默认根目录,classpath/html .setRoot(FileUtil.file("html")) // get数据测试,返回请求的PATH .addAction("/get", (request, response) -> - response.write(request.getURI().toString(), ContentType.TEXT_PLAIN.toString()) + response.write(((SunServerRequest)request).getURI().toString(), ContentType.TEXT_PLAIN.toString()) ) // 返回JSON数据测试 .addAction("/restTest", (request, response) -> { diff --git a/hutool-http/src/test/java/org/dromara/hutool/http/server/engine/UndertowTest.java b/hutool-http/src/test/java/org/dromara/hutool/http/server/engine/UndertowTest.java new file mode 100644 index 000000000..0e5c34207 --- /dev/null +++ b/hutool-http/src/test/java/org/dromara/hutool/http/server/engine/UndertowTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.http.server.engine; + +import org.dromara.hutool.core.lang.Console; +import org.dromara.hutool.http.server.ServerConfig; +import org.dromara.hutool.http.server.engine.undertow.UndertowEngine; + +public class UndertowTest { + public static void main(String[] args) { + final UndertowEngine undertowEngine = new UndertowEngine(); + undertowEngine.init(ServerConfig.of()); + undertowEngine.setHandler((request, response) -> { + Console.log(request.getPath()); + response.write("Hutool Undertow response test"); + }); + undertowEngine.start(); + } +}