{"id":19993250,"url":"https://github.com/dadiyang/http-api-invoker","last_synced_at":"2026-01-12T08:28:54.084Z","repository":{"id":34294590,"uuid":"160011252","full_name":"dadiyang/http-api-invoker","owner":"dadiyang","description":"一个让http接口调用跟调用本地方法一样自然优雅的项目","archived":false,"fork":false,"pushed_at":"2022-09-01T22:59:43.000Z","size":241,"stargazers_count":171,"open_issues_count":9,"forks_count":40,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-11-13T04:56:10.639Z","etag":null,"topics":["httpclient","java","spring"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dadiyang.png","metadata":{"files":{"readme":"README-en.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-12-02T03:51:57.000Z","updated_at":"2024-09-04T15:46:40.000Z","dependencies_parsed_at":"2023-01-15T05:59:44.567Z","dependency_job_id":null,"html_url":"https://github.com/dadiyang/http-api-invoker","commit_stats":null,"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dadiyang%2Fhttp-api-invoker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dadiyang%2Fhttp-api-invoker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dadiyang%2Fhttp-api-invoker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dadiyang%2Fhttp-api-invoker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dadiyang","download_url":"https://codeload.github.com/dadiyang/http-api-invoker/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252334264,"owners_count":21731369,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["httpclient","java","spring"],"created_at":"2024-11-13T04:52:33.011Z","updated_at":"2026-01-12T08:28:54.077Z","avatar_url":"https://github.com/dadiyang.png","language":"Java","readme":"# HTTP API INVOKER\n\n**Make HTTP api invokes as natural and elegant as calling local methods.** \n\nBinding HTTP url to interface, the framework will generate a proxy class which send HTTP request and handle response when we call the interface's method. Generic is supported.\n\nThe only thing we need to do is defining the interface.\n\n# FEATURE\n\n1. Just define interface, and the framework provide the proxy implement like MyBatis.\n2. Light-weigh.\n3. Upload and download file is supported.\n4. Autowired annotation is supported if integrate with Spring.\n5. Well documented and unit tested.\n6. Mock supported\n7. JDK6+ (1.2.0 and above)\n\n# TECHNOLOGY STACK\n \n* dynamic proxy\n* reflection\n* annotation\n* auto package scanning\n\n# GET STARTED\n \n## I. Add maven dependency\n\n```xml\n \u003cdependency\u003e\n    \u003cgroupId\u003ecom.github.dadiyang\u003c/groupId\u003e\n    \u003cartifactId\u003ehttp-api-invoker\u003c/artifactId\u003e\n    \u003cversion\u003e1.2.4\u003c/version\u003e\n \u003c/dependency\u003e\n\u003c!-- choose between fastjson and gson --\u003e\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.alibaba\u003c/groupId\u003e\n    \u003cartifactId\u003efastjson\u003c/artifactId\u003e\n    \u003cversion\u003e1.2.75\u003c/version\u003e\n\u003c/dependency\u003e\n```\n## II. Define interface\n\nProvided `http://localhost:8080/city/allCities` response: \n```json\n[\n    {\n        \"id\":1,\n        \"name\":\"beijing\"\n    },\n    {\n        \"id\":2,\n        \"name\":\"shanghai\"\n    }\n]\n```\n\nDefine an interface like this: \n```java\n@HttpApi\npublic interface CityService {\n    @HttpReq(\"http://localhost:8080/city/allCities\")\n    List\u003cCity\u003e getAllCities();\n}\n```\n\n## III. Get proxy\n \n### HttpApiProxyFactory\n\nNow we can get a proxy implement of the interface by calling\n\n`CityService cityService = HttpApiProxyFactory.getProxy(CityService.class)`\n\nAnd just use this instance to send a request to the HTTP api:\n\n`List\u003cCity\u003e cities = cityService.getAllCities()`\n\n### Spring Integration\n\n#### Configuration\n\nAdd @HttpApiScan to a @Configuration class for enabling package scanning.\n\n```java\n@Configuration\n@HttpApiScan\npublic class TestApplication {\n}\n```\n \n#### Autowired the interface\n\n```java\n@Autowired\nprivate CityService cityService;\n\npublic void test() {\n    List\u003cCity\u003e cities = cityService.getAllCities();\n    for (City city : cities) {\n        System.out.println(city.getId() + \", \" + city.getName());\n    }\n}\n```\n\nNote: if your IDE complain \"Could not autowired. no beans of type 'xxx' type found\", just ignore that message.\n\n## IV. PLACEHOLDER\n\nPlaceholder is supported for reading config properties (using **${}**, like ${api.url.city}) and path variables (using **{}**, like {cityName}). \n\nNote: Params matched the path variables will be removed from the request body, but we can use placeholder **#{id}** to keep it.\n\nWe can use placeholder in **@HttpApi's prefix and @HttpReq's url**.\n\nNote：\n    \n- path variable using **`{}`**, \n- path variable and keep it in request params using **`#{}`**,\n- and config using **`${}`**.\n- set default value through : , such as ${api.url.city:beijing} {cityId:1} #{cityId:}\n\nThe framework will get the config property from: \n\n* **property file** set by **configPaths** in @HttpApiScan,\n* **System property**: System.getProperty(\"property\"),\n* and **Spring Environment** in Spring integration scenario.\n\n## V. Retry policy\n\nIn some cases, we need to retry a request if network is not available, response status code is not 2xx etc. We can use `@RetryPolicy` annotation to indicate that this method need to be retry when an unexpected condition occur. It can be annotated on method and class. The class's policy prior to the method's. \n\n* times: try times, 3 by default;\n* retryFor: what exception to retry, IOException by default;\n* retryForStatus: what status code would retry, other than 20x by default;\n* fixedBackOffPeriod: back off strategy, the number of seconds to sleep when retry is required, not to sleep by default.\n\n\n## VI. EXTENSION\n\n### RequestPreprocessor\n\nSometimes, we need to get all request added some specific headers, cookies or params.\n\nIt would be redundant to add these stuff.\n\nNow we can register a RequestPreprocessor. RequestPreprocessor's process() method will be call when a request is prepared but not yet send.\n\nWe are provided a chance to access the request and set anything we need. \n\n```java\npublic void preprocessorTest() {\n    HttpApiProxyFactory factory = new HttpApiProxyFactory(request -\u003e {\n        // we add cookie and header for all request invoked by the proxy get from this factory\n        request.addCookie(\"authCookies\", authKey);\n        request.addHeader(\"authHeaders\", authKey);\n        // get current proxied method from CURRENT_METHOD_THREAD_LOCAL\n        Method method = CURRENT_METHOD_THREAD_LOCAL.get();\n    });\n    CityService cityService = factory.getProxy(CityService.class);\n    City city = cityService.getCity(id);\n}\n```\n\nor in Spring scenario, register a RequestPreprocessor Bean.\n\n\n### ResponseProcessor\n\nSimilar to RequestPreprocessor, ResponseProcessor enable us to get access to take over the response by implementing the ResponseProcessor interface.\n\n```java\nResponseProcessor cityResultProcessor = (response, method) -\u003e {\n    ResultBean\u003cCity\u003e cityResultBean = JSON.parseObject(response.getBody(), \n            new TypeReference\u003cResultBean\u003cCity\u003e\u003e() {\n    });\n    return cityResultBean.getData();\n};\nHttpApiProxyFactory factory = new HttpApiProxyFactory(cityResultProcessor);\nCityService cityServiceWithResponseProcessor = factory.getProxy(CityService.class);\nCity city = cityServiceWithResponseProcessor.getCity(id);\n```\n\nor in Spring scenario, register a RequestPreprocessor Bean.\n\nUsually, an HTTP api would response a ResultBean formed with code/msg/data fields, like: \n\n```json\n{\n    \"code\": 0, \n    \"data\": {\n        \"name\": \"Hello\"\n    },\n    \"msg or message\": \"xx\"\n}\n```\n\nWe provided a `ResultBeanResponseProcessor` for this scenario. \n\nIt will check the code specify by @ExpectedCode annotation or 0 by default. \n\nIf the code in response body is not equals to the expected one, an IllegalStateException will be thrown.\n\nIf they are equal, the data field will be parse to return value, unless the method do not need it or its return type is a ResultBean.\n\n### JsonSerializer\n\nFastjson is used for JSON serialization and deserialization in this project. However, some users reported that they could not introduce fastjson due to objective reasons such as the company's regulations. Therefore, we decouple the serializer and **still use fastjson by default**. If there are special requirements, the specific implementation can be specified by the following methods:\n\n```java\nJsonSerializerDecider.registerJsonSerializer(\"Gson\", GsonJsonSerializer.getInstance());\nJsonSerializerDecider.setJsonInstanceKey(\"Gson\");\n```\n\nWe provided two implementations, fastjson and gson, and makes the behavior of these two implementations consistent to the maximum extent through some configuration, so that the replacement of JSON implementation will not affect the original code. If you need other implementations, you can do so through implementing ` com.github.dadiyang . httpinvoker.serializer.JsonSerializer` interface, and then according to the above way to replace your own implementation.\n\n# CORE ANNOTATION\n\n## @HttpApiScan\n\nEnable package scanning similar to @ComponentScan\n\n* value: to set basePackage，the annotated class's package by default\n* configPaths: to specify config files\n\n## @HttpApi\n\nDeclare a class it HTTP api binding class which need to be scanned. Similar to Spring's @Component\n\n## @HttpReq\n\nSet the url and request method (GET/POST/DELETE etc.) binding to the method.\n\n## @Param\n\nvalue: the key of request param\nisBody: mark that the argument is the request body, if the argument is an non-primary object, all the field-value will be a part of request params.\n\nvalue and isBody should not both be empty/false, otherwise the param will be ignored\n\n## @Headers\n\nHeaders of the request, must be `Map\u003cString, String\u003e` otherwise an `IllegalArgumentException` will be thrown.\n\n## @Cookies\n\nCookies of the request, must be `Map\u003cString, String\u003e` otherwise an `IllegalArgumentException` will be thrown.\n\n## @Form\n\nindicate a method or all methods in a class would send a form request, Content-Type of application/x-www-form-urlencoded.\n\n## @RetryPolicy\n\nRetry policy can be annotated to both class and method.\n\n* times: try times, 3 by default;\n* retryFor: what exception to retry, IOException by default;\n* retryForStatus: what status code would retry, other than 20x by default;\n* fixedBackOffPeriod: back off strategy, the number of seconds to sleep when retry is required, not to sleep by default.","funding_links":[],"categories":["Java","网络编程"],"sub_categories":["Spring Cloud框架"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdadiyang%2Fhttp-api-invoker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdadiyang%2Fhttp-api-invoker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdadiyang%2Fhttp-api-invoker/lists"}