diff --git a/pom.xml b/pom.xml index a65f28eb..135311fb 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ scx pom - 3.1.3 + 3.1.4 SCX https://github.com/scx567888/scx diff --git a/scx-ansi/pom.xml b/scx-ansi/pom.xml index 2c3b67c2..5c8b8ccd 100644 --- a/scx-ansi/pom.xml +++ b/scx-ansi/pom.xml @@ -6,7 +6,7 @@ cool.scx scx - 3.1.3 + 3.1.4 scx-ansi diff --git a/scx-common/pom.xml b/scx-common/pom.xml index 3159ba34..95903c4b 100644 --- a/scx-common/pom.xml +++ b/scx-common/pom.xml @@ -6,7 +6,7 @@ cool.scx scx - 3.1.3 + 3.1.4 scx-common diff --git a/scx-config/pom.xml b/scx-config/pom.xml index 15fddb84..5ea55043 100644 --- a/scx-config/pom.xml +++ b/scx-config/pom.xml @@ -6,7 +6,7 @@ cool.scx scx - 3.1.3 + 3.1.4 scx-config diff --git a/scx-core/pom.xml b/scx-core/pom.xml index fbf71a2d..b3223337 100644 --- a/scx-core/pom.xml +++ b/scx-core/pom.xml @@ -6,7 +6,7 @@ cool.scx scx - 3.1.3 + 3.1.4 scx-core diff --git a/scx-core/src/main/java/cool/scx/core/ScxVersion.java b/scx-core/src/main/java/cool/scx/core/ScxVersion.java index 2ed15659..26ebb2ce 100644 --- a/scx-core/src/main/java/cool/scx/core/ScxVersion.java +++ b/scx-core/src/main/java/cool/scx/core/ScxVersion.java @@ -13,7 +13,7 @@ public class ScxVersion { /** * SCX 版本号 */ - public static final String SCX_VERSION = "3.1.3"; + public static final String SCX_VERSION = "3.1.4"; /** * 在控制台上打印 banner diff --git a/scx-data-jdbc/pom.xml b/scx-data-jdbc/pom.xml index 46b1498c..baac4f1b 100644 --- a/scx-data-jdbc/pom.xml +++ b/scx-data-jdbc/pom.xml @@ -6,7 +6,7 @@ cool.scx scx - 3.1.3 + 3.1.4 scx-data-jdbc diff --git a/scx-data-mysql-x/pom.xml b/scx-data-mysql-x/pom.xml index 60379049..69bf243e 100644 --- a/scx-data-mysql-x/pom.xml +++ b/scx-data-mysql-x/pom.xml @@ -6,7 +6,7 @@ cool.scx scx - 3.1.3 + 3.1.4 scx-data-mysql-x diff --git a/scx-data/pom.xml b/scx-data/pom.xml index a6c8dab6..700c608a 100644 --- a/scx-data/pom.xml +++ b/scx-data/pom.xml @@ -6,7 +6,7 @@ cool.scx scx - 3.1.3 + 3.1.4 scx-data diff --git a/scx-ext/pom.xml b/scx-ext/pom.xml index 422facce..2a714b95 100644 --- a/scx-ext/pom.xml +++ b/scx-ext/pom.xml @@ -6,7 +6,7 @@ cool.scx scx - 3.1.3 + 3.1.4 scx-ext diff --git a/scx-ffm/pom.xml b/scx-ffm/pom.xml index 7631cfde..5db9ae2c 100644 --- a/scx-ffm/pom.xml +++ b/scx-ffm/pom.xml @@ -6,7 +6,7 @@ cool.scx scx - 3.1.3 + 3.1.4 scx-ffm diff --git a/scx-http-helidon/pom.xml b/scx-http-helidon/pom.xml index 17af6ebe..40550011 100644 --- a/scx-http-helidon/pom.xml +++ b/scx-http-helidon/pom.xml @@ -6,7 +6,7 @@ cool.scx scx - 3.1.3 + 3.1.4 scx-http-helidon diff --git a/scx-http-helidon/src/main/java/cool/scx/http/helidon/HelidonHelper.java b/scx-http-helidon/src/main/java/cool/scx/http/helidon/HelidonHelper.java index 714b7de8..869e2794 100644 --- a/scx-http-helidon/src/main/java/cool/scx/http/helidon/HelidonHelper.java +++ b/scx-http-helidon/src/main/java/cool/scx/http/helidon/HelidonHelper.java @@ -3,7 +3,9 @@ import cool.scx.http.ScxHttpHeaders; import cool.scx.http.ScxHttpHeadersWritable; import cool.scx.reflect.ReflectFactory; -import io.helidon.http.*; +import io.helidon.http.Header; +import io.helidon.http.Headers; +import io.helidon.http.PathMatchers; import io.helidon.webserver.websocket.WsRoute; import io.helidon.webserver.websocket.WsRouting; import io.helidon.websocket.WsListener; @@ -41,11 +43,4 @@ public static ScxHttpHeadersWritable convertHeaders(Headers o) { return h; } - public static void updateHeaders(ScxHttpHeaders o, ServerResponseHeaders u) { - u.clear(); - for (var header : o) { - u.add(HeaderNames.create(header.getKey().value()), header.getValue().toArray(String[]::new)); - } - } - } diff --git a/scx-http-helidon/src/test/java/cool/scx/http/helidon/test/Apple.java b/scx-http-helidon/src/test/java/cool/scx/http/helidon/test/Apple.java new file mode 100644 index 00000000..695ab5fb --- /dev/null +++ b/scx-http-helidon/src/test/java/cool/scx/http/helidon/test/Apple.java @@ -0,0 +1,5 @@ +package cool.scx.http.helidon.test; + +public record Apple(String color, String name, int weight) { + +} diff --git a/scx-http-helidon/src/test/java/cool/scx/http/helidon/test/ClientTest.java b/scx-http-helidon/src/test/java/cool/scx/http/helidon/test/ClientTest.java index 091b00de..df98e841 100644 --- a/scx-http-helidon/src/test/java/cool/scx/http/helidon/test/ClientTest.java +++ b/scx-http-helidon/src/test/java/cool/scx/http/helidon/test/ClientTest.java @@ -1,11 +1,15 @@ package cool.scx.http.helidon.test; +import cool.scx.http.MediaType; import cool.scx.http.ScxHttpServerOptions; import cool.scx.http.helidon.HelidonHttpClient; import cool.scx.http.helidon.HelidonHttpServer; +import cool.scx.http.media.form_params.FormParams; import java.io.IOException; +import static cool.scx.http.HttpFieldName.ACCEPT; + public class ClientTest { public static void main(String[] args) throws IOException, InterruptedException { @@ -18,7 +22,8 @@ public static void test1() throws IOException, InterruptedException { var httpServer = new HelidonHttpServer(new ScxHttpServerOptions().setPort(8990)); httpServer.requestHandler(c -> { System.out.println(c.uri()); - c.response().send("Hi Client !!!"); + System.out.println(c.body().asFormParams()); + c.response().send(new Apple("red", "red apple", 99)); }); httpServer.webSocketHandler(c -> { System.out.println(c.uri()); @@ -32,7 +37,7 @@ public static void test1() throws IOException, InterruptedException { public static void test2() { var httpClient = new HelidonHttpClient(); - var webSocketBuilder = httpClient.webSocket().uri("http://localhost:8990/中:文|路径/ddd?查询=🎈🎈|🎈"); + var webSocketBuilder = httpClient.webSocket().uri("http://localhost:8990/中:文|路@径/ddd?查询=🎈🎈|🎈#🎃🎃"); webSocketBuilder.onConnect(webSocket -> { webSocket.onTextMessage(t -> { System.out.println(t); @@ -45,9 +50,17 @@ public static void test2() { public static void test3() { var httpClient = new HelidonHttpClient(); var response = httpClient.request() - .uri("http://localhost:8990/中:文|路径/ddd?查询=🎈🎈|🎈") - .send(); + //支持 ACCEPT + .addHeader(ACCEPT, MediaType.APPLICATION_XML.value()) + .uri("http://localhost:8990/中:文|路@径/ddd?查询=🎈🎈|🎈#🎃🎃") + .send(new FormParams() + .add("中文|||/ |||===", "嘎 嘎 嘎🧶🧶🛒") + .add("🏓🏓🏓", "!@#%^%&*%%") + ); + //可以用不同的方式重复读取 + var apple = response.body().asObject(Apple.class); var string = response.body().asString(); + var jsonNode = response.body().asJsonNode(); System.out.println(string); } diff --git a/scx-http/pom.xml b/scx-http/pom.xml index 452812a2..6fb124d2 100644 --- a/scx-http/pom.xml +++ b/scx-http/pom.xml @@ -6,7 +6,7 @@ cool.scx scx - 3.1.3 + 3.1.4 scx-http diff --git a/scx-http/src/main/java/cool/scx/http/ScxHttpBody.java b/scx-http/src/main/java/cool/scx/http/ScxHttpBody.java index e36fa8f4..75e2090e 100644 --- a/scx-http/src/main/java/cool/scx/http/ScxHttpBody.java +++ b/scx-http/src/main/java/cool/scx/http/ScxHttpBody.java @@ -1,6 +1,7 @@ package cool.scx.http; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import cool.scx.http.media.MediaReader; import cool.scx.http.media.form_params.FormParams; import cool.scx.http.media.multi_part.MultiPart; @@ -16,6 +17,7 @@ import static cool.scx.http.media.byte_array.ByteArrayReader.BYTE_ARRAY_READER; import static cool.scx.http.media.form_params.FormParamsReader.FORM_PARAMS_READER; +import static cool.scx.http.media.json_node.JsonNodeReader.JSON_NODE_READER; import static cool.scx.http.media.multi_part.MultiPartStreamCachedReader.MULTI_PART_READER_CACHED; import static cool.scx.http.media.multi_part.MultiPartStreamReader.MULTI_PART_READER; import static cool.scx.http.media.string.StringReader.STRING_READER; @@ -27,7 +29,11 @@ public interface ScxHttpBody { InputStream inputStream(); - T as(MediaReader t); + ScxHttpHeaders headers(); + + default T as(MediaReader t) { + return t.read(inputStream(), headers()); + } default byte[] asBytes() { return as(BYTE_ARRAY_READER); @@ -61,6 +67,10 @@ default Path asPath(Path path, OpenOption... options) { return as(new PathReader(path, options)); } + default JsonNode asJsonNode() { + return as(JSON_NODE_READER); + } + default T asObject(Class c) { return as(new ObjectReader<>(c)); } diff --git a/scx-http/src/main/java/cool/scx/http/ScxHttpBodyImpl.java b/scx-http/src/main/java/cool/scx/http/ScxHttpBodyImpl.java index 74dd5259..a7074197 100644 --- a/scx-http/src/main/java/cool/scx/http/ScxHttpBodyImpl.java +++ b/scx-http/src/main/java/cool/scx/http/ScxHttpBodyImpl.java @@ -1,7 +1,7 @@ package cool.scx.http; -import cool.scx.http.media.MediaReader; - +import java.io.BufferedInputStream; +import java.io.IOException; import java.io.InputStream; public class ScxHttpBodyImpl implements ScxHttpBody { @@ -9,30 +9,34 @@ public class ScxHttpBodyImpl implements ScxHttpBody { private final InputStream inputStream; private final ScxHttpHeaders headers; - // 简单做一个缓存 - private String cacheString; + // InputStream 的缓存 + private final BufferedInputStream bufferedInputStream; public ScxHttpBodyImpl(InputStream inputStream, ScxHttpHeaders headers) { this.inputStream = inputStream; + this.bufferedInputStream = new BufferedInputStream(inputStream); this.headers = headers; + this.bufferedInputStream.mark(0); } @Override - public InputStream inputStream() { - return inputStream; + public ScxHttpHeaders headers() { + return headers; } @Override - public T as(MediaReader t) { - return t.read(inputStream, headers); + public InputStream inputStream() { + try { + bufferedInputStream.reset(); + } catch (IOException e) { + throw new RuntimeException(e); + } + return bufferedInputStream; } @Override - public String asString() { - if (cacheString == null) { - cacheString = ScxHttpBody.super.asString(); - } - return cacheString; + public String toString() { + return asString(); } } diff --git a/scx-http/src/main/java/cool/scx/http/ScxHttpClientRequest.java b/scx-http/src/main/java/cool/scx/http/ScxHttpClientRequest.java index 53b28cde..16e03f0f 100644 --- a/scx-http/src/main/java/cool/scx/http/ScxHttpClientRequest.java +++ b/scx-http/src/main/java/cool/scx/http/ScxHttpClientRequest.java @@ -1,11 +1,15 @@ package cool.scx.http; +import com.fasterxml.jackson.databind.JsonNode; import cool.scx.http.content_type.ContentType; import cool.scx.http.cookie.Cookie; import cool.scx.http.media.MediaWriter; import cool.scx.http.media.byte_array.ByteArrayWriter; import cool.scx.http.media.empty.EmptyWriter; +import cool.scx.http.media.form_params.FormParams; +import cool.scx.http.media.form_params.FormParamsWriter; import cool.scx.http.media.input_stream.InputStreamWriter; +import cool.scx.http.media.json_node.JsonNodeWriter; import cool.scx.http.media.multi_part.MultiPart; import cool.scx.http.media.multi_part.MultiPartWriter; import cool.scx.http.media.object.ObjectWriter; @@ -72,10 +76,18 @@ default ScxHttpClientResponse send(InputStream inputStream) { return send(new InputStreamWriter(inputStream)); } + default ScxHttpClientResponse send(FormParams formParams) { + return send(new FormParamsWriter(formParams)); + } + default ScxHttpClientResponse send(MultiPart multiPart) { return send(new MultiPartWriter(multiPart)); } + default ScxHttpClientResponse send(JsonNode jsonNode) { + return send(new JsonNodeWriter(jsonNode)); + } + default ScxHttpClientResponse send(Object object) { return send(new ObjectWriter(object)); } diff --git a/scx-http/src/main/java/cool/scx/http/ScxHttpServerResponse.java b/scx-http/src/main/java/cool/scx/http/ScxHttpServerResponse.java index 203532ef..3fc13992 100644 --- a/scx-http/src/main/java/cool/scx/http/ScxHttpServerResponse.java +++ b/scx-http/src/main/java/cool/scx/http/ScxHttpServerResponse.java @@ -1,11 +1,15 @@ package cool.scx.http; +import com.fasterxml.jackson.databind.JsonNode; import cool.scx.http.content_type.ContentType; import cool.scx.http.cookie.Cookie; import cool.scx.http.media.MediaWriter; import cool.scx.http.media.byte_array.ByteArrayWriter; import cool.scx.http.media.empty.EmptyWriter; +import cool.scx.http.media.form_params.FormParams; +import cool.scx.http.media.form_params.FormParamsWriter; import cool.scx.http.media.input_stream.InputStreamWriter; +import cool.scx.http.media.json_node.JsonNodeWriter; import cool.scx.http.media.multi_part.MultiPart; import cool.scx.http.media.multi_part.MultiPartWriter; import cool.scx.http.media.object.ObjectWriter; @@ -77,10 +81,18 @@ default void send(InputStream inputStream) { send(new InputStreamWriter(inputStream)); } + default void send(FormParams formParams) { + send(new FormParamsWriter(formParams)); + } + default void send(MultiPart multiPart) { send(new MultiPartWriter(multiPart)); } + default void send(JsonNode jsonNode) { + send(new JsonNodeWriter(jsonNode)); + } + default void send(Object object) { send(new ObjectWriter(object)); } diff --git a/scx-http/src/main/java/cool/scx/http/media/form_params/FormParams.java b/scx-http/src/main/java/cool/scx/http/media/form_params/FormParams.java index 30d10ad8..f42ab4ad 100644 --- a/scx-http/src/main/java/cool/scx/http/media/form_params/FormParams.java +++ b/scx-http/src/main/java/cool/scx/http/media/form_params/FormParams.java @@ -4,4 +4,24 @@ public class FormParams extends ParametersImpl { + @Override + public FormParams set(String name, String... value) { + return (FormParams) super.set(name, value); + } + + @Override + public FormParams add(String name, String... value) { + return (FormParams) super.add(name, value); + } + + @Override + public FormParams remove(String name) { + return (FormParams) super.remove(name); + } + + @Override + public FormParams clear() { + return (FormParams) super.clear(); + } + } diff --git a/scx-http/src/main/java/cool/scx/http/media/form_params/FormParamsHelper.java b/scx-http/src/main/java/cool/scx/http/media/form_params/FormParamsHelper.java new file mode 100644 index 00000000..a0a1cb9f --- /dev/null +++ b/scx-http/src/main/java/cool/scx/http/media/form_params/FormParamsHelper.java @@ -0,0 +1,39 @@ +package cool.scx.http.media.form_params; + +import java.util.ArrayList; + +import static java.net.URLDecoder.decode; +import static java.net.URLEncoder.encode; +import static java.nio.charset.StandardCharsets.UTF_8; + +public class FormParamsHelper { + + public static String encodeFormParams(FormParams formParams) { + var l = new ArrayList(); + for (var formParam : formParams) { + var key = formParam.getKey(); + var values = formParam.getValue(); + for (var value : values) { + var k = encode(key, UTF_8); + var v = encode(value, UTF_8); + l.add(k + "=" + v); + } + } + return String.join("&", l); + } + + public static FormParams decodeFormParams(String str) { + var formParams = new FormParams(); + var pairs = str.split("&"); + for (var pair : pairs) { + var keyValue = pair.split("=", 2); + if (keyValue.length == 2) { + var key = decode(keyValue[0], UTF_8); + var value = decode(keyValue[1], UTF_8); + formParams.add(key, value); + } + } + return formParams; + } + +} diff --git a/scx-http/src/main/java/cool/scx/http/media/form_params/FormParamsReader.java b/scx-http/src/main/java/cool/scx/http/media/form_params/FormParamsReader.java index 48a69cf0..8a2fd0a0 100644 --- a/scx-http/src/main/java/cool/scx/http/media/form_params/FormParamsReader.java +++ b/scx-http/src/main/java/cool/scx/http/media/form_params/FormParamsReader.java @@ -4,9 +4,8 @@ import cool.scx.http.media.MediaReader; import java.io.InputStream; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; +import static cool.scx.http.media.form_params.FormParamsHelper.decodeFormParams; import static cool.scx.http.media.string.StringReader.STRING_READER; public class FormParamsReader implements MediaReader { @@ -19,18 +18,8 @@ private FormParamsReader() { @Override public FormParams read(InputStream inputStream, ScxHttpHeaders headers) { - var formParams = new FormParams(); var str = STRING_READER.read(inputStream, headers); - var pairs = str.split("&"); - for (var pair : pairs) { - var keyValue = pair.split("="); - if (keyValue.length == 2) { - String key = URLDecoder.decode(keyValue[0], StandardCharsets.UTF_8); - String value = URLDecoder.decode(keyValue[1], StandardCharsets.UTF_8); - formParams.add(key, value); - } - } - return formParams; + return decodeFormParams(str); } } diff --git a/scx-http/src/main/java/cool/scx/http/media/form_params/FormParamsWriter.java b/scx-http/src/main/java/cool/scx/http/media/form_params/FormParamsWriter.java new file mode 100644 index 00000000..22374468 --- /dev/null +++ b/scx-http/src/main/java/cool/scx/http/media/form_params/FormParamsWriter.java @@ -0,0 +1,44 @@ +package cool.scx.http.media.form_params; + +import cool.scx.http.MediaType; +import cool.scx.http.ScxHttpHeaders; +import cool.scx.http.ScxHttpHeadersWritable; +import cool.scx.http.content_type.ContentType; +import cool.scx.http.media.MediaWriter; + +import java.io.IOException; +import java.io.OutputStream; + +import static cool.scx.http.media.form_params.FormParamsHelper.encodeFormParams; +import static java.nio.charset.StandardCharsets.UTF_8; + +public class FormParamsWriter implements MediaWriter { + + private final FormParams formParams; + private final byte[] bytes; + + public FormParamsWriter(FormParams formParams) { + this.formParams = formParams; + this.bytes = encodeFormParams(formParams).getBytes(UTF_8); + } + + @Override + public void beforeWrite(ScxHttpHeadersWritable headersWritable, ScxHttpHeaders headers) { + if (headersWritable.contentLength() == null) { + headersWritable.contentLength(bytes.length); + } + if (headersWritable.contentType() == null) { + headersWritable.contentType(ContentType.of(MediaType.APPLICATION_X_WWW_FORM_URLENCODED)); + } + } + + @Override + public void write(OutputStream outputStream) { + try (outputStream) { + outputStream.write(bytes); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/scx-http/src/main/java/cool/scx/http/media/json_node/JsonNodeReader.java b/scx-http/src/main/java/cool/scx/http/media/json_node/JsonNodeReader.java new file mode 100644 index 00000000..1bd4aef6 --- /dev/null +++ b/scx-http/src/main/java/cool/scx/http/media/json_node/JsonNodeReader.java @@ -0,0 +1,53 @@ +package cool.scx.http.media.json_node; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import cool.scx.http.ScxHttpHeaders; +import cool.scx.http.exception.BadRequestException; +import cool.scx.http.media.MediaReader; + +import java.io.InputStream; + +import static cool.scx.common.util.ObjectUtils.jsonMapper; +import static cool.scx.common.util.ObjectUtils.xmlMapper; +import static cool.scx.http.MediaType.APPLICATION_JSON; +import static cool.scx.http.MediaType.APPLICATION_XML; +import static cool.scx.http.media.string.StringReader.STRING_READER; + +public class JsonNodeReader implements MediaReader { + + public static final JsonNodeReader JSON_NODE_READER = new JsonNodeReader(); + + @Override + public JsonNode read(InputStream inputStream, ScxHttpHeaders requestHeaders) { + var str = STRING_READER.read(inputStream, requestHeaders); + var contentType = requestHeaders.contentType(); + var mediaType = contentType != null ? contentType.mediaType() : null; + //猜测一下 + if (APPLICATION_JSON.equals(mediaType)) { + try { + return jsonMapper().readTree(str); + } catch (JsonProcessingException e) { + throw new BadRequestException(e); + } + } + if (APPLICATION_XML.equals(mediaType)) { + try { + return xmlMapper().readTree(str); + } catch (JsonProcessingException e) { + throw new BadRequestException(e); + } + } + try { //先尝试以 json 格式进行尝试转换 + return jsonMapper().readTree(str); + } catch (Exception exception) { + try {//再尝试以 xml 的格式进行转换 + return xmlMapper().readTree(str); + } catch (JsonProcessingException e) { + // json 和 xml 均转换失败 直接报错 + throw new BadRequestException(); + } + } + } + +} diff --git a/scx-http/src/main/java/cool/scx/http/media/json_node/JsonNodeWriter.java b/scx-http/src/main/java/cool/scx/http/media/json_node/JsonNodeWriter.java new file mode 100644 index 00000000..675b9579 --- /dev/null +++ b/scx-http/src/main/java/cool/scx/http/media/json_node/JsonNodeWriter.java @@ -0,0 +1,84 @@ +package cool.scx.http.media.json_node; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import cool.scx.http.ScxHttpHeaders; +import cool.scx.http.ScxHttpHeadersWritable; +import cool.scx.http.content_type.ContentType; +import cool.scx.http.content_type.ContentTypeWritable; +import cool.scx.http.media.MediaWriter; + +import java.io.OutputStream; + +import static cool.scx.common.util.ObjectUtils.jsonMapper; +import static cool.scx.common.util.ObjectUtils.xmlMapper; +import static cool.scx.http.MediaType.APPLICATION_JSON; +import static cool.scx.http.MediaType.APPLICATION_XML; +import static java.nio.charset.StandardCharsets.UTF_8; + +public class JsonNodeWriter implements MediaWriter { + + private final JsonNode jsonNode; + private byte[] data; + + public JsonNodeWriter(JsonNode jsonNode) { + this.jsonNode = jsonNode; + this.data = null; + } + + public static ContentTypeWritable trySetContentType(ScxHttpHeadersWritable headersWritable, ScxHttpHeaders headers) { + //已经设置了则跳过设置 + if (headersWritable.contentType() != null) { + return headersWritable.contentType(); + } + var accepts = headers.accepts(); + // 如果客户端未指明 accepts 则返回 json + if (accepts == null) { + headersWritable.contentType(ContentType.of(APPLICATION_JSON).charset(UTF_8)); + return headersWritable.contentType(); + } + //如果 拥有 XML 或者 JSON 则返回二者其一 + for (var accept : accepts) { + if (accept.mediaType() == APPLICATION_XML) { + headersWritable.contentType(ContentType.of(APPLICATION_XML).charset(UTF_8)); + return headersWritable.contentType(); + } else if (accept.mediaType() == APPLICATION_JSON) { + headersWritable.contentType(ContentType.of(APPLICATION_JSON).charset(UTF_8)); + return headersWritable.contentType(); + } + } + //否则回退到 JSON + headersWritable.contentType(ContentType.of(APPLICATION_JSON).charset(UTF_8)); + return headersWritable.contentType(); + } + + @Override + public void beforeWrite(ScxHttpHeadersWritable headersWritable, ScxHttpHeaders headers) { + var contentType = trySetContentType(headersWritable, headers); + //根据类型确定内容长度 + try { + if (contentType.mediaType() == APPLICATION_JSON) { + data = jsonMapper().writeValueAsBytes(jsonNode); + } else if (contentType.mediaType() == APPLICATION_XML) { + data = xmlMapper().writeValueAsBytes(jsonNode); + } else { + throw new IllegalArgumentException("Unsupported media type: " + contentType.mediaType()); + } + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + if (headersWritable.contentLength() == null) { + headersWritable.contentLength(data.length); + } + } + + @Override + public void write(OutputStream outputStream) { + try (outputStream) { + outputStream.write(data); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/scx-http/src/main/java/cool/scx/http/media/object/ObjectReader.java b/scx-http/src/main/java/cool/scx/http/media/object/ObjectReader.java index 0268dc2a..76599503 100644 --- a/scx-http/src/main/java/cool/scx/http/media/object/ObjectReader.java +++ b/scx-http/src/main/java/cool/scx/http/media/object/ObjectReader.java @@ -1,20 +1,14 @@ package cool.scx.http.media.object; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JavaType; import cool.scx.common.util.ObjectUtils; import cool.scx.http.ScxHttpHeaders; -import cool.scx.http.exception.BadRequestException; import cool.scx.http.media.MediaReader; import java.io.InputStream; -import static cool.scx.common.util.ObjectUtils.jsonMapper; -import static cool.scx.common.util.ObjectUtils.xmlMapper; -import static cool.scx.http.MediaType.APPLICATION_JSON; -import static cool.scx.http.MediaType.APPLICATION_XML; -import static cool.scx.http.media.string.StringReader.STRING_READER; +import static cool.scx.http.media.json_node.JsonNodeReader.JSON_NODE_READER; public class ObjectReader implements MediaReader { @@ -34,44 +28,8 @@ public ObjectReader(JavaType clazz) { @Override public T read(InputStream inputStream, ScxHttpHeaders requestHeaders) { - var str = STRING_READER.read(inputStream, requestHeaders); - var contentType = requestHeaders.contentType(); - var mediaType = contentType != null ? contentType.mediaType() : null; - //猜测一下 - return switch (mediaType) { - case APPLICATION_JSON -> readJson(str); - case APPLICATION_XML -> readXml(str); - case null, default -> tryReadOrTextNode(str); - }; - } - - public T readJson(String jsonStr) { - try { - return jsonMapper().readValue(jsonStr, type); - } catch (JsonProcessingException e) { - throw new BadRequestException(e); - } - } - - public T readXml(String xmlStr) { - try { - return xmlMapper().readValue(xmlStr, type); - } catch (JsonProcessingException e) { - throw new BadRequestException(e); - } - } - - public T tryReadOrTextNode(String str) { - try { //先尝试以 json 格式进行尝试转换 - return jsonMapper().readValue(str, type); - } catch (Exception exception) { - try {//再尝试以 xml 的格式进行转换 - return xmlMapper().readValue(str, type); - } catch (JsonProcessingException e) { - // json 和 xml 均转换失败 直接报错 - throw new BadRequestException(); - } - } + var jsonNode = JSON_NODE_READER.read(inputStream, requestHeaders); + return ObjectUtils.convertValue(jsonNode, type); } } diff --git a/scx-http/src/main/java/cool/scx/http/media/object/ObjectWriter.java b/scx-http/src/main/java/cool/scx/http/media/object/ObjectWriter.java index 3d4dfa4f..fbcd20bd 100644 --- a/scx-http/src/main/java/cool/scx/http/media/object/ObjectWriter.java +++ b/scx-http/src/main/java/cool/scx/http/media/object/ObjectWriter.java @@ -1,76 +1,34 @@ package cool.scx.http.media.object; +import com.fasterxml.jackson.databind.JsonNode; +import cool.scx.common.util.ObjectUtils; import cool.scx.http.ScxHttpHeaders; import cool.scx.http.ScxHttpHeadersWritable; -import cool.scx.http.content_type.ContentType; -import cool.scx.http.content_type.ContentTypeWritable; import cool.scx.http.media.MediaWriter; +import cool.scx.http.media.json_node.JsonNodeWriter; import java.io.OutputStream; -import static cool.scx.common.util.ObjectUtils.toJson; -import static cool.scx.common.util.ObjectUtils.toXml; -import static cool.scx.http.MediaType.APPLICATION_JSON; -import static cool.scx.http.MediaType.APPLICATION_XML; -import static java.nio.charset.StandardCharsets.UTF_8; - public class ObjectWriter implements MediaWriter { private final Object object; - private byte[] data; + private final JsonNode jsonNode; + private final JsonNodeWriter jsonNodeWriter; public ObjectWriter(Object object) { this.object = object; - } - - public static ContentTypeWritable trySetContentType(ScxHttpHeadersWritable headersWritable, ScxHttpHeaders headers) { - var x = ContentType.of(APPLICATION_XML).charset(UTF_8); - var j = ContentType.of(APPLICATION_JSON).charset(UTF_8); - //尝试设置 contentType - if (headersWritable.contentType() == null) { - var accepts = headers.accepts(); - // 只有明确指定 接受参数是 application/xml 的才返回 xml - if (accepts != null) { - for (var accept : accepts) { - if (accept.mediaType() == APPLICATION_XML) { - headersWritable.contentType(x); - return x; - } - } - } - headersWritable.contentType(j); - } - return headersWritable.contentType(); + this.jsonNode = ObjectUtils.jsonMapper().valueToTree(object); + this.jsonNodeWriter = new JsonNodeWriter(this.jsonNode); } @Override public void beforeWrite(ScxHttpHeadersWritable headersWritable, ScxHttpHeaders headers) { - //尝试设置 contentType - var ccc = trySetContentType(headersWritable, headers); - //根据类型确定内容长度 - try { - if (ccc.mediaType() == APPLICATION_JSON) { - data = toJson(object).getBytes(); - } else if (ccc.mediaType() == APPLICATION_XML) { - data = toXml(object).getBytes(); - } else { - throw new RuntimeException("Unsupported media type: " + ccc.mediaType()); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - if (headersWritable.contentLength() == null) { - headersWritable.contentLength(data.length); - } + jsonNodeWriter.beforeWrite(headersWritable, headers); } @Override public void write(OutputStream outputStream) { - try (outputStream) { - outputStream.write(data); - } catch (Exception e) { - throw new RuntimeException(e); - } + jsonNodeWriter.write(outputStream); } } diff --git a/scx-http/src/main/java/cool/scx/http/uri/ScxURI.java b/scx-http/src/main/java/cool/scx/http/uri/ScxURI.java index 5a597d55..9d665c2a 100644 --- a/scx-http/src/main/java/cool/scx/http/uri/ScxURI.java +++ b/scx-http/src/main/java/cool/scx/http/uri/ScxURI.java @@ -4,7 +4,7 @@ import java.net.URI; -import static cool.scx.http.uri.ScxURIHelper.parseURI; +import static cool.scx.http.uri.URIEncoder.encodeURI; /** * ScxURI @@ -16,7 +16,7 @@ static ScxURIWritable of() { } static ScxURIWritable of(String uri) { - return of(parseURI(uri)); + return of(URI.create(encodeURI(uri))); } static ScxURIWritable of(URI u) { @@ -55,7 +55,7 @@ default String encode() { } default String encode(boolean uriEncoding) { - return ScxURIHelper.encodeURI(this, uriEncoding); + return ScxURIHelper.encodeScxURI(this, uriEncoding); } } diff --git a/scx-http/src/main/java/cool/scx/http/uri/ScxURIHelper.java b/scx-http/src/main/java/cool/scx/http/uri/ScxURIHelper.java index 29b5feb7..a385fb7e 100644 --- a/scx-http/src/main/java/cool/scx/http/uri/ScxURIHelper.java +++ b/scx-http/src/main/java/cool/scx/http/uri/ScxURIHelper.java @@ -3,11 +3,9 @@ import cool.scx.http.Parameters; import cool.scx.http.ParametersWritable; -import java.net.URI; -import java.net.URLEncoder; import java.util.ArrayList; -import static java.nio.charset.StandardCharsets.UTF_8; +import static cool.scx.http.uri.URIEncoder.encodeURIComponent; /** * URIHelper @@ -36,8 +34,8 @@ public static String encodeQuery(Parameters query, boolean uriEn var value = v.getValue(); for (var s : value) { if (uriEncoding) { - var kk = URLEncoder.encode(key, UTF_8); - var vv = URLEncoder.encode(s, UTF_8); + var kk = encodeURIComponent(key); + var vv = encodeURIComponent(s); l.add(kk + "=" + vv); } else { l.add(key + "=" + s); @@ -47,7 +45,7 @@ public static String encodeQuery(Parameters query, boolean uriEn return String.join("&", l); } - public static String encodeURI(ScxURI uri, boolean uriEncoding) { + public static String encodeScxURI(ScxURI uri, boolean uriEncoding) { var scheme = uri.scheme(); var host = uri.host(); var port = uri.port(); @@ -80,7 +78,7 @@ public static String encodeURI(ScxURI uri, boolean uriEncoding) { //是否需要进行 uri 编码 if (uriEncoding) { //我们不编码 "/" - sb.append(URLEncoder.encode(path, UTF_8).replace("%2F", "/")); + sb.append(URIEncoder.encodeURI(path)); } else { sb.append(path); } @@ -96,7 +94,7 @@ public static String encodeURI(ScxURI uri, boolean uriEncoding) { sb.append('#'); //是否需要进行 uri 编码 if (uriEncoding) { - sb.append(URLEncoder.encode(fragment, UTF_8)); + sb.append(URIEncoder.encodeURI(fragment)); } else { sb.append(fragment); } @@ -104,23 +102,4 @@ public static String encodeURI(ScxURI uri, boolean uriEncoding) { return sb.toString(); } - public static URI parseURI(String uriStr) { - // 解析 URI - return URI.create(encodeUri(uriStr)); - } - - public static String encodeUri(String uriStr) { - // 预处理 URI,将特殊字符进行编码 - uriStr = URLEncoder.encode(uriStr, UTF_8) - .replace("%3A", ":") - .replace("%2F", "/") - .replace("%3F", "?") - .replace("%3D", "=") - .replace("%26", "&") - .replace("%23", "#"); - - // 解析 URI - return uriStr; - } - } diff --git a/scx-http/src/main/java/cool/scx/http/uri/URIEncoder.java b/scx-http/src/main/java/cool/scx/http/uri/URIEncoder.java new file mode 100644 index 00000000..1f99c9fe --- /dev/null +++ b/scx-http/src/main/java/cool/scx/http/uri/URIEncoder.java @@ -0,0 +1,86 @@ +package cool.scx.http.uri; + +import java.nio.CharBuffer; + +import static java.nio.charset.StandardCharsets.UTF_8; + +public class URIEncoder { + + private static final char[] HEX_DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + //按照 codePoint 排列 + private static final boolean[] DONT_NEED_ENCODING_URI = initDontNeedEncoding( + "!", "#", "$", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", + ":", ";", "=", "?", "@", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + "_", + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", + "~" + ); + + private static final boolean[] DONT_NEED_ENCODING_URI_COMPONENT = initDontNeedEncoding( + "!", "'", "(", ")", "*", "-", ".", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + "_", + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", + "~" + ); + + /** + * 此方法会生成一个 128 位的 boolean 数组 索引表示 codePoint 值表示是否需要编码 + * + * @param c a + * @return a + */ + private static boolean[] initDontNeedEncoding(String... c) { + var table = new boolean[128]; + for (var s : c) { + var i = s.codePointAt(0); + table[i] = true; + } + return table; + } + + private static String encode(String str, boolean[] dontNeedEncoding) { + var sb = new StringBuilder(); + var codePoints = str.codePoints().toArray(); + for (int codePoint : codePoints) { + //如果 不需要编码 + if (codePoint < 128 && dontNeedEncoding[codePoint]) { + sb.appendCodePoint(codePoint); + } else { + appendUTF8EncodedCharacter(sb, codePoint); + } + } + return sb.toString(); + } + + public static String encodeURI(String uri) { + return encode(uri, DONT_NEED_ENCODING_URI); + } + + public static String encodeURIComponent(String uriComponent) { + return encode(uriComponent, DONT_NEED_ENCODING_URI_COMPONENT); + } + + private static void appendUTF8EncodedCharacter(StringBuilder sb, int codePoint) { + var chars = CharBuffer.wrap(Character.toChars(codePoint)); + var bytes = UTF_8.encode(chars); + + while (bytes.hasRemaining()) { + appendEscape(sb, bytes.get() & 0xFF); + } + } + + private static void appendEscape(StringBuilder appender, int b) { + appender.append('%'); + appender.append(HEX_DIGITS[b >> 4]); + appender.append(HEX_DIGITS[b & 0x0F]); + } + +} diff --git a/scx-jdbc-mysql/pom.xml b/scx-jdbc-mysql/pom.xml index 704b5405..16799188 100644 --- a/scx-jdbc-mysql/pom.xml +++ b/scx-jdbc-mysql/pom.xml @@ -6,7 +6,7 @@ cool.scx scx - 3.1.3 + 3.1.4 scx-jdbc-mysql diff --git a/scx-jdbc-sqlite/pom.xml b/scx-jdbc-sqlite/pom.xml index 9cc485d1..7d2f829e 100644 --- a/scx-jdbc-sqlite/pom.xml +++ b/scx-jdbc-sqlite/pom.xml @@ -6,7 +6,7 @@ cool.scx scx - 3.1.3 + 3.1.4 scx-jdbc-sqlite diff --git a/scx-jdbc/pom.xml b/scx-jdbc/pom.xml index 9efe3865..a302e49f 100644 --- a/scx-jdbc/pom.xml +++ b/scx-jdbc/pom.xml @@ -6,7 +6,7 @@ cool.scx scx - 3.1.3 + 3.1.4 scx-jdbc diff --git a/scx-logging/pom.xml b/scx-logging/pom.xml index dcd6fc99..365c0f96 100644 --- a/scx-logging/pom.xml +++ b/scx-logging/pom.xml @@ -6,7 +6,7 @@ cool.scx scx - 3.1.3 + 3.1.4 scx-logging diff --git a/scx-reflect/pom.xml b/scx-reflect/pom.xml index b12ded1d..d3d5c600 100644 --- a/scx-reflect/pom.xml +++ b/scx-reflect/pom.xml @@ -6,7 +6,7 @@ cool.scx scx - 3.1.3 + 3.1.4 scx-reflect diff --git a/scx-scheduling/pom.xml b/scx-scheduling/pom.xml index 16501287..77e5156f 100644 --- a/scx-scheduling/pom.xml +++ b/scx-scheduling/pom.xml @@ -6,7 +6,7 @@ cool.scx scx - 3.1.3 + 3.1.4 scx-scheduling diff --git a/scx-socket/pom.xml b/scx-socket/pom.xml index 13a0ce62..88a983b4 100644 --- a/scx-socket/pom.xml +++ b/scx-socket/pom.xml @@ -6,7 +6,7 @@ cool.scx scx - 3.1.3 + 3.1.4 scx-socket diff --git a/scx-web/pom.xml b/scx-web/pom.xml index 55b0374e..938834f2 100644 --- a/scx-web/pom.xml +++ b/scx-web/pom.xml @@ -6,7 +6,7 @@ cool.scx scx - 3.1.3 + 3.1.4 scx-web