diff --git a/examples/fit-example/07-http-client-proxy/plugin-http-client/pom.xml b/examples/fit-example/07-http-client-proxy/plugin-http-client/pom.xml new file mode 100644 index 00000000..13995cff --- /dev/null +++ b/examples/fit-example/07-http-client-proxy/plugin-http-client/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + org.fitframework.example + http-client + 1.0-SNAPSHOT + + + UTF-8 + 17 + + + 3.5.0-SNAPSHOT + + + 3.14.0 + + + + + org.fitframework + fit-api + ${fit.version} + + + org.fitframework + fit-util + ${fit.version} + + + org.fitframework.service + fit-http-classic + ${fit.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler.version} + + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + -parameters + + + + + org.fitframework + fit-build-maven-plugin + ${fit.version} + + + build-plugin + + build-plugin + + + + package-plugin + + package-plugin + + + + + + + \ No newline at end of file diff --git a/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestInterface.java b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestInterface.java new file mode 100644 index 00000000..dcb5b025 --- /dev/null +++ b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestInterface.java @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.example.client; + +import modelengine.fit.example.entity.Education; + +import java.util.List; + +/** + * This interface defines a set of methods for testing HTTP client proxy functionality. + * Each method corresponds to a specific HTTP request type and parameter binding scenario. + * + * @author 季聿阶 + * @since 2025-06-01 + */ +public interface TestInterface { + /** + * Tests request bean binding by sending an Education object in the request. + * + * @param education The Education object to be sent in the request. + * @return The modified Education object received in the response. + */ + Education requestBean(Education education); + + /** + * Tests path variable binding by extracting a path variable from the URL. + * + * @param variable The path variable extracted from the URL. + * @return A string containing the path variable value. + */ + String pathVariable(String variable); + + /** + * Tests header binding by extracting header values from the request. + * + * @param header The value of the "header" header. + * @param headers The list of values for the "headers" header. + * @return A string containing the header values. + */ + String header(String header, List headers); + + /** + * Tests cookie binding by extracting a cookie value from the request. + * + * @param cookieValue The value of the "cookie" cookie. + * @return A string containing the cookie value. + */ + String cookie(String cookieValue); + + /** + * Tests query parameter binding by extracting a query parameter from the request. + * + * @param query The value of the "query" query parameter. + * @return A string containing the query parameter value. + */ + String query(String query); + + /** + * Tests request body binding by extracting the request body content. + * + * @param requestBody The content of the request body. + * @return A string containing the request body content. + */ + String requestBody(String requestBody); + + /** + * Tests form data binding by extracting a form field value from the request. + * + * @param form The value of the "form" form field. + * @return A string containing the form field value. + */ + String form(String form); +} \ No newline at end of file diff --git a/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestRequestAddress.java b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestRequestAddress.java new file mode 100644 index 00000000..93713457 --- /dev/null +++ b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestRequestAddress.java @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.example.client; + +import modelengine.fit.example.entity.Education; +import modelengine.fit.http.annotation.GetMapping; +import modelengine.fit.http.annotation.HttpProxy; +import modelengine.fit.http.annotation.PatchMapping; +import modelengine.fit.http.annotation.PathVariable; +import modelengine.fit.http.annotation.PostMapping; +import modelengine.fit.http.annotation.PutMapping; +import modelengine.fit.http.annotation.RequestAddress; +import modelengine.fit.http.annotation.RequestBean; +import modelengine.fit.http.annotation.RequestBody; +import modelengine.fit.http.annotation.RequestCookie; +import modelengine.fit.http.annotation.RequestForm; +import modelengine.fit.http.annotation.RequestHeader; +import modelengine.fit.http.annotation.RequestMapping; +import modelengine.fit.http.annotation.RequestQuery; + +import java.util.List; + +/** + * This interface defines a set of methods for testing HTTP client proxy functionality. + * It extends the TestInterface and provides specific annotations for configuring the HTTP request details. + * The interface is marked with @HttpProxy to indicate it's a proxy for HTTP requests. + * The @RequestAddress annotation specifies the base URL and port for the requests. + * The @RequestMapping annotation sets the base path for all methods in this interface. + * + * @author 季聿阶 + * @since 2025-06-01 + */ +@HttpProxy +@RequestAddress(protocol = "http", host = "localhost", port = "8080") +@RequestMapping(path = "/http-server") +public interface TestRequestAddress extends TestInterface { + @Override + @PostMapping(path = "/request-bean") + Education requestBean(@RequestBean Education education); + + @Override + @GetMapping(path = "/path-variable/{variable}") + String pathVariable(@PathVariable(name = "variable") String variable); + + @Override + @GetMapping(path = "/header") + String header(@RequestHeader(name = "header") String header, + @RequestHeader(name = "headers") List headers); + + @Override + @GetMapping(path = "/cookie") + String cookie(@RequestCookie(name = "cookie") String cookieValue); + + @Override + @GetMapping(path = "/query") + String query(@RequestQuery(name = "query") String query); + + @Override + @PatchMapping(path = "/request-body") + String requestBody(@RequestBody String requestBody); + + @Override + @PutMapping(path = "/form") + String form(@RequestForm(name = "form") String form); +} \ No newline at end of file diff --git a/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestRequestAddressClass.java b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestRequestAddressClass.java new file mode 100644 index 00000000..00935134 --- /dev/null +++ b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestRequestAddressClass.java @@ -0,0 +1,70 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.example.client; + +import modelengine.fit.example.config.DefaultAddressLocator; +import modelengine.fit.example.entity.Education; +import modelengine.fit.http.annotation.GetMapping; +import modelengine.fit.http.annotation.HttpProxy; +import modelengine.fit.http.annotation.PatchMapping; +import modelengine.fit.http.annotation.PathVariable; +import modelengine.fit.http.annotation.PostMapping; +import modelengine.fit.http.annotation.PutMapping; +import modelengine.fit.http.annotation.RequestAddress; +import modelengine.fit.http.annotation.RequestBean; +import modelengine.fit.http.annotation.RequestBody; +import modelengine.fit.http.annotation.RequestCookie; +import modelengine.fit.http.annotation.RequestForm; +import modelengine.fit.http.annotation.RequestHeader; +import modelengine.fit.http.annotation.RequestMapping; +import modelengine.fit.http.annotation.RequestQuery; + +import java.util.List; + +/** + * This interface defines a set of methods for testing HTTP client proxy functionality. + * It extends the TestInterface and provides specific annotations for configuring the HTTP request details. + * The interface is marked with @HttpProxy to indicate it's a proxy for HTTP requests. + * The @RequestAddress annotation specifies the address locator class for the requests. + * The @RequestMapping annotation sets the base path for all methods in this interface. + * + * @author 季聿阶 + * @since 2025-06-01 + */ +@HttpProxy +@RequestAddress(address = DefaultAddressLocator.class) +@RequestMapping(path = "/http-server") +public interface TestRequestAddressClass extends TestInterface { + @Override + @PostMapping(path = "/request-bean") + Education requestBean(@RequestBean Education education); + + @Override + @GetMapping(path = "/path-variable/{variable}") + String pathVariable(@PathVariable(name = "variable") String variable); + + @Override + @GetMapping(path = "/header") + String header(@RequestHeader(name = "header") String header, + @RequestHeader(name = "headers") List headers); + + @Override + @GetMapping(path = "/cookie") + String cookie(@RequestCookie(name = "cookie") String cookieValue); + + @Override + @GetMapping(path = "/query") + String query(@RequestQuery(name = "query") String query); + + @Override + @PatchMapping(path = "/request-body") + String requestBody(@RequestBody String requestBody); + + @Override + @PutMapping(path = "/form") + String form(@RequestForm(name = "form") String form); +} diff --git a/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestRequestAddressInClassMapping.java b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestRequestAddressInClassMapping.java new file mode 100644 index 00000000..b88b8b27 --- /dev/null +++ b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestRequestAddressInClassMapping.java @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.example.client; + +import modelengine.fit.example.entity.Education; +import modelengine.fit.http.annotation.GetMapping; +import modelengine.fit.http.annotation.HttpProxy; +import modelengine.fit.http.annotation.PatchMapping; +import modelengine.fit.http.annotation.PathVariable; +import modelengine.fit.http.annotation.PostMapping; +import modelengine.fit.http.annotation.PutMapping; +import modelengine.fit.http.annotation.RequestBean; +import modelengine.fit.http.annotation.RequestBody; +import modelengine.fit.http.annotation.RequestCookie; +import modelengine.fit.http.annotation.RequestForm; +import modelengine.fit.http.annotation.RequestHeader; +import modelengine.fit.http.annotation.RequestMapping; +import modelengine.fit.http.annotation.RequestQuery; + +import java.util.List; + +/** + * This interface defines a set of methods for testing HTTP client proxy functionality. + * It extends the TestInterface and provides specific annotations for configuring the HTTP request details. + * The interface is marked with @HttpProxy to indicate it's a proxy for HTTP requests. + * The @RequestMapping annotation sets the base URL for all methods in this interface. + * + * @author 季聿阶 + * @since 2025-06-01 + */ +@HttpProxy +@RequestMapping(path = "http://localhost:8080/http-server") +public interface TestRequestAddressInClassMapping extends TestInterface { + @Override + @PostMapping(path = "/request-bean") + Education requestBean(@RequestBean Education education); + + @Override + @GetMapping(path = "/path-variable/{variable}") + String pathVariable(@PathVariable(name = "variable") String variable); + + @Override + @GetMapping(path = "/header") + String header(@RequestHeader(name = "header") String header, + @RequestHeader(name = "headers") List headers); + + @Override + @GetMapping(path = "/cookie") + String cookie(@RequestCookie(name = "cookie") String cookieValue); + + @Override + @GetMapping(path = "/query") + String query(@RequestQuery(name = "query") String query); + + @Override + @PatchMapping(path = "/request-body") + String requestBody(@RequestBody String requestBody); + + @Override + @PutMapping(path = "/form") + String form(@RequestForm(name = "form") String form); +} \ No newline at end of file diff --git a/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestRequestAddressInMethodMapping.java b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestRequestAddressInMethodMapping.java new file mode 100644 index 00000000..658b5074 --- /dev/null +++ b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/client/TestRequestAddressInMethodMapping.java @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.example.client; + +import modelengine.fit.example.entity.Education; +import modelengine.fit.http.annotation.GetMapping; +import modelengine.fit.http.annotation.HttpProxy; +import modelengine.fit.http.annotation.PatchMapping; +import modelengine.fit.http.annotation.PathVariable; +import modelengine.fit.http.annotation.PostMapping; +import modelengine.fit.http.annotation.PutMapping; +import modelengine.fit.http.annotation.RequestBean; +import modelengine.fit.http.annotation.RequestBody; +import modelengine.fit.http.annotation.RequestCookie; +import modelengine.fit.http.annotation.RequestForm; +import modelengine.fit.http.annotation.RequestHeader; +import modelengine.fit.http.annotation.RequestQuery; + +import java.util.List; + +/** + * This interface defines a set of methods for testing HTTP client proxy functionality. + * It extends the TestInterface and provides specific annotations for configuring the HTTP request details. + * The interface is marked with @HttpProxy to indicate it's a proxy for HTTP requests. + * Each method in this interface specifies the full URL for the HTTP request using the @GetMapping, @PostMapping, etc. + * annotations. + * + * @author 季聿阶 + * @since 2025-06-01 + */ +@HttpProxy +public interface TestRequestAddressInMethodMapping extends TestInterface { + @Override + @PostMapping(path = "http://localhost:8080/http-server/request-bean") + Education requestBean(@RequestBean Education education); + + @Override + @GetMapping(path = "http://localhost:8080/http-server/path-variable/{variable}") + String pathVariable(@PathVariable(name = "variable") String variable); + + @Override + @GetMapping(path = "http://localhost:8080/http-server/header") + String header(@RequestHeader(name = "header") String header, + @RequestHeader(name = "headers") List headers); + + @Override + @GetMapping(path = "http://localhost:8080/http-server/cookie") + String cookie(@RequestCookie(name = "cookie") String cookieValue); + + @Override + @GetMapping(path = "http://localhost:8080/http-server/query") + String query(@RequestQuery(name = "query") String query); + + @Override + @PatchMapping(path = "http://localhost:8080/http-server/request-body") + String requestBody(@RequestBody String requestBody); + + @Override + @PutMapping(path = "http://localhost:8080/http-server/form") + String form(@RequestForm(name = "form") String form); +} \ No newline at end of file diff --git a/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/config/DefaultAddressLocator.java b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/config/DefaultAddressLocator.java new file mode 100644 index 00000000..c3636abc --- /dev/null +++ b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/config/DefaultAddressLocator.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.example.config; + +import modelengine.fit.http.client.proxy.scanner.AddressLocator; +import modelengine.fit.http.client.proxy.scanner.entity.Address; +import modelengine.fitframework.annotation.Component; + +/** + * Provides a default implementation of the AddressLocator interface. + * This class is responsible for returning a default address configuration for HTTP requests. + * The default address includes the protocol (http), host (localhost), and port (8080). + * + * @author 季聿阶 + * @since 2025-06-01 + */ +@Component +public class DefaultAddressLocator implements AddressLocator { + @Override + public Address address() { + Address address = new Address(); + address.setProtocol("http"); + address.setHost("localhost"); + address.setPort(8080); + return address; + } +} \ No newline at end of file diff --git a/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/controller/TestClientController.java b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/controller/TestClientController.java new file mode 100644 index 00000000..11f4ea59 --- /dev/null +++ b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/controller/TestClientController.java @@ -0,0 +1,92 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.example.controller; + +import modelengine.fit.example.client.TestInterface; +import modelengine.fit.example.client.TestRequestAddress; +import modelengine.fit.example.client.TestRequestAddressClass; +import modelengine.fit.example.client.TestRequestAddressInClassMapping; +import modelengine.fit.example.client.TestRequestAddressInMethodMapping; +import modelengine.fit.example.entity.Education; +import modelengine.fit.http.annotation.GetMapping; +import modelengine.fit.http.annotation.RequestMapping; +import modelengine.fit.http.annotation.RequestQuery; +import modelengine.fitframework.annotation.Component; + +import java.util.Arrays; + +/** + * Controller for handling HTTP client test operations. + * This class provides endpoints for testing various HTTP client proxy implementations. + * + * @author 季聿阶 + * @since 2025-06-01 + */ +@Component +@RequestMapping(path = "/http-client") +public class TestClientController { + private final TestRequestAddress t1; + private final TestRequestAddressClass t2; + private final TestRequestAddressInClassMapping t3; + private final TestRequestAddressInMethodMapping t4; + + /** + * Constructs a TestClientController with the specified test interfaces. + * + * @param t1 The TestRequestAddress interface. + * @param t2 The TestRequestAddressClass interface. + * @param t3 The TestRequestAddressInClassMapping interface. + * @param t4 The TestRequestAddressInMethodMapping interface. + */ + public TestClientController(TestRequestAddress t1, TestRequestAddressClass t2, TestRequestAddressInClassMapping t3, + TestRequestAddressInMethodMapping t4) { + this.t1 = t1; + this.t2 = t2; + this.t3 = t3; + this.t4 = t4; + } + + /** + * Endpoint for running HTTP client tests. + * This method allows testing different HTTP client proxy implementations and methods. + * + * @param type The type of test interface to use (t1, t2, t3, or t4). + * @param method The method to invoke on the selected test interface. + * @return The result of the invoked method. + */ + @GetMapping(path = "/test") + public Object test(@RequestQuery("type") String type, @RequestQuery("method") String method) { + TestInterface testClass = switch (type) { + case "t1" -> t1; + case "t2" -> t2; + case "t3" -> t3; + case "t4" -> t4; + default -> throw new IllegalArgumentException("Invalid type: " + type); + }; + switch (method) { + case "requestBean": + Education education = new Education(); + education.setBachelor("PKU"); + education.setMaster("THU"); + return testClass.requestBean(education); + case "pathVariable": + return testClass.pathVariable("variable"); + case "header": + return testClass.header("header", Arrays.asList(1, 2, 3)); + case "cookie": + return testClass.cookie("cookie"); + case "query": + return testClass.query("query"); + case "requestBody": + return testClass.requestBody("requestBody"); + case "form": + return testClass.form("form"); + default: + throw new IllegalArgumentException("Invalid method: " + method); + } + } +} \ No newline at end of file diff --git a/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/entity/Education.java b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/entity/Education.java new file mode 100644 index 00000000..7eb39ffb --- /dev/null +++ b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/java/modelengine/fit/example/entity/Education.java @@ -0,0 +1,72 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.example.entity; + +import modelengine.fit.http.annotation.RequestHeader; +import modelengine.fitframework.annotation.Property; + +/** + * Represents educational information, including bachelor's and master's degrees. + * This class is used to encapsulate educational details and is annotated with {@link RequestHeader} + * to map specific fields to HTTP request headers. + * + * @author 季聿阶 + * @since 2025-06-01 + */ +public class Education { + /** + * Represents the bachelor's degree information. + * This field is mapped to the HTTP request header named "bachelor". + */ + @Property(description = "Indicates the bachelor's degree", example = "PKU") + @RequestHeader(name = "bachelor") + private String bachelor; + + /** + * Represents the master's degree information. + * This field is mapped to the HTTP request header named "master". + */ + @Property(description = "Indicates the master's degree", example = "THU") + @RequestHeader(name = "master") + private String master; + + /** + * Gets the bachelor's degree information. + * + * @return The bachelor's degree. + */ + public String getBachelor() { + return this.bachelor; + } + + /** + * Sets the bachelor's degree information. + * + * @param bachelor The bachelor's degree to set. + */ + public void setBachelor(String bachelor) { + this.bachelor = bachelor; + } + + /** + * Gets the master's degree information. + * + * @return The master's degree. + */ + public String getMaster() { + return this.master; + } + + /** + * Sets the master's degree information. + * + * @param master The master's degree to set. + */ + public void setMaster(String master) { + this.master = master; + } +} diff --git a/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/resources/application.yml b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/resources/application.yml new file mode 100644 index 00000000..c6f07dad --- /dev/null +++ b/examples/fit-example/07-http-client-proxy/plugin-http-client/src/main/resources/application.yml @@ -0,0 +1,10 @@ +fit: + beans: + packages: + - 'modelengine.fit.example' + +http: + client: + interface: + package: + - 'modelengine.fit.example.client' \ No newline at end of file diff --git a/examples/fit-example/07-http-client-proxy/plugin-http-server/pom.xml b/examples/fit-example/07-http-client-proxy/plugin-http-server/pom.xml new file mode 100644 index 00000000..4d89a11a --- /dev/null +++ b/examples/fit-example/07-http-client-proxy/plugin-http-server/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + org.fitframework.example + http-server + 1.0-SNAPSHOT + + + UTF-8 + 17 + + + 3.5.0-SNAPSHOT + + + 3.14.0 + + + + + org.fitframework + fit-api + ${fit.version} + + + org.fitframework + fit-util + ${fit.version} + + + org.fitframework.service + fit-http-classic + ${fit.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler.version} + + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + -parameters + + + + + org.fitframework + fit-build-maven-plugin + ${fit.version} + + + build-plugin + + build-plugin + + + + package-plugin + + package-plugin + + + + + + + \ No newline at end of file diff --git a/examples/fit-example/07-http-client-proxy/plugin-http-server/src/main/java/modelengine/fit/example/controller/TestServerController.java b/examples/fit-example/07-http-client-proxy/plugin-http-server/src/main/java/modelengine/fit/example/controller/TestServerController.java new file mode 100644 index 00000000..74fd7154 --- /dev/null +++ b/examples/fit-example/07-http-client-proxy/plugin-http-server/src/main/java/modelengine/fit/example/controller/TestServerController.java @@ -0,0 +1,122 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.example.controller; + +import modelengine.fit.example.entity.Education; +import modelengine.fit.http.annotation.GetMapping; +import modelengine.fit.http.annotation.PatchMapping; +import modelengine.fit.http.annotation.PathVariable; +import modelengine.fit.http.annotation.PostMapping; +import modelengine.fit.http.annotation.PutMapping; +import modelengine.fit.http.annotation.RequestBean; +import modelengine.fit.http.annotation.RequestBody; +import modelengine.fit.http.annotation.RequestCookie; +import modelengine.fit.http.annotation.RequestForm; +import modelengine.fit.http.annotation.RequestHeader; +import modelengine.fit.http.annotation.RequestMapping; +import modelengine.fit.http.annotation.RequestQuery; +import modelengine.fitframework.annotation.Component; + +import java.util.List; + +/** + * Controller for handling HTTP requests related to test server operations. + * This class provides endpoints for testing various HTTP request types and parameter bindings. + * + * @author 季聿阶 + * @since 2025-06-01 + */ +@Component +@RequestMapping(path = "/http-server") +public class TestServerController { + /** + * Endpoint for testing request bean binding. + * This method receives an Education object as a request bean and modifies its master degree field. + * + * @param education The Education object received in the request. + * @return The modified Education object. + */ + @PostMapping(path = "/request-bean") + public Education requestBean(@RequestBean Education education) { + education.setMaster("SJTU"); + return education; + } + + /** + * Endpoint for testing path variable binding. + * This method retrieves a path variable from the URL and returns it in a response. + * + * @param variable The path variable extracted from the URL. + * @return A string containing the path variable value. + */ + @GetMapping(path = "/path-variable/{variable}") + public String pathVariable(@PathVariable(name = "variable") String variable) { + return "PathVariable: " + variable; + } + + /** + * Endpoint for testing header binding. + * This method retrieves header values from the request and returns them in a response. + * + * @param header The value of the "header" header. + * @param headers The list of values for the "headers" header. + * @return A string containing the header values. + */ + @GetMapping(path = "/header") + public String header(@RequestHeader(name = "header") String header, + @RequestHeader(name = "headers") List headers) { + return "Header: " + header + ", Headers: " + headers; + } + + /** + * Endpoint for testing cookie binding. + * This method retrieves a cookie value from the request and returns it in a response. + * + * @param cookieValue The value of the "cookie" cookie. + * @return A string containing the cookie value. + */ + @GetMapping(path = "/cookie") + public String cookie(@RequestCookie(name = "cookie") String cookieValue) { + return "Cookie: " + cookieValue; + } + + /** + * Endpoint for testing query parameter binding. + * This method retrieves a query parameter from the request and returns it in a response. + * + * @param query The value of the "query" query parameter. + * @return A string containing the query parameter value. + */ + @GetMapping(path = "/query") + public String query(@RequestQuery(name = "query") String query) { + return "Query: " + query; + } + + /** + * Endpoint for testing request body binding. + * This method retrieves the request body as a string and returns it in a response. + * + * @param requestBody The content of the request body. + * @return A string containing the request body content. + */ + @PatchMapping(path = "/request-body") + public String requestBody(@RequestBody String requestBody) { + return "RequestBody: " + requestBody; + } + + /** + * Endpoint for testing form data binding. + * This method retrieves a form field value from the request and returns it in a response. + * + * @param form The value of the "form" form field. + * @return A string containing the form field value. + */ + @PutMapping(path = "/form") + public String form(@RequestForm(name = "form") String form) { + return "Form: " + form; + } +} \ No newline at end of file diff --git a/examples/fit-example/07-http-client-proxy/plugin-http-server/src/main/java/modelengine/fit/example/entity/Education.java b/examples/fit-example/07-http-client-proxy/plugin-http-server/src/main/java/modelengine/fit/example/entity/Education.java new file mode 100644 index 00000000..7eb39ffb --- /dev/null +++ b/examples/fit-example/07-http-client-proxy/plugin-http-server/src/main/java/modelengine/fit/example/entity/Education.java @@ -0,0 +1,72 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.example.entity; + +import modelengine.fit.http.annotation.RequestHeader; +import modelengine.fitframework.annotation.Property; + +/** + * Represents educational information, including bachelor's and master's degrees. + * This class is used to encapsulate educational details and is annotated with {@link RequestHeader} + * to map specific fields to HTTP request headers. + * + * @author 季聿阶 + * @since 2025-06-01 + */ +public class Education { + /** + * Represents the bachelor's degree information. + * This field is mapped to the HTTP request header named "bachelor". + */ + @Property(description = "Indicates the bachelor's degree", example = "PKU") + @RequestHeader(name = "bachelor") + private String bachelor; + + /** + * Represents the master's degree information. + * This field is mapped to the HTTP request header named "master". + */ + @Property(description = "Indicates the master's degree", example = "THU") + @RequestHeader(name = "master") + private String master; + + /** + * Gets the bachelor's degree information. + * + * @return The bachelor's degree. + */ + public String getBachelor() { + return this.bachelor; + } + + /** + * Sets the bachelor's degree information. + * + * @param bachelor The bachelor's degree to set. + */ + public void setBachelor(String bachelor) { + this.bachelor = bachelor; + } + + /** + * Gets the master's degree information. + * + * @return The master's degree. + */ + public String getMaster() { + return this.master; + } + + /** + * Sets the master's degree information. + * + * @param master The master's degree to set. + */ + public void setMaster(String master) { + this.master = master; + } +} diff --git a/examples/fit-example/07-http-client-proxy/plugin-http-server/src/main/resources/application.yml b/examples/fit-example/07-http-client-proxy/plugin-http-server/src/main/resources/application.yml new file mode 100644 index 00000000..13c4081e --- /dev/null +++ b/examples/fit-example/07-http-client-proxy/plugin-http-server/src/main/resources/application.yml @@ -0,0 +1,4 @@ +fit: + beans: + packages: + - 'modelengine.fit.example' \ No newline at end of file diff --git a/examples/fit-example/07-http-client-proxy/pom.xml b/examples/fit-example/07-http-client-proxy/pom.xml new file mode 100644 index 00000000..a8cc9135 --- /dev/null +++ b/examples/fit-example/07-http-client-proxy/pom.xml @@ -0,0 +1,15 @@ + + + 4.0.0 + + org.fitframework.example + http-client-proxy + 1.0-SNAPSHOT + pom + + + plugin-http-client + plugin-http-server + + \ No newline at end of file diff --git a/examples/fit-example/pom.xml b/examples/fit-example/pom.xml index 33ffafe9..769be155 100644 --- a/examples/fit-example/pom.xml +++ b/examples/fit-example/pom.xml @@ -15,5 +15,6 @@ 04-complicated-apps 05-aop-log-plugin 06-spring-boot-starter + 07-http-client-proxy diff --git a/framework/fit/java/fit-builtin/plugins/fit-client-http/src/main/java/modelengine/fit/client/http/support/HttpProxyCreator.java b/framework/fit/java/fit-builtin/plugins/fit-client-http/src/main/java/modelengine/fit/client/http/support/HttpProxyCreator.java new file mode 100644 index 00000000..8dda7d05 --- /dev/null +++ b/framework/fit/java/fit-builtin/plugins/fit-client-http/src/main/java/modelengine/fit/client/http/support/HttpProxyCreator.java @@ -0,0 +1,100 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.client.http.support; + +import static modelengine.fitframework.inspection.Validation.notNull; + +import modelengine.fit.http.annotation.HttpProxy; +import modelengine.fit.http.client.HttpClassicClientFactory; +import modelengine.fit.http.client.proxy.scanner.AnnotationParser; +import modelengine.fit.http.client.proxy.scanner.HttpInvocationHandler; +import modelengine.fit.http.client.proxy.scanner.entity.HttpInfo; +import modelengine.fitframework.annotation.Component; +import modelengine.fitframework.annotation.Order; +import modelengine.fitframework.conf.Config; +import modelengine.fitframework.ioc.BeanContainer; +import modelengine.fitframework.ioc.lifecycle.container.BeanContainerInitializedObserver; +import modelengine.fitframework.jvm.scan.PackageScanner; +import modelengine.fitframework.plugin.Plugin; +import modelengine.fitframework.util.StringUtils; +import modelengine.fitframework.value.ValueFetcher; + +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Creates HTTP proxy objects for interfaces annotated with {@link HttpProxy}. + * This class implements the {@link BeanContainerInitializedObserver} interface and is responsible for + * scanning packages for interfaces annotated with {@link HttpProxy}, parsing their annotations, + * and creating proxy objects that can be used to make HTTP requests. + * + * @author 王攀博 + * @author 季聿阶 + * @since 2025-05-31 + */ +@Component +@Order(Order.NEARLY_HIGH) +public class HttpProxyCreator implements BeanContainerInitializedObserver { + private static final String CONFIG_PREFIX = "http.client.interface.package"; + + private final HttpClassicClientFactory factory; + private final ValueFetcher valueFetcher; + + /** + * Constructs an HttpProxyCreator with the specified HTTP client factory and value fetcher. + * + * @param factory The HTTP client factory used to create HTTP clients. + * @param valueFetcher The value fetcher used to fetch values for property setters. + */ + public HttpProxyCreator(HttpClassicClientFactory factory, ValueFetcher valueFetcher) { + this.factory = notNull(factory, "The http classic client factory cannot be null."); + this.valueFetcher = notNull(valueFetcher, "The value fetcher cannot be null."); + } + + @Override + public void onBeanContainerInitialized(BeanContainer container) { + Config config = container.beans().get(Config.class); + List packages = this.packages(config); + if (packages.isEmpty()) { + return; + } + List> classes = this.scan(container, packages); + for (Class clazz : classes) { + AnnotationParser annotationParser = new AnnotationParser(this.valueFetcher); + Map httpInfoMap = annotationParser.parseInterface(clazz); + // Scan all interfaces, create proxy objects for each, and register them in the container. + container.registry() + .register(Proxy.newProxyInstance(clazz.getClassLoader(), + new Class[] {clazz}, + new HttpInvocationHandler(httpInfoMap, container, this.factory))); + } + } + + private List> scan(BeanContainer container, List packages) { + List> interfaceClasses = new ArrayList<>(); + if (packages != null) { + Plugin plugin = container.beans().get(Plugin.class); + PackageScanner.forClassLoader(plugin.pluginClassLoader(), (scanner, clazz) -> { + if (clazz.isInterface() && clazz.isAnnotationPresent(HttpProxy.class)) { + interfaceClasses.add(clazz); + } + }).scan(packages); + } + return interfaceClasses; + } + + private List packages(Config config) { + String value = config.get(CONFIG_PREFIX, String.class); + if (StringUtils.isNotBlank(value)) { + return StringUtils.splitToList(value, ","); + } + return new ArrayList<>(); + } +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/annotation/HttpProxy.java b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/annotation/HttpProxy.java new file mode 100644 index 00000000..05141e83 --- /dev/null +++ b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/annotation/HttpProxy.java @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.http.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks an interface as a client proxy for HTTP requests. + * This annotation is used to indicate that an interface should be treated as a proxy for making HTTP requests. + * Interfaces annotated with {@code @HttpProxy} will be processed by the framework to generate dynamic proxy objects + * that can be used to invoke HTTP endpoints. + * + * @author 王攀博 + * @since 2025-01-13 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface HttpProxy {} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/annotation/RequestAddress.java b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/annotation/RequestAddress.java new file mode 100644 index 00000000..eed8f9b0 --- /dev/null +++ b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/annotation/RequestAddress.java @@ -0,0 +1,72 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.http.annotation; + +import modelengine.fit.http.client.proxy.scanner.AddressLocator; +import modelengine.fitframework.annotation.Forward; +import modelengine.fitframework.util.StringUtils; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Defines the address information for HTTP requests. + * This annotation is used to specify the protocol, host, port, and address locator for an HTTP request. + * It can be applied to interfaces annotated with {@link HttpProxy} to configure the base URL and other connection + * details. + * + * @author 王攀博 + * @since 2025-01-24 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface RequestAddress { + /** + * Specifies the class used to locate the address dynamically. + * This property is optional and defaults to {@link AddressLocator}. + * + * @return The class used to locate the address. + */ + @Forward(annotation = RequestAddress.class, + property = "source") Class value() default AddressLocator.class; + + /** + * Specifies the protocol for the HTTP request. + * This property is optional and defaults to an empty string. + * + * @return The protocol for the HTTP request. + */ + String protocol() default StringUtils.EMPTY; + + /** + * Specifies the host for the HTTP request. + * This property is optional and defaults to an empty string. + * + * @return The host for the HTTP request. + */ + String host() default StringUtils.EMPTY; + + /** + * Specifies the port for the HTTP request. + * This property is optional and defaults to an empty string. + * + * @return The port for the HTTP request. + */ + String port() default StringUtils.EMPTY; + + /** + * Specifies the class used to locate the address dynamically. + * This property is optional and defaults to {@link AddressLocator}. + * + * @return The class used to locate the address. + */ + Class address() default AddressLocator.class; +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/annotation/RequestAuthorization.java b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/annotation/RequestAuthorization.java new file mode 100644 index 00000000..3b9a5e81 --- /dev/null +++ b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/annotation/RequestAuthorization.java @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.http.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Defines the authorization information for HTTP requests. + * This annotation is used to specify the authentication details for an HTTP request. + * It can be applied to methods annotated with HTTP mapping annotations (e.g., @GetMapping, @PostMapping) + * to configure the authorization headers or tokens required for the request. + * + * @author 王攀博 + * @since 2025-01-24 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface RequestAuthorization {} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/RequestBuilder.java b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/RequestBuilder.java index 3ee603d4..acf26b00 100644 --- a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/RequestBuilder.java +++ b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/RequestBuilder.java @@ -13,144 +13,160 @@ import modelengine.fit.http.protocol.HttpRequestMethod; /** - * 表示 Http 请求提供建造者。 + * Represents a builder for HTTP requests. * * @author 王攀博 * @since 2024-06-08 */ public interface RequestBuilder { /** - * 设置 Http 客户端。 + * Sets the HTTP client. * - * @param httpClassicClient 表示测试需要的客户端。 - * @return 表示客户端请求参数的建造者 {@link RequestBuilder}。 + * @param httpClassicClient The HTTP client to be used. + * @return The builder for HTTP request parameters {@link RequestBuilder}. */ RequestBuilder client(HttpClassicClient httpClassicClient); /** - * 设置客户端请协议。 + * Sets the HTTP request method. * - * @param method 表示客户端请求的协议 {@link HttpRequestMethod}。 - * @return 表示客户端请求参数的建造者 {@link RequestBuilder}。 + * @param method The HTTP request method {@link HttpRequestMethod}. + * @return The builder for HTTP request parameters {@link RequestBuilder}. */ RequestBuilder method(HttpRequestMethod method); /** - * 设置客户端请协议。 + * Sets the protocol for the HTTP request. * - * @param protocol 表示客户端请求的协议 {@link String}。 - * @return 表示客户端请求参数的建造者 {@link RequestBuilder}。 + * @param protocol The protocol for the HTTP request {@link String}. + * @return The builder for HTTP request parameters {@link RequestBuilder}. */ RequestBuilder protocol(String protocol); /** - * 设置客户端请求域名。 + * Sets the domain for the HTTP request. * - * @param domain 表示客户端请求的域名 {@link String}。 - * @return 表示客户端请求参数的建造者 {@link RequestBuilder}。 + * @param domain The domain for the HTTP request {@link String}. + * @return The builder for HTTP request parameters {@link RequestBuilder}. */ RequestBuilder domain(String domain); /** - * 设置客户端请求模板。 + * Sets the host for the HTTP request. * - * @param pathPattern 表示客户端请求的路径模板的 {@link String}。 - * @return 表示客户端请求参数的建造者 {@link RequestBuilder}。 + * @param host The host for the HTTP request {@link String}. + * @return The builder for HTTP request parameters {@link RequestBuilder}. + */ + RequestBuilder host(String host); + + /** + * Sets the port for the HTTP request. + * + * @param port The port for the HTTP request {@link int}. + * @return The builder for HTTP request parameters {@link RequestBuilder}. + */ + RequestBuilder port(int port); + + /** + * Sets the path pattern for the HTTP request. + * + * @param pathPattern The path pattern for the HTTP request {@link String}. + * @return The builder for HTTP request parameters {@link RequestBuilder}. */ RequestBuilder pathPattern(String pathPattern); /** - * 设置客户端请求路径段。 + * Sets the path variable for the HTTP request. * - * @param key 表示客户端请求的路径变量的 {@link String}。 - * @param pathVariable 表示客户端请求的路径变量的值的 {@link String}。 - * @return 表示客户端请求参数的建造者 {@link RequestBuilder}。 + * @param key The key for the path variable {@link String}. + * @param pathVariable The value for the path variable {@link String}. + * @return The builder for HTTP request parameters {@link RequestBuilder}. */ RequestBuilder pathVariable(String key, String pathVariable); /** - * 为请求插件结构体添加键值对参数。 + * Adds a key-value pair to the query parameters of the HTTP request. * - * @param key 表示请求体内请求参数的键的 {@link String}。 - * @param value 表示请求体内请求参数的值的 {@link String}。 - * @return 表示客户端请求参数的建造者 {@link RequestBuilder}。 + * @param key The key for the query parameter {@link String}. + * @param value The value for the query parameter {@link String}. + * @return The builder for HTTP request parameters {@link RequestBuilder}. */ RequestBuilder query(String key, String value); /** - * 设置请求结构体的消息头。 + * Sets the header for the HTTP request. * - * @param name 表示待设置的消息头的名字的 {@link String}。 - * @param header 表示待设置的消息头内容的 {@link String}。 - * @return 表示客户端请求参数的建造者 {@link RequestBuilder}。 + * @param name The name of the header {@link String}. + * @param header The content of the header {@link String}. + * @return The builder for HTTP request parameters {@link RequestBuilder}. */ RequestBuilder header(String name, String header); /** - * 设置请求结构体的 Cookie。 + * Sets the cookie for the HTTP request. * - * @param key 表示待设置的 Cookie 的键的 {@link String}。 - * @param value 表示待设置的 Cookie 的值的 {@link String}。 - * @return 表示客户端请求参数的建造者 {@link RequestBuilder}。 + * @param key The key for the cookie {@link String}. + * @param value The value for the cookie {@link String}. + * @return The builder for HTTP request parameters {@link RequestBuilder}. */ RequestBuilder cookie(String key, String value); /** - * 设置请求结构体的消息体内容。 + * Sets the entity (body content) for the HTTP request. * - * @param entity 表示 Http 请求的消息体内容的 {@link Entity}。 - * @return 表示客户端请求参数的建造者 {@link RequestBuilder}。 + * @param entity The entity (body content) for the HTTP request {@link Entity}. + * @return The builder for HTTP request parameters {@link RequestBuilder}. */ RequestBuilder entity(Entity entity); /** - * 设置 Http 请求的 Form 格式的消息体内容。 + * Sets the form entity (body content) for the HTTP request. * - * @param key 表示 Http 请求的 Form 格式的消息体内容键值对键的 {@link String}。 - * @param value 表示 Http 请求的 Form 格式的消息体内容键值对值的 {@link String}。 - * @return 表示客户端请求参数的建造者 {@link RequestBuilder}。 + * @param key The key for the form entity {@link String}. + * @param value The value for the form entity {@link String}. + * @return The builder for HTTP request parameters {@link RequestBuilder}. */ RequestBuilder formEntity(String key, String value); /** - * 设置 Http 请求的 Json 格式的消息体内容。 + * Sets the JSON entity (body content) for the HTTP request. * - * @param propertyValuePath 表示 Http 请求的 json 格式的消息体内容属性路径的 {@link String}。 - * @param value 表示 Http 请求的消息体内容属性值的 {@link Object}。 - * @return 表示客户端请求参数的建造者 {@link RequestBuilder}。 + * @param propertyValuePath The property path for the JSON entity {@link String}. + * @param value The value for the JSON entity {@link Object}. + * @return The builder for HTTP request parameters {@link RequestBuilder}. */ RequestBuilder jsonEntity(String propertyValuePath, Object value); /** - * 设置 Http 请求的鉴权类型。 + * Sets the authorization type for the HTTP request. * - * @param authorization 表示 Http 请求的鉴权信息结构的 {@link Authorization}。 - * @return 表示客户端请求参数的建造者 {@link RequestBuilder}。 + * @param authorization The authorization information for the HTTP request {@link Authorization}. + * @return The builder for HTTP request parameters {@link RequestBuilder}. */ RequestBuilder authorization(Authorization authorization); /** - * 设置 Http 请求的鉴权信息。 + * Sets the authorization information for the HTTP request. * - * @param key 表示 Http 鉴权信息的参数键值的 {@link String}。 - * @param value 表示鉴权信息的值的 {@link Object}。 - * @return 表示客户端请求参数的建造者 {@link RequestBuilder}。 + * @param key The key for the authorization information {@link String}. + * @param value The value for the authorization information {@link Object}. + * @return The builder for HTTP request parameters {@link RequestBuilder}. */ RequestBuilder authorizationInfo(String key, Object value); /** - * 构建客户端的请求参数。 + * Builds the HTTP request parameters. * - * @return 表示返回构建完成的客户端的请求参数 {@link HttpClassicClientRequest}。 + * @return The built HTTP request parameters {@link HttpClassicClientRequest}. */ HttpClassicClientRequest build(); /** - * 创建请求构建器。 + * Creates a new instance of the request builder. * - * @return 表示创建出的请求构建器的 {@link RequestBuilder}。 + * @return A new instance of the request builder {@link RequestBuilder}. */ static RequestBuilder create() { return new DefaultRequestBuilder(); } -} +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/AddressLocator.java b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/AddressLocator.java new file mode 100644 index 00000000..f9d72149 --- /dev/null +++ b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/AddressLocator.java @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.http.client.proxy.scanner; + +import modelengine.fit.http.client.proxy.scanner.entity.Address; + +/** + * Defines a contract for locating and providing address information for HTTP requests. + * Implementations of this interface are responsible for returning an {@link Address} object + * that contains details such as protocol, host, port, and locator class. + * + * @author 王攀博 + * @since 2025-01-24 + */ +public interface AddressLocator { + /** + * Retrieves the address information for an HTTP request. + * + * @return The address information as an {@link Address} object. + */ + Address address(); +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/AnnotationParser.java b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/AnnotationParser.java new file mode 100644 index 00000000..8704672e --- /dev/null +++ b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/AnnotationParser.java @@ -0,0 +1,194 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.http.client.proxy.scanner; + +import static modelengine.fitframework.inspection.Validation.notNull; + +import modelengine.fit.http.annotation.DeleteMapping; +import modelengine.fit.http.annotation.GetMapping; +import modelengine.fit.http.annotation.PatchMapping; +import modelengine.fit.http.annotation.PathVariable; +import modelengine.fit.http.annotation.PostMapping; +import modelengine.fit.http.annotation.PutMapping; +import modelengine.fit.http.annotation.RequestAddress; +import modelengine.fit.http.annotation.RequestBean; +import modelengine.fit.http.annotation.RequestBody; +import modelengine.fit.http.annotation.RequestCookie; +import modelengine.fit.http.annotation.RequestForm; +import modelengine.fit.http.annotation.RequestHeader; +import modelengine.fit.http.annotation.RequestMapping; +import modelengine.fit.http.annotation.RequestQuery; +import modelengine.fit.http.client.proxy.PropertyValueApplier; +import modelengine.fit.http.client.proxy.scanner.entity.Address; +import modelengine.fit.http.client.proxy.scanner.entity.HttpInfo; +import modelengine.fit.http.client.proxy.scanner.resolver.PathVariableResolver; +import modelengine.fit.http.client.proxy.scanner.resolver.RequestBodyResolver; +import modelengine.fit.http.client.proxy.scanner.resolver.RequestCookieResolver; +import modelengine.fit.http.client.proxy.scanner.resolver.RequestFormResolver; +import modelengine.fit.http.client.proxy.scanner.resolver.RequestHeaderResolver; +import modelengine.fit.http.client.proxy.scanner.resolver.RequestQueryResolver; +import modelengine.fit.http.client.proxy.support.applier.MultiDestinationsPropertyValueApplier; +import modelengine.fit.http.client.proxy.support.setter.DestinationSetterInfo; +import modelengine.fitframework.util.ArrayUtils; +import modelengine.fitframework.util.ReflectionUtils; +import modelengine.fitframework.util.StringUtils; +import modelengine.fitframework.value.ValueFetcher; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Parses annotations on interfaces and methods to extract HTTP-related information. + * This class is responsible for interpreting annotations such as @RequestMapping, @GetMapping, @PostMapping, etc., + * and converting them into structured data that can be used to build HTTP requests. + * + * @author 王攀博 + * @since 2025-01-07 + */ +public class AnnotationParser { + private static final int MAX_PATH_SIZE = 1; + private static final String KEY_OF_METHOD_PATH = "path"; + private static final Set> mappingMethodAnnotations = + Stream.of(PostMapping.class, PutMapping.class, GetMapping.class, DeleteMapping.class, PatchMapping.class) + .collect(Collectors.toSet()); + private static final Map, ParamResolver> annotationParsers = new HashMap<>(); + + static { + annotationParsers.put(RequestQuery.class, new RequestQueryResolver()); + annotationParsers.put(RequestHeader.class, new RequestHeaderResolver()); + annotationParsers.put(RequestCookie.class, new RequestCookieResolver()); + annotationParsers.put(RequestBody.class, new RequestBodyResolver()); + annotationParsers.put(RequestForm.class, new RequestFormResolver()); + annotationParsers.put(PathVariable.class, new PathVariableResolver()); + } + + private final ValueFetcher valueFetcher; + + /** + * Constructs an AnnotationParser with the specified ValueFetcher. + * + * @param valueFetcher The ValueFetcher used to fetch values for property setters. + */ + public AnnotationParser(ValueFetcher valueFetcher) { + this.valueFetcher = notNull(valueFetcher, "The value fetcher cannot be null."); + } + + /** + * Parses the given interface class to extract HTTP information for each method. + * + * @param clazz The interface class to parse. + * @return A map of methods to their corresponding HTTP information. + */ + public Map parseInterface(Class clazz) { + Map httpInfoMap = new HashMap<>(); + if (clazz.isInterface()) { + String pathPatternPrefix = this.getPathPatternPrefix(clazz); + Address address = this.getAddress(clazz); + Arrays.stream(clazz.getMethods()).forEach(method -> { + HttpInfo httpInfo = this.parseMethod(method, pathPatternPrefix); + httpInfo.setAddress(address); + httpInfoMap.put(method, httpInfo); + }); + } + return httpInfoMap; + } + + private HttpInfo parseMethod(Method method, String pathPatternPrefix) { + HttpInfo httpInfo = new HttpInfo(); + this.parseHttpMethod(method, httpInfo, pathPatternPrefix); + List appliers = new ArrayList<>(); + Arrays.stream(method.getParameters()).forEach(parameter -> appliers.add(this.parseParam(parameter))); + httpInfo.setAppliers(appliers); + return httpInfo; + } + + private void parseHttpMethod(Method method, HttpInfo httpInfo, String pathPatternPrefix) { + Arrays.stream(method.getAnnotations()) + .filter(annotation -> mappingMethodAnnotations.contains(annotation.annotationType())) + .forEach(annotation -> { + Method pathMethod = + ReflectionUtils.getDeclaredMethod(annotation.annotationType(), KEY_OF_METHOD_PATH); + String[] paths = + (String[]) ReflectionUtils.invoke(method.getAnnotation(annotation.annotationType()), + pathMethod); + if (ArrayUtils.isEmpty(paths)) { + return; + } + if (paths.length > MAX_PATH_SIZE) { + throw new IllegalArgumentException("The path size cannot be more than one."); + } + httpInfo.setPathPattern(pathPatternPrefix + paths[0]); + httpInfo.setMethod(annotation.annotationType() + .getDeclaredAnnotation(RequestMapping.class) + .method()[0]); + }); + } + + private String getPathPatternPrefix(Class clazz) { + String pathPatternPrefix = StringUtils.EMPTY; + if (clazz.isAnnotationPresent(RequestMapping.class)) { + RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class); + String[] paths = requestMapping.path(); + if (ArrayUtils.isNotEmpty(paths)) { + pathPatternPrefix = paths[0]; + } + } + return pathPatternPrefix; + } + + private Address getAddress(Class requestAddressClazz) { + Address address = null; + if (requestAddressClazz.isAnnotationPresent(RequestAddress.class)) { + address = new Address(); + RequestAddress requestAddress = requestAddressClazz.getAnnotation(RequestAddress.class); + address.setProtocol(requestAddress.protocol()); + address.setHost(requestAddress.host()); + if (StringUtils.isNotEmpty(requestAddress.port())) { + address.setPort(Integer.parseInt(requestAddress.port())); + } + address.setLocator(requestAddress.address()); + } + return address; + } + + private PropertyValueApplier parseParam(Parameter parameter) { + Annotation[] annotations = parameter.getAnnotations(); + Class type = parameter.getType(); + List setterInfos = getSetterInfos(annotations, type.getDeclaredFields(), "$"); + return new MultiDestinationsPropertyValueApplier(setterInfos, this.valueFetcher); + } + + private List getSetterInfos(Annotation[] annotations, Field[] fields, String prefix) { + List setterInfos = new ArrayList<>(); + for (Annotation annotation : annotations) { + if (annotation.annotationType() == RequestBean.class) { + Arrays.stream(fields) + .forEach(field -> setterInfos.addAll(this.getSetterInfos(field.getAnnotations(), + field.getType().getDeclaredFields(), + prefix + "." + field.getName()))); + return setterInfos; + } else { + ParamResolver resolver = annotationParsers.get(annotation.annotationType()); + if (resolver != null) { + setterInfos.add(resolver.resolve(annotation, prefix)); + return setterInfos; + } + } + } + return setterInfos; + } +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/HttpInvocationHandler.java b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/HttpInvocationHandler.java new file mode 100644 index 00000000..0a4526a4 --- /dev/null +++ b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/HttpInvocationHandler.java @@ -0,0 +1,116 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.http.client.proxy.scanner; + +import static modelengine.fitframework.inspection.Validation.notNull; +import static modelengine.fitframework.util.ObjectUtils.cast; + +import modelengine.fit.http.client.HttpClassicClient; +import modelengine.fit.http.client.HttpClassicClientFactory; +import modelengine.fit.http.client.HttpClassicClientRequest; +import modelengine.fit.http.client.HttpClassicClientResponse; +import modelengine.fit.http.client.HttpClientErrorException; +import modelengine.fit.http.client.HttpClientException; +import modelengine.fit.http.client.HttpClientResponseException; +import modelengine.fit.http.client.HttpServerErrorException; +import modelengine.fit.http.client.proxy.PropertyValueApplier; +import modelengine.fit.http.client.proxy.RequestBuilder; +import modelengine.fit.http.client.proxy.scanner.entity.Address; +import modelengine.fit.http.client.proxy.scanner.entity.HttpInfo; +import modelengine.fit.http.entity.ObjectEntity; +import modelengine.fit.http.entity.TextEntity; +import modelengine.fitframework.ioc.BeanContainer; +import modelengine.fitframework.util.StringUtils; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; + +/** + * Handles HTTP invocations for proxy interfaces by mapping method calls to HTTP requests. + * This class implements the {@link InvocationHandler} interface and is responsible for + * constructing and executing HTTP requests based on the annotations and parameters of the + * invoked methods. + * + * @author 王攀博 + * @since 2025-01-07 + */ +public class HttpInvocationHandler implements InvocationHandler { + private final HttpClassicClientFactory factory; + private final Map httpInfoMap; + private final BeanContainer container; + + /** + * Constructs an HttpInvocationHandler with the specified HTTP client factory, HTTP info map, and bean container. + * + * @param httpInfoMap The map of methods to their corresponding HTTP information. + * @param container The bean container used to retrieve beans for address resolution. + * @param factory The HTTP client factory used to create HTTP clients. + */ + public HttpInvocationHandler(Map httpInfoMap, BeanContainer container, + HttpClassicClientFactory factory) { + this.httpInfoMap = notNull(httpInfoMap, "The http info cannot be null."); + this.container = notNull(container, "The bean container cannot be null."); + this.factory = notNull(factory, "The HTTP client factory cannot be null."); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + HttpInfo httpInfo = this.httpInfoMap.get(method); + if (httpInfo == null) { + throw new HttpClientException("No method http info."); + } + List appliers = httpInfo.getAppliers(); + if (args.length != appliers.size()) { + throw new HttpClientException("Args length not equals to appliers size."); + } + HttpClassicClient client = this.factory.create(); + RequestBuilder requestBuilder = RequestBuilder.create() + .client(client) + .method(httpInfo.getMethod()) + .pathPattern(httpInfo.getPathPattern()); + Address address = this.updateAddress(httpInfo.getAddress()); + if (address != null) { + requestBuilder.protocol(address.getProtocol()).host(address.getHost()).port(address.getPort()); + } + for (int i = 0; i < appliers.size(); i++) { + appliers.get(i).apply(requestBuilder, args[i]); + } + HttpClassicClientRequest request = requestBuilder.build(); + try (HttpClassicClientResponse response = client.exchange(request, method.getReturnType())) { + if (response.statusCode() >= 200 && response.statusCode() < 300) { + if (method.getReturnType() == String.class) { + return cast(response.textEntity().map(TextEntity::content).orElse(StringUtils.EMPTY)); + } + return cast(response.objectEntity().map(ObjectEntity::object).orElse(null)); + } else if (response.statusCode() >= 400 && response.statusCode() < 500) { + throw new HttpClientErrorException(request, response); + } else if (response.statusCode() >= 500 && response.statusCode() < 600) { + throw new HttpServerErrorException(request, response); + } else { + throw new HttpClientResponseException(request, response); + } + } + } + + private Address updateAddress(Address addressIn) { + if (addressIn == null || StringUtils.isNotEmpty(addressIn.getHost())) { + return addressIn; + } + if (addressIn.getLocator() != null) { + AddressLocator addressSource = this.container.beans().get(addressIn.getLocator()); + if (addressSource != null) { + Address address = addressSource.address(); + addressIn.setProtocol(address.getProtocol()); + addressIn.setHost(address.getHost()); + addressIn.setPort(address.getPort()); + } + } + return addressIn; + } +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/ParamResolver.java b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/ParamResolver.java new file mode 100644 index 00000000..a0681647 --- /dev/null +++ b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/ParamResolver.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.http.client.proxy.scanner; + +import modelengine.fit.http.client.proxy.support.setter.DestinationSetterInfo; + +/** + * Defines a contract for resolving annotations into destination setter information. + * Implementations of this interface are responsible for parsing specific annotations + * and converting them into {@link DestinationSetterInfo} objects that can be used to + * set properties on HTTP request objects. + * + * @param The type of annotation that this resolver handles. + * @author 王攀博 + * @since 2025-02-10 + */ +public interface ParamResolver { + /** + * Resolves the given annotation into a destination setter information object. + * + * @param annotation The annotation to resolve. + * @param jsonPath The JSON path associated with the annotation. + * @return A {@link DestinationSetterInfo} object containing the resolved information. + */ + DestinationSetterInfo resolve(T annotation, String jsonPath); +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/entity/Address.java b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/entity/Address.java new file mode 100644 index 00000000..d924c4d8 --- /dev/null +++ b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/entity/Address.java @@ -0,0 +1,94 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.http.client.proxy.scanner.entity; + +import modelengine.fit.http.client.proxy.scanner.AddressLocator; + +/** + * Represents the address information for an HTTP request. + * + * @author 王攀博 + * @since 2025-01-10 + */ +public class Address { + private String protocol; + private String host; + private int port; + private Class locator; + + /** + * Gets the protocol for the HTTP request. + * + * @return The protocol for the HTTP request. + */ + public String getProtocol() { + return this.protocol; + } + + /** + * Sets the protocol for the HTTP request. + * + * @param protocol The protocol for the HTTP request. + */ + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + /** + * Gets the host for the HTTP request. + * + * @return The host for the HTTP request. + */ + public String getHost() { + return this.host; + } + + /** + * Sets the host for the HTTP request. + * + * @param host The host for the HTTP request. + */ + public void setHost(String host) { + this.host = host; + } + + /** + * Gets the port for the HTTP request. + * + * @return The port for the HTTP request. + */ + public int getPort() { + return this.port; + } + + /** + * Sets the port for the HTTP request. + * + * @param port The port for the HTTP request. + */ + public void setPort(int port) { + this.port = port; + } + + /** + * Gets the class used to locate the address. + * + * @return The class used to locate the address. + */ + public Class getLocator() { + return this.locator; + } + + /** + * Sets the class used to locate the address. + * + * @param locator The class used to locate the address. + */ + public void setLocator(Class locator) { + this.locator = locator; + } +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/entity/HttpInfo.java b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/entity/HttpInfo.java new file mode 100644 index 00000000..dd673728 --- /dev/null +++ b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/entity/HttpInfo.java @@ -0,0 +1,98 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.http.client.proxy.scanner.entity; + +import modelengine.fit.http.client.proxy.PropertyValueApplier; +import modelengine.fit.http.protocol.HttpRequestMethod; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents the HTTP request information, including address, method, path pattern, and property value appliers. + * + * @author 王攀博 + * @since 2025-01-10 + */ +public class HttpInfo { + private Address address; + private HttpRequestMethod method; + private String pathPattern; + private List appliers = new ArrayList<>(); + + /** + * Gets the address information for the HTTP request. + * + * @return The address information. + */ + public Address getAddress() { + return this.address; + } + + /** + * Sets the address information for the HTTP request. + * + * @param address The address information to set. + */ + public void setAddress(Address address) { + this.address = address; + } + + /** + * Gets the HTTP request method. + * + * @return The HTTP request method. + */ + public HttpRequestMethod getMethod() { + return this.method; + } + + /** + * Sets the HTTP request method. + * + * @param method The HTTP request method to set. + */ + public void setMethod(HttpRequestMethod method) { + this.method = method; + } + + /** + * Gets the path pattern for the HTTP request. + * + * @return The path pattern. + */ + public String getPathPattern() { + return this.pathPattern; + } + + /** + * Sets the path pattern for the HTTP request. + * + * @param pathPattern The path pattern to set. + */ + public void setPathPattern(String pathPattern) { + this.pathPattern = pathPattern; + } + + /** + * Gets the list of property value appliers for the HTTP request. + * + * @return The list of property value appliers. + */ + public List getAppliers() { + return this.appliers; + } + + /** + * Sets the list of property value appliers for the HTTP request. + * + * @param appliers The list of property value appliers to set. + */ + public void setAppliers(List appliers) { + this.appliers = appliers; + } +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/resolver/PathVariableResolver.java b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/resolver/PathVariableResolver.java new file mode 100644 index 00000000..2f999752 --- /dev/null +++ b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/resolver/PathVariableResolver.java @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.http.client.proxy.scanner.resolver; + +import modelengine.fit.http.annotation.PathVariable; +import modelengine.fit.http.client.proxy.scanner.ParamResolver; +import modelengine.fit.http.client.proxy.support.setter.DestinationSetterInfo; +import modelengine.fit.http.client.proxy.support.setter.PathVariableDestinationSetter; + +/** + * Resolves the {@link PathVariable} annotation into a destination setter information object. + * This class implements the {@link ParamResolver} interface and is responsible for parsing + * the {@link PathVariable} annotation and converting it into a {@link DestinationSetterInfo} + * object that can be used to set path variables on HTTP request objects. + * + * @author 王攀博 + * @since 2025-02-10 + */ +public class PathVariableResolver implements ParamResolver { + @Override + public DestinationSetterInfo resolve(PathVariable annotation, String jsonPath) { + return new DestinationSetterInfo(new PathVariableDestinationSetter(annotation.name()), jsonPath); + } +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/resolver/RequestBodyResolver.java b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/resolver/RequestBodyResolver.java new file mode 100644 index 00000000..73f3244d --- /dev/null +++ b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/resolver/RequestBodyResolver.java @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.http.client.proxy.scanner.resolver; + +import modelengine.fit.http.annotation.RequestBody; +import modelengine.fit.http.client.proxy.scanner.ParamResolver; +import modelengine.fit.http.client.proxy.support.setter.DestinationSetterInfo; +import modelengine.fit.http.client.proxy.support.setter.ObjectEntitySetter; + +/** + * Resolves the {@link RequestBody} annotation into a destination setter information object. + * This class implements the {@link ParamResolver} interface and is responsible for parsing + * the {@link RequestBody} annotation and converting it into a {@link DestinationSetterInfo} + * object that can be used to set the request body on HTTP request objects. + * + * @author 王攀博 + * @since 2025-02-10 + */ +public class RequestBodyResolver implements ParamResolver { + @Override + public DestinationSetterInfo resolve(RequestBody annotation, String jsonPath) { + return new DestinationSetterInfo(new ObjectEntitySetter(annotation.key()), jsonPath); + } +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/resolver/RequestCookieResolver.java b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/resolver/RequestCookieResolver.java new file mode 100644 index 00000000..599c7943 --- /dev/null +++ b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/resolver/RequestCookieResolver.java @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.http.client.proxy.scanner.resolver; + +import modelengine.fit.http.annotation.RequestCookie; +import modelengine.fit.http.client.proxy.scanner.ParamResolver; +import modelengine.fit.http.client.proxy.support.setter.CookieDestinationSetter; +import modelengine.fit.http.client.proxy.support.setter.DestinationSetterInfo; + +/** + * Resolves the {@link RequestCookie} annotation into a destination setter information object. + * This class implements the {@link ParamResolver} interface and is responsible for parsing + * the {@link RequestCookie} annotation and converting it into a {@link DestinationSetterInfo} + * object that can be used to set cookies on HTTP request objects. + * + * @author 王攀博 + * @since 2025-02-10 + */ +public class RequestCookieResolver implements ParamResolver { + @Override + public DestinationSetterInfo resolve(RequestCookie annotation, String jsonPath) { + return new DestinationSetterInfo(new CookieDestinationSetter(annotation.name()), jsonPath); + } +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/resolver/RequestFormResolver.java b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/resolver/RequestFormResolver.java new file mode 100644 index 00000000..54804431 --- /dev/null +++ b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/resolver/RequestFormResolver.java @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.http.client.proxy.scanner.resolver; + +import modelengine.fit.http.annotation.RequestForm; +import modelengine.fit.http.client.proxy.scanner.ParamResolver; +import modelengine.fit.http.client.proxy.support.setter.DestinationSetterInfo; +import modelengine.fit.http.client.proxy.support.setter.FormUrlEncodedEntitySetter; + +/** + * Resolves the {@link RequestForm} annotation into a destination setter information object. + * This class implements the {@link ParamResolver} interface and is responsible for parsing + * the {@link RequestForm} annotation and converting it into a {@link DestinationSetterInfo} + * object that can be used to set form data on HTTP request objects. + * + * @author 王攀博 + * @since 2025-02-10 + */ +public class RequestFormResolver implements ParamResolver { + @Override + public DestinationSetterInfo resolve(RequestForm annotation, String jsonPath) { + return new DestinationSetterInfo(new FormUrlEncodedEntitySetter(annotation.name()), jsonPath); + } +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/resolver/RequestHeaderResolver.java b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/resolver/RequestHeaderResolver.java new file mode 100644 index 00000000..c56d5d92 --- /dev/null +++ b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/resolver/RequestHeaderResolver.java @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.http.client.proxy.scanner.resolver; + +import modelengine.fit.http.annotation.RequestHeader; +import modelengine.fit.http.client.proxy.scanner.ParamResolver; +import modelengine.fit.http.client.proxy.support.setter.DestinationSetterInfo; +import modelengine.fit.http.client.proxy.support.setter.HeaderDestinationSetter; + +/** + * Resolves the {@link RequestHeader} annotation into a destination setter information object. + * This class implements the {@link ParamResolver} interface and is responsible for parsing + * the {@link RequestHeader} annotation and converting it into a {@link DestinationSetterInfo} + * object that can be used to set headers on HTTP request objects. + * + * @author 王攀博 + * @since 2025-02-10 + */ +public class RequestHeaderResolver implements ParamResolver { + @Override + public DestinationSetterInfo resolve(RequestHeader annotation, String jsonPath) { + return new DestinationSetterInfo(new HeaderDestinationSetter(annotation.name()), jsonPath); + } +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/resolver/RequestQueryResolver.java b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/resolver/RequestQueryResolver.java new file mode 100644 index 00000000..45513ac4 --- /dev/null +++ b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/scanner/resolver/RequestQueryResolver.java @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fit.http.client.proxy.scanner.resolver; + +import modelengine.fit.http.annotation.RequestQuery; +import modelengine.fit.http.client.proxy.scanner.ParamResolver; +import modelengine.fit.http.client.proxy.support.setter.DestinationSetterInfo; +import modelengine.fit.http.client.proxy.support.setter.QueryDestinationSetter; + +/** + * Resolves the {@link RequestQuery} annotation into a destination setter information object. + * This class implements the {@link ParamResolver} interface and is responsible for parsing + * the {@link RequestQuery} annotation and converting it into a {@link DestinationSetterInfo} + * object that can be used to set query parameters on HTTP request objects. + * + * @author 王攀博 + * @since 2025-02-10 + */ +public class RequestQueryResolver implements ParamResolver { + @Override + public DestinationSetterInfo resolve(RequestQuery annotation, String jsonPath) { + return new DestinationSetterInfo(new QueryDestinationSetter(annotation.name()), jsonPath); + } +} \ No newline at end of file diff --git a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/support/DefaultRequestBuilder.java b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/support/DefaultRequestBuilder.java index 758277bb..c3d942ad 100644 --- a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/support/DefaultRequestBuilder.java +++ b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/proxy/support/DefaultRequestBuilder.java @@ -42,6 +42,8 @@ public class DefaultRequestBuilder implements RequestBuilder { private HttpRequestMethod method; private String protocol; private String domain; + private String host; + private int port; private String pathPattern; private Entity entity; private Authorization authorization; @@ -70,6 +72,18 @@ public RequestBuilder domain(String domain) { return this; } + @Override + public RequestBuilder host(String host) { + this.host = host; + return this; + } + + @Override + public RequestBuilder port(int port) { + this.port = port; + return this; + } + @Override public RequestBuilder pathPattern(String pathPattern) { this.pathPattern = pathPattern; @@ -163,16 +177,23 @@ public RequestBuilder authorizationInfo(String key, Object value) { @Override public HttpClassicClientRequest build() { - StringBuilder url = new StringBuilder(this.protocol); - url.append("://").append(this.domain); + if (this.authorization != null) { + this.authorization.assemble(this); + } + StringBuilder url = new StringBuilder(); + if (StringUtils.isNotEmpty(this.protocol)) { + url.append(this.protocol).append("://"); + } + if (StringUtils.isNotEmpty(this.domain)) { + url.append(this.domain); + } else if (StringUtils.isNotEmpty(this.host)) { + url.append(this.host).append(":").append(this.port); + } if (!this.pathVariables.isEmpty()) { this.pathVariables.forEach((key, value) -> this.pathPattern = this.pathPattern.replace("{" + key + "}", value)); } url.append(this.pathPattern); - if (this.authorization != null) { - this.authorization.assemble(this); - } if (!this.queries.isEmpty()) { url.append("?"); this.queries.forEach((key, values) -> values.forEach(value -> url.append(key) diff --git a/framework/fit/java/fit-plugin/src/main/java/modelengine/fitframework/globalization/ResourceBundleStringResource.java b/framework/fit/java/fit-plugin/src/main/java/modelengine/fitframework/globalization/ResourceBundleStringResource.java index ab46da3c..bca8afc3 100644 --- a/framework/fit/java/fit-plugin/src/main/java/modelengine/fitframework/globalization/ResourceBundleStringResource.java +++ b/framework/fit/java/fit-plugin/src/main/java/modelengine/fitframework/globalization/ResourceBundleStringResource.java @@ -12,12 +12,12 @@ import modelengine.fitframework.util.StringUtils; import java.util.Enumeration; -import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.MissingResourceException; import java.util.NoSuchElementException; import java.util.ResourceBundle; +import java.util.concurrent.ConcurrentHashMap; /** * 为 {@link StringResource} 提供基于 {@link ResourceBundle} 的实现。 @@ -35,7 +35,7 @@ final class ResourceBundleStringResource implements StringResource { ResourceBundleStringResource(ClassLoader loader, String baseName, String encoding) { this.loader = loader; this.baseName = baseName; - this.bundles = new HashMap<>(); + this.bundles = new ConcurrentHashMap<>(); this.control = new MessageBundleControl(encoding); }