mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-08-18 20:38:02 +08:00
add http server engine
This commit is contained in:
@@ -37,6 +37,10 @@
|
|||||||
<httpclient5.version>5.4.1</httpclient5.version>
|
<httpclient5.version>5.4.1</httpclient5.version>
|
||||||
<httpclient4.version>4.5.14</httpclient4.version>
|
<httpclient4.version>4.5.14</httpclient4.version>
|
||||||
<okhttp.version>4.12.0</okhttp.version>
|
<okhttp.version>4.12.0</okhttp.version>
|
||||||
|
<!-- 固定 2.2.x支持到JDK8 -->
|
||||||
|
<undertow.version>2.2.36.Final</undertow.version>
|
||||||
|
<jetty.version>12.0.15</jetty.version>
|
||||||
|
<tomcat.version>11.0.1</tomcat.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -100,6 +104,26 @@
|
|||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 第三方HTTP服务器库 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.undertow</groupId>
|
||||||
|
<artifactId>undertow-core</artifactId>
|
||||||
|
<version>${undertow.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-server</artifactId>
|
||||||
|
<version>${jetty.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.tomcat.embed</groupId>
|
||||||
|
<artifactId>tomcat-embed-core</artifactId>
|
||||||
|
<version>${tomcat.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- 仅用于测试 -->
|
<!-- 仅用于测试 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.dromara.hutool</groupId>
|
<groupId>org.dromara.hutool</groupId>
|
||||||
|
|||||||
@@ -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.ClientEngine;
|
||||||
import org.dromara.hutool.http.client.engine.ClientEngineFactory;
|
import org.dromara.hutool.http.client.engine.ClientEngineFactory;
|
||||||
import org.dromara.hutool.http.meta.Method;
|
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.nio.charset.Charset;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|||||||
@@ -16,17 +16,13 @@
|
|||||||
|
|
||||||
package org.dromara.hutool.http.multipart;
|
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.IoUtil;
|
||||||
import org.dromara.hutool.core.io.file.FileNameUtil;
|
import org.dromara.hutool.core.io.file.FileNameUtil;
|
||||||
|
import org.dromara.hutool.core.io.file.FileUtil;
|
||||||
import org.dromara.hutool.core.text.StrUtil;
|
import org.dromara.hutool.core.text.StrUtil;
|
||||||
|
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.*;
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.file.NoSuchFileException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传的文件对象
|
* 上传的文件对象
|
||||||
@@ -80,9 +76,9 @@ public class UploadFile {
|
|||||||
*
|
*
|
||||||
* @param destPath 目标文件路径
|
* @param destPath 目标文件路径
|
||||||
* @return 目标文件
|
* @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) {
|
if (data != null || tempFile != null) {
|
||||||
return write(FileUtil.file(destPath));
|
return write(FileUtil.file(destPath));
|
||||||
}
|
}
|
||||||
@@ -95,9 +91,9 @@ public class UploadFile {
|
|||||||
*
|
*
|
||||||
* @param destination 目标文件
|
* @param destination 目标文件
|
||||||
* @return 目标文件
|
* @return 目标文件
|
||||||
* @throws IOException IO异常
|
* @throws IORuntimeException IO异常
|
||||||
*/
|
*/
|
||||||
public File write(File destination) throws IOException {
|
public File write(File destination) throws IORuntimeException {
|
||||||
assertValid();
|
assertValid();
|
||||||
|
|
||||||
if (destination.isDirectory()) {
|
if (destination.isDirectory()) {
|
||||||
@@ -113,7 +109,7 @@ public class UploadFile {
|
|||||||
throw new NullPointerException("Temp file is null !");
|
throw new NullPointerException("Temp file is null !");
|
||||||
}
|
}
|
||||||
if(!this.tempFile.exists()){
|
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);
|
FileUtil.move(tempFile, destination, true);
|
||||||
@@ -123,9 +119,9 @@ public class UploadFile {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return 获得文件字节流
|
* @return 获得文件字节流
|
||||||
* @throws IOException IO异常
|
* @throws IORuntimeException IO异常
|
||||||
*/
|
*/
|
||||||
public byte[] getFileContent() throws IOException {
|
public byte[] getFileContent() throws IORuntimeException {
|
||||||
assertValid();
|
assertValid();
|
||||||
|
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
@@ -139,9 +135,9 @@ public class UploadFile {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return 获得文件流
|
* @return 获得文件流
|
||||||
* @throws IOException IO异常
|
* @throws IORuntimeException IO异常
|
||||||
*/
|
*/
|
||||||
public InputStream getFileInputStream() throws IOException {
|
public InputStream getFileInputStream() throws IORuntimeException {
|
||||||
assertValid();
|
assertValid();
|
||||||
|
|
||||||
if (data != null) {
|
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()) {
|
if (!isUploaded()) {
|
||||||
throw new IOException(StrUtil.format("File [{}] upload fail", getFileName()));
|
throw new IORuntimeException(StrUtil.format("File [{}] upload fail", getFileName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ---------------------------------------------------------------------------- Private method end
|
// ---------------------------------------------------------------------------- Private method end
|
||||||
|
|||||||
@@ -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<String> value) {
|
|
||||||
getHeaders().put(header, value);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置所有响应头,如果已经存在,则覆盖
|
|
||||||
*
|
|
||||||
* @param headers 响应头map
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
public HttpServerResponse setHeaders(final Map<String, List<String>> headers) {
|
|
||||||
getHeaders().putAll(headers);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置Content-Type头,类似于:text/html;charset=utf-8<br>
|
|
||||||
* 如果用户传入的信息无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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,获取规则如下:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 1、从Content-Type头中获取编码,类似于:text/html;charset=utf-8
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @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等任意内容<br>
|
||||||
|
* 使用{@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 表单内容<br>
|
||||||
|
* 包括文件和普通表单数据<br>
|
||||||
|
* 在同一次请求中,此方法只能被执行一次!
|
||||||
|
*
|
||||||
|
* @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<String> getParams(final String name){
|
||||||
|
return getParams().get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取参数Map
|
||||||
|
*
|
||||||
|
* @return 参数map
|
||||||
|
*/
|
||||||
|
default ListValueMap<String, String> getParams() {
|
||||||
|
final ListValueMap<String, String> 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
|
||||||
|
}
|
||||||
@@ -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<String> 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<br>
|
||||||
|
* 如果用户传入的信息无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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
||||||
@@ -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;
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* 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.Headers;
|
||||||
import com.sun.net.httpserver.HttpContext;
|
import com.sun.net.httpserver.HttpContext;
|
||||||
@@ -36,8 +36,8 @@ import java.net.URI;
|
|||||||
public class HttpExchangeWrapper extends HttpExchange implements Wrapper<HttpExchange> {
|
public class HttpExchangeWrapper extends HttpExchange implements Wrapper<HttpExchange> {
|
||||||
|
|
||||||
private final HttpExchange raw;
|
private final HttpExchange raw;
|
||||||
private final HttpServerRequest request;
|
private final SunServerRequest request;
|
||||||
private final HttpServerResponse response;
|
private final SunServerResponse response;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造
|
* 构造
|
||||||
@@ -46,8 +46,8 @@ public class HttpExchangeWrapper extends HttpExchange implements Wrapper<HttpExc
|
|||||||
*/
|
*/
|
||||||
public HttpExchangeWrapper(final HttpExchange raw) {
|
public HttpExchangeWrapper(final HttpExchange raw) {
|
||||||
this.raw = raw;
|
this.raw = raw;
|
||||||
this.request = new HttpServerRequest(this);
|
this.request = new SunServerRequest(this);
|
||||||
this.response = new HttpServerResponse(this);
|
this.response = new SunServerResponse(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -60,7 +60,7 @@ public class HttpExchangeWrapper extends HttpExchange implements Wrapper<HttpExc
|
|||||||
*
|
*
|
||||||
* @return 请求
|
* @return 请求
|
||||||
*/
|
*/
|
||||||
public HttpServerRequest getRequest() {
|
public SunServerRequest getRequest() {
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ public class HttpExchangeWrapper extends HttpExchange implements Wrapper<HttpExc
|
|||||||
*
|
*
|
||||||
* @return 响应
|
* @return 响应
|
||||||
*/
|
*/
|
||||||
public HttpServerResponse getResponse() {
|
public SunServerResponse getResponse() {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,25 +14,20 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.dromara.hutool.http.server;
|
package org.dromara.hutool.http.server.engine.sun;
|
||||||
|
|
||||||
import com.sun.net.httpserver.*;
|
import com.sun.net.httpserver.*;
|
||||||
import org.dromara.hutool.core.io.IORuntimeException;
|
|
||||||
import org.dromara.hutool.core.lang.Console;
|
import org.dromara.hutool.core.lang.Console;
|
||||||
import org.dromara.hutool.core.text.StrUtil;
|
import org.dromara.hutool.core.text.StrUtil;
|
||||||
import org.dromara.hutool.core.thread.GlobalThreadPool;
|
import org.dromara.hutool.http.server.ServerConfig;
|
||||||
import org.dromara.hutool.http.server.action.Action;
|
import org.dromara.hutool.http.server.engine.sun.filter.HttpFilter;
|
||||||
import org.dromara.hutool.http.server.action.RootAction;
|
import org.dromara.hutool.http.server.engine.sun.filter.SimpleFilter;
|
||||||
import org.dromara.hutool.http.server.filter.HttpFilter;
|
import org.dromara.hutool.http.server.handler.RootHandler;
|
||||||
import org.dromara.hutool.http.server.filter.SimpleFilter;
|
|
||||||
import org.dromara.hutool.http.server.handler.ActionHandler;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,8 +38,7 @@ import java.util.concurrent.Executor;
|
|||||||
*/
|
*/
|
||||||
public class SimpleServer {
|
public class SimpleServer {
|
||||||
|
|
||||||
private final HttpServer server;
|
private final SunHttpServerEngine engine;
|
||||||
private final List<Filter> filters;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造
|
* 构造
|
||||||
@@ -71,7 +65,7 @@ public class SimpleServer {
|
|||||||
* @param address 监听地址
|
* @param address 监听地址
|
||||||
*/
|
*/
|
||||||
public SimpleServer(final InetSocketAddress address) {
|
public SimpleServer(final InetSocketAddress address) {
|
||||||
this(address, (HttpsConfigurator) null);
|
this(address, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,29 +75,13 @@ public class SimpleServer {
|
|||||||
* @param sslContext ssl配置
|
* @param sslContext ssl配置
|
||||||
*/
|
*/
|
||||||
public SimpleServer(final InetSocketAddress address, final SSLContext sslContext) {
|
public SimpleServer(final InetSocketAddress address, final SSLContext sslContext) {
|
||||||
this(address, new HttpsConfigurator(sslContext));
|
this.engine = new SunHttpServerEngine();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
final ServerConfig serverConfig = ServerConfig.of()
|
||||||
* 构造
|
.setHost(address.getHostName())
|
||||||
*
|
.setPort(address.getPort())
|
||||||
* @param address 监听地址
|
.setSslContext(sslContext);
|
||||||
* @param configurator https配置信息,用于使用自定义SSL(TLS)证书等
|
this.engine.init(serverConfig);
|
||||||
*/
|
|
||||||
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<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -115,7 +93,6 @@ public class SimpleServer {
|
|||||||
* <li>{@link #setRoot(String)} </li>
|
* <li>{@link #setRoot(String)} </li>
|
||||||
* <li>{@link #createContext(String, HttpHandler)} </li>
|
* <li>{@link #createContext(String, HttpHandler)} </li>
|
||||||
* <li>{@link #addHandler(String, HttpHandler)}</li>
|
* <li>{@link #addHandler(String, HttpHandler)}</li>
|
||||||
* <li>{@link #addAction(String, Action)} </li>
|
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @param filter {@link Filter} 请求过滤器
|
* @param filter {@link Filter} 请求过滤器
|
||||||
@@ -123,7 +100,7 @@ public class SimpleServer {
|
|||||||
* @since 5.5.7
|
* @since 5.5.7
|
||||||
*/
|
*/
|
||||||
public SimpleServer addFilter(final Filter filter) {
|
public SimpleServer addFilter(final Filter filter) {
|
||||||
this.filters.add(filter);
|
this.engine.addFilter(filter);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +113,6 @@ public class SimpleServer {
|
|||||||
* <li>{@link #setRoot(String)} </li>
|
* <li>{@link #setRoot(String)} </li>
|
||||||
* <li>{@link #createContext(String, HttpHandler)} </li>
|
* <li>{@link #createContext(String, HttpHandler)} </li>
|
||||||
* <li>{@link #addHandler(String, HttpHandler)}</li>
|
* <li>{@link #addHandler(String, HttpHandler)}</li>
|
||||||
* <li>{@link #addAction(String, Action)} </li>
|
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @param filter {@link Filter} 请求过滤器
|
* @param filter {@link Filter} 请求过滤器
|
||||||
@@ -177,10 +153,22 @@ public class SimpleServer {
|
|||||||
public HttpContext createContext(String path, final HttpHandler handler) {
|
public HttpContext createContext(String path, final HttpHandler handler) {
|
||||||
// 非/开头的路径会报错
|
// 非/开头的路径会报错
|
||||||
path = StrUtil.addPrefixIfNot(path, StrUtil.SLASH);
|
path = StrUtil.addPrefixIfNot(path, StrUtil.SLASH);
|
||||||
final HttpContext context = this.server.createContext(path, handler);
|
return this.engine.createContext(path, handler);
|
||||||
// 增加整体过滤器
|
}
|
||||||
context.getFilters().addAll(this.filters);
|
|
||||||
return context;
|
/**
|
||||||
|
* 增加请求处理规则,使用默认的{@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
|
* @return this
|
||||||
*/
|
*/
|
||||||
public SimpleServer setRoot(final File root) {
|
public SimpleServer setRoot(final File root) {
|
||||||
return addAction("/", new RootAction(root));
|
this.engine.setHandler(new RootHandler(root));
|
||||||
}
|
return this;
|
||||||
|
|
||||||
/**
|
|
||||||
* 增加请求处理规则
|
|
||||||
*
|
|
||||||
* @param path 路径
|
|
||||||
* @param action 处理器
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
public SimpleServer addAction(final String path, final Action action) {
|
|
||||||
return addHandler(path, new ActionHandler(action));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -221,7 +199,7 @@ public class SimpleServer {
|
|||||||
* @return this
|
* @return this
|
||||||
*/
|
*/
|
||||||
public SimpleServer setExecutor(final Executor executor) {
|
public SimpleServer setExecutor(final Executor executor) {
|
||||||
this.server.setExecutor(executor);
|
this.engine.setExecutor(executor);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +209,7 @@ public class SimpleServer {
|
|||||||
* @return {@link HttpServer}
|
* @return {@link HttpServer}
|
||||||
*/
|
*/
|
||||||
public HttpServer getRawServer() {
|
public HttpServer getRawServer() {
|
||||||
return this.server;
|
return this.engine.getRawEngine();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -240,7 +218,7 @@ public class SimpleServer {
|
|||||||
* @return {@link InetSocketAddress}
|
* @return {@link InetSocketAddress}
|
||||||
*/
|
*/
|
||||||
public InetSocketAddress getAddress() {
|
public InetSocketAddress getAddress() {
|
||||||
return this.server.getAddress();
|
return getRawServer().getAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -249,6 +227,6 @@ public class SimpleServer {
|
|||||||
public void start() {
|
public void start() {
|
||||||
final InetSocketAddress address = getAddress();
|
final InetSocketAddress address = getAddress();
|
||||||
Console.log("Hutool Simple Http Server listen on 【{}:{}】", address.getHostName(), address.getPort());
|
Console.log("Hutool Simple Http Server listen on 【{}:{}】", address.getHostName(), address.getPort());
|
||||||
this.server.start();
|
this.engine.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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<Filter> filters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*/
|
||||||
|
public SunHttpServerEngine() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
initEngine();
|
||||||
|
this.server.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpServer getRawEngine() {
|
||||||
|
return this.server;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加请求过滤器,此过滤器对所有请求有效<br>
|
||||||
|
* 此方法需在以下方法前之前调用:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link #createContext(String, HttpHandler)} </li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加请求过滤器,此过滤器对所有请求有效<br>
|
||||||
|
* 此方法需在以下方法前之前调用:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link #createContext(String, HttpHandler)} </li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @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)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,14 +14,12 @@
|
|||||||
* limitations under the License.
|
* 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.HttpContext;
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.nio.charset.Charset;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HttpServer公用对象,提供HttpExchange包装和公用方法
|
* HttpServer公用对象,提供HttpExchange包装和公用方法
|
||||||
@@ -29,18 +27,16 @@ import java.nio.charset.Charset;
|
|||||||
* @author looly
|
* @author looly
|
||||||
* @since 5.2.6
|
* @since 5.2.6
|
||||||
*/
|
*/
|
||||||
public class HttpServerBase implements Closeable {
|
public class SunServerBase implements Closeable {
|
||||||
|
|
||||||
final static Charset DEFAULT_CHARSET = CharsetUtil.UTF_8;
|
protected final HttpExchange httpExchange;
|
||||||
|
|
||||||
final HttpExchange httpExchange;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造
|
* 构造
|
||||||
*
|
*
|
||||||
* @param httpExchange {@link HttpExchange}
|
* @param httpExchange {@link HttpExchange}
|
||||||
*/
|
*/
|
||||||
public HttpServerBase(final HttpExchange httpExchange) {
|
public SunServerBase(final HttpExchange httpExchange) {
|
||||||
this.httpExchange = httpExchange;
|
this.httpExchange = httpExchange;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +45,7 @@ public class HttpServerBase implements Closeable {
|
|||||||
*
|
*
|
||||||
* @return {@link HttpExchange}对象
|
* @return {@link HttpExchange}对象
|
||||||
*/
|
*/
|
||||||
public HttpExchange getHttpExchange() {
|
public HttpExchange getExchange() {
|
||||||
return this.httpExchange;
|
return this.httpExchange;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +56,7 @@ public class HttpServerBase implements Closeable {
|
|||||||
* @since 5.5.7
|
* @since 5.5.7
|
||||||
*/
|
*/
|
||||||
public HttpContext getHttpContext() {
|
public HttpContext getHttpContext() {
|
||||||
return getHttpExchange().getHttpContext();
|
return getExchange().getHttpContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* 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.Headers;
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
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.MapUtil;
|
||||||
import org.dromara.hutool.core.map.multi.ListValueMap;
|
import org.dromara.hutool.core.map.multi.ListValueMap;
|
||||||
import org.dromara.hutool.core.net.NetUtil;
|
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.net.url.UrlQueryUtil;
|
||||||
import org.dromara.hutool.core.text.StrUtil;
|
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.core.util.ObjUtil;
|
||||||
import org.dromara.hutool.http.meta.ContentTypeUtil;
|
import org.dromara.hutool.http.meta.ContentTypeUtil;
|
||||||
import org.dromara.hutool.http.meta.HeaderName;
|
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.useragent.UserAgent;
|
import org.dromara.hutool.http.multipart.UploadSetting;
|
||||||
import org.dromara.hutool.http.useragent.UserAgentUtil;
|
import org.dromara.hutool.http.server.ServerRequest;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.HttpCookie;
|
import java.net.HttpCookie;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
@@ -47,12 +43,11 @@ import java.util.Collection;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Http请求对象,对{@link HttpExchange}封装
|
* Sun Http请求包装
|
||||||
*
|
*
|
||||||
* @author looly
|
* @author Looly
|
||||||
* @since 5.2.6
|
|
||||||
*/
|
*/
|
||||||
public class HttpServerRequest extends HttpServerBase {
|
public class SunServerRequest extends SunServerBase implements ServerRequest {
|
||||||
|
|
||||||
private Map<String, HttpCookie> cookieCache;
|
private Map<String, HttpCookie> cookieCache;
|
||||||
private ListValueMap<String, String> paramsCache;
|
private ListValueMap<String, String> paramsCache;
|
||||||
@@ -65,37 +60,15 @@ public class HttpServerRequest extends HttpServerBase {
|
|||||||
*
|
*
|
||||||
* @param httpExchange {@link HttpExchange}
|
* @param httpExchange {@link HttpExchange}
|
||||||
*/
|
*/
|
||||||
public HttpServerRequest(final HttpExchange httpExchange) {
|
public SunServerRequest(final HttpExchange httpExchange) {
|
||||||
super(httpExchange);
|
super(httpExchange);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* 获得Http Method
|
|
||||||
*
|
|
||||||
* @return Http Method
|
|
||||||
*/
|
|
||||||
public String getMethod() {
|
public String getMethod() {
|
||||||
return this.httpExchange.getRequestMethod();
|
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
|
* 获得请求URI
|
||||||
*
|
*
|
||||||
@@ -105,20 +78,12 @@ public class HttpServerRequest extends HttpServerBase {
|
|||||||
return this.httpExchange.getRequestURI();
|
return this.httpExchange.getRequestURI();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* 获得请求路径Path
|
|
||||||
*
|
|
||||||
* @return 请求路径
|
|
||||||
*/
|
|
||||||
public String getPath() {
|
public String getPath() {
|
||||||
return getURI().getPath();
|
return getURI().getPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* 获取请求参数
|
|
||||||
*
|
|
||||||
* @return 参数字符串
|
|
||||||
*/
|
|
||||||
public String getQuery() {
|
public String getQuery() {
|
||||||
return getURI().getQuery();
|
return getURI().getQuery();
|
||||||
}
|
}
|
||||||
@@ -132,59 +97,12 @@ public class HttpServerRequest extends HttpServerBase {
|
|||||||
return this.httpExchange.getRequestHeaders();
|
return this.httpExchange.getRequestHeaders();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* 获得请求header中的信息
|
public String getHeader(final String name) {
|
||||||
*
|
return getHeaders().getFirst(name);
|
||||||
* @param headerNameKey 头信息的KEY
|
|
||||||
* @return header值
|
|
||||||
*/
|
|
||||||
public String getHeader(final HeaderName headerNameKey) {
|
|
||||||
return getHeader(headerNameKey.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* 获得请求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,获取规则如下:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* 1、从Content-Type头中获取编码,类似于:text/html;charset=utf-8
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @return 编码,默认UTF-8
|
|
||||||
*/
|
|
||||||
public Charset getCharset() {
|
public Charset getCharset() {
|
||||||
if(null == this.charsetCache){
|
if(null == this.charsetCache){
|
||||||
final String contentType = getContentType();
|
final String contentType = getContentType();
|
||||||
@@ -194,33 +112,6 @@ public class HttpServerRequest extends HttpServerBase {
|
|||||||
return this.charsetCache;
|
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信息列表
|
* 获得Cookie信息列表
|
||||||
*
|
*
|
||||||
@@ -255,60 +146,7 @@ public class HttpServerRequest extends HttpServerBase {
|
|||||||
return getCookieMap().get(cookieName);
|
return getCookieMap().get(cookieName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* 是否为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等任意内容<br>
|
|
||||||
* 使用{@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 流
|
|
||||||
*/
|
|
||||||
public InputStream getBodyStream() {
|
public InputStream getBodyStream() {
|
||||||
InputStream bodyStream = this.httpExchange.getRequestBody();
|
InputStream bodyStream = this.httpExchange.getRequestBody();
|
||||||
|
|
||||||
@@ -330,32 +168,23 @@ public class HttpServerRequest extends HttpServerBase {
|
|||||||
return bodyStream;
|
return bodyStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* 获取指定名称的参数值,取第一个值
|
public byte[] getBodyBytes(){
|
||||||
* @param name 参数名
|
if(null == this.bodyCache){
|
||||||
* @return 参数值
|
this.bodyCache = IoUtil.readBytes(getBodyStream(), true);
|
||||||
* @since 5.5.8
|
}
|
||||||
*/
|
return this.bodyCache;
|
||||||
public String getParam(final String name){
|
|
||||||
return getParams().getValue(name, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* 获取指定名称的参数值
|
public MultipartFormData getMultipart() throws IORuntimeException {
|
||||||
*
|
if(null == this.multipartFormDataCache){
|
||||||
* @param name 参数名
|
this.multipartFormDataCache = parseMultipart(new UploadSetting());
|
||||||
* @return 参数值
|
}
|
||||||
* @since 5.5.8
|
return this.multipartFormDataCache;
|
||||||
*/
|
|
||||||
public Collection<String> getParams(final String name){
|
|
||||||
return getParams().get(name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* 获取参数Map
|
|
||||||
*
|
|
||||||
* @return 参数map
|
|
||||||
*/
|
|
||||||
public ListValueMap<String, String> getParams() {
|
public ListValueMap<String, String> getParams() {
|
||||||
if (null == this.paramsCache) {
|
if (null == this.paramsCache) {
|
||||||
this.paramsCache = new ListValueMap<>();
|
this.paramsCache = new ListValueMap<>();
|
||||||
@@ -436,39 +265,4 @@ public class HttpServerRequest extends HttpServerBase {
|
|||||||
ip = this.httpExchange.getRemoteAddress().getHostName();
|
ip = this.httpExchange.getRemoteAddress().getHostName();
|
||||||
return NetUtil.getMultistageReverseProxyIp(ip);
|
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 表单内容<br>
|
|
||||||
* 包括文件和普通表单数据<br>
|
|
||||||
* 在同一次请求中,此方法只能被执行一次!
|
|
||||||
*
|
|
||||||
* @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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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<String> value) {
|
||||||
|
getHeaders().put(header, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置所有响应头,如果已经存在,则覆盖
|
||||||
|
*
|
||||||
|
* @param headers 响应头map
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public SunServerResponse setHeaders(final Map<String, List<String>> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,12 +14,12 @@
|
|||||||
* limitations under the License.
|
* 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.exception.ExceptionUtil;
|
||||||
import org.dromara.hutool.core.text.StrUtil;
|
import org.dromara.hutool.core.text.StrUtil;
|
||||||
import org.dromara.hutool.http.server.HttpServerRequest;
|
import org.dromara.hutool.http.server.engine.sun.SunServerRequest;
|
||||||
import org.dromara.hutool.http.server.HttpServerResponse;
|
import org.dromara.hutool.http.server.engine.sun.SunServerResponse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认异常处理拦截器
|
* 默认异常处理拦截器
|
||||||
@@ -31,7 +31,7 @@ public class DefaultExceptionFilter extends ExceptionFilter{
|
|||||||
private final static String TEMPLATE_ERROR = "<!DOCTYPE html><html><head><title>Hutool - Error report</title><style>h1,h3 {color:white; background-color: gray;}</style></head><body><h1>HTTP Status {} - {}</h1><hr size=\"1\" noshade=\"noshade\" /><p>{}</p><hr size=\"1\" noshade=\"noshade\" /><h3>Hutool</h3></body></html>";
|
private final static String TEMPLATE_ERROR = "<!DOCTYPE html><html><head><title>Hutool - Error report</title><style>h1,h3 {color:white; background-color: gray;}</style></head><body><h1>HTTP Status {} - {}</h1><hr size=\"1\" noshade=\"noshade\" /><p>{}</p><hr size=\"1\" noshade=\"noshade\" /><h3>Hutool</h3></body></html>";
|
||||||
|
|
||||||
@Override
|
@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);
|
String content = ExceptionUtil.stacktraceToString(e);
|
||||||
content = content.replace("\n", "<br/>\n");
|
content = content.replace("\n", "<br/>\n");
|
||||||
content = StrUtil.format(TEMPLATE_ERROR, 500, req.getURI(), content);
|
content = StrUtil.format(TEMPLATE_ERROR, 500, req.getURI(), content);
|
||||||
@@ -14,11 +14,11 @@
|
|||||||
* limitations under the License.
|
* 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 com.sun.net.httpserver.Filter;
|
||||||
import org.dromara.hutool.http.server.HttpServerRequest;
|
import org.dromara.hutool.http.server.engine.sun.SunServerRequest;
|
||||||
import org.dromara.hutool.http.server.HttpServerResponse;
|
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 {
|
public abstract class ExceptionFilter implements HttpFilter {
|
||||||
|
|
||||||
@Override
|
@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 {
|
try {
|
||||||
chain.doFilter(req.getHttpExchange());
|
chain.doFilter(req.getExchange());
|
||||||
} catch (final Throwable e) {
|
} catch (final Throwable e) {
|
||||||
afterException(req, res, e);
|
afterException(req, res, e);
|
||||||
}
|
}
|
||||||
@@ -39,9 +39,9 @@ public abstract class ExceptionFilter implements HttpFilter {
|
|||||||
/**
|
/**
|
||||||
* 异常之后的处理逻辑
|
* 异常之后的处理逻辑
|
||||||
*
|
*
|
||||||
* @param req {@link HttpServerRequest}
|
* @param req {@link SunServerRequest}
|
||||||
* @param res {@link HttpServerResponse}
|
* @param res {@link SunServerResponse}
|
||||||
* @param e 异常
|
* @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);
|
||||||
}
|
}
|
||||||
@@ -14,11 +14,11 @@
|
|||||||
* limitations under the License.
|
* 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 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;
|
import java.io.IOException;
|
||||||
|
|
||||||
@@ -33,10 +33,10 @@ public interface HttpFilter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行过滤
|
* 执行过滤
|
||||||
* @param req {@link HttpServerRequest} 请求对象,用于获取请求内容
|
* @param req {@link SunServerRequest} 请求对象,用于获取请求内容
|
||||||
* @param res {@link HttpServerResponse} 响应对象,用于写出内容
|
* @param res {@link SunServerResponse} 响应对象,用于写出内容
|
||||||
* @param chain {@link Filter.Chain}
|
* @param chain {@link Filter.Chain}
|
||||||
* @throws IOException IO异常
|
* @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;
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* 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 com.sun.net.httpserver.Filter;
|
||||||
|
|
||||||
@@ -17,4 +17,4 @@
|
|||||||
/**
|
/**
|
||||||
* {@link com.sun.net.httpserver.Filter} 实现包装
|
* {@link com.sun.net.httpserver.Filter} 实现包装
|
||||||
*/
|
*/
|
||||||
package org.dromara.hutool.http.server.filter;
|
package org.dromara.hutool.http.server.engine.sun.filter;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -14,29 +14,23 @@
|
|||||||
* limitations under the License.
|
* 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.ServerRequest;
|
||||||
import org.dromara.hutool.http.server.HttpServerResponse;
|
import org.dromara.hutool.http.server.ServerResponse;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求处理接口<br>
|
* HTTP请求处理器
|
||||||
* 当用户请求某个Path,则调用相应Action的doAction方法
|
|
||||||
*
|
*
|
||||||
* @author Looly
|
* @author Looly
|
||||||
* @since 5.2.6
|
|
||||||
*/
|
*/
|
||||||
@FunctionalInterface
|
public interface HttpHandler {
|
||||||
public interface Action {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理请求
|
* 处理请求
|
||||||
*
|
*
|
||||||
* @param request 请求对象
|
* @param request 请求对象
|
||||||
* @param response 响应对象
|
* @param response 响应对象
|
||||||
* @throws IOException IO异常
|
|
||||||
*/
|
*/
|
||||||
void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException;
|
void handle(ServerRequest request, ServerResponse response);
|
||||||
}
|
}
|
||||||
@@ -14,12 +14,13 @@
|
|||||||
* limitations under the License.
|
* 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.collection.ListUtil;
|
||||||
import org.dromara.hutool.core.io.file.FileUtil;
|
import org.dromara.hutool.core.io.file.FileUtil;
|
||||||
import org.dromara.hutool.http.server.HttpServerRequest;
|
import org.dromara.hutool.http.meta.HttpStatus;
|
||||||
import org.dromara.hutool.http.server.HttpServerResponse;
|
import org.dromara.hutool.http.server.ServerRequest;
|
||||||
|
import org.dromara.hutool.http.server.ServerResponse;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -30,8 +31,11 @@ import java.util.List;
|
|||||||
* @author looly
|
* @author looly
|
||||||
* @since 5.2.6
|
* @since 5.2.6
|
||||||
*/
|
*/
|
||||||
public class RootAction implements Action {
|
public class RootHandler implements HttpHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认主页文件名
|
||||||
|
*/
|
||||||
public static final String DEFAULT_INDEX_FILE_NAME = "index.html";
|
public static final String DEFAULT_INDEX_FILE_NAME = "index.html";
|
||||||
|
|
||||||
private final File rootDir;
|
private final File rootDir;
|
||||||
@@ -42,7 +46,7 @@ public class RootAction implements Action {
|
|||||||
*
|
*
|
||||||
* @param rootDir 网页根目录
|
* @param rootDir 网页根目录
|
||||||
*/
|
*/
|
||||||
public RootAction(final String rootDir) {
|
public RootHandler(final String rootDir) {
|
||||||
this(new File(rootDir));
|
this(new File(rootDir));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +55,7 @@ public class RootAction implements Action {
|
|||||||
*
|
*
|
||||||
* @param rootDir 网页根目录
|
* @param rootDir 网页根目录
|
||||||
*/
|
*/
|
||||||
public RootAction(final File rootDir) {
|
public RootHandler(final File rootDir) {
|
||||||
this(rootDir, DEFAULT_INDEX_FILE_NAME);
|
this(rootDir, DEFAULT_INDEX_FILE_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +65,7 @@ public class RootAction implements Action {
|
|||||||
* @param rootDir 网页根目录
|
* @param rootDir 网页根目录
|
||||||
* @param indexFileNames 主页文件名列表
|
* @param indexFileNames 主页文件名列表
|
||||||
*/
|
*/
|
||||||
public RootAction(final String rootDir, final String... indexFileNames) {
|
public RootHandler(final String rootDir, final String... indexFileNames) {
|
||||||
this(new File(rootDir), indexFileNames);
|
this(new File(rootDir), indexFileNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,13 +76,13 @@ public class RootAction implements Action {
|
|||||||
* @param indexFileNames 主页文件名列表
|
* @param indexFileNames 主页文件名列表
|
||||||
* @since 5.4.0
|
* @since 5.4.0
|
||||||
*/
|
*/
|
||||||
public RootAction(final File rootDir, final String... indexFileNames) {
|
public RootHandler(final File rootDir, final String... indexFileNames) {
|
||||||
this.rootDir = rootDir;
|
this.rootDir = rootDir;
|
||||||
this.indexFileNames = ListUtil.of(indexFileNames);
|
this.indexFileNames = ListUtil.of(indexFileNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doAction(final HttpServerRequest request, final HttpServerResponse response) {
|
public void handle(final ServerRequest request, final ServerResponse response) {
|
||||||
final String path = request.getPath();
|
final String path = request.getPath();
|
||||||
|
|
||||||
File file = FileUtil.file(rootDir, path);
|
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 !");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,17 +16,16 @@
|
|||||||
|
|
||||||
package org.dromara.hutool.http.server;
|
package org.dromara.hutool.http.server;
|
||||||
|
|
||||||
|
import org.dromara.hutool.core.io.IORuntimeException;
|
||||||
import org.dromara.hutool.http.HttpUtil;
|
import org.dromara.hutool.http.HttpUtil;
|
||||||
import org.dromara.hutool.http.server.filter.DefaultExceptionFilter;
|
import org.dromara.hutool.http.server.engine.sun.filter.DefaultExceptionFilter;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class ExceptionServerTest {
|
public class ExceptionServerTest {
|
||||||
public static void main(final String[] args) {
|
public static void main(final String[] args) {
|
||||||
HttpUtil.createServer(8888)
|
HttpUtil.createServer(8888)
|
||||||
.addFilter(new DefaultExceptionFilter())
|
.addFilter(new DefaultExceptionFilter())
|
||||||
.addAction("/", (req, res) -> {
|
.addAction("/", (req, res) -> {
|
||||||
throw new IOException("Test Exception");
|
throw new IORuntimeException("Test Exception");
|
||||||
})
|
})
|
||||||
.start();
|
.start();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import org.dromara.hutool.core.date.DateUtil;
|
|||||||
import org.dromara.hutool.core.lang.Console;
|
import org.dromara.hutool.core.lang.Console;
|
||||||
import org.dromara.hutool.core.map.multi.ListValueMap;
|
import org.dromara.hutool.core.map.multi.ListValueMap;
|
||||||
import org.dromara.hutool.http.HttpUtil;
|
import org.dromara.hutool.http.HttpUtil;
|
||||||
|
import org.dromara.hutool.http.server.engine.sun.SimpleServer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* http://localhost:8888/?name=hutool
|
* http://localhost:8888/?name=hutool
|
||||||
@@ -33,7 +34,7 @@ public class Issue3343Test {
|
|||||||
Console.log(" > from : " + req.getClientIP());
|
Console.log(" > from : " + req.getClientIP());
|
||||||
// 过滤器中获取请求参数
|
// 过滤器中获取请求参数
|
||||||
Console.log(" > params : " + req.getParams());
|
Console.log(" > params : " + req.getParams());
|
||||||
chain.doFilter(req.getHttpExchange());
|
chain.doFilter(req.getExchange());
|
||||||
});
|
});
|
||||||
|
|
||||||
server.addAction("/", Issue3343Test::index);
|
server.addAction("/", Issue3343Test::index);
|
||||||
@@ -41,9 +42,9 @@ public class Issue3343Test {
|
|||||||
server.start();
|
server.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void index(HttpServerRequest request, HttpServerResponse response) {
|
private static void index(final ServerRequest request, final ServerResponse response) {
|
||||||
// 具体逻辑中再次获取请求参数
|
// 具体逻辑中再次获取请求参数
|
||||||
ListValueMap<String, String> params = request.getParams();
|
final ListValueMap<String, String> params = request.getParams();
|
||||||
Console.log("index params: " + params);
|
Console.log("index params: " + params);
|
||||||
response.getWriter().write("GOT: " + params);
|
response.getWriter().write("GOT: " + params);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,18 +19,19 @@ package org.dromara.hutool.http.server;
|
|||||||
import org.dromara.hutool.core.data.id.IdUtil;
|
import org.dromara.hutool.core.data.id.IdUtil;
|
||||||
import org.dromara.hutool.http.HttpUtil;
|
import org.dromara.hutool.http.HttpUtil;
|
||||||
import org.dromara.hutool.http.meta.ContentType;
|
import org.dromara.hutool.http.meta.ContentType;
|
||||||
|
import org.dromara.hutool.http.server.engine.sun.SimpleServer;
|
||||||
|
|
||||||
public class Issue3723Test {
|
public class Issue3723Test {
|
||||||
public static void main(final String[] args) {
|
public static void main(final String[] args) {
|
||||||
final SimpleServer server = HttpUtil.createServer(8888);
|
final SimpleServer server = HttpUtil.createServer(8888);
|
||||||
server.addFilter((req, res, chain) -> {
|
server.addFilter((req, res, chain) -> {
|
||||||
final String requestId = IdUtil.fastSimpleUUID();
|
final String requestId = IdUtil.fastSimpleUUID();
|
||||||
req.getHttpExchange().setAttribute("requestId", requestId);
|
req.getExchange().setAttribute("requestId", requestId);
|
||||||
res.addHeader("X-Request-Id", requestId);
|
res.addHeader("X-Request-Id", requestId);
|
||||||
|
|
||||||
res.write("new Content");
|
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.addAction("/", (req, res)-> res.write("Hello Hutool Server", ContentType.JSON.getValue()));
|
||||||
server.start();
|
server.start();
|
||||||
|
|||||||
@@ -19,8 +19,10 @@ package org.dromara.hutool.http.server;
|
|||||||
import org.dromara.hutool.core.date.DateUtil;
|
import org.dromara.hutool.core.date.DateUtil;
|
||||||
import org.dromara.hutool.core.date.StopWatch;
|
import org.dromara.hutool.core.date.StopWatch;
|
||||||
import org.dromara.hutool.core.lang.Console;
|
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.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;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@@ -35,7 +37,7 @@ public class IssueI6Q30XTest {
|
|||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
final SimpleServer server = HttpUtil.createServer(8888);
|
final SimpleServer server = HttpUtil.createServer(8888);
|
||||||
server.addAction("/file", (request, response) -> {
|
server.addAction("/file", (request, response) -> {
|
||||||
Console.log(request.getHeaders().entrySet());
|
Console.log(((SunServerRequest)request).getHeaders().entrySet());
|
||||||
|
|
||||||
final StopWatch stopWatch = DateUtil.createStopWatch();
|
final StopWatch stopWatch = DateUtil.createStopWatch();
|
||||||
stopWatch.start();
|
stopWatch.start();
|
||||||
|
|||||||
@@ -19,25 +19,26 @@ package org.dromara.hutool.http.server;
|
|||||||
import org.dromara.hutool.core.lang.Console;
|
import org.dromara.hutool.core.lang.Console;
|
||||||
import org.dromara.hutool.http.HttpUtil;
|
import org.dromara.hutool.http.HttpUtil;
|
||||||
import org.dromara.hutool.http.meta.HeaderName;
|
import org.dromara.hutool.http.meta.HeaderName;
|
||||||
|
import org.dromara.hutool.http.meta.HttpStatus;
|
||||||
|
|
||||||
public class RedirectServerTest {
|
public class RedirectServerTest {
|
||||||
public static void main(final String[] args) {
|
public static void main(final String[] args) {
|
||||||
HttpUtil.createServer(8888).addAction("/redirect1", (request, response) -> {
|
HttpUtil.createServer(8888).addAction("/redirect1", (request, response) -> {
|
||||||
response.addHeader(HeaderName.LOCATION.getValue(),"http://localhost:8888/redirect2");
|
response.addHeader(HeaderName.LOCATION.getValue(),"http://localhost:8888/redirect2");
|
||||||
response.addHeader(HeaderName.SET_COOKIE.getValue(),"redirect1=1; path=/; HttpOnly");
|
response.addHeader(HeaderName.SET_COOKIE.getValue(),"redirect1=1; path=/; HttpOnly");
|
||||||
response.send(301);
|
response.setStatus(301);
|
||||||
}).addAction("/redirect2", (request, response) -> {
|
}).addAction("/redirect2", (request, response) -> {
|
||||||
response.addHeader(HeaderName.LOCATION.getValue(),"http://localhost:8888/redirect3");
|
response.addHeader(HeaderName.LOCATION.getValue(),"http://localhost:8888/redirect3");
|
||||||
response.addHeader(HeaderName.SET_COOKIE.getValue(), "redirect2=2; path=/; HttpOnly");
|
response.addHeader(HeaderName.SET_COOKIE.getValue(), "redirect2=2; path=/; HttpOnly");
|
||||||
response.send(301);
|
response.setStatus(301);
|
||||||
}).addAction("/redirect3", (request, response) -> {
|
}).addAction("/redirect3", (request, response) -> {
|
||||||
response.addHeader(HeaderName.LOCATION.getValue(),"http://localhost:8888/redirect4");
|
response.addHeader(HeaderName.LOCATION.getValue(),"http://localhost:8888/redirect4");
|
||||||
response.addHeader(HeaderName.SET_COOKIE.getValue(),"redirect3=3; path=/; HttpOnly");
|
response.addHeader(HeaderName.SET_COOKIE.getValue(),"redirect3=3; path=/; HttpOnly");
|
||||||
response.send(301);
|
response.setStatus(301);
|
||||||
}).addAction("/redirect4", (request, response) -> {
|
}).addAction("/redirect4", (request, response) -> {
|
||||||
final String cookie = request.getHeader(HeaderName.COOKIE);
|
final String cookie = request.getHeader(HeaderName.COOKIE);
|
||||||
Console.log(cookie);
|
Console.log(cookie);
|
||||||
response.sendOk();
|
response.setStatus(HttpStatus.HTTP_OK);
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ package org.dromara.hutool.http.server;
|
|||||||
import org.dromara.hutool.core.collection.ListUtil;
|
import org.dromara.hutool.core.collection.ListUtil;
|
||||||
import org.dromara.hutool.core.io.file.FileUtil;
|
import org.dromara.hutool.core.io.file.FileUtil;
|
||||||
import org.dromara.hutool.core.lang.Console;
|
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.ContentType;
|
||||||
import org.dromara.hutool.http.meta.HeaderName;
|
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 org.dromara.hutool.json.JSONUtil;
|
||||||
|
|
||||||
import java.net.HttpCookie;
|
import java.net.HttpCookie;
|
||||||
@@ -33,13 +34,13 @@ public class SimpleServerTest {
|
|||||||
HttpUtil.createServer(8888)
|
HttpUtil.createServer(8888)
|
||||||
.addFilter(((req, res, chain) -> {
|
.addFilter(((req, res, chain) -> {
|
||||||
Console.log("Filter: " + req.getPath());
|
Console.log("Filter: " + req.getPath());
|
||||||
chain.doFilter(req.getHttpExchange());
|
chain.doFilter(req.getExchange());
|
||||||
}))
|
}))
|
||||||
// 设置默认根目录,classpath/html
|
// 设置默认根目录,classpath/html
|
||||||
.setRoot(FileUtil.file("html"))
|
.setRoot(FileUtil.file("html"))
|
||||||
// get数据测试,返回请求的PATH
|
// get数据测试,返回请求的PATH
|
||||||
.addAction("/get", (request, response) ->
|
.addAction("/get", (request, response) ->
|
||||||
response.write(request.getURI().toString(), ContentType.TEXT_PLAIN.toString())
|
response.write(((SunServerRequest)request).getURI().toString(), ContentType.TEXT_PLAIN.toString())
|
||||||
)
|
)
|
||||||
// 返回JSON数据测试
|
// 返回JSON数据测试
|
||||||
.addAction("/restTest", (request, response) -> {
|
.addAction("/restTest", (request, response) -> {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user