diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..97da27b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,33 @@ + +name: build + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 8 + uses: actions/setup-java@v4 + with: + java-version: '8' + distribution: 'temurin' + cache: maven + - name: Install cloc + run: sudo apt-get install -y cloc + - name: Count lines of code + run: cloc . + - name: Build with Maven + run: mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent package + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + token: 25db9047-7c6d-461a-b63d-181a4749d6f2 + slug: cowave5/http-client + file: ${{ github.workspace }}/target/site/jacoco/jacoco.xml + diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml deleted file mode 100644 index 40f7b14..0000000 --- a/.github/workflows/maven.yml +++ /dev/null @@ -1,35 +0,0 @@ -# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -name: build - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - cache: maven - - name: Build with Maven - run: mvn -B package --file pom.xml - - # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive - #- name: Update dependency graph - # uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6 diff --git a/README.md b/README.md index dd0b9b4..bc5464a 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,22 @@ -[![Build Status](https://github.com/cowave5/spring-feign/actions/workflows/maven.yml/badge.svg?branch=master)](https://github.com/cowave5/spring-feign/actions) -![Static Badge](https://img.shields.io/badge/Java-17-brightgreen) -![Maven central](https://img.shields.io/badge/maven--central-2.7.4-brightgreen) +[![Build Status](https://github.com/cowave5/http-client/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/cowave5/http-client/actions) +![Static Badge](https://img.shields.io/badge/Java-1.8-brightgreen) +![Maven central](https://img.shields.io/badge/maven--central-2.7.5-brightgreen) +[![codecov.io](https://codecov.io/github/cowave5/http-client/coverage.svg?branch=master)](https://codecov.io/github/cowave5/http-client?branch=master) [![License](https://img.shields.io/badge/license-Apache--2.0-brightgreen)](http://www.apache.org/licenses/LICENSE-2.0.txt) -## spring-feign +## http-client -一个Http调用客户端,依赖netflix feign实现,方便在spring中声明 +Http调用客户端,参考Netflix Feign实现,使用的Apache Httpclient进行调用 - 依赖 ```xml com.cowave.commons - spring-feign - 2.7.4 + http-client + 2.7.5 ``` -- 使用说明:[wiki](https://github.com/cowave5/spring-feign/wiki/%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E) -- 问题建议:[issues](https://github.com/cowave5/spring-feign/issues) +- 使用说明:[wiki](https://github.com/cowave5/http-client/wiki/%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E) +- 问题建议:[issues](https://github.com/cowave5/http-client/issues) diff --git a/pom.xml b/pom.xml index b1b9b71..1dc7dc4 100644 --- a/pom.xml +++ b/pom.xml @@ -1,15 +1,15 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 com.cowave.commons - spring-feign - 2.7.4 + http-client + 2.7.5 jar - spring-feign - A simple http client, based on netflix feign - https://github.com/cowave5/spring-feign + http-client + A simple http client + https://github.com/cowave5/http-client @@ -21,13 +21,13 @@ github - https://github.com/cowave5/spring-feign/issues + https://github.com/cowave5/http-client/issues - git@github.com:cowave5/spring-feign.git - scm:git@github.com:cowave5/spring-feign.git - scm:git@github.com:cowave5/spring-feign.git + git@github.com:cowave5/http-client.git + scm:git@github.com:cowave5/http-client.git + scm:git@github.com:cowave5/http-client.git @@ -59,8 +59,8 @@ maven-compiler-plugin 3.8.1 - 17 - 17 + 1.8 + 1.8 UTF-8 @@ -81,6 +81,32 @@ + + org.jacoco + jacoco-maven-plugin + 0.8.11 + + + prepare-agent + + prepare-agent + + test-compile + + + report + + report + + test + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + @@ -145,48 +171,82 @@ - com.cowave.commons - commons-response - 2.7.5 + org.reflections + reflections + 0.9.10 + - com.netflix.feign - feign-core - 8.18.0 + org.apache.commons + commons-lang3 + 3.12.0 + - com.netflix.feign - feign-jackson - 8.18.0 + commons-io + commons-io + 2.14.0 + - org.reflections - reflections - 0.9.10 + org.apache.commons + commons-collections4 + 4.4 + - org.springframework.boot - spring-boot-starter-web - 2.7.0 - provided + org.apache.httpcomponents + httpclient + 4.5.13 + - com.fasterxml.jackson.core - jackson-databind - 2.13.5 - provided + org.apache.httpcomponents + httpmime + 4.5.13 + - org.slf4j - slf4j-api - 1.7.30 - provided + org.apache.commons + commons-text + 1.10.0 + + + + com.alibaba + transmittable-thread-local + 2.14.2 + + + + com.github.pagehelper + pagehelper + 6.1.0 + + + + com.baomidou + mybatis-plus-extension + 3.5.4.1 + org.projectlombok lombok 1.18.24 provided + + org.springframework.boot + spring-boot-starter-web + 2.7.0 + provided + + + org.springframework.boot + spring-boot-starter-test + 2.7.0 + test + diff --git a/src/main/java/com/cowave/commons/client/http/HttpClientConfiguration.java b/src/main/java/com/cowave/commons/client/http/HttpClientConfiguration.java new file mode 100644 index 0000000..209e1de --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/HttpClientConfiguration.java @@ -0,0 +1,21 @@ +package com.cowave.commons.client.http; + +import com.cowave.commons.client.http.annotation.EnableHttpClient; +import com.cowave.commons.client.http.asserts.I18Messages; +import org.springframework.context.MessageSource; + +import javax.annotation.Resource; + +/** + * + * @author shanhuiming + * + */ +@EnableHttpClient +public class HttpClientConfiguration { + + @Resource + public void setMessageSource(MessageSource messageSource) { + I18Messages.setMessageSource(messageSource); + } +} diff --git a/src/main/java/com/cowave/commons/client/http/HttpCode.java b/src/main/java/com/cowave/commons/client/http/HttpCode.java new file mode 100644 index 0000000..6dfff41 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/HttpCode.java @@ -0,0 +1,575 @@ +package com.cowave.commons.client.http; + +import com.cowave.commons.client.http.response.ResponseCode; + +/** + * + * @author shanhuiming + * + */ +public enum HttpCode implements ResponseCode { + + /* ******************************************************************************** + * 1.xx Informational + * ********************************************************************************/ + + /** + * @see 100 Continue + */ + CONTINUE(100, "100", "Continue"), + + /** + * @see 101 Switching Protocols + */ + SWITCHING_PROTOCOLS(101, "101", "Switching Protocols"), + + /** + * @see 102 Processing + */ + PROCESSING(102, "102", "Processing"), + + /** + * @see 103 Early Hints + */ + EARLY_HINTS(103, "103", "Early Hints"), + + /* ******************************************************************************** + * 2.xx Success + * ********************************************************************************/ + + /** + * @see 200 Success + */ + SUCCESS(200, "200", "Success"), + + /** + * @see 201 Created + */ + CREATED(201, "201", "Created"), + + /** + * @see 202 Accepted + */ + ACCEPTED(202, "202", "Accepted"), + + /** + * @see 203 Non-Authoritative Information + */ + NON_AUTHORITATIVE_INFORMATION(203, "203", "Non-Authoritative Information"), + + /** + * @see 204 No Content + */ + NO_CONTENT(204, "204", "No Content"), + + /** + * @see 205 Reset Content + */ + RESET_CONTENT(205, "205", "Reset Content"), + + /** + * @see 206 Partial Content + */ + PARTIAL_CONTENT(206, "206", "Partial Content"), + + /** + * @see 207 Multi-Status + */ + MULTI_STATUS(207, "207", "Multi-Status"), + + /** + * @see 208 Already Reported + */ + ALREADY_REPORTED(208, "208", "Already Reported"), + + /** + * @see 218 This Is Fine + */ + THIS_IS_FINE(218, "218", "This Is Fine"), + + /** + * @see 226 IM Used + */ + IM_USED(226, "226", "IM Used"), + + /* ******************************************************************************** + * 3.xx Redirection + * ********************************************************************************/ + + /** + * @see 300 Multiple Choices + */ + MULTIPLE_CHOICES(300, "300", "Multiple Choices"), + + /** + * @see 301 Moved Permanently + */ + MOVED_PERMANENTLY(301, "301", "Moved Permanently"), + + /** + * @see 302 Found + */ + FOUND(302, "302", "Found"), + + /** + * @see 303 See Other + */ + SEE_OTHER(303, "303", "See Other"), + + /** + * @see 304 Not Modified + */ + NOT_MODIFIED(304, "304", "Not Modified"), + + /** + * @see 305 Use Proxy + */ + USE_PROXY(305, "305", "Use Proxy"), + + /** + * @see 306 Switch Proxy + */ + SWITCH_PROXY(306, "306", "Switch Proxy"), + + /** + * @see 307 Temporary Redirect + */ + TEMPORARY_REDIRECT(307, "307", "Temporary Redirect"), + + /** + * @see 308 Permanent Redirect + */ + PERMANENT_REDIRECT(308, "308", "Permanent Redirect"), + + /* ******************************************************************************** + * 4.xx Client Error + * ********************************************************************************/ + + /** + * @see 400 Bad Request + */ + BAD_REQUEST(400, "400", "Bad Request"), + + /** + * @see 401 Unauthorized + */ + UNAUTHORIZED(401, "401", "Unauthorized"), + + /** + * @see 402 Payment Required + */ + PAYMENT_REQUIRED(402, "402", "Payment Required"), + + /** + * @see 403 Forbidden + */ + FORBIDDEN(403, "403", "Forbidden"), + + /** + * @see 404 Not Found + */ + NOT_FOUND(404, "404", "Not Found"), + + /** + * @see 405 Method Not Allowed + */ + METHOD_NOT_ALLOWED(405, "405", "Method Not Allowed"), + + /** + * @see 406 Not Acceptable + */ + NOT_ACCEPTABLE(406, "406", "Not Acceptable"), + + /** + * @see 407 Proxy Authentication Required + */ + PROXY_AUTHENTICATION_REQUIRED(407, "407", "Proxy Authentication Required"), + + /** + * @see 408 Request Timeout + */ + REQUEST_TIMEOUT(408, "408", "Request Timeout"), + + /** + * @see 409 Conflict + */ + CONFLICT(409, "409", "Conflict"), + + /** + * @see 410 Gone + */ + GONE(410, "410", "Gone"), + + /** + * @see 411 Length Required + */ + LENGTH_REQUIRED(411, "411", "Length Required"), + + /** + * @see 412 Precondition Failed + */ + PRECONDITION_FAILED(412, "412", "Precondition Failed"), + + /** + * @see 413 Payload Too Large + */ + PAYLOAD_TOO_LARGE(413, "413", "Payload Too Large"), + + /** + * @see 414 URI Too Long + */ + URI_TOO_LONG(414, "414", "URI Too Long"), + + /** + * @see 415 Unsupported Media Type + */ + UNSUPPORTED_MEDIA_TYPE(415, "415", "Unsupported Media Type"), + + /** + * @see 416 Range Not Satisfiable + */ + REQUESTED_RANGE_NOT_SATISFIABLE(416, "416", "Requested range not satisfiable"), + + /** + * @see 417 Expectation Failed + */ + EXPECTATION_FAILED(417, "417", "Expectation Failed"), + + /** + * @see 418 I'm a Teapot + */ + I_AM_A_TEAPOT(418, "418", "I'm a teapot"), + + /** + * @see 419 Page Expired + */ + PAGE_EXPIRED(419, "419", "Page Expired"), + + /** + * @see 420 Method Failure + */ + METHOD_FAILURE(420, "420", "Method Failure"), + + /** + * @see 421 Misdirected Request + */ + MISDIRECTED_REQUEST(421, "421", "Misdirected Request"), + + /** + * @see 422 Unprocessable Entity + */ + UNPROCESSABLE_ENTITY(422, "422", "Unprocessable Entity"), + + /** + * @see 423 Locked + */ + LOCKED(423, "423", "Locked"), + + /** + * @see 424 Failed Dependency + */ + FAILED_DEPENDENCY(424, "424", "Failed Dependency"), + + /** + * @see 425 Too Early + */ + TOO_EARLY(425, "425", "Too Early"), + + /** + * @see 426 Upgrade Required + */ + UPGRADE_REQUIRED(426, "426", "Upgrade Required"), + + /** + * @see 428 Precondition Required + */ + PRECONDITION_REQUIRED(428, "428", "Precondition Required"), + + /** + * @see 429 Too Many Requests + */ + TOO_MANY_REQUESTS(429, "429", "Too Many Requests"), + + /** + * @see 430 HTTP Status Code + */ + HTTP_STATUS_CODE(430, "430", "HTTP Status Code"), + + /** + * @see 431 Request Header Fields Too Large + */ + REQUEST_HEADER_FIELDS_TOO_LARGE(431, "431", "Request Header Fields Too Large"), + + /** + * @see 440 Login Time-Out + */ + LOGIN_TIME_OUT(440, "440", "Login Time-Out"), + + /** + * @see 444 No Response + */ + NO_RESPONSE(444, "444", "No Response"), + + /** + * @see 449 Retry With + */ + RETRY_WITH(449, "449", "Retry With"), + + /** + * @see 450 Blocked by Windows Parental Controls + */ + BLOCKED_BY_WINDOWS_PARENTAL_CONTROLS(450, "450", "Blocked by Windows Parental Controls"), + + /** + * @see 451 Unavailable For Legal Reasons + */ + UNAVAILABLE_FOR_LEGAL_REASONS(451, "451", "Unavailable For Legal Reasons"), + + /** + * @see 460 Client Closed Connection Prematurely + */ + CLIENT_CLOSED_CONNECTION_PREMATURELY(460, "460", "Client Closed Connection Prematurely"), + + /** + * @see 463 Too Many Forwarded IP Addresses + */ + TOO_MANY_FORWARDED_IP_ADDRESSES(463, "463", "Too Many Forwarded IP Addresses"), + + /** + * @see 464 Incompatible Protocol + */ + INCOMPATIBLE_PROTOCOL(464, "464", "Incompatible Protocol"), + + /** + * @see 494 Request Header Too Large + */ + REQUEST_HEADER_TOO_LARGE(494, "494", "Request Header Too Large"), + + /** + * @see 495 SSL Certificate Error + */ + SSL_CERTIFICATE_ERROR(495, "495", "SSL Certificate Error"), + + /** + * @see 496 SSL Certificate Required + */ + SSL_CERTIFICATE_REQUIRED(496, "496", "SSL Certificate Required"), + + /** + * @see 497 HTTP Request Sent to HTTPS Port + */ + HTTP_REQUEST_SENT_TO_HTTPS_PORT(497, "497", "HTTP Request Sent to HTTPS Port"), + + /** + * @see 498 Invalid Token + */ + INVALID_TOKEN(498, "498", "Invalid Token"), + + /** + * @see 499 Token Required or Client Closed Request + */ + TOKEN_REQUIRED_OR_CLIENT_CLOSED_REQUEST(499, "499", "Token Required or Client Closed Request"), + + /* ******************************************************************************** + * 5.xx Server Error + * ********************************************************************************/ + + /** + * @see 500 Internal Server Error + */ + INTERNAL_SERVER_ERROR(500, "500", "Internal Server Error"), + + /** + * @see 501 Not Implemented + */ + NOT_IMPLEMENTED(501, "501", "Not Implemented"), + + /** + * @see 502 Bad Gateway + */ + BAD_GATEWAY(502, "502", "Bad Gateway"), + + /** + * @see 503 Service Unavailable + */ + SERVICE_UNAVAILABLE(503, "503", "Service Unavailable"), + + /** + * @see 504 Gateway Timeout + */ + GATEWAY_TIMEOUT(504, "504", "Gateway Timeout"), + + /** + * @see 505 HTTP Version Not Supported + */ + HTTP_VERSION_NOT_SUPPORTED(505, "505", "HTTP Version not supported"), + + /** + * @see 506 Variant Also Negotiates + */ + VARIANT_ALSO_NEGOTIATES(506, "506", "Variant Also Negotiates"), + + /** + * @see 507 Insufficient Storage + */ + INSUFFICIENT_STORAGE(507, "507", "Insufficient Storage"), + + /** + * @see 508 Loop Detected + */ + LOOP_DETECTED(508, "508", "Loop Detected"), + + /** + * @see 509 Bandwidth Limit Exceeded + */ + BANDWIDTH_LIMIT_EXCEEDED(509, "509", "Bandwidth Limit Exceeded"), + + /** + * @see 510 Not Extended + */ + NOT_EXTENDED(510, "510", "Not Extended"), + + /** + * @see 511 Network Authentication Required + */ + NETWORK_AUTHENTICATION_REQUIRED(511, "511", "Network Authentication Required"), + + /** + * @see 520 Web Server Is Returning an Unknown Error + */ + WEB_SERVER_IS_RETURNING_AN_UNKNOWN_ERROR(520, "520", "Web Server Is Returning an Unknown Error"), + + /** + * @see 521 Web Server Is Down + */ + WEB_SERVER_IS_DOWN(521, "521", "Web Server Is Down"), + + /** + * @see 522 Connection Timed Out + */ + CONNECTION_TIMED_OUT(522, "522", "Connection Timed Out"), + + /** + * @see 523 Origin Is Unreachable + */ + ORIGIN_IS_UNREACHABLE(523, "523", "Origin Is Unreachable"), + + /** + * @see 524 A Timeout Occurred + */ + A_TIMEOUT_OCCURRED(524, "524", "A Timeout Occurred"), + + /** + * @see 525 SSL Handshake Failed + */ + SSL_HANDSHAKE_FAILED(525, "525", "SSL Handshake Failed"), + + /** + * @see 526 Invalid SSL Certificate + */ + INVALID_SSL_CERTIFICATE(526, "526", "Invalid SSL Certificate"), + + /** + * @see 527 Railgun Listener to Origin + */ + RAILGUN_LISTENER_TO_ORIGIN(527, "527", "Railgun Listener to Origin"), + + /** + * @see 529 The Service Is Overloaded + */ + THE_SERVER_IS_OVERLOADED(529, "529", "The Service Is Overloaded"), + + /** + * @see 530 Site Frozen + */ + SITE_FROZEN(530, "530", "Site Frozen"), + + /** + * @see 561 Unauthorized + */ + SERVER_UNAUTHORIZED(561, "561", "Unauthorized"), + + /** + * @see 598 Network Read Timeout Error + */ + NETWORK_READ_TIMEOUT_ERROR(598, "598", "Network Read Timeout Error"), + + /** + * @see 599 Network Connect Timeout Error + */ + NETWORK_CONNECT_TIMEOUT_ERROR(599, "599", "Network Connect Timeout Error"), + + /* ******************************************************************************** + * In addition to the five primary categories of HTTP status codes mentioned above, + * the following status codes can also be encountered on the World Wide Web + * ********************************************************************************/ + + /** + * 597 服务出错 + */ + SERVICE_ERROR(597, "597", "Service Error"), + + /** + * @see 110 Response Is Stale + */ + RESPONSE_IS_STALE(110, "110", "Response Is Stale"), + + /** + * @see 111 Revalidation Failed + */ + REVALIDATION_FAILED(111, "111", "Revalidation Failed"), + + /** + * @see 112 Disconnected Operation + */ + DISCONNECTED_OPERATION(112, "112", "Disconnected Operation"), + + /** + * @see 113 Heuristic Expiration + */ + HEURISTIC_EXPIRATION(113, "113", "Heuristic Expiration"), + + /** + * @see 199 Miscellaneous Warning + */ + MISCELLANEOUS_WARNING(199, "199", "Miscellaneous Warning"), + + /** + * @see 214 Transformation Applied + */ + TRANSFORMATION_APPLIED(214, "214", "Transformation Applied"), + + /** + * @see 299 Miscellaneous Persistent Warning + */ + MISCELLANEOUS_PERSISTENT_WARNING(299, "299", "Miscellaneous Persistent Warning"), + + /** + * @see 999 999 + */ + UNKNOWN(999, "999", "Unknown"); + + private final Integer status; + + private final String code; + + private final String msg; + + HttpCode(Integer status, String code, String msg) { + this.status = status; + this.code = code; + this.msg = msg; + } + + public int getStatus(){ + return status; + } + + public String getCode() { + return code; + } + + public String getMsg() { + return msg; + } +} diff --git a/src/main/java/com/cowave/commons/client/http/HttpExceptionHandler.java b/src/main/java/com/cowave/commons/client/http/HttpExceptionHandler.java new file mode 100644 index 0000000..8116f05 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/HttpExceptionHandler.java @@ -0,0 +1,13 @@ +package com.cowave.commons.client.http; + +import com.cowave.commons.client.http.asserts.HttpException; + +/** + * + * @author shanhuiming + * + */ +public interface HttpExceptionHandler { + + void handle(HttpException e); +} diff --git a/src/main/java/com/cowave/commons/client/http/HttpHeader.java b/src/main/java/com/cowave/commons/client/http/HttpHeader.java new file mode 100644 index 0000000..51f21e3 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/HttpHeader.java @@ -0,0 +1,571 @@ +package com.cowave.commons.client.http; + +/** + * + * @author shanhuiming + * + */ +public interface HttpHeader { + + /* ******************************************************************************** + * Authentication + * ********************************************************************************/ + + /** + * @see WWW-Authenticate + */ + String WWW_Authenticate = "WWW-Authenticate"; + + /** + * @see Authorization + */ + String Authorization = "Authorization"; + + /** + * @see Proxy-Authenticate + */ + String Proxy_Authenticate = "Proxy-Authenticate"; + + /** + * @see Proxy-Authorization + */ + String Proxy_Authorization = "Proxy-Authorization"; + + /* ******************************************************************************** + * Caching + * ********************************************************************************/ + + /** + * @see Age + */ + String Age = "Age"; + + /** + * @see Cache-Control + */ + String Cache_Control = "Cache-Control"; + + /** + * @see Clear-Site-Data + */ + String Clear_Site_Data = "Clear-Site-Data"; + + /** + * @see Expires + */ + String Expires = "Expires"; + + /** + * @see Pragma + */ + String Pragma = "Pragma"; + + /** + * @see Warning + */ + String Warning = "Warning"; + + /* ******************************************************************************** + * Client Hints + * ********************************************************************************/ + + /** + * @see Accept-CH + */ + String Accept_CH = "Accept-CH"; + + /* ******************************************************************************** + * Network client hints + * ********************************************************************************/ + + /** + * @see Save-Data + */ + String Save_Data = "Save-Data"; + + /* ******************************************************************************** + * Conditionals + * ********************************************************************************/ + + /** + * @see Last-Modified + */ + String Last_Modified = "Last-Modified"; + + /** + * @see ETag + */ + String ETag = "ETag"; + + /** + * @see If-Match + */ + String If_Match = "If-Match"; + + /** + * @see If-None-Match + */ + String If_None_Match = "If-None-Match"; + + /** + * @see If-Modified-Since + */ + String If_Modified_Since = "If-Modified-Since"; + + /** + * @see If-Unmodified-Since + */ + String If_Unmodified_Since = "If-Unmodified-Since"; + + /** + * @see Vary + */ + String Vary = "Vary"; + + /** + * @see Delta-Base + */ + String Delta_Base = "Delta-Base"; + + /* ******************************************************************************** + * Connection Management + * ********************************************************************************/ + + /** + * @see Connection + */ + String Connection = "Connection"; + + /** + * @see Keep-Alive + */ + String Keep_Alive = "Keep-Alive"; + + /* ******************************************************************************** + * Content negotiation + * ********************************************************************************/ + + /** + * @see Accept + */ + String Accept = "Accept"; + + /** + * @see Accept-Encoding + */ + String Accept_Encoding = "Accept-Encoding"; + + /** + * @see Accept-Language + */ + String Accept_Language = "Accept-Language"; + + /** + * @see A-IM + */ + String A_IM = "A-IM"; + + /** + * @see IM + */ + String IM = "IM"; + + /* ******************************************************************************** + * Controls + * ********************************************************************************/ + + /** + * @see Expect + */ + String Expect = "Expect"; + + /** + * @see Max-Forwards + */ + String Max_Forwards = "Max-Forwards"; + + /* ******************************************************************************** + * Cookies + * ********************************************************************************/ + + /** + * @see Cookie + */ + String Cookie = "Cookie"; + + /** + * @see Set-Cookie + */ + String Set_Cookie = "Set-Cookie"; + + /* ******************************************************************************** + * CORS + * ********************************************************************************/ + + /** + * @see Access-Control-Allow-Origin + */ + String Access_Control_Allow_Origin = "Access-Control-Allow-Origin"; + + /** + * @see Access-Control-Allow-Credentials + */ + String Access_Control_Allow_Credentials = "Access-Control-Allow-Credentials"; + + /** + * @see Access-Control-Allow-Headers + */ + String Access_Control_Allow_Headers = "Access-Control-Allow-Headers"; + + /** + * @see Access-Control-Expose-Headers + */ + String Access_Control_Expose_Headers = "Access-Control-Expose-Headers"; + + /** + * @see Access-Control-Max-Age + */ + String Access_Control_Max_Age = "Access-Control-Max-Age"; + + /** + * @see Access-Control-Request-Headers + */ + String Access_Control_Request_Headers = "Access-Control-Request-Headers"; + + /** + * @see Access-Control-Request-Method + */ + String Access_Control_Request_Method = "Access-Control-Request-Method"; + + /** + * @see Timing-Allow-Origin + */ + String Timing_Allow_Origin = "Timing-Allow-Origin"; + + /* ******************************************************************************** + * Downloads + * ********************************************************************************/ + + /** + * @see Content-Disposition + */ + String Content_Disposition = "Content-Disposition"; + + /* ******************************************************************************** + * Message body information + * ********************************************************************************/ + + /** + * @see Content-Length + */ + String Content_Length = "Content-Length"; + + /** + * @see Content-Type + */ + String Content_Type = "Content-Type"; + + /** + * @see Content-Encoding + */ + String Content_Encoding = "Content-Encoding"; + + /** + * @see Content-Language + */ + String Content_Language = "Content-Language"; + + /** + * @see Content-Location + */ + String Content_Location = "Content-Location"; + + /* ******************************************************************************** + * Proxies + * ********************************************************************************/ + + /** + * @see Forwarded + */ + String Forwarded = "Forwarded"; + + /** + * @see X-Forwarded-For + */ + String X_Forwarded_For = "X-Forwarded-For"; + + /** + * @see X-Forwarded-Host + */ + String X_Forwarded_Host = "X-Forwarded-Host"; + + /** + * @see X-Forwarded-Proto + */ + String X_Forwarded_Proto = "X-Forwarded-Proto"; + + /** + * @see Via + */ + String Via = "Via"; + + /* ******************************************************************************** + * Redirects + * ********************************************************************************/ + + /** + * @see Location + */ + String Location = "Location"; + + /* ******************************************************************************** + * Request context + * ********************************************************************************/ + + /** + * @see From + */ + String From = "From"; + + /** + * @see Host + */ + String Host = "Host"; + + /** + * @see Referer + */ + String Referer = "Referer"; + + /** + * @see Referrer-Policy + */ + String Referrer_Policy = "Referrer-Policy"; + + /** + * @see User-Agent + */ + String User_Agent = "User-Agent"; + + /* ******************************************************************************** + * Response context + * ********************************************************************************/ + + /** + * @see Allow + */ + String Allow = "Allow"; + + /** + * @see Server + */ + String Server = "Server"; + + /* ******************************************************************************** + * Range requests + * ********************************************************************************/ + + /** + * @see Accept-Ranges + */ + String Accept_Ranges = "Accept-Ranges"; + + /** + * @see Range + */ + String Range = "Range"; + + /** + * @see If-Range + */ + String If_Range = "If-Range"; + + /** + * @see Content-Range + */ + String Content_Range = "Content-Range"; + + /* ******************************************************************************** + * Security + * ********************************************************************************/ + + /** + * @see Cross-Origin-Embedder-Policy + */ + String Cross_Origin_Embedder_Policy = "Cross-Origin-Embedder-Policy"; + + /** + * @see Cross-Origin-Opener-Policy + */ + String Cross_Origin_Opener_Policy = "Cross-Origin-Opener-Policy"; + + /** + * @see Cross-Origin-Resource-Policy + */ + String Cross_Origin_Resource_Policy = "Cross-Origin-Resource-Policy"; + + /** + * @see Content-Security-Policy + */ + String Content_Security_Policy = "Content-Security-Policy"; + + /** + * @see Content-Security-Policy-Report-Only + */ + String Content_Security_Policy_Report_Only = "Content-Security-Policy-Report-Only"; + + /** + * @see Expect-CT + */ + String Expect_CT = "Expect-CT"; + + /** + * @see Strict-Transport-Security + */ + String Strict_Transport_Security = "Strict-Transport-Security"; + + /** + * @see Upgrade-Insecure-Requests + */ + String Upgrade_Insecure_Requests = "Upgrade-Insecure-Requests"; + + /** + * @see X-Content-Type-Options + */ + String X_Content_Type_Options = "X-Content-Type-Options"; + + /** + * @see X-Frame-Options + */ + String X_Frame_Options = "X-Frame-Options"; + + /** + * @see X-Powered-By + */ + String X_Powered_By = "X-Powered-By"; + + /** + * @see X-XSS-Protection + */ + String X_XSS_Protection = "X-XSS-Protection"; + + /* ******************************************************************************** + * Fetch metadata request headers + * ********************************************************************************/ + + /** + * @see Sec-Fetch-Site + */ + String Sec_Fetch_Site = "Sec-Fetch-Site"; + + /** + * @see Sec-Fetch-Mode + */ + String Sec_Fetch_Mode = "Sec-Fetch-Mode"; + + /** + * @see Sec-Fetch-User + */ + String Sec_Fetch_User = "Sec-Fetch-User"; + + /** + * @see Sec-Fetch-Dest + */ + String Sec_Fetch_Dest = "Sec-Fetch-Dest"; + + /* ******************************************************************************** + * Server-Sent events + * ********************************************************************************/ + + /** + * @see NEL + */ + String NEL = "NEL"; + + /* ******************************************************************************** + * Transfer coding + * ********************************************************************************/ + + /** + * @see Transfer-Encoding + */ + String Transfer_Encoding = "Transfer-Encoding"; + + /** + * @see TE + */ + String TE = "TE"; + + /** + * @see Trailer + */ + String Trailer = "Trailer"; + + /* ******************************************************************************** + * WebSockets + * ********************************************************************************/ + + /** + * @see Sec-Websocket-Accept + */ + String Sec_Websocket_Accept = "Sec-Websocket-Accept"; + + /* ******************************************************************************** + * Other + * ********************************************************************************/ + + /** + * @see Alt-Svc + */ + String Alt_Svc = "Alt-Svc"; + + /** + * @see Date + */ + String Date = "Date"; + + /** + * @see Link + */ + String Link = "Link"; + + /** + * @see Retry-After + */ + String Retry_After = "Retry-After"; + + /** + * @see Server-Timing + */ + String Server_Timing = "Server-Timing"; + + /** + * @see Sourcemap + */ + String Sourcemap = "Sourcemap"; + + /** + * @see Upgrade + */ + String Upgrade = "Upgrade"; + + /** + * @see X-DNS-Prefetch-Control + */ + String X_DNS_Prefetch_Control = "X-DNS-Prefetch-Control"; + + /** + * @see X-Request-ID + */ + String X_Request_ID = "X-Request-ID"; + + /** + * @see X-Robots-Tag + */ + String X_Robots_Tag = "X-Robots-Tag"; + + /** + * @see X-UA-Compatible + */ + String X_UA_Compatible = "X-UA-Compatible"; +} diff --git a/src/main/java/com/cowave/commons/client/http/HttpInterceptor.java b/src/main/java/com/cowave/commons/client/http/HttpInterceptor.java new file mode 100644 index 0000000..335c2c5 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/HttpInterceptor.java @@ -0,0 +1,13 @@ +package com.cowave.commons.client.http; + +import com.cowave.commons.client.http.request.HttpRequest; + +/** + * + * @author shanhuiming + * + */ +public interface HttpInterceptor { + + void apply(HttpRequest httpRequest); +} diff --git a/src/main/java/com/cowave/commons/client/http/HttpServiceChooser.java b/src/main/java/com/cowave/commons/client/http/HttpServiceChooser.java new file mode 100644 index 0000000..3a4b7f5 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/HttpServiceChooser.java @@ -0,0 +1,11 @@ +package com.cowave.commons.client.http; + +/** + * + * @author shanhuiming + * + */ +public interface HttpServiceChooser { + + String choose(String name); +} diff --git a/src/main/java/org/springframework/feign/annotation/EnableFeign.java b/src/main/java/com/cowave/commons/client/http/annotation/EnableHttpClient.java similarity index 56% rename from src/main/java/org/springframework/feign/annotation/EnableFeign.java rename to src/main/java/com/cowave/commons/client/http/annotation/EnableHttpClient.java index 2884045..62eb822 100644 --- a/src/main/java/org/springframework/feign/annotation/EnableFeign.java +++ b/src/main/java/com/cowave/commons/client/http/annotation/EnableHttpClient.java @@ -1,7 +1,7 @@ -package org.springframework.feign.annotation; +package com.cowave.commons.client.http.annotation; import org.springframework.context.annotation.Import; -import org.springframework.feign.FeignBeanDefinitionRegistrar; +import com.cowave.commons.client.http.register.HttpClientBeanDefinitionRegistrar; import java.lang.annotation.*; @@ -16,7 +16,7 @@ @Target({TYPE}) @Retention(RUNTIME) @Documented -@Import(FeignBeanDefinitionRegistrar.class) -public @interface EnableFeign { +@Import(HttpClientBeanDefinitionRegistrar.class) +public @interface EnableHttpClient { } diff --git a/src/main/java/com/cowave/commons/client/http/annotation/HttpBody.java b/src/main/java/com/cowave/commons/client/http/annotation/HttpBody.java new file mode 100644 index 0000000..6cce61b --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/annotation/HttpBody.java @@ -0,0 +1,28 @@ +package com.cowave.commons.client.http.annotation; + +import com.cowave.commons.client.http.request.HttpRequest; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.Map; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * A possibly templated body of a PUT or POST command. variables wrapped in curly braces are + * expanded before the request is submitted.
ex.
+ *
+ * @Body("<v01:getResourceRecordsOfZone><zoneName>{zoneName}</zoneName><rrType>0</rrType></v01:getResourceRecordsOfZone>")
+ * List<Record> listByZone(@Param("zoneName") String zoneName);
+ * 
+ *
Note that if you'd like curly braces literally in the body, urlencode them first. + * + * @see HttpRequest#expand(String, Map) + */ +@Target(METHOD) +@Retention(RUNTIME) +public @interface HttpBody { + + String value(); +} diff --git a/src/main/java/com/cowave/commons/client/http/annotation/HttpClient.java b/src/main/java/com/cowave/commons/client/http/annotation/HttpClient.java new file mode 100644 index 0000000..101d75d --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/annotation/HttpClient.java @@ -0,0 +1,66 @@ +package com.cowave.commons.client.http.annotation; + +import com.cowave.commons.client.http.request.ssl.NoopHostnameVerifier; +import com.cowave.commons.client.http.request.ssl.NoopTlsSocketFactory; +import org.slf4j.event.Level; +import org.springframework.core.annotation.AliasFor; +import com.cowave.commons.client.http.invoke.codec.decoder.JacksonDecoder; +import com.cowave.commons.client.http.invoke.codec.encoder.JacksonEncoder; +import org.springframework.stereotype.Component; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSocketFactory; +import java.lang.annotation.*; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * + * @author shanhuiming + * + */ +@Target({TYPE}) +@Retention(RUNTIME) +@Documented +@Component +public @interface HttpClient { + + @AliasFor(attribute = "url") + String value() default ""; + + @AliasFor(attribute = "value") + String url() default ""; + + String name() default ""; + + int poolConnections() default 10; + + int connectTimeout() default 10000; + + String connectTimeoutStr() default ""; + + int readTimeout() default 60000; + + String readTimeoutStr() default ""; + + int retryTimes() default 0; + + String retryTimesStr() default ""; + + int retryInterval() default 1000; + + String retryIntervalStr() default ""; + + Class encoder() default JacksonEncoder.class; + + Class decoder() default JacksonDecoder.class; + + Class sslSocketFactory() default NoopTlsSocketFactory.class; + + Class hostnameVerifier() default NoopHostnameVerifier.class; + + Level level() default Level.INFO; + + boolean ignoreError() default false; +} diff --git a/src/main/java/com/cowave/commons/client/http/annotation/HttpHeaderMap.java b/src/main/java/com/cowave/commons/client/http/annotation/HttpHeaderMap.java new file mode 100644 index 0000000..665c5ab --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/annotation/HttpHeaderMap.java @@ -0,0 +1,62 @@ +package com.cowave.commons.client.http.annotation; + +import com.cowave.commons.client.http.HttpInterceptor; +import com.cowave.commons.client.http.request.HttpRequest; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.List; +import java.util.Map; + +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * A template parameter that can be applied to a Map that contains header + * entries, where the keys are Strings that are the header field names and the + * values are the header field values. The headers specified by the map will be + * applied to the request after all other processing, and will take precedence + * over any previously specified header parameters. + *
+ * This parameter is useful in cases where different header fields and values + * need to be set on an API method on a per-request basis in a thread-safe manner + * and independently of client construction. A concrete example of a case + * like this are custom metadata header fields (e.g. as "x-amz-meta-*" or + * "x-goog-meta-*") where the header field names are dynamic and the range of keys + * cannot be determined a priori. The {@link HttpHeaders} annotation does not allow this + * because the header fields that it defines are static (it is not possible to add or + * remove fields on a per-request basis), and doing this using a custom {@link Target} + * or {@link HttpInterceptor} can be cumbersome (it requires more code for per-method + * customization, it is difficult to implement in a thread-safe manner and it requires + * customization when the client for the API is built). + *
+ *
+ * ...
+ * @RequestLine("GET /servers/{serverId}")
+ * void get(@Param("serverId") String serverId, @HttpHeaderMap Map);
+ * ...
+ * 
+ * The annotated parameter must be an instance of {@link Map}, and the keys must + * be Strings. The header field value of a key will be the value of its toString + * method, except in the following cases: + *
+ *
+ * + *
+ * Once this conversion is applied, the query keys and resulting String values + * follow the same contract as if they were set using + * {@link HttpRequest#header(String, String...)}. + */ +@Retention(RUNTIME) +@Target(PARAMETER) +@Documented +public @interface HttpHeaderMap { + +} diff --git a/src/main/java/com/cowave/commons/client/http/annotation/HttpHeaders.java b/src/main/java/com/cowave/commons/client/http/annotation/HttpHeaders.java new file mode 100644 index 0000000..347b541 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/annotation/HttpHeaders.java @@ -0,0 +1,55 @@ +package com.cowave.commons.client.http.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Expands headers supplied in the {@code value}. Variables to the the right of the colon are expanded.
+ *
+ * @Headers("Content-Type: application/xml")
+ * interface SoapApi {
+ * ...
+ * @RequestLine("GET /")
+ * @Headers("Cache-Control: max-age=640000")
+ * ...
+ *
+ * @RequestLine("POST /")
+ * @Headers({
+ *   "X-Foo: Bar",
+ *   "X-Ping: {token}"
+ * }) void post(@HttpParam("token") String token);
+ * ...
+ * 
+ *
Notes: + * + *
Relationship to JAXRS

The following two forms are identical.

+ *
+ * @RequestLine("POST /")
+ * @Headers({
+ *   "X-Ping: {token}"
+ * }) void post(@Named("token") String token);
+ * ...
+ * 
+ *
+ *
+ * @POST @Path("/")
+ * void post(@HttpParam("X-Ping") String token);
+ * ...
+ * 
+ */ +@Target({METHOD, TYPE}) +@Retention(RUNTIME) +@Documented +public @interface HttpHeaders { + + String[] value(); +} diff --git a/src/main/java/com/cowave/commons/client/http/annotation/HttpHost.java b/src/main/java/com/cowave/commons/client/http/annotation/HttpHost.java new file mode 100644 index 0000000..fc8eb4d --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/annotation/HttpHost.java @@ -0,0 +1,20 @@ +package com.cowave.commons.client.http.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 服务地址,优先级高于@HttpClient,格式: http(s)://ip:port + * + * @author shanhuiming + */ +@Target(PARAMETER) +@Retention(RUNTIME) +@Documented +public @interface HttpHost { + +} diff --git a/src/main/java/com/cowave/commons/client/http/annotation/HttpLine.java b/src/main/java/com/cowave/commons/client/http/annotation/HttpLine.java new file mode 100644 index 0000000..28f5073 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/annotation/HttpLine.java @@ -0,0 +1,55 @@ +package com.cowave.commons.client.http.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Expands the request-line supplied in the {@code value}, permitting path and query variables, or + * just the http method.
+ *
+ * ...
+ * @HttpLine("POST /servers")
+ * ...
+ *
+ * @HttpLine("GET /servers/{serverId}?count={count}")
+ * void get(@Param("serverId") String serverId, @Param("count") int count);
+ * ...
+ *
+ * @HttpLine("GET")
+ * Response getNext(URI nextLink);
+ * ...
+ * 
+ * HTTP version suffix is optional, but permitted. There are no guarantees this version will impact + * that sent by the client.
+ *
+ * @HttpLine("POST /servers HTTP/1.1")
+ * ...
+ * 
+ *
Note: Query params do not overwrite each other. All queries with the same + * name will be included in the request.

Relationship to JAXRS

The following + * two forms are identical.
+ *
+ * @HttpLine("GET /servers/{serverId}?count={count}")
+ * void get(@Param("serverId") String serverId, @Param("count") int count);
+ * ...
+ * 
+ *
+ *
+ * @GET @Path("/servers/{serverId}")
+ * void get(@PathParam("serverId") String serverId, @QueryParam("count") int count);
+ * ...
+ * 
+ */ +@Target(METHOD) +@Retention(RUNTIME) +@Documented +public @interface HttpLine { + + String value(); + + boolean decodeSlash() default true; +} diff --git a/src/main/java/org/springframework/feign/annotation/MultipartFile.java b/src/main/java/com/cowave/commons/client/http/annotation/HttpMultiFile.java similarity index 69% rename from src/main/java/org/springframework/feign/annotation/MultipartFile.java rename to src/main/java/com/cowave/commons/client/http/annotation/HttpMultiFile.java index 901d854..00cd6d3 100644 --- a/src/main/java/org/springframework/feign/annotation/MultipartFile.java +++ b/src/main/java/com/cowave/commons/client/http/annotation/HttpMultiFile.java @@ -1,4 +1,4 @@ -package org.springframework.feign.annotation; +package com.cowave.commons.client.http.annotation; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -8,14 +8,14 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; /** + * 文件参数,仅支持参数类型:InputStream、File、MultipartFile、byte[] * * @author shanhuiming - * */ @Target(PARAMETER) @Retention(RUNTIME) @Documented -public @interface MultipartFile { +public @interface HttpMultiFile { String fileName(); diff --git a/src/main/java/com/cowave/commons/client/http/annotation/HttpMultiForm.java b/src/main/java/com/cowave/commons/client/http/annotation/HttpMultiForm.java new file mode 100644 index 0000000..c832c7a --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/annotation/HttpMultiForm.java @@ -0,0 +1,20 @@ +package com.cowave.commons.client.http.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 表单参数,仅支持参数类型:Map + * + * @author shanhuiming + */ +@Target(PARAMETER) +@Retention(RUNTIME) +@Documented +public @interface HttpMultiForm { + +} diff --git a/src/main/java/org/springframework/feign/annotation/Host.java b/src/main/java/com/cowave/commons/client/http/annotation/HttpMultiParam.java similarity index 75% rename from src/main/java/org/springframework/feign/annotation/Host.java rename to src/main/java/com/cowave/commons/client/http/annotation/HttpMultiParam.java index a212009..741b386 100644 --- a/src/main/java/org/springframework/feign/annotation/Host.java +++ b/src/main/java/com/cowave/commons/client/http/annotation/HttpMultiParam.java @@ -1,4 +1,4 @@ -package org.springframework.feign.annotation; +package com.cowave.commons.client.http.annotation; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -15,6 +15,7 @@ @Target(PARAMETER) @Retention(RUNTIME) @Documented -public @interface Host { +public @interface HttpMultiParam { + String value(); } diff --git a/src/main/java/org/springframework/feign/annotation/Options.java b/src/main/java/com/cowave/commons/client/http/annotation/HttpOptions.java similarity index 54% rename from src/main/java/org/springframework/feign/annotation/Options.java rename to src/main/java/com/cowave/commons/client/http/annotation/HttpOptions.java index 75f4bf3..9ab2232 100644 --- a/src/main/java/org/springframework/feign/annotation/Options.java +++ b/src/main/java/com/cowave/commons/client/http/annotation/HttpOptions.java @@ -1,4 +1,4 @@ -package org.springframework.feign.annotation; +package com.cowave.commons.client.http.annotation; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -8,16 +8,20 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; /** + * 超时参数,优先级高于@HttpClient * * @author shanhuiming - * */ @Target(METHOD) @Retention(RUNTIME) @Documented -public @interface Options { +public @interface HttpOptions { + + int connectTimeout() default -1; + + int readTimeout() default -1; - int connectTimeoutMillis() default -1; + int retryTimes() default -1; - int readTimeoutMillis() default -1; + int retryInterval() default -1; } diff --git a/src/main/java/org/springframework/feign/annotation/MultipartForm.java b/src/main/java/com/cowave/commons/client/http/annotation/HttpParam.java similarity index 72% rename from src/main/java/org/springframework/feign/annotation/MultipartForm.java rename to src/main/java/com/cowave/commons/client/http/annotation/HttpParam.java index 5eda2c8..eb5ff11 100644 --- a/src/main/java/org/springframework/feign/annotation/MultipartForm.java +++ b/src/main/java/com/cowave/commons/client/http/annotation/HttpParam.java @@ -1,4 +1,4 @@ -package org.springframework.feign.annotation; +package com.cowave.commons.client.http.annotation; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -8,13 +8,14 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; /** + * 请求行参数 * * @author shanhuiming - * */ -@Target(PARAMETER) @Retention(RUNTIME) +@Target(PARAMETER) @Documented -public @interface MultipartForm { +public @interface HttpParam { + String value(); } diff --git a/src/main/java/com/cowave/commons/client/http/annotation/HttpParamMap.java b/src/main/java/com/cowave/commons/client/http/annotation/HttpParamMap.java new file mode 100644 index 0000000..81e49b5 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/annotation/HttpParamMap.java @@ -0,0 +1,55 @@ +package com.cowave.commons.client.http.annotation; + +import com.cowave.commons.client.http.request.HttpRequest; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.List; +import java.util.Map; + +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * A template parameter that can be applied to a Map that contains query + * parameters, where the keys are Strings that are the parameter names and the + * values are the parameter values. The queries specified by the map will be + * applied to the request after all other processing, and will take precedence + * over any previously specified query parameters. It is not necessary to + * reference the parameter map as a variable.
+ *
+ *
+ * ...
+ * @RequestLine("POST /servers")
+ * void servers(@HttpParamMap Map);
+ * ...
+ *
+ * @RequestLine("GET /servers/{serverId}?count={count}")
+ * void get(@Param("serverId") String serverId, @Param("count") int count, @HttpParamMap Map);
+ * ...
+ * 
+ * The annotated parameter must be an instance of {@link Map}, and the keys must + * be Strings. The query value of a key will be the value of its toString + * method, except in the following cases: + *
+ *
+ * + *
+ * Once this conversion is applied, the query keys and resulting String values + * follow the same contract as if they were set using + * {@link HttpRequest#query(String, String...)}. + */ +@Retention(RUNTIME) +@Target(PARAMETER) +@Documented +public @interface HttpParamMap { + + boolean encoded() default false; +} diff --git a/src/main/java/com/cowave/commons/client/http/asserts/Asserts.java b/src/main/java/com/cowave/commons/client/http/asserts/Asserts.java new file mode 100644 index 0000000..b442df7 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/asserts/Asserts.java @@ -0,0 +1,187 @@ +package com.cowave.commons.client.http.asserts; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; + +/** + * + * @author shanhuiming + * + */ +public class Asserts { + + public static void isTrue(boolean expression, String message, Object... args) { + if (!expression) { + throw new AssertsException(message, args); + } + } + + public static void isTrue(boolean expression, Supplier errorSupplier) { + if (!expression) { + throw new AssertsException(errorSupplier.get()); + } + } + + public static void isFalse(boolean expression, String message, Object... args) { + if (expression) { + throw new AssertsException(message, args); + } + } + + public static void isFalse(boolean expression, Supplier errorSupplier) { + if (expression) { + throw new AssertsException(errorSupplier.get()); + } + } + + public static void notEquals(Object a, Object b, String message, Object... args) { + if (Objects.equals(a, b)) { + throw new AssertsException(message, args); + } + } + + public static void notEquals(Object a, Object b, Supplier errorSupplier) { + if (Objects.equals(a, b)) { + throw new AssertsException(errorSupplier.get()); + } + } + + public static void equals(Object a, Object b, String message, Object... args) { + if (!Objects.equals(a, b)) { + throw new AssertsException(message, args); + } + } + + public static void equals(Object a, Object b, Supplier errorSupplier) { + if (!Objects.equals(a, b)) { + throw new AssertsException(errorSupplier.get()); + } + } + + public static void notBlank(String text, String message, Object... args) { + if (StringUtils.isBlank(text)) { + throw new AssertsException(message, args); + } + } + + public static void notBlank(String text, Supplier errorSupplier) { + if (StringUtils.isBlank(text)) { + throw new AssertsException(errorSupplier.get()); + } + } + + public static void isBlank(String text, String message, Object... args) { + if (StringUtils.isNotBlank(text)) { + throw new AssertsException(message, args); + } + } + + public static void isBlank(String text, Supplier errorSupplier) { + if (StringUtils.isNotBlank(text)) { + throw new AssertsException(errorSupplier.get()); + } + } + + public static void notNull(Object object, String message, Object... args) { + if (object == null) { + throw new AssertsException(message, args); + } + } + + public static void notNull(Object object, Supplier errorSupplier) { + if (object == null) { + throw new AssertsException(errorSupplier.get()); + } + } + + public static void isNull(Object object, String message, Object... args) { + if (object != null) { + throw new AssertsException(message, args); + } + } + + public static void isNull(Object object, Supplier errorSupplier) { + if (object != null) { + throw new AssertsException(errorSupplier.get()); + } + } + + public static void notEmpty(Map map, String message, Object... args) { + if (ObjectUtils.isEmpty(map)) { + throw new AssertsException(message, args); + } + } + + public static void notEmpty(Map map, Supplier errorSupplier) { + if (ObjectUtils.isEmpty(map)) { + throw new AssertsException(errorSupplier.get()); + } + } + + public static void isEmpty(Map map, String message, Object... args) { + if (ObjectUtils.isNotEmpty(map)) { + throw new AssertsException(message, args); + } + } + + public static void isEmpty(Map map, Supplier errorSupplier) { + if (ObjectUtils.isNotEmpty(map)) { + throw new AssertsException(errorSupplier.get()); + } + } + + public static void notEmpty(Collection collection, String message, Object... args) { + if (CollectionUtils.isEmpty(collection)) { + throw new AssertsException(message, args); + } + } + + public static void notEmpty(Collection collection, Supplier errorSupplier) { + if (CollectionUtils.isEmpty(collection)) { + throw new AssertsException(errorSupplier.get()); + } + } + + public static void isEmpty(Collection collection, String message, Object... args) { + if (!CollectionUtils.isEmpty(collection)) { + throw new AssertsException(message, args); + } + } + + public static void isEmpty(Collection collection, Supplier errorSupplier) { + if (!CollectionUtils.isEmpty(collection)) { + throw new AssertsException(errorSupplier.get()); + } + } + + public static void notEmpty(Object[] array, String message, Object... args) { + if (ObjectUtils.isEmpty(array)) { + throw new AssertsException(message, args); + } + } + + public static void notEmpty(Object[] array, Supplier errorSupplier) { + if (ObjectUtils.isEmpty(array)) { + throw new AssertsException(errorSupplier.get()); + } + } + + + public static void isEmpty(Object[] array, String message, Object... args) { + if (!ObjectUtils.isEmpty(array)) { + throw new AssertsException(message, args); + } + } + + public static void isEmpty(Object[] array, Supplier errorSupplier) { + if (!ObjectUtils.isEmpty(array)) { + throw new AssertsException(errorSupplier.get()); + } + } +} diff --git a/src/main/java/com/cowave/commons/client/http/asserts/AssertsException.java b/src/main/java/com/cowave/commons/client/http/asserts/AssertsException.java new file mode 100644 index 0000000..bc22c10 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/asserts/AssertsException.java @@ -0,0 +1,17 @@ +package com.cowave.commons.client.http.asserts; + +/** + * + * @author shanhuiming + * + */ +public class AssertsException extends RuntimeException { + + public AssertsException(String message, Object... args) { + super(I18Messages.translateIfNeed(message, args)); + } + + public AssertsException(Throwable cause, String message, Object... args) { + super(I18Messages.translateIfNeed(message, args), cause); + } +} diff --git a/src/main/java/com/cowave/commons/client/http/asserts/HttpAsserts.java b/src/main/java/com/cowave/commons/client/http/asserts/HttpAsserts.java new file mode 100644 index 0000000..9ad5ee4 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/asserts/HttpAsserts.java @@ -0,0 +1,271 @@ +package com.cowave.commons.client.http.asserts; + +import com.cowave.commons.client.http.response.ResponseCode; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; + +/** + * + * @author shanhuiming + * + */ +public class HttpAsserts { + + public static void isTrue(boolean expression, ResponseCode responseCode){ + if (!expression) { + throw new HttpException(responseCode); + } + } + + public static void isTrue(boolean expression, int status, String code, String message, Object... args) { + if (!expression) { + throw new HttpException(status, code, message, args); + } + } + + public static void isTrue(boolean expression, int status, String code, Supplier errorSupplier) { + if (!expression) { + throw new HttpException(status, code, errorSupplier.get()); + } + } + + public static void isFalse(boolean expression, ResponseCode responseCode){ + if (expression) { + throw new HttpException(responseCode); + } + } + + public static void isFalse(boolean expression, int status, String code, String message, Object... args) { + if (expression) { + throw new HttpException(status, code, message, args); + } + } + + public static void isFalse(boolean expression, int status, String code, Supplier errorSupplier) { + if (expression) { + throw new HttpException(status, code, errorSupplier.get()); + } + } + + public static void notEquals(Object a, Object b, ResponseCode responseCode){ + if (Objects.equals(a, b)) { + throw new HttpException(responseCode); + } + } + + public static void notEquals(Object a, Object b, int status, String code, String message, Object... args) { + if (Objects.equals(a, b)) { + throw new HttpException(status, code, message, args); + } + } + + public static void notEquals(Object a, Object b, int status, String code, Supplier errorSupplier) { + if (Objects.equals(a, b)) { + throw new HttpException(status, code, errorSupplier.get()); + } + } + + public static void equals(Object a, Object b, ResponseCode responseCode){ + if (!Objects.equals(a, b)) { + throw new HttpException(responseCode); + } + } + + public static void equals(Object a, Object b, int status, String code, String message, Object... args) { + if (!Objects.equals(a, b)) { + throw new HttpException(status, code, message, args); + } + } + + public static void equals(Object a, Object b, int status, String code, Supplier errorSupplier) { + if (!Objects.equals(a, b)) { + throw new HttpException(status, code, errorSupplier.get()); + } + } + + public static void notBlank(String text, ResponseCode responseCode){ + if (StringUtils.isBlank(text)) { + throw new HttpException(responseCode); + } + } + + public static void notBlank(String text, int status, String code, String message, Object... args) { + if (StringUtils.isBlank(text)) { + throw new HttpException(status, code, message, args); + } + } + + public static void notBlank(String text, int status, String code, Supplier errorSupplier) { + if (StringUtils.isBlank(text)) { + throw new HttpException(status, code, errorSupplier.get()); + } + } + + public static void isBlank(String text, ResponseCode responseCode){ + if (StringUtils.isNotBlank(text)) { + throw new HttpException(responseCode); + } + } + + public static void isBlank(String text, int status, String code, String message, Object... args) { + if (StringUtils.isNotBlank(text)) { + throw new HttpException(status, code, message, args); + } + } + + public static void isBlank(String text, int status, String code, Supplier errorSupplier) { + if (StringUtils.isNotBlank(text)) { + throw new HttpException(status, code, errorSupplier.get()); + } + } + + public static void notNull(Object object, ResponseCode responseCode){ + if (object == null) { + throw new HttpException(responseCode); + } + } + + public static void notNull(Object object, int status, String code, String message, Object... args) { + if (object == null) { + throw new HttpException(status, code, message, args); + } + } + + public static void notNull(Object object, int status, String code, Supplier errorSupplier) { + if (object == null) { + throw new HttpException(status, code, errorSupplier.get()); + } + } + + public static void isNull(Object object, ResponseCode responseCode){ + if (object != null) { + throw new HttpException(responseCode); + } + } + + public static void isNull(Object object, int status, String code, String message, Object... args) { + if (object != null) { + throw new HttpException(status, code, message, args); + } + } + + public static void isNull(Object object, int status, String code, Supplier errorSupplier) { + if (object != null) { + throw new HttpException(status, code, errorSupplier.get()); + } + } + + public static void notEmpty(Map map, ResponseCode responseCode){ + if (ObjectUtils.isEmpty(map)) { + throw new HttpException(responseCode); + } + } + + public static void notEmpty(Map map, int status, String code, String message, Object... args) { + if (ObjectUtils.isEmpty(map)) { + throw new HttpException(status, code, message, args); + } + } + + public static void notEmpty(Map map, int status, String code, Supplier errorSupplier) { + if (ObjectUtils.isEmpty(map)) { + throw new HttpException(status, code, errorSupplier.get()); + } + } + + public static void isEmpty(Map map, ResponseCode responseCode){ + if (ObjectUtils.isNotEmpty(map)) { + throw new HttpException(responseCode); + } + } + + public static void isEmpty(Map map, int status, String code, String message, Object... args) { + if (ObjectUtils.isNotEmpty(map)) { + throw new HttpException(status, code, message, args); + } + } + + public static void isEmpty(Map map, int status, String code, Supplier errorSupplier) { + if (ObjectUtils.isNotEmpty(map)) { + throw new HttpException(status, code, errorSupplier.get()); + } + } + + public static void notEmpty(Collection collection, ResponseCode responseCode){ + if (CollectionUtils.isEmpty(collection)) { + throw new HttpException(responseCode); + } + } + + public static void notEmpty(Collection collection, int status, String code, String message, Object... args) { + if (CollectionUtils.isEmpty(collection)) { + throw new HttpException(status, code, message, args); + } + } + + public static void notEmpty(Collection collection, int status, String code, Supplier errorSupplier) { + if (CollectionUtils.isEmpty(collection)) { + throw new HttpException(status, code, errorSupplier.get()); + } + } + + public static void isEmpty(Collection collection, ResponseCode responseCode){ + if (!CollectionUtils.isEmpty(collection)) { + throw new HttpException(responseCode); + } + } + + public static void isEmpty(Collection collection, int status, String code, String message, Object... args) { + if (!CollectionUtils.isEmpty(collection)) { + throw new HttpException(status, code, message, args); + } + } + + public static void isEmpty(Collection collection, int status, String code, Supplier errorSupplier) { + if (!CollectionUtils.isEmpty(collection)) { + throw new HttpException(status, code, errorSupplier.get()); + } + } + + public static void notEmpty(Object[] array, ResponseCode responseCode){ + if (ObjectUtils.isEmpty(array)) { + throw new HttpException(responseCode); + } + } + + public static void notEmpty(Object[] array, int status, String code, String message, Object... args) { + if (ObjectUtils.isEmpty(array)) { + throw new HttpException(status, code, message, args); + } + } + + public static void notEmpty(Object[] array, int status, String code, Supplier errorSupplier) { + if (ObjectUtils.isEmpty(array)) { + throw new HttpException(status, code, errorSupplier.get()); + } + } + + public static void isEmpty(Object[] array, ResponseCode responseCode){ + if (!ObjectUtils.isEmpty(array)) { + throw new HttpException(responseCode); + } + } + + public static void isEmpty(Object[] array, int status, String code, String message, Object... args) { + if (!ObjectUtils.isEmpty(array)) { + throw new HttpException(status, code, message, args); + } + } + + public static void isEmpty(Object[] array, int status, String code, Supplier errorSupplier) { + if (!ObjectUtils.isEmpty(array)) { + throw new HttpException(status, code, errorSupplier.get()); + } + } +} diff --git a/src/main/java/com/cowave/commons/client/http/asserts/HttpException.java b/src/main/java/com/cowave/commons/client/http/asserts/HttpException.java new file mode 100644 index 0000000..df87fbe --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/asserts/HttpException.java @@ -0,0 +1,66 @@ +package com.cowave.commons.client.http.asserts; + +import com.cowave.commons.client.http.response.ResponseCode; +import lombok.Getter; + +import static com.cowave.commons.client.http.HttpCode.SERVICE_ERROR; + +/** + * + * @author shanhuiming + * + */ +@Getter +public class HttpException extends RuntimeException { + + private final int status; + + private final String code; + + public HttpException(String message, Object... args) { + super(I18Messages.translateIfNeed(message, args)); + this.code = SERVICE_ERROR.getCode(); + this.status = SERVICE_ERROR.getStatus(); + } + + public HttpException(Throwable cause, String message, Object... args) { + super(I18Messages.translateIfNeed(message, args), cause); + this.code = SERVICE_ERROR.getCode(); + this.status = SERVICE_ERROR.getStatus(); + } + + public HttpException(ResponseCode responseCode) { + super(I18Messages.translateIfNeed(responseCode.getMsg())); + this.code = responseCode.getCode(); + this.status = responseCode.getStatus(); + } + + public HttpException(ResponseCode responseCode, String message, Object... args) { + super(I18Messages.translateIfNeed(message, args)); + this.code = responseCode.getCode(); + this.status = responseCode.getStatus(); + } + + public HttpException(ResponseCode responseCode, Throwable cause, String message, Object... args) { + super(I18Messages.translateIfNeed(message, args), cause); + this.code = responseCode.getCode(); + this.status = responseCode.getStatus(); + } + + public HttpException(int status, String code) { + this.code = code; + this.status = status; + } + + public HttpException(int status, String code, String message, Object... args) { + super(I18Messages.translateIfNeed(message, args)); + this.code = code; + this.status = status; + } + + public HttpException(int status, String code, Throwable cause, String message, Object... args) { + super(I18Messages.translateIfNeed(message, args), cause); + this.code = code; + this.status = status; + } +} diff --git a/src/main/java/com/cowave/commons/client/http/asserts/HttpHintException.java b/src/main/java/com/cowave/commons/client/http/asserts/HttpHintException.java new file mode 100644 index 0000000..35e05e4 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/asserts/HttpHintException.java @@ -0,0 +1,45 @@ +package com.cowave.commons.client.http.asserts; + +import com.cowave.commons.client.http.response.ResponseCode; +import lombok.Getter; + +/** + * 不打印异常日志 + * + * @author shanhuiming + */ +@Getter +public class HttpHintException extends HttpException { + + public HttpHintException(String message, Object... args) { + super(message, args); + } + + public HttpHintException(Throwable cause, String message, Object... args) { + super(cause, message, args); + } + + public HttpHintException(ResponseCode responseCode) { + super(responseCode); + } + + public HttpHintException(ResponseCode responseCode, String message, Object... args) { + super(responseCode, message, args); + } + + public HttpHintException(ResponseCode responseCode, Throwable cause, String message, Object... args) { + super(responseCode, cause, message, args); + } + + public HttpHintException(int status, String code) { + super(status, code); + } + + public HttpHintException(int status, String code, String message, Object... args) { + super(status, code, message, args); + } + + public HttpHintException(int status, String code, Throwable cause, String message, Object... args) { + super(status, code, cause, message, args); + } +} diff --git a/src/main/java/com/cowave/commons/client/http/asserts/HttpWarnException.java b/src/main/java/com/cowave/commons/client/http/asserts/HttpWarnException.java new file mode 100644 index 0000000..419b5b5 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/asserts/HttpWarnException.java @@ -0,0 +1,45 @@ +package com.cowave.commons.client.http.asserts; + +import com.cowave.commons.client.http.response.ResponseCode; +import lombok.Getter; + +/** + * 异常日志只打印e.message + * + * @author shanhuiming + */ +@Getter +public class HttpWarnException extends HttpException { + + public HttpWarnException(String message, Object... args) { + super(message, args); + } + + public HttpWarnException(Throwable cause, String message, Object... args) { + super(cause, message, args); + } + + public HttpWarnException(ResponseCode responseCode) { + super(responseCode); + } + + public HttpWarnException(ResponseCode responseCode, String message, Object... args) { + super(responseCode, message, args); + } + + public HttpWarnException(ResponseCode responseCode, Throwable cause, String message, Object... args) { + super(responseCode, cause, message, args); + } + + public HttpWarnException(int status, String code) { + super(status, code); + } + + public HttpWarnException(int status, String code, String message, Object... args) { + super(status, code, message, args); + } + + public HttpWarnException(int status, String code, Throwable cause, String message, Object... args) { + super(status, code, cause, message, args); + } +} diff --git a/src/main/java/com/cowave/commons/client/http/asserts/I18Messages.java b/src/main/java/com/cowave/commons/client/http/asserts/I18Messages.java new file mode 100644 index 0000000..bafd294 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/asserts/I18Messages.java @@ -0,0 +1,79 @@ +package com.cowave.commons.client.http.asserts; + +import com.alibaba.ttl.TransmittableThreadLocal; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.text.StringSubstitutor; +import org.springframework.context.MessageSource; +import org.springframework.util.StringUtils; + +import java.util.Locale; +import java.util.Map; + +/** + * + * @author shanhuiming + * + */ +public class I18Messages { + + private static final ThreadLocal LANGUAGE = new TransmittableThreadLocal<>(); + + private static MessageSource messageSource; + + public static void setMessageSource(MessageSource messageSource) { + I18Messages.messageSource = messageSource; + } + + public static void setLanguage(String language){ + if(StringUtils.hasText(language)){ + if(language.toLowerCase().contains("en")) { + LANGUAGE.set(new Locale("en", "US")); + }else if(language.toLowerCase().contains("zh")) { + LANGUAGE.set(new Locale("zh", "CN")); + } + } + } + + public static Locale getLanguage() { + Locale local = LANGUAGE.get(); + if(local != null){ + return local; + } + return Locale.CHINA; + } + + public static void clearLanguage() { + LANGUAGE.remove(); + } + + public static String msg(String message, Object... args) { + return messageSource.getMessage(message, args, message, getLanguage()); + } + + public static String msgWithDefault(String message, String defaultMessage, Object... args) { + return messageSource.getMessage(message, args, defaultMessage, getLanguage()); + } + + public static String translateIfNeed(String message, Object... args){ + if(!StringUtils.hasText(message)){ + return ""; + } + if (message.startsWith("{") && message.endsWith("}")) { + message = message.substring(1, message.length() - 1); + return messageSource.getMessage(message, args, message, getLanguage()); + } + return message; + } + + public static String translateAndReplace(String message, Map args, String prefix, String suffix) { + if(!StringUtils.hasText(message)){ + return ""; + } + String msg = messageSource.getMessage(message, null, message, getLanguage()); + if(MapUtils.isNotEmpty(args)){ + StringSubstitutor substitutor = new StringSubstitutor(args, prefix, suffix); + return substitutor.replace(msg); + } + return msg; + } +} diff --git a/src/main/java/com/cowave/commons/client/http/invoke/codec/HttpDecoder.java b/src/main/java/com/cowave/commons/client/http/invoke/codec/HttpDecoder.java new file mode 100644 index 0000000..a2ad68f --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/invoke/codec/HttpDecoder.java @@ -0,0 +1,16 @@ +package com.cowave.commons.client.http.invoke.codec; + +import com.cowave.commons.client.http.response.HttpResponseTemplate; +import org.slf4j.event.Level; + +import java.lang.reflect.Type; + +/** + * + * @author shanhuiming + * + */ +public interface HttpDecoder { + + Object decode(HttpResponseTemplate response, Type type, String url, long cost, int httpCode, Level level) throws Exception; +} diff --git a/src/main/java/com/cowave/commons/client/http/invoke/codec/HttpEncoder.java b/src/main/java/com/cowave/commons/client/http/invoke/codec/HttpEncoder.java new file mode 100644 index 0000000..f1f9dda --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/invoke/codec/HttpEncoder.java @@ -0,0 +1,13 @@ +package com.cowave.commons.client.http.invoke.codec; + +import com.cowave.commons.client.http.request.HttpRequest; + +/** + * + * @author shanhuiming + * + */ +public interface HttpEncoder { + + void encode(HttpRequest request, Object object) throws Exception; +} diff --git a/src/main/java/com/cowave/commons/client/http/invoke/codec/decoder/JacksonDecoder.java b/src/main/java/com/cowave/commons/client/http/invoke/codec/decoder/JacksonDecoder.java new file mode 100644 index 0000000..6b3bbf1 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/invoke/codec/decoder/JacksonDecoder.java @@ -0,0 +1,93 @@ +package com.cowave.commons.client.http.invoke.codec.decoder; + +import com.cowave.commons.client.http.response.HttpResponseTemplate; +import com.cowave.commons.client.http.invoke.codec.HttpDecoder; +import com.cowave.commons.client.http.response.Response; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.event.Level; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.TimeZone; + +import static com.cowave.commons.client.http.HttpCode.SUCCESS; +import static org.slf4j.event.Level.WARN; + +/** + * + * @author shanhuiming + * + */ +@Slf4j +public class JacksonDecoder implements HttpDecoder { + + public static final ObjectMapper MAPPER = new ObjectMapper(); + + static { + MAPPER.setTimeZone(TimeZone.getDefault()); + MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); + MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + private final ObjectMapper mapper; + + public JacksonDecoder() { + this(MAPPER); + } + + public JacksonDecoder(ObjectMapper mapper) { + this.mapper = mapper; + } + + @Override + public Object decode(HttpResponseTemplate response, Type type, String url, long cost, int status, Level level) throws Exception { + Reader reader = new InputStreamReader(response.getInputStream(), StandardCharsets.UTF_8); + if (!reader.markSupported()) { + reader = new BufferedReader(reader, 1); + } + + reader.mark(1); + if (reader.read() == -1) { + return null; + } + reader.reset(); + + if (void.class == type) { + if(log.isDebugEnabled() || level.toInt() < WARN.toInt()){ + log.info(">< {} {}ms {}", status, cost, url); + } + return null; + } + + Object obj = mapper.readValue(reader, mapper.constructType(type)); + if(obj != null){ + if(Response.class.isAssignableFrom(obj.getClass())){ + Response resp = (Response)obj; + if(!Objects.equals(SUCCESS.getCode(), resp.getCode())){ + log.error(">< {} {}ms {} {code={}, msg={}}", status, cost, url, resp.getCode(), resp.getMsg()); + }else if(log.isDebugEnabled() || level.toInt() < WARN.toInt()){ + log.info(">< {} {}ms {} {code={}, msg={}}", status, cost, url, resp.getCode(), resp.getMsg()); + } + }else if(log.isDebugEnabled() || level.toInt() < WARN.toInt()){ + log.info(">< {} {}ms {}", status, cost, url); + } + }else if(log.isDebugEnabled() || level.toInt() < WARN.toInt()){ + log.info(">< {} {}ms {}", status, cost, url); + } + return obj; + } + + public static Object readValue(String json, Type type) throws IOException { + return MAPPER.readValue(json, MAPPER.constructType(type)); + } +} diff --git a/src/main/java/com/cowave/commons/client/http/invoke/codec/decoder/ResponseDecoder.java b/src/main/java/com/cowave/commons/client/http/invoke/codec/decoder/ResponseDecoder.java new file mode 100644 index 0000000..034a72d --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/invoke/codec/decoder/ResponseDecoder.java @@ -0,0 +1,83 @@ +package com.cowave.commons.client.http.invoke.codec.decoder; + +import com.cowave.commons.client.http.response.HttpResponseTemplate; +import com.cowave.commons.client.http.response.Response; +import com.cowave.commons.client.http.asserts.HttpHintException; +import com.fasterxml.jackson.databind.*; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.event.Level; +import com.cowave.commons.client.http.invoke.codec.HttpDecoder; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.Reader; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +import static com.cowave.commons.client.http.HttpCode.SUCCESS; +import static org.slf4j.event.Level.WARN; + +/** + * + * @author shanhuiming + * + */ +@Slf4j +public class ResponseDecoder implements HttpDecoder { + + private final ObjectMapper mapper; + + public ResponseDecoder() { + this.mapper = JacksonDecoder.MAPPER; + } + + public ResponseDecoder(ObjectMapper mapper) { + this.mapper = mapper; + } + + @Override + public Object decode(HttpResponseTemplate response, Type type, String url, long cost, int status, Level level) throws Exception { + if (response.getInputStream() == null) { + if(log.isDebugEnabled() || level.toInt() < WARN.toInt()){ + log.info(">< {} {}ms {}", status, cost, url); + } + return null; + } + + Reader reader = new InputStreamReader(response.getInputStream(), StandardCharsets.UTF_8); + if (!reader.markSupported()) { + reader = new BufferedReader(reader, 1); + } + + // Read the first byte to see if we have any data + reader.mark(1); + // Eagerly returning null avoids "No content to map due to end-of-input" + if (reader.read() == -1) { + if(log.isDebugEnabled() || level.toInt() < WARN.toInt()){ + log.info(">< {} {}ms {}", status, cost, url); + } + return null; + } + reader.reset(); + + Response resp = mapper.readValue(reader, Response.class); + if(!Objects.equals(SUCCESS.getCode(), resp.getCode())){ + log.error(">< {} {}ms {} {code={}, msg={}}", status, cost, url, resp.getCode(), resp.getMsg()); + throw new HttpHintException(status, resp.getCode(), resp.getMsg()); + } + + if (void.class == type) { + if(log.isDebugEnabled() || level.toInt() < WARN.toInt()){ + log.info(">< {} {}ms {} {code={}, msg={}}", status, cost, url, resp.getCode(), resp.getMsg()); + } + return null; + } + + if(log.isDebugEnabled() || level.toInt() < WARN.toInt()){ + log.info(">< {} {}ms {} {code={}, msg={}}", status, cost, url, resp.getCode(), resp.getMsg()); + } + String data = mapper.writeValueAsString(resp.getData()); + return mapper.readValue(data, mapper.constructType(type)); + } +} diff --git a/src/main/java/com/cowave/commons/client/http/invoke/codec/encoder/JacksonEncoder.java b/src/main/java/com/cowave/commons/client/http/invoke/codec/encoder/JacksonEncoder.java new file mode 100644 index 0000000..0b0eb06 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/invoke/codec/encoder/JacksonEncoder.java @@ -0,0 +1,39 @@ +package com.cowave.commons.client.http.invoke.codec.encoder; + +import com.cowave.commons.client.http.invoke.codec.HttpEncoder; +import com.cowave.commons.client.http.request.HttpRequest; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +import java.util.TimeZone; + +/** + * @author shanhuiming + */ +public class JacksonEncoder implements HttpEncoder { + + public static final ObjectMapper MAPPER = new ObjectMapper(); + + static { + MAPPER.setTimeZone(TimeZone.getDefault()); + MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); + MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + } + + private final ObjectMapper mapper; + + public JacksonEncoder() { + this(MAPPER); + } + + public JacksonEncoder(ObjectMapper mapper) { + this.mapper = mapper; + } + + @Override + public void encode(HttpRequest request, Object object) throws JsonProcessingException { + request.body(mapper.writeValueAsString(object)); + } +} diff --git a/src/main/java/com/cowave/commons/client/http/invoke/exec/HttpClientExecutor.java b/src/main/java/com/cowave/commons/client/http/invoke/exec/HttpClientExecutor.java new file mode 100644 index 0000000..e39de36 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/invoke/exec/HttpClientExecutor.java @@ -0,0 +1,232 @@ +package com.cowave.commons.client.http.invoke.exec; + +import com.cowave.commons.client.http.request.HttpRequestTemplate; +import com.cowave.commons.client.http.response.HttpResponseTemplate; +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.NoHttpResponseException; +import org.apache.http.client.HttpClient; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.*; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.ConnectTimeoutException; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.entity.mime.content.InputStreamBody; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSocketFactory; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.GZIPOutputStream; + +import static com.cowave.commons.client.http.HttpHeader.*; + +/** + * + * @author shanhuiming + * + */ +public class HttpClientExecutor implements HttpExecutor { + + private final HttpClient httpClient; + + public HttpClientExecutor(SSLSocketFactory sslSocketFactory, HostnameVerifier hostnameVerifier) { + SSLConnectionSocketFactory sslConnectionSocketFactory = + new SSLConnectionSocketFactory(sslSocketFactory, hostnameVerifier); + Registry registry = RegistryBuilder.create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .register("https", sslConnectionSocketFactory).build(); + + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry); + connectionManager.setMaxTotal(100); + + HttpRequestRetryHandler retryHandler = (exception, execCount, context) -> { + Integer retryTimes = (Integer) context.getAttribute("retryTimes"); + Integer retryInterval = (Integer) context.getAttribute("retryInterval"); + if (retryTimes == null) { + retryTimes = 0; + } + if (execCount > retryTimes) { + return false; + } + + if (retryInterval == null) { + retryInterval = 1000; + } + try { + Thread.sleep(retryInterval); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } + return exception instanceof ConnectTimeoutException + || exception instanceof NoHttpResponseException + || exception instanceof SocketException; + }; + + HttpClientBuilder httpClientBuilder = HttpClients.custom(); + httpClientBuilder.setConnectionManager(connectionManager); + httpClientBuilder.setRetryHandler(retryHandler); + this.httpClient = httpClientBuilder.build(); + } + + @Override + public HttpResponseTemplate execute(HttpRequestTemplate request) throws IOException { + HttpRequestBase httpRequest = buildRequest(request); + // 超时参数 + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(request.getConnectTimeout()) + .setSocketTimeout(request.getReadTimeout()).build(); + // 重试设置 + HttpClientContext context = HttpClientContext.create(); + context.setAttribute("retryTimes", request.getRetryTimes()); + context.setAttribute("retryInterval", request.getRetryInterval()); + + httpRequest.setConfig(requestConfig); + HttpResponse httpResponse = httpClient.execute(httpRequest, context); + + int status = httpResponse.getStatusLine().getStatusCode(); + String reason = httpResponse.getStatusLine().getReasonPhrase(); + + // 响应 Header + Map> headers = new HashMap<>(); + for (Header header : httpResponse.getAllHeaders()) { + headers.computeIfAbsent(header.getName(), k -> new ArrayList<>()).add(header.getValue()); + } + + InputStream stream = httpResponse.getEntity() != null ? httpResponse.getEntity().getContent() : null; + long length = httpResponse.getEntity() != null ? httpResponse.getEntity().getContentLength() : 0; + return new HttpResponseTemplate(status, headers, reason, stream, (int) length); + } + + /** + * @see GET, DELETE, POST, PATCH, PUT, HEAD, OPTIONS, TRACE, CONNECT + */ + private HttpRequestBase buildRequest(HttpRequestTemplate request) throws IOException { + // 请求压缩 + Collection encodings = request.getHeaders().get(Content_Encoding); + // gzip + boolean useGzipEncode = encodings != null && encodings.contains("gzip"); + // deflate + boolean useDeflateEncode = encodings != null && encodings.contains("deflate"); + + String url = request.getUrl(); + String method = request.getMethod().toUpperCase(); + switch (method.toUpperCase()) { + case "GET": + HttpGet httpGet = new HttpGet(url); + setHeader(httpGet, request); + return httpGet; + case "DELETE": + HttpDelete httpDelete = new HttpDelete(url); + setHeader(httpDelete, request); + return httpDelete; + case "POST": + HttpPost httpPost = new HttpPost(url); + setHeader(httpPost, request); + setEntity(httpPost, request, useGzipEncode, useDeflateEncode); + return httpPost; + case "PATCH": + HttpPatch httpPatch = new HttpPatch(url); + setHeader(httpPatch, request); + setEntity(httpPatch, request, useGzipEncode, useDeflateEncode); + return httpPatch; + case "PUT": + HttpPut httpPut = new HttpPut(url); + setHeader(httpPut, request); + setEntity(httpPut, request, useGzipEncode, useDeflateEncode); + return httpPut; + case "HEAD": + HttpHead httpHead = new HttpHead(url); + setHeader(httpHead, request); + return httpHead; + case "OPTIONS": + HttpOptions httpOptions = new HttpOptions(url); + setHeader(httpOptions, request); + return httpOptions; + case "TRACE": + HttpTrace httpTrace = new HttpTrace(url); + setHeader(httpTrace, request); + return httpTrace; + default: + throw new UnsupportedOperationException("Unsupported HTTP method: " + method); + } + } + + private void setEntity(HttpEntityEnclosingRequestBase httpRequest, HttpRequestTemplate request, + boolean useGzipEncode, boolean useDeflateEncode) throws IOException { + if (request.getBody() != null) { + if (useGzipEncode) { + try (ByteArrayOutputStream byteArrayOutStream = new ByteArrayOutputStream(); + GZIPOutputStream gzipOutStream = new GZIPOutputStream(byteArrayOutStream)) { + gzipOutStream.write(request.getBody()); + HttpEntity httpEntity = new ByteArrayEntity(byteArrayOutStream.toByteArray()); + httpRequest.setEntity(httpEntity); + } + } else if (useDeflateEncode) { + try (ByteArrayOutputStream byteArrayOutStream = new ByteArrayOutputStream(); + DeflaterOutputStream defeaterOutStream = new DeflaterOutputStream(byteArrayOutStream)) { + defeaterOutStream.write(request.getBody()); + HttpEntity httpEntity = new ByteArrayEntity(byteArrayOutStream.toByteArray()); + httpRequest.setEntity(httpEntity); + } + } else { + HttpEntity httpEntity = new ByteArrayEntity(request.getBody()); + httpRequest.setEntity(httpEntity); + } + } else if (request.getMultiFile() != null || request.getMultiForm() != null) { + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); + if (request.getMultiFile() != null) { + builder.addPart("file", new InputStreamBody( + request.getMultiFile(), ContentType.APPLICATION_OCTET_STREAM, request.getMultiFileName())); + } + if (request.getMultiForm() != null) { + for (Map.Entry entry : request.getMultiForm().entrySet()) { + builder.addTextBody(entry.getKey(), entry.getValue().toString()); + } + } + HttpEntity httpEntity = builder.build(); + httpRequest.setEntity(httpEntity); + } + } + + private void setHeader(HttpRequestBase httpRequest, HttpRequestTemplate request) { + boolean hasAcceptHeader = false; + for (String field : request.getHeaders().keySet()) { + if (field.equalsIgnoreCase(Accept)) { + hasAcceptHeader = true; + } + for (String value : request.getHeaders().get(field)) { + if (!field.equals(Content_Length)) { + httpRequest.addHeader(field, value); + } + } + } + + // 接受任意类型的响应 + if (!hasAcceptHeader) { + httpRequest.setHeader(Accept, "*/*"); + } + } +} diff --git a/src/main/java/com/cowave/commons/client/http/invoke/exec/HttpExecutor.java b/src/main/java/com/cowave/commons/client/http/invoke/exec/HttpExecutor.java new file mode 100644 index 0000000..116dcfd --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/invoke/exec/HttpExecutor.java @@ -0,0 +1,16 @@ +package com.cowave.commons.client.http.invoke.exec; + +import com.cowave.commons.client.http.request.HttpRequestTemplate; +import com.cowave.commons.client.http.response.HttpResponseTemplate; + +import java.io.IOException; + +/** + * + * @author shanhuiming + * + */ +public interface HttpExecutor { + + HttpResponseTemplate execute(HttpRequestTemplate httpRequestTemplate) throws IOException; +} diff --git a/src/main/java/org/springframework/feign/invoke/FeignMethodHandler.java b/src/main/java/com/cowave/commons/client/http/invoke/proxy/DefaultMethodInvoker.java similarity index 70% rename from src/main/java/org/springframework/feign/invoke/FeignMethodHandler.java rename to src/main/java/com/cowave/commons/client/http/invoke/proxy/DefaultMethodInvoker.java index 7194c77..80a0e11 100644 --- a/src/main/java/org/springframework/feign/invoke/FeignMethodHandler.java +++ b/src/main/java/com/cowave/commons/client/http/invoke/proxy/DefaultMethodInvoker.java @@ -1,6 +1,4 @@ -package org.springframework.feign.invoke; - -import feign.InvocationHandlerFactory; +package com.cowave.commons.client.http.invoke.proxy; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -12,22 +10,22 @@ * @author shanhuiming * */ -public class FeignMethodHandler implements InvocationHandlerFactory.MethodHandler { +public class DefaultMethodInvoker implements MethodInvoker { + // Uses Java 7 MethodHandle based reflection. As default methods will only exist when // run on a Java 8 JVM this will not affect use on legacy JVMs. - // When Feign upgrades to Java 7, remove the @IgnoreJRERequirement annotation. + // When upgrades to Java 7, remove the @IgnoreJRERequirement annotation. private final MethodHandle unboundHandle; // handle is effectively final after bindTo has been called. private MethodHandle handle; - public FeignMethodHandler(Method defaultMethod) { + public DefaultMethodInvoker(Method defaultMethod) { try { Class declaringClass = defaultMethod.getDeclaringClass(); Field field = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP"); field.setAccessible(true); MethodHandles.Lookup lookup = (MethodHandles.Lookup) field.get(null); - this.unboundHandle = lookup.unreflectSpecial(defaultMethod, declaringClass); } catch (NoSuchFieldException ex) { throw new IllegalStateException(ex); @@ -36,10 +34,6 @@ public FeignMethodHandler(Method defaultMethod) { } } - /** - * Bind this handler to a proxy object. After bound, DefaultMethodHandler#invoke will act as if it was called - * on the proxy object. Must be called once and only once for a given instance of DefaultMethodHandler - */ public void bindTo(Object proxy) { if(handle != null) { throw new IllegalStateException("Attempted to rebind a default method handler that was already bound"); @@ -47,10 +41,6 @@ public void bindTo(Object proxy) { handle = unboundHandle.bindTo(proxy); } - /** - * Invoke this method. DefaultMethodHandler#bindTo must be called before the first - * time invoke is called. - */ @Override public Object invoke(Object[] argv) throws Throwable { if(handle == null) { @@ -58,4 +48,4 @@ public Object invoke(Object[] argv) throws Throwable { } return handle.invokeWithArguments(argv); } -} \ No newline at end of file +} diff --git a/src/main/java/com/cowave/commons/client/http/invoke/proxy/HttpMethodInvoker.java b/src/main/java/com/cowave/commons/client/http/invoke/proxy/HttpMethodInvoker.java new file mode 100644 index 0000000..6ce94c2 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/invoke/proxy/HttpMethodInvoker.java @@ -0,0 +1,230 @@ +package com.cowave.commons.client.http.invoke.proxy; + +import com.cowave.commons.client.http.HttpInterceptor; +import com.cowave.commons.client.http.invoke.codec.decoder.JacksonDecoder; +import com.cowave.commons.client.http.invoke.exec.HttpExecutor; +import com.cowave.commons.client.http.response.HttpResponse; +import com.cowave.commons.client.http.response.HttpResponseTemplate; +import com.cowave.commons.client.http.request.Options; +import com.cowave.commons.client.http.request.HttpRequestTemplate; +import com.cowave.commons.client.http.request.HttpRequest; +import com.cowave.commons.client.http.request.HttpRequestFactory; +import com.cowave.commons.client.http.request.meta.HttpMethodMeta; +import com.cowave.commons.client.http.asserts.HttpException; +import com.cowave.commons.client.http.asserts.HttpHintException; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.event.Level; +import com.cowave.commons.client.http.HttpExceptionHandler; +import com.cowave.commons.client.http.invoke.codec.HttpDecoder; +import org.springframework.http.HttpHeaders; +import org.springframework.util.StreamUtils; +import org.springframework.util.StringUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.TimeUnit; + +import static com.cowave.commons.client.http.HttpCode.SERVICE_ERROR; +import static org.slf4j.event.Level.WARN; + +/** + * + * @author shanhuiming + * + */ +@Slf4j +public class HttpMethodInvoker implements MethodInvoker { + private final Level level; + private final boolean ignoreError; + private final HttpMethodMeta metadata; + private final ProxyTarget proxyTarget; + private final HttpExecutor httpExecutor; + private final List httpInterceptors; + private final HttpRequestFactory httpRequestFactory; + private final Options options; + private final HttpDecoder decoder; + private final HttpExceptionHandler exceptionHandler; + + public HttpMethodInvoker(ProxyTarget proxyTarget, + HttpMethodMeta metadata, + HttpRequestFactory httpRequestFactory, + HttpExecutor httpExecutor, + List httpInterceptors, + Options options, + HttpDecoder decoder, + Level level, + boolean ignoreError, + HttpExceptionHandler exceptionHandler) { + this.proxyTarget = proxyTarget; + this.httpExecutor = httpExecutor; + this.httpInterceptors = httpInterceptors; + this.metadata = metadata; + this.httpRequestFactory = httpRequestFactory; + this.options = options; + this.decoder = decoder; + this.level = level; + this.ignoreError = ignoreError; + this.exceptionHandler = exceptionHandler; + } + + @Override + public Object invoke(Object[] args) throws Throwable { + HttpRequest httpRequest = httpRequestFactory.create(args); + return executeAndDecode(httpRequest); + } + + Object executeAndDecode(HttpRequest httpRequest) throws Throwable { + Type returnType = metadata.getReturnType(); + Type httpType = getParamTypeOf(returnType, HttpResponse.class); + + // Http请求 + HttpRequestTemplate httpRequestTemplate = applyTemplate(httpRequest); + String url = httpRequestTemplate.getUrl(); + String method = httpRequestTemplate.getMethod(); + + long start = System.nanoTime(); + HttpResponseTemplate httpResponseTemplate; + try { + httpResponseTemplate = httpExecutor.execute(httpRequestTemplate); + } catch (IOException e) { + log.error(">< {} {} {}", e.getMessage(), method, url, e); + return throwOrReturn(httpType, new HttpHintException("Remote failed")); + } + + long cost = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); + int status = httpResponseTemplate.getStatus(); + try { + // 3.响应类型: org.springframework.feign.invoke.codec.HttpResponse + if (httpType != null) { + return parseHttpResponse(httpType, httpResponseTemplate, status, method, url, cost); + } + + // 4.decoder解码 + if (status == 200) { + return decoder.decode(httpResponseTemplate, metadata.getReturnType(), url, cost, status, level); + } + + if (httpResponseTemplate.getInputStream() != null) { + String body = StreamUtils.copyToString(httpResponseTemplate.getInputStream(), StandardCharsets.UTF_8); + log.error(">< {} {}ms {} {} {}", status, cost, method, url, body); + } else { + log.error(">< {} {}ms {} {}", status, cost, method, url); + } + throw new HttpHintException(status, SERVICE_ERROR.getCode(), "Remote failed"); + } catch (HttpException e) { + if (exceptionHandler != null) { + exceptionHandler.handle(e); + } + return throwOrReturn(httpType, e); + } catch (Exception e) { + log.error(">< {}ms {} {} {}", cost, e.getMessage(), method, url, e); + return throwOrReturn(httpType, new HttpHintException("Remote failed", e)); + } + } + + private Object throwOrReturn(Type httpType, Exception exception) throws Throwable { + if (ignoreError && httpType != null) { + HttpResponse httpResponse = new HttpResponse<>(SERVICE_ERROR); + httpResponse.setCause(exception); + httpResponse.setMessage(exception.getMessage()); + return httpResponse; + } + throw exception; + } + + private HttpResponse parseHttpResponse(Type paramType, HttpResponseTemplate response, + int status, String method, String url, long cost) throws IOException { + // Header信息 + HttpHeaders headers = new HttpHeaders(); + for (Map.Entry> entry : response.getHeaders().entrySet()) { + headers.put(entry.getKey(), new ArrayList<>(entry.getValue())); + } + + // InputStream交给调用者处理 + if (paramType.equals(InputStream.class)) { + if (status == 200) { + if (log.isDebugEnabled() || level.toInt() < WARN.toInt()) { + log.info(">< {} {}ms {} {}", status, cost, method, url); + } + } else { + log.warn(">< {} {}ms {}, {}", status, cost, method, url); + } + return new HttpResponse<>(response.getStatus(), headers, response.getInputStream()); + } + + // 获取响应主体 + String body = null; + if (response.getInputStream() != null) { + body = StreamUtils.copyToString(response.getInputStream(), StandardCharsets.UTF_8); + } + + if (status == 200) { + if (log.isDebugEnabled() || level.toInt() < WARN.toInt()) { + log.info(">< {} {}ms {} {}", status, cost, method, url); + } + if (!StringUtils.hasText(body) || paramType.equals(String.class) || paramType.equals(Void.class)) { + return new HttpResponse<>(response.getStatus(), headers, body); + } else { + return new HttpResponse<>(response.getStatus(), headers, JacksonDecoder.readValue(body, paramType)); + } + } else if (status > 200 && status < 300) { + log.warn(">< {} {}ms {} {} {}", status, cost, method, url, body); + HttpResponse httpResponse = new HttpResponse<>(response.getStatus(), headers, null); + httpResponse.setMessage(body); + return httpResponse; + } else { + log.error(">< {} {}ms {} {} {}", status, cost, method, url, body); + HttpResponse httpResponse = new HttpResponse<>(response.getStatus(), headers, null); + httpResponse.setMessage(body); + return httpResponse; + } + } + + private Type getParamTypeOf(Type type, Class clazz) { + if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + Type rawType = parameterizedType.getRawType(); + if (rawType instanceof Class && clazz.equals(rawType)) { + Type[] paramTypes = parameterizedType.getActualTypeArguments(); + if (paramTypes == null || paramTypes.length == 0) { + return Object.class; + } else { + return paramTypes[0]; + } + } + } + return null; + } + + HttpRequestTemplate applyTemplate(HttpRequest httpRequest) throws UnsupportedEncodingException { + for (HttpInterceptor interceptor : httpInterceptors) { + interceptor.apply(httpRequest); + } + + // 方法Options注解指定的超时优先级更高 + int connectTimeout = options.getConnectTimeout(); + int readTimeout = options.getReadTimeout(); + int retryTimes = options.getRetryTimes(); + int retryInterval = options.getRetryInterval(); + if (metadata.getConnectTimeout() != -1) { + connectTimeout = metadata.getConnectTimeout(); + } + if (metadata.getReadTimeout() != -1) { + readTimeout = metadata.getReadTimeout(); + } + if (metadata.getRetryTimes() != -1) { + retryTimes = metadata.getRetryTimes(); + } + if (metadata.getRetryInterval() != -1) { + retryInterval = metadata.getRetryInterval(); + } + // 设置url及一些参数 + return proxyTarget.apply(httpRequest, + httpRequest.getHostUrl(), retryTimes, retryInterval, connectTimeout, readTimeout); + } +} diff --git a/src/main/java/com/cowave/commons/client/http/invoke/proxy/HttpMethodInvokerFactory.java b/src/main/java/com/cowave/commons/client/http/invoke/proxy/HttpMethodInvokerFactory.java new file mode 100644 index 0000000..e88aaf5 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/invoke/proxy/HttpMethodInvokerFactory.java @@ -0,0 +1,65 @@ +package com.cowave.commons.client.http.invoke.proxy; + +import com.cowave.commons.client.http.request.MultipartRequestFactory; +import lombok.RequiredArgsConstructor; +import org.slf4j.event.Level; +import com.cowave.commons.client.http.HttpExceptionHandler; +import com.cowave.commons.client.http.HttpInterceptor; +import com.cowave.commons.client.http.invoke.codec.HttpDecoder; +import com.cowave.commons.client.http.invoke.codec.HttpEncoder; +import com.cowave.commons.client.http.invoke.exec.HttpExecutor; +import com.cowave.commons.client.http.request.Options; +import com.cowave.commons.client.http.request.meta.HttpMethodMetaParser; +import com.cowave.commons.client.http.request.meta.HttpMethodMeta; +import com.cowave.commons.client.http.request.HttpRequestFactory; +import com.cowave.commons.client.http.request.BodyRequestFactory; + +import java.io.UnsupportedEncodingException; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +public class HttpMethodInvokerFactory { + private final HttpExecutor httpExecutor; + private final HttpMethodMetaParser metaParser; + private final HttpEncoder encoder; + private final HttpDecoder decoder; + private final Options options; + private final List httpInterceptors; + private final HttpExceptionHandler exceptionHandler; + + private final boolean ignoreError; + private final Level level; + + public Map create(ProxyTarget proxyTarget) throws UnsupportedEncodingException { + + List metaList = metaParser.parse(proxyTarget.type()); + + Map result = new LinkedHashMap<>(); + for (HttpMethodMeta meta : metaList) { + HttpRequestFactory httpRequestFactory; + if (meta.getMultipartFileIndex() != null + || meta.getMultipartFormIndex() != null || !meta.getMultipartParams().isEmpty()) { + // multipart/form-data + httpRequestFactory = new MultipartRequestFactory(meta); + } else if (meta.getBodyIndex() != null) { + // 存在body + httpRequestFactory = new BodyRequestFactory(meta, encoder); + } else { + httpRequestFactory = new HttpRequestFactory(meta); + } + + // + result.put(meta.getMethodKey(), + new HttpMethodInvoker(proxyTarget, meta, httpRequestFactory, + httpExecutor, httpInterceptors, options, decoder, level, ignoreError, exceptionHandler)); + } + return result; + } +} diff --git a/src/main/java/com/cowave/commons/client/http/invoke/proxy/MethodInvoker.java b/src/main/java/com/cowave/commons/client/http/invoke/proxy/MethodInvoker.java new file mode 100644 index 0000000..d22ca76 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/invoke/proxy/MethodInvoker.java @@ -0,0 +1,11 @@ +package com.cowave.commons.client.http.invoke.proxy; + +/** + * + * @author shanhuiming + * + */ +public interface MethodInvoker { + + Object invoke(Object[] argv) throws Throwable; +} diff --git a/src/main/java/com/cowave/commons/client/http/invoke/proxy/ProxyFactory.java b/src/main/java/com/cowave/commons/client/http/invoke/proxy/ProxyFactory.java new file mode 100644 index 0000000..4ef5883 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/invoke/proxy/ProxyFactory.java @@ -0,0 +1,58 @@ +package com.cowave.commons.client.http.invoke.proxy; + +import com.cowave.commons.client.http.request.meta.HttpMethodMetaParser; + +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * + * @author shanhuiming + * + */ +public class ProxyFactory { + + private final HttpMethodInvokerFactory httpMethodInvokerFactory; + + public ProxyFactory(HttpMethodInvokerFactory httpMethodInvokerFactory) { + this.httpMethodInvokerFactory = httpMethodInvokerFactory; + } + + @SuppressWarnings("unchecked") + public T newProxy(ProxyTarget proxyTarget) throws UnsupportedEncodingException { + // 默认方法 + List defaultProxyInvokerList = new LinkedList<>(); + + // Http调用方法 + Map httpProxyInvokerMap = httpMethodInvokerFactory.create(proxyTarget); + + Map methodInvokeHandlerMap = new LinkedHashMap<>(); + for (Method method : proxyTarget.type().getMethods()) { + if (method.getDeclaringClass() == Object.class) { + // object方法不进行代理 + } else if (HttpMethodMetaParser.isDefault(method)) { + DefaultMethodInvoker handler = new DefaultMethodInvoker(method); + defaultProxyInvokerList.add(handler); + methodInvokeHandlerMap.put(method, handler); + } else { + methodInvokeHandlerMap.put( + method, httpProxyInvokerMap.get(HttpMethodMetaParser.methodKey(proxyTarget.type(), method))); + } + } + + // 创建代理 + T proxy = (T) Proxy.newProxyInstance( + proxyTarget.type().getClassLoader(), + new Class[]{proxyTarget.type()}, + new ProxyInvoker(proxyTarget, methodInvokeHandlerMap)); + for (DefaultMethodInvoker methodHandler : defaultProxyInvokerList) { + methodHandler.bindTo(proxy); + } + return proxy; + } +} diff --git a/src/main/java/com/cowave/commons/client/http/invoke/proxy/ProxyFactoryBuilder.java b/src/main/java/com/cowave/commons/client/http/invoke/proxy/ProxyFactoryBuilder.java new file mode 100644 index 0000000..bb8b451 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/invoke/proxy/ProxyFactoryBuilder.java @@ -0,0 +1,46 @@ +package com.cowave.commons.client.http.invoke.proxy; + +import com.cowave.commons.client.http.request.meta.HttpMethodMetaParser; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.slf4j.event.Level; +import com.cowave.commons.client.http.HttpExceptionHandler; +import com.cowave.commons.client.http.HttpInterceptor; +import com.cowave.commons.client.http.invoke.codec.HttpDecoder; +import com.cowave.commons.client.http.invoke.codec.HttpEncoder; +import com.cowave.commons.client.http.invoke.codec.decoder.JacksonDecoder; +import com.cowave.commons.client.http.invoke.codec.encoder.JacksonEncoder; +import com.cowave.commons.client.http.invoke.exec.HttpExecutor; +import com.cowave.commons.client.http.request.Options; +import com.cowave.commons.client.http.request.meta.HttpMethodMetaParserImpl; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author shanhuiming + * + */ +@Data +@RequiredArgsConstructor +public class ProxyFactoryBuilder { + private final HttpMethodMetaParser metaParser = new HttpMethodMetaParserImpl(); + private final List httpInterceptors = new ArrayList<>(); + private final Options options; + private HttpEncoder encoder = new JacksonEncoder(); + private HttpDecoder decoder = new JacksonDecoder(); + private HttpExecutor httpExecutor; + private HttpExceptionHandler exceptionHandler; + + public void addHttpInterceptor(HttpInterceptor httpInterceptor) { + this.httpInterceptors.add(httpInterceptor); + } + + public ProxyFactory newProxyFactory(Level level, boolean ignoreError) { + HttpMethodInvokerFactory methodHandlerFactory = new HttpMethodInvokerFactory( + httpExecutor, metaParser, encoder, decoder, + options, httpInterceptors, exceptionHandler, ignoreError, level); + return new ProxyFactory(methodHandlerFactory); + } +} diff --git a/src/main/java/org/springframework/feign/invoke/FeignInvocationHandler.java b/src/main/java/com/cowave/commons/client/http/invoke/proxy/ProxyInvoker.java similarity index 56% rename from src/main/java/org/springframework/feign/invoke/FeignInvocationHandler.java rename to src/main/java/com/cowave/commons/client/http/invoke/proxy/ProxyInvoker.java index 4e5173e..c03a6a3 100644 --- a/src/main/java/org/springframework/feign/invoke/FeignInvocationHandler.java +++ b/src/main/java/com/cowave/commons/client/http/invoke/proxy/ProxyInvoker.java @@ -1,30 +1,26 @@ -package org.springframework.feign.invoke; - -import feign.*; +package com.cowave.commons.client.http.invoke.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Map; -import static feign.Util.checkNotNull; - /** * * @author shanhuiming * */ -public class FeignInvocationHandler implements InvocationHandler { +public class ProxyInvoker implements InvocationHandler { @SuppressWarnings("rawtypes") - private final Target target; - - private final Map dispatch; + private final ProxyTarget proxyTarget; + + private final Map methodInvokeHandlerMap; @SuppressWarnings("rawtypes") - FeignInvocationHandler(Target target, Map dispatch) { - this.target = checkNotNull(target, "target"); - this.dispatch = checkNotNull(dispatch, "dispatch for %s", target); + ProxyInvoker(ProxyTarget proxyTarget, Map methodInvokeHandlerMap) { + this.proxyTarget = proxyTarget; + this.methodInvokeHandlerMap = methodInvokeHandlerMap; } @Override @@ -41,25 +37,25 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl } else if ("toString".equals(method.getName())) { return toString(); } - return dispatch.get(method).invoke(args); + return methodInvokeHandlerMap.get(method).invoke(args); } @Override public boolean equals(Object obj) { - if (obj instanceof FeignInvocationHandler) { - FeignInvocationHandler other = (FeignInvocationHandler) obj; - return target.equals(other.target); + if (obj instanceof ProxyInvoker) { + ProxyInvoker other = (ProxyInvoker) obj; + return proxyTarget.equals(other.proxyTarget); } return false; } @Override public int hashCode() { - return target.hashCode(); + return proxyTarget.hashCode(); } @Override public String toString() { - return target.toString(); + return proxyTarget.toString(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/cowave/commons/client/http/invoke/proxy/ProxyTarget.java b/src/main/java/com/cowave/commons/client/http/invoke/proxy/ProxyTarget.java new file mode 100644 index 0000000..55d5c42 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/invoke/proxy/ProxyTarget.java @@ -0,0 +1,101 @@ +package com.cowave.commons.client.http.invoke.proxy; + +import com.cowave.commons.client.http.asserts.HttpHintException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationContext; +import com.cowave.commons.client.http.HttpServiceChooser; +import com.cowave.commons.client.http.request.HttpRequestTemplate; +import com.cowave.commons.client.http.request.HttpRequest; +import org.springframework.util.StringUtils; +import org.springframework.util.StringValueResolver; + +import java.io.UnsupportedEncodingException; + +/** + * + * @author shanhuiming + * + */ +@Slf4j +@RequiredArgsConstructor +public class ProxyTarget { + private final ApplicationContext applicationContext; + private final StringValueResolver valueResolver; + private final Class type; + private final String name; + private final String url; + + public HttpRequestTemplate apply(HttpRequest httpRequest, String hostUrl, + int retryTimes, int retryInterval, int connectTimeout, int readTimeout) throws UnsupportedEncodingException { + if(StringUtils.hasText(hostUrl)){ + httpRequest.insert(0, hostUrl); + return httpRequest.requestTemplate(retryTimes, retryInterval, connectTimeout, readTimeout); + }else{ + return apply(httpRequest, retryTimes, retryInterval, connectTimeout, readTimeout); + } + } + + public HttpRequestTemplate apply(HttpRequest httpRequest, + int retryTimes, int retryInterval, int connectTimeout, int readTimeout) throws UnsupportedEncodingException { + String prasedName = ""; + if(StringUtils.hasText(name)){ + HttpServiceChooser serviceChooser = applicationContext.getBean(HttpServiceChooser.class); + prasedName = serviceChooser.choose(name); + } + + String prasedUrl = url; + if(StringUtils.hasText(url) && url.contains("${")){ + prasedUrl = valueResolver.resolveStringValue(url); + } + + String parsed = prasedName + prasedUrl; + if (httpRequest.url().indexOf("http") != 0) { + if(parsed.indexOf("http") != 0){ + log.error(">< Remote failed due to illegal url, {}", parsed); + throw new HttpHintException("Remote failed"); + } + httpRequest.insert(0, parsed); + } + return httpRequest.requestTemplate(retryTimes, retryInterval, connectTimeout, readTimeout); + } + + public Class type() { + return type; + } + + public String name() { + return name; + } + + public String url() { + return url; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + ProxyTarget other = (ProxyTarget) obj; + return type.equals(other.type) && name.equals(other.name) && url.equals(other.url); + } + + @Override + public int hashCode() { + int result = 17; + result = 3 * result + type.hashCode(); + result = 5 * result + name.hashCode(); + result = 7 * result + url.hashCode(); + return result; + } + + @Override + public String toString() { + return "{type=" + type.getSimpleName() + ", name=" + name + ", url=" + url + "}"; + } +} diff --git a/src/main/java/org/springframework/feign/FeignBeanDefinitionRegistrar.java b/src/main/java/com/cowave/commons/client/http/register/HttpClientBeanDefinitionRegistrar.java similarity index 78% rename from src/main/java/org/springframework/feign/FeignBeanDefinitionRegistrar.java rename to src/main/java/com/cowave/commons/client/http/register/HttpClientBeanDefinitionRegistrar.java index 1420dce..1625ee2 100644 --- a/src/main/java/org/springframework/feign/FeignBeanDefinitionRegistrar.java +++ b/src/main/java/com/cowave/commons/client/http/register/HttpClientBeanDefinitionRegistrar.java @@ -1,5 +1,6 @@ -package org.springframework.feign; +package com.cowave.commons.client.http.register; +import com.cowave.commons.client.http.annotation.HttpClient; import org.reflections.Reflections; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; @@ -11,7 +12,6 @@ import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.type.AnnotationMetadata; -import org.springframework.feign.annotation.FeignClient; import org.springframework.lang.NonNull; import java.util.*; @@ -21,36 +21,41 @@ * @author shanhuiming * */ -public class FeignBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { +public class HttpClientBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata meta, @NonNull BeanDefinitionRegistry registry) { TreeSet packageSet = new TreeSet<>(); String[] beanNames = registry.getBeanDefinitionNames(); + + // 省掉package指定扫描,就检查下启动类下的路径,以及@ComponentScan指定的路径 try { for (String beanName : beanNames) { BeanDefinition beanDefinition = registry.getBeanDefinition(beanName); String className = beanDefinition.getBeanClassName(); if (className != null) { Class clazz = Class.forName(className); + // 启动类路径 SpringBootApplication springBoot = clazz.getAnnotation(SpringBootApplication.class); if (springBoot != null) { packageSet.add(clazz.getPackage().getName()); } + // 扫描类路径 ComponentScan componentScan = clazz.getAnnotation(ComponentScan.class); if (componentScan != null) { - packageSet.addAll(List.of(componentScan.basePackages())); + packageSet.addAll(Arrays.asList(componentScan.basePackages())); } } } } catch (ClassNotFoundException e) { throw new ApplicationContextException("", e); } - List packageList = packageSet.stream().toList(); - // 去下重 + List packageList = new ArrayList<>(packageSet); + + // 合并package List packages = new ArrayList<>(); for(int right = packageList.size() - 1; right >= 0; right--){ boolean hasPrefix = false; @@ -69,16 +74,17 @@ public void registerBeanDefinitions(AnnotationMetadata meta, @NonNull BeanDefini // 扫描注册 for(String pack : packages){ Reflections reflections = new Reflections(pack); - for(Class clazz : reflections.getTypesAnnotatedWith(FeignClient.class)){ - FeignClient feign = AnnotationUtils.getAnnotation(clazz, FeignClient.class); + for(Class clazz : reflections.getTypesAnnotatedWith(HttpClient.class)){ + HttpClient feign = AnnotationUtils.getAnnotation(clazz, HttpClient.class); if(feign == null){ continue; } + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz); GenericBeanDefinition beanDefinition = (GenericBeanDefinition)builder.getBeanDefinition(); beanDefinition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE); - beanDefinition.getPropertyValues().add("feignClass", clazz); - beanDefinition.setBeanClass(FeignFactory.class); + beanDefinition.getPropertyValues().add("targetClass", clazz); + beanDefinition.setBeanClass(HttpClientFactoryBean.class); registry.registerBeanDefinition(clazz.getSimpleName(), beanDefinition); } } diff --git a/src/main/java/com/cowave/commons/client/http/register/HttpClientFactoryBean.java b/src/main/java/com/cowave/commons/client/http/register/HttpClientFactoryBean.java new file mode 100644 index 0000000..3babbc6 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/register/HttpClientFactoryBean.java @@ -0,0 +1,50 @@ +package com.cowave.commons.client.http.register; + +import com.cowave.commons.client.http.annotation.HttpClient; +import lombok.Data; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.EmbeddedValueResolverAware; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.lang.NonNull; +import org.springframework.util.StringValueResolver; + +import java.io.UnsupportedEncodingException; + +/** + * + * @author shanhuiming + * + */ +@Data +public class HttpClientFactoryBean implements FactoryBean, EmbeddedValueResolverAware, ApplicationContextAware { + + private Class targetClass; + + public HttpClientFactoryBean() { + + } + + @Override + public T getObject() throws UnsupportedEncodingException { + HttpClient httpClient = AnnotationUtils.getAnnotation(targetClass, HttpClient.class); + return HttpProxyFactory.newProxy(targetClass, httpClient); + } + + @Override + public Class getObjectType() { + return targetClass; + } + + @Override + public void setEmbeddedValueResolver(@NonNull StringValueResolver valueResolver) { + HttpProxyFactory.setStringValueResolver(valueResolver); + } + + @Override + public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException { + HttpProxyFactory.setApplicationContext(applicationContext); + } +} diff --git a/src/main/java/com/cowave/commons/client/http/register/HttpProxyFactory.java b/src/main/java/com/cowave/commons/client/http/register/HttpProxyFactory.java new file mode 100644 index 0000000..74d2327 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/register/HttpProxyFactory.java @@ -0,0 +1,88 @@ +package com.cowave.commons.client.http.register; + +import java.io.UnsupportedEncodingException; +import java.util.Objects; + +import com.cowave.commons.client.http.HttpInterceptor; +import com.cowave.commons.client.http.annotation.HttpClient; +import com.cowave.commons.client.http.invoke.exec.HttpClientExecutor; +import com.cowave.commons.client.http.invoke.codec.HttpEncoder; +import com.cowave.commons.client.http.invoke.proxy.ProxyFactory; +import com.cowave.commons.client.http.invoke.proxy.ProxyFactoryBuilder; +import com.cowave.commons.client.http.invoke.proxy.ProxyTarget; +import com.cowave.commons.client.http.request.Options; +import org.springframework.context.ApplicationContext; +import com.cowave.commons.client.http.HttpExceptionHandler; +import com.cowave.commons.client.http.invoke.codec.HttpDecoder; +import org.springframework.util.StringUtils; +import org.springframework.util.StringValueResolver; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSocketFactory; + +/** + * + * @author shanhuiming + * + */ +class HttpProxyFactory { + + private static ApplicationContext applicationContext; + + private static StringValueResolver valueResolver; + + static void setApplicationContext(ApplicationContext applicationContext) { + HttpProxyFactory.applicationContext = applicationContext; + } + + static void setStringValueResolver(StringValueResolver valueResolver) { + HttpProxyFactory.valueResolver = valueResolver; + } + + static T newProxy(Class clazz, HttpClient httpClient) throws UnsupportedEncodingException { + int retryTimes = getInt(httpClient.retryTimes(), httpClient.retryTimesStr()); + int retryInterval = getInt(httpClient.retryInterval(), httpClient.retryIntervalStr()); + int connectTimeout = getInt(httpClient.connectTimeout(), httpClient.connectTimeoutStr()); + int readTimeout = getInt(httpClient.readTimeout(), httpClient.readTimeoutStr()); + ProxyFactoryBuilder proxyFactoryBuilder = new ProxyFactoryBuilder(new Options(connectTimeout, readTimeout, retryTimes, retryInterval)); + + String[] interceptors = applicationContext.getBeanNamesForType(HttpInterceptor.class); + for (String interceptorName : interceptors) { + HttpInterceptor httpInterceptor = applicationContext.getBean(interceptorName, HttpInterceptor.class); + proxyFactoryBuilder.addHttpInterceptor(httpInterceptor); + } + + String[] handlers = applicationContext.getBeanNamesForType(HttpExceptionHandler.class); + if (handlers.length > 0) { + String handler = handlers[0]; + HttpExceptionHandler exceptionHandler = applicationContext.getBean(handler, HttpExceptionHandler.class); + proxyFactoryBuilder.setExceptionHandler(exceptionHandler); + } + + try { + // encode / decoder + proxyFactoryBuilder.setEncoder((HttpEncoder) httpClient.encoder().newInstance()); + proxyFactoryBuilder.setDecoder((HttpDecoder) httpClient.decoder().newInstance()); + // Https设置 + Class sslSocketFactoryClass = httpClient.sslSocketFactory(); + Class hostnameVerifierClass = httpClient.hostnameVerifier(); + SSLSocketFactory SSLSocketFactory = sslSocketFactoryClass.newInstance(); + HostnameVerifier hostnameVerifier = hostnameVerifierClass.newInstance(); + proxyFactoryBuilder.setHttpExecutor(new HttpClientExecutor(SSLSocketFactory, hostnameVerifier)); + } catch (Exception e) { + throw new RuntimeException(e); + } + + ProxyFactory proxyFactory = proxyFactoryBuilder.newProxyFactory(httpClient.level(), httpClient.ignoreError()); + ProxyTarget proxyTarget = new ProxyTarget<>( + applicationContext, valueResolver, clazz, httpClient.name(), httpClient.url()); + return proxyFactory.newProxy(proxyTarget); + } + + private static int getInt(int defaultValue, String regex) { + if (!StringUtils.hasText(regex)) { + return defaultValue; + } + return Integer.parseInt(Objects.requireNonNull(valueResolver.resolveStringValue(regex))); + } +} diff --git a/src/main/java/com/cowave/commons/client/http/request/BodyRequestFactory.java b/src/main/java/com/cowave/commons/client/http/request/BodyRequestFactory.java new file mode 100644 index 0000000..cf40dfe --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/request/BodyRequestFactory.java @@ -0,0 +1,39 @@ +package com.cowave.commons.client.http.request; + +import com.cowave.commons.client.http.request.meta.HttpMethodMeta; +import com.cowave.commons.client.http.invoke.codec.HttpEncoder; + +import java.util.Collection; +import java.util.Map; + +import static com.cowave.commons.client.http.HttpHeader.Content_Type; + +/** + * + * @author shanhuiming + * + */ +public class BodyRequestFactory extends HttpRequestFactory { + + private final HttpEncoder encoder; + + public BodyRequestFactory(HttpMethodMeta metadata, HttpEncoder encoder) { + super(metadata); + this.encoder = encoder; + } + + @Override + protected HttpRequest resolve(Object[] argv, HttpRequest httpRequest, + Map variables, Map multiParams) throws Exception { + Object body = argv[metadata.getBodyIndex()]; + + encoder.encode(httpRequest, body); + + // body请求,默认设置下application/json + Map> headers = httpRequest.headers(); + if(!headers.containsKey(Content_Type)){ + httpRequest.header(Content_Type, "application/json"); + } + return super.resolve(argv, httpRequest, variables, multiParams); + } +} diff --git a/src/main/java/com/cowave/commons/client/http/request/HttpRequest.java b/src/main/java/com/cowave/commons/client/http/request/HttpRequest.java new file mode 100644 index 0000000..58b47c6 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/request/HttpRequest.java @@ -0,0 +1,626 @@ +package com.cowave.commons.client.http.request; + +import com.cowave.commons.client.http.asserts.Asserts; +import org.springframework.util.StringUtils; + +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Array; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.*; + +import static com.cowave.commons.client.http.request.HttpRequestTemplate.valuesOrEmpty; + +/** + * + * @author shanhuiming + * + */ +public class HttpRequest { + private final Map> queries = new LinkedHashMap<>(); + private final Map> headers = new LinkedHashMap<>(); + private StringBuilder url = new StringBuilder(); + private String method; + private byte[] body; + private Map multiForm; + private InputStream multiFile; + private String multiFileName; + private String bodyTemplate; + private transient Charset charset; + private boolean decodeSlash = true; + // 调用时指定的url + private String hostUrl; + + public HttpRequest() { + + } + + public HttpRequest(HttpRequest copy) { + this.queries.putAll(copy.queries); + this.headers.putAll(copy.headers); + this.url.append(copy.url); + this.method = copy.method; + this.body = copy.body; + this.bodyTemplate = copy.bodyTemplate; + this.charset = copy.charset; + this.decodeSlash = copy.decodeSlash; + this.hostUrl = copy.hostUrl; + } + + public HttpRequestTemplate requestTemplate(int retryTimes, int retryInterval, int connectTimeout, int readTimeout) { + Map> safeCopy = new LinkedHashMap<>(headers); + return new HttpRequestTemplate(method, url + queryLine(), body, charset, + Collections.unmodifiableMap(safeCopy), + connectTimeout, readTimeout, retryTimes, retryInterval, + multiFile, multiFileName, multiForm); + } + + void setHostUrl(String hostUrl) { + this.hostUrl = hostUrl; + } + + public String getHostUrl() { + return hostUrl; + } + + void setMultiFile(InputStream multiFile) { + this.multiFile = multiFile; + } + + void setMultiFileName(String multiFileName) { + this.multiFileName = multiFileName; + } + + void setMultiForm(Map multiForm) { + this.multiForm = multiForm; + } + + private static String urlDecode(String arg) throws UnsupportedEncodingException { + return URLDecoder.decode(arg, "UTF-8"); + } + + private static String urlEncode(Object arg) throws UnsupportedEncodingException { + return URLEncoder.encode(String.valueOf(arg), "UTF-8"); + } + + private static boolean isHttpUrl(CharSequence value) { + return value.length() >= 4 && value.subSequence(0, 3).equals("http".substring(0, 3)); + } + + private static CharSequence removeTrailingSlash(CharSequence charSequence) { + if (charSequence != null && charSequence.length() > 0 && charSequence.charAt(charSequence.length() - 1) == '/') { + return charSequence.subSequence(0, charSequence.length() - 1); + } else { + return charSequence; + } + } + + /** + * Expands a {@code template}, such as {@code username}, using the {@code variables} supplied. Any + * unresolved parameters will remain.
Note that if you'd like curly braces literally in the + * {@code template}, urlencode them first. + * + * @param template URI template that can be in level 1 RFC6570 + * form. + * @param variables to the URI template + * @return expanded template, leaving any unresolved parameters literal + */ + public static String expand(String template, Map variables) { + // skip expansion if there's no valid variables set. ex. {a} is the + // first valid + Asserts.notNull(template, "template can't be null"); + Asserts.notNull(variables, "variables for " + template + " can't be null"); + if (template.length() < 3) { + return template; + } + + boolean inVar = false; + StringBuilder var = new StringBuilder(); + StringBuilder builder = new StringBuilder(); + for (char c : template.toCharArray()) { + switch (c) { + case '{': + if (inVar) { + // '{{' is an escape: write the brace and don't interpret as a variable + builder.append("{"); + inVar = false; + break; + } + inVar = true; + break; + case '}': + if (!inVar) { // then write the brace literally + builder.append('}'); + break; + } + inVar = false; + String key = var.toString(); + Object value = variables.get(var.toString()); + if (value != null) { + builder.append(value); + } else { + builder.append('{').append(key).append('}'); + } + var = new StringBuilder(); + break; + default: + if (inVar) { + var.append(c); + } else { + builder.append(c); + } + } + } + return builder.toString(); + } + + private static Map> parseAndDecodeQueries(String queryLine) throws UnsupportedEncodingException { + Map> map = new LinkedHashMap<>(); + if (!StringUtils.hasText(queryLine)) { + return map; + } + if (queryLine.indexOf('&') == -1) { + putKV(queryLine, map); + } else { + char[] chars = queryLine.toCharArray(); + int start = 0; + int i = 0; + for (; i < chars.length; i++) { + if (chars[i] == '&') { + putKV(queryLine.substring(start, i), map); + start = i + 1; + } + } + putKV(queryLine.substring(start, i), map); + } + return map; + } + + private static void putKV(String stringToParse, Map> map) throws UnsupportedEncodingException { + String key; + String value; + // note that '=' can be a valid part of the value + int firstEq = stringToParse.indexOf('='); + if (firstEq == -1) { + key = urlDecode(stringToParse); + value = null; + } else { + key = urlDecode(stringToParse.substring(0, firstEq)); + value = urlDecode(stringToParse.substring(firstEq + 1)); + } + Collection values = map.containsKey(key) ? map.get(key) : new ArrayList<>(); + values.add(value); + map.put(key, values); + } + + public HttpRequest resolve(Map paramMap) throws UnsupportedEncodingException { + replaceQueryValues(paramMap); + Map encoded = new LinkedHashMap<>(); + for (Map.Entry entry : paramMap.entrySet()) { + encoded.put(entry.getKey(), urlEncode(String.valueOf(entry.getValue()))); + } + String resolvedUrl = expand(url.toString(), encoded).replace("+", "%20"); + if (decodeSlash) { + resolvedUrl = resolvedUrl.replace("%2F", "/"); + } + url = new StringBuilder(resolvedUrl); + + Map> resolvedHeaders = new LinkedHashMap<>(); + for (String field : headers.keySet()) { + Collection resolvedValues = new ArrayList<>(); + for (String value : valuesOrEmpty(headers, field)) { + String resolved = expand(value, paramMap); + resolvedValues.add(resolved); + } + resolvedHeaders.put(field, resolvedValues); + } + headers.clear(); + headers.putAll(resolvedHeaders); + if (bodyTemplate != null) { + body(urlDecode(expand(bodyTemplate, encoded))); + } + return this; + } + + public HttpRequest method(String method) { + Asserts.notNull(method, "method can't be null"); + Asserts.isTrue(method.matches("^[A-Z]+$"), "Invalid HTTP Method: " + method); + this.method = method; + return this; + } + + /* @see Request#method() */ + public String method() { + return method; + } + + public HttpRequest decodeSlash(boolean decodeSlash) { + this.decodeSlash = decodeSlash; + return this; + } + + public boolean decodeSlash() { + return decodeSlash; + } + + /* @see #url() */ + public HttpRequest append(CharSequence value) throws UnsupportedEncodingException { + url.append(value); + url = pullAnyQueriesOutOfUrl(url); + return this; + } + + /* @see #url() */ + public HttpRequest insert(int pos, CharSequence value) throws UnsupportedEncodingException { + if (isHttpUrl(value)) { + value = removeTrailingSlash(value); + if (url.length() > 0 && url.charAt(0) != '/') { + url.insert(0, '/'); + } + } + url.insert(pos, pullAnyQueriesOutOfUrl(new StringBuilder(value))); + return this; + } + + public String url() { + return url.toString(); + } + + /** + * Replaces queries with the specified {@code name} with the {@code values} supplied. + *
Values can be passed in decoded or in url-encoded form depending on the value of the + * {@code encoded} parameter. + *
When the {@code value} is {@code null}, all queries with the {@code configKey} are + * removed.


relationship to JAXRS 2.0

Like {@code WebTarget.query}, + * except the values can be templatized.
ex.
+ *
+     * template.query("Signature", "{signature}");
+     * 
+ *
Note: behavior of RequestTemplate is not consistent if a query parameter with + * unsafe characters is passed as both encoded and unencoded, although no validation is performed. + *
ex.
+ *
+     * template.query(true, "param[]", "value");
+     * template.query(false, "param[]", "value");
+     * 
+ * + * @param encoded whether name and values are already url-encoded + * @param name the name of the query + * @param values can be a single null to imply removing all values. Else no values are expected + * to be null. + * @see #queries() + */ + public HttpRequest query(boolean encoded, String name, String... values) throws UnsupportedEncodingException { + return doQuery(encoded, name, values); + } + + /* @see #query(boolean, String, String...) */ + public HttpRequest query(boolean encoded, String name, Iterable values) throws UnsupportedEncodingException { + return doQuery(encoded, name, values); + } + + /** + * Shortcut for {@code query(false, String, String...)} + * + * @see #query(boolean, String, String...) + */ + public HttpRequest query(String name, String... values) throws UnsupportedEncodingException { + return doQuery(false, name, values); + } + + /** + * Shortcut for {@code query(false, String, Iterable)} + * + * @see #query(boolean, String, String...) + */ + public HttpRequest query(String name, Iterable values) throws UnsupportedEncodingException { + return doQuery(false, name, values); + } + + private HttpRequest doQuery(boolean encoded, String name, String... values) throws UnsupportedEncodingException { + Asserts.notNull(name, "name can't be null"); + String paramName = encoded ? name : encodeIfNotVariable(name); + queries.remove(paramName); + if (values != null && values.length > 0 && values[0] != null) { + ArrayList paramValues = new ArrayList(); + for (String value : values) { + paramValues.add(encoded ? value : encodeIfNotVariable(value)); + } + this.queries.put(paramName, paramValues); + } + return this; + } + + private HttpRequest doQuery(boolean encoded, String name, Iterable values) throws UnsupportedEncodingException { + if (values != null) { + return doQuery(encoded, name, toArray(values, String.class)); + } + return doQuery(encoded, name, (String[]) null); + } + + private static String encodeIfNotVariable(String in) throws UnsupportedEncodingException { + if (in == null || in.indexOf('{') == 0) { + return in; + } + return urlEncode(in); + } + + /** + * Replaces all existing queries with the newly supplied url decoded queries.
+ *

relationship to JAXRS 2.0

Like {@code WebTarget.queries}, except the + * values can be templatized.
ex.
+ *
+     * template.queries(ImmutableMultimap.of("Signature", "{signature}"));
+     * 
+ * + * @param queries if null, remove all queries. else value to replace all queries with. + * @see #queries() + */ + public HttpRequest queries(Map> queries) throws UnsupportedEncodingException { + if (queries == null || queries.isEmpty()) { + this.queries.clear(); + } else { + for (Map.Entry> entry : queries.entrySet()) { + query(entry.getKey(), toArray(entry.getValue(), String.class)); + } + } + return this; + } + + /** + * Returns an immutable copy of the url decoded queries. + */ + public Map> queries() throws UnsupportedEncodingException { + Map> decoded = new LinkedHashMap>(); + for (String field : queries.keySet()) { + Collection decodedValues = new ArrayList(); + for (String value : valuesOrEmpty(queries, field)) { + if (value != null) { + decodedValues.add(urlDecode(value)); + } else { + decodedValues.add(null); + } + } + decoded.put(urlDecode(field), decodedValues); + } + return Collections.unmodifiableMap(decoded); + } + + /** + * Replaces headers with the specified {@code configKey} with the {@code values} supplied.
+ * When the {@code value} is {@code null}, all headers with the {@code configKey} are removed. + *


relationship to JAXRS 2.0

Like {@code WebTarget.queries} and + * {@code javax.ws.rs.client.Invocation.Builder.header}, except the values can be templatized. + *
ex.
+ *
+     * template.query("X-Application-Version", "{version}");
+     * 
+ * + * @param name the name of the header + * @param values can be a single null to imply removing all values. Else no values are expected to + * be null. + * @see #headers() + */ + public HttpRequest header(String name, String... values) { + Asserts.notNull(name, "header name can't be null"); + if (values == null || (values.length == 1 && values[0] == null)) { + headers.remove(name); + } else { + List headers = new ArrayList(); + headers.addAll(Arrays.asList(values)); + this.headers.put(name, headers); + } + return this; + } + + /* @see #header(String, String...) */ + public HttpRequest header(String name, Iterable values) { + if (values != null) { + return header(name, toArray(values, String.class)); + } + return header(name, (String[]) null); + } + + /** + * Replaces all existing headers with the newly supplied headers.


relationship to + * JAXRS 2.0

Like {@code Invocation.Builder.headers(MultivaluedMap)}, except the + * values can be templatized.
ex.
+ *
+     * template.headers(mapOf("X-Application-Version", asList("{version}")));
+     * 
+ * + * @param headers if null, remove all headers. else value to replace all headers with. + * @see #headers() + */ + public HttpRequest headers(Map> headers) { + if (headers == null || headers.isEmpty()) { + this.headers.clear(); + } else { + this.headers.putAll(headers); + } + return this; + } + + /** + * Returns an immutable copy of the current headers. + */ + public Map> headers() { + return Collections.unmodifiableMap(headers); + } + + public HttpRequest body(String bodyText) { + byte[] bodyData = bodyText != null ? bodyText.getBytes(StandardCharsets.UTF_8) : null; + return body(bodyData, StandardCharsets.UTF_8); + } + + public HttpRequest body(byte[] bodyData, Charset charset) { + this.bodyTemplate = null; + this.charset = charset; + this.body = bodyData; + int bodyLength = bodyData != null ? bodyData.length : 0; + header("Content-Length", String.valueOf(bodyLength)); + return this; + } + + public Charset charset() { + return charset; + } + + public byte[] body() { + return body; + } + + + public HttpRequest bodyTemplate(String bodyTemplate) { + this.bodyTemplate = bodyTemplate; + this.charset = null; + this.body = null; + return this; + } + + + public String bodyTemplate() { + return bodyTemplate; + } + + /** + * if there are any query params in the URL, this will extract them out. + */ + private StringBuilder pullAnyQueriesOutOfUrl(StringBuilder url) throws UnsupportedEncodingException { + // parse out queries + int queryIndex = url.indexOf("?"); + if (queryIndex != -1) { + String queryLine = url.substring(queryIndex + 1); + Map> firstQueries = parseAndDecodeQueries(queryLine); + if (!queries.isEmpty()) { + firstQueries.putAll(queries); + queries.clear(); + } + //Since we decode all queries, we want to use the + //query()-method to re-add them to ensure that all + //logic (such as url-encoding) are executed, giving + //a valid queryLine() + for (String key : firstQueries.keySet()) { + Collection values = firstQueries.get(key); + if (allValuesAreNull(values)) { + //Queries where all values are null will + //be ignored by the query(key, value)-method + //So we manually avoid this case here, to ensure that + //we still fulfill the contract (ex. parameters without values) + queries.put(urlEncode(key), values); + } else { + query(key, values); + } + + } + return new StringBuilder(url.substring(0, queryIndex)); + } + return url; + } + + private boolean allValuesAreNull(Collection values) { + if (values == null || values.isEmpty()) { + return true; + } + for (String val : values) { + if (val != null) { + return false; + } + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + // 请求行 + builder.append(method).append(' ').append(url).append(" HTTP/1.1\n"); + // Header + for (String field : headers.keySet()) { + for (String value : valuesOrEmpty(headers, field)) { + builder.append(field).append(": ").append(value).append('\n'); + } + } + // Body + if (body != null) { + builder.append('\n').append(charset != null ? new String(body, charset) : "Binary data"); + } + return builder.toString(); + } + + /** + * Replaces query values which are templated with corresponding values from the {@code unencoded} + * map. Any unresolved queries are removed. + */ + public void replaceQueryValues(Map unencoded) throws UnsupportedEncodingException { + Iterator>> iterator = queries.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry> entry = iterator.next(); + if (entry.getValue() == null) { + continue; + } + Collection values = new ArrayList(); + for (String value : entry.getValue()) { + if (value.indexOf('{') == 0 && value.indexOf('}') == value.length() - 1) { + Object variableValue = unencoded.get(value.substring(1, value.length() - 1)); + // only add non-null expressions + if (variableValue == null) { + continue; + } + if (variableValue instanceof Iterable) { + for (Object val : Iterable.class.cast(variableValue)) { + values.add(urlEncode(String.valueOf(val))); + } + } else { + values.add(urlEncode(String.valueOf(variableValue))); + } + } else { + values.add(value); + } + } + if (values.isEmpty()) { + iterator.remove(); + } else { + entry.setValue(values); + } + } + } + + public String queryLine() { + if (queries.isEmpty()) { + return ""; + } + StringBuilder queryBuilder = new StringBuilder(); + for (String field : queries.keySet()) { + for (String value : valuesOrEmpty(queries, field)) { + queryBuilder.append('&'); + queryBuilder.append(field); + if (value != null) { + queryBuilder.append('='); + if (!value.isEmpty()) { + queryBuilder.append(value); + } + } + } + } + queryBuilder.deleteCharAt(0); + return queryBuilder.insert(0, '?').toString(); + } + + private static T[] toArray(Iterable iterable, Class type) { + Collection collection; + if (iterable instanceof Collection) { + collection = (Collection) iterable; + } else { + collection = new ArrayList(); + for (T element : iterable) { + collection.add(element); + } + } + T[] array = (T[]) Array.newInstance(type, collection.size()); + return collection.toArray(array); + } +} diff --git a/src/main/java/com/cowave/commons/client/http/request/HttpRequestFactory.java b/src/main/java/com/cowave/commons/client/http/request/HttpRequestFactory.java new file mode 100644 index 0000000..a6f664e --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/request/HttpRequestFactory.java @@ -0,0 +1,141 @@ +package com.cowave.commons.client.http.request; + +import com.cowave.commons.client.http.request.meta.HttpMethodMeta; +import com.cowave.commons.client.http.asserts.Asserts; + +import java.io.UnsupportedEncodingException; +import java.util.*; + +/** + * + * @author shanhuiming + * + */ +public class HttpRequestFactory { + protected final HttpMethodMeta metadata; + + public HttpRequestFactory(HttpMethodMeta metadata) { + this.metadata = metadata; + } + + public HttpRequest create(Object[] args) throws Exception { + HttpRequest httpRequest = new HttpRequest(metadata.getHttpRequest()); + // 处理hostUrl + if (metadata.getUrlIndex() != null) { + int urlIndex = metadata.getUrlIndex(); + Asserts.isTrue(args[urlIndex] != null, "URI parameter " + urlIndex + " was null"); + httpRequest.insert(0, String.valueOf(args[urlIndex])); + } + + Map paramMap = new LinkedHashMap<>(); + for (Map.Entry> entry : metadata.getParamIndexName().entrySet()) { + Object paramValue = args[entry.getKey()]; + if (paramValue != null) { + paramValue = expandElements(paramValue); + for (String paramName : entry.getValue()) { + paramMap.put(paramName, paramValue); + } + } + } + + Map multiParamMap = new LinkedHashMap<>(); + for (Map.Entry entry : metadata.getMultiParamIndexName().entrySet()) { + Object paramValue = args[entry.getKey()]; + multiParamMap.put(entry.getValue(), paramValue); + } + + // 请求模板 + HttpRequest resolvedRequest = resolve(args, httpRequest, paramMap, multiParamMap); + + if (metadata.getParamMapIndex() != null) { + addParamMapParameters(args, resolvedRequest); + } + + if (metadata.getHeaderMapIndex() != null) { + addHeaderMapHeaders(args, resolvedRequest); + } + return resolvedRequest; + } + + @SuppressWarnings("rawtypes") + private Object expandElements(Object value) { + if (value instanceof Iterable) { + return expandIterable((Iterable) value); + } + return value.toString(); + } + + @SuppressWarnings("rawtypes") + private String expandIterable(Iterable value) { + StringBuilder builder = new StringBuilder(); + Iterator iterator = value.iterator(); + while (iterator.hasNext()) { + Object element = iterator.next(); + if (element != null) { + builder.append(element); + } + if (iterator.hasNext()) { + builder.append(","); + } + } + return builder.toString(); + } + + @SuppressWarnings("unchecked") + private HttpRequest addHeaderMapHeaders(Object[] argv, HttpRequest template) { + Map headerMap = (Map) argv[metadata.getHeaderMapIndex()]; + for (Map.Entry currEntry : headerMap.entrySet()) { + Asserts.isTrue(currEntry.getKey().getClass() == String.class, + "HeaderMap key must be a String: " + currEntry.getKey()); + Collection values = new ArrayList(); + Object currValue = currEntry.getValue(); + if (currValue instanceof Iterable) { + Iterator iter = ((Iterable) currValue).iterator(); + while (iter.hasNext()) { + Object nextObject = iter.next(); + values.add(nextObject == null ? null : nextObject.toString()); + } + } else { + values.add(currValue == null ? null : currValue.toString()); + } + + template.header((String) currEntry.getKey(), values); + } + return template; + } + + @SuppressWarnings("unchecked") + private HttpRequest addParamMapParameters(Object[] argv, HttpRequest httpRequest) throws UnsupportedEncodingException { + Map paramMap = (Map) argv[metadata.getParamMapIndex()]; + for (Map.Entry currEntry : paramMap.entrySet()) { + Asserts.isTrue(currEntry.getKey().getClass() == String.class, + "ParamMap key must be a String: " + currEntry.getKey()); + Collection values = new ArrayList<>(); + Object currValue = currEntry.getValue(); + if (currValue instanceof Iterable) { + Iterator iter = ((Iterable) currValue).iterator(); + while (iter.hasNext()) { + Object nextObject = iter.next(); + values.add(nextObject == null ? null : nextObject.toString()); + } + } else { + values.add(currValue == null ? null : currValue.toString()); + } + httpRequest.query(metadata.isParamMapEncoded(), (String) currEntry.getKey(), values); + } + return httpRequest; + } + + protected HttpRequest resolve(Object[] argv, HttpRequest httpRequest, + Map variables, Map multiParams) throws Exception { + // 参数拼接 + httpRequest.resolve(variables); + + // 解析一下hostUrl + if (metadata.getHostIndex() != null) { + Object url = argv[metadata.getHostIndex()]; + httpRequest.setHostUrl(url.toString()); + } + return httpRequest; + } +} diff --git a/src/main/java/com/cowave/commons/client/http/request/HttpRequestTemplate.java b/src/main/java/com/cowave/commons/client/http/request/HttpRequestTemplate.java new file mode 100644 index 0000000..2f0190c --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/request/HttpRequestTemplate.java @@ -0,0 +1,51 @@ +package com.cowave.commons.client.http.request; + +import lombok.Data; +import lombok.RequiredArgsConstructor; + +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +/** + * + * @author shanhuiming + * + */ +@Data +@RequiredArgsConstructor +public class HttpRequestTemplate { + private final String method; + private final String url; + private final byte[] body; + private final Charset charset; + private final Map> headers; + private final int connectTimeout; + private final int readTimeout; + private final int retryTimes; + private final int retryInterval; + private final InputStream multiFile; + private final String multiFileName; + private final Map multiForm; + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(method).append(' ').append(url).append(" HTTP/1.1\n"); + for (String field : headers.keySet()) { + for (String value : valuesOrEmpty(headers, field)) { + builder.append(field).append(": ").append(value).append('\n'); + } + } + if (body != null) { + builder.append('\n').append(charset != null ? new String(body, charset) : "Binary data"); + } + return builder.toString(); + } + + public static Collection valuesOrEmpty(Map> map, String key) { + return map.containsKey(key) && map.get(key) != null ? map.get(key) : Collections.emptyList(); + } +} diff --git a/src/main/java/com/cowave/commons/client/http/request/MultipartRequestFactory.java b/src/main/java/com/cowave/commons/client/http/request/MultipartRequestFactory.java new file mode 100644 index 0000000..f8e9125 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/request/MultipartRequestFactory.java @@ -0,0 +1,66 @@ +package com.cowave.commons.client.http.request; + +import com.cowave.commons.client.http.asserts.Asserts; +import com.cowave.commons.client.http.request.meta.HttpMethodMeta; +import org.springframework.web.multipart.MultipartFile; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.rmi.RemoteException; +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author shanhuiming + * + */ +public class MultipartRequestFactory extends HttpRequestFactory { + + public MultipartRequestFactory(HttpMethodMeta metadata) { + super(metadata); + } + + @Override + protected HttpRequest resolve(Object[] argv, HttpRequest httpRequest, + Map variables, Map multiParams) throws Exception { + Map multiParamMap = new HashMap<>(); + if (metadata.getMultipartFormIndex() != null) { + Object form = argv[metadata.getMultipartFormIndex()]; + if (form instanceof Map) { + Map multiForm = (Map) form; + multiParamMap.putAll(multiForm); + } else { + throw new RemoteException("HttpMultiForm parameter must be a Map"); + } + } + multiParamMap.putAll(multiParams); + + InputStream multiFile = null; + String multiFileName = null; + if (metadata.getMultipartFileIndex() != null) { + multiFileName = metadata.getMultipartFileName(); + + Object multipartFile = argv[metadata.getMultipartFileIndex()]; + Asserts.notNull(multipartFile, "HttpMultiFile parameter was null"); + if (multipartFile instanceof InputStream) { + multiFile = (InputStream) multipartFile; + } else if (multipartFile instanceof File) { + multiFile = new FileInputStream((File) multipartFile); + } else if (multipartFile instanceof MultipartFile) { + multiFile = ((MultipartFile) multipartFile).getInputStream(); + } else if (multipartFile instanceof byte[]) { + multiFile = new ByteArrayInputStream((byte[]) multipartFile); + } else { + throw new RemoteException("HttpMultiFile only supports type of (InputStream、File、MultipartFile、byte[])"); + } + } + + httpRequest.setMultiForm(multiParamMap); + httpRequest.setMultiFile(multiFile); + httpRequest.setMultiFileName(multiFileName); + return super.resolve(argv, httpRequest, variables, multiParams); + } +} diff --git a/src/main/java/com/cowave/commons/client/http/request/Options.java b/src/main/java/com/cowave/commons/client/http/request/Options.java new file mode 100644 index 0000000..0528612 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/request/Options.java @@ -0,0 +1,22 @@ +package com.cowave.commons.client.http.request; + +import lombok.Data; +import lombok.RequiredArgsConstructor; + +/** + * + * @author shanhuiming + * + */ +@Data +@RequiredArgsConstructor +public class Options { + private final int connectTimeout; + private final int readTimeout; + private final int retryTimes; + private final int retryInterval; + + public Options() { + this(10000, 601000, 1, 1000); + } +} diff --git a/src/main/java/com/cowave/commons/client/http/request/meta/HttpMethodMeta.java b/src/main/java/com/cowave/commons/client/http/request/meta/HttpMethodMeta.java new file mode 100644 index 0000000..fe70b17 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/request/meta/HttpMethodMeta.java @@ -0,0 +1,37 @@ +package com.cowave.commons.client.http.request.meta; + +import com.cowave.commons.client.http.request.HttpRequest; +import lombok.Data; + +import java.lang.reflect.Type; +import java.util.*; + +/** + * + * @author shanhuiming + * + */ +@Data +public class HttpMethodMeta { + private final HttpRequest httpRequest = new HttpRequest(); + private final Map> paramIndexName = new LinkedHashMap<>(); + private final Map multiParamIndexName = new LinkedHashMap<>(); + private final List multipartParams = new ArrayList<>(); + private Integer urlIndex; + private Integer bodyIndex; + private Integer headerMapIndex; + private Integer paramMapIndex; + private Integer hostIndex; + private Integer multipartFileIndex; + private Integer multipartFormIndex; + private String multipartFileName; + private String multipartFileBoundary; + private String methodKey; + private boolean paramMapEncoded; + private transient Type bodyType; + private transient Type returnType; + private int connectTimeout = -1; + private int readTimeout = -1; + private int retryTimes; + private int retryInterval; +} diff --git a/src/main/java/com/cowave/commons/client/http/request/meta/HttpMethodMetaParser.java b/src/main/java/com/cowave/commons/client/http/request/meta/HttpMethodMetaParser.java new file mode 100644 index 0000000..97ef279 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/request/meta/HttpMethodMetaParser.java @@ -0,0 +1,41 @@ +package com.cowave.commons.client.http.request.meta; + +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.util.*; + +/** + * + * @author shanhuiming + * + */ +public interface HttpMethodMetaParser { + + List parse(Class clazz) throws UnsupportedEncodingException; + + // Default methods are public non-abstract, non-synthetic, and non-static instance methods + // declared in an interface. + // method.isDefault() is not sufficient for our usage as it does not check + // for synthetic methods. As a result, it picks up overridden methods as well as actual default methods. + static boolean isDefault(Method method) { + final int SYNTHETIC = 0x00001000; + return method.getDeclaringClass().isInterface() && + ((method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC | SYNTHETIC)) == Modifier.PUBLIC); + } + + static String methodKey(Class clazz, Method method) { + StringBuilder builder = new StringBuilder(); + builder.append(clazz.getSimpleName()); + builder.append('#').append(method.getName()).append('('); + for (Type param : method.getGenericParameterTypes()) { + param = Types.resolve(clazz, clazz, param); + builder.append(Types.getRawType(param).getSimpleName()).append(','); + } + if (method.getParameterTypes().length > 0) { + builder.deleteCharAt(builder.length() - 1); + } + return builder.append(')').toString(); + } +} diff --git a/src/main/java/com/cowave/commons/client/http/request/meta/HttpMethodMetaParserImpl.java b/src/main/java/com/cowave/commons/client/http/request/meta/HttpMethodMetaParserImpl.java new file mode 100644 index 0000000..413661e --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/request/meta/HttpMethodMetaParserImpl.java @@ -0,0 +1,217 @@ +package com.cowave.commons.client.http.request.meta; + +import com.cowave.commons.client.http.annotation.*; +import com.cowave.commons.client.http.asserts.Asserts; + +import java.io.UnsupportedEncodingException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URI; +import java.util.*; + +/** + * + * @author shanhuiming + * + */ +public class HttpMethodMetaParserImpl implements HttpMethodMetaParser { + + @Override + public List parse(Class clazz) throws UnsupportedEncodingException { + Asserts.isTrue(clazz.getTypeParameters().length == 0, + "Parameterized types unsupported: " + clazz.getSimpleName()); + Asserts.isTrue(clazz.getInterfaces().length <= 1, + "Only single inheritance supported: " + clazz.getSimpleName()); + if (clazz.getInterfaces().length == 1) { + Asserts.isTrue(clazz.getInterfaces()[0].getInterfaces().length == 0, + "Only single-level inheritance supported: " + clazz.getSimpleName()); + } + + Map result = new LinkedHashMap<>(); + for (Method method : clazz.getMethods()) { + if (method.getDeclaringClass() == Object.class + || (method.getModifiers() & Modifier.STATIC) != 0 + || HttpMethodMetaParser.isDefault(method)) { + continue; + } + + // 解析方法 + HttpMethodMeta metadata = parseAndValidateMetadata(clazz, method); + Asserts.isTrue(!result.containsKey(metadata.getMethodKey()), + "Overrides unsupported: " + metadata.getMethodKey()); + result.put(metadata.getMethodKey(), metadata); + } + return new ArrayList<>(result.values()); + } + + protected HttpMethodMeta parseAndValidateMetadata(Class targetType, Method method) throws UnsupportedEncodingException { + HttpMethodMeta metadata = new HttpMethodMeta(); + metadata.setReturnType(Types.resolve(targetType, targetType, method.getGenericReturnType())); + metadata.setMethodKey(HttpMethodMetaParser.methodKey(targetType, method)); + + // class注解 + if(targetType.getInterfaces().length == 1) { + processAnnotationOnClass(metadata, targetType.getInterfaces()[0]); + } + processAnnotationOnClass(metadata, targetType); + + // method注解 + for (Annotation methodAnnotation : method.getAnnotations()) { + processAnnotationOnMethod(metadata, methodAnnotation, method); + } + Asserts.isTrue(metadata.getHttpRequest().method() != null, + "Method " + method.getName() + " not annotated with HTTP method type (ex. GET, POST)"); + + // parameter注解 + Class[] parameterTypes = method.getParameterTypes(); + Annotation[][] parameterAnnotations = method.getParameterAnnotations(); + for (int i = 0; i < parameterAnnotations.length; i++) { + boolean isHttpAnnotation = false; + if (parameterAnnotations[i] != null) { + isHttpAnnotation = processAnnotationsOnParameter(metadata, parameterAnnotations[i], i); + } + + if (parameterTypes[i] == URI.class) { + metadata.setUrlIndex(i); + } else if (!isHttpAnnotation) { + Asserts.isTrue(metadata.getBodyIndex() == null, "Method has too many Body parameters: " + method); + metadata.setBodyIndex(i); + metadata.setBodyType(Types.resolve(targetType, targetType, method.getGenericParameterTypes()[i])); + } + } + + // 校验 + if (metadata.getHeaderMapIndex() != null) { + Asserts.isTrue(Map.class.isAssignableFrom(parameterTypes[metadata.getHeaderMapIndex()]), + "HttpHeaderMap parameter must be a Map: " + parameterTypes[metadata.getHeaderMapIndex()]); + } + if (metadata.getParamMapIndex() != null) { + Asserts.isTrue(Map.class.isAssignableFrom(parameterTypes[metadata.getParamMapIndex()]), + "HttpParamMap parameter must be a Map: " + parameterTypes[metadata.getParamMapIndex()]); + } + if (metadata.getMultipartFormIndex() != null) { + Asserts.isTrue(Map.class.isAssignableFrom(parameterTypes[metadata.getMultipartFormIndex()]), + "HttpMultiForm parameter must be a Map: " + parameterTypes[metadata.getMultipartFormIndex()]); + } + return metadata; + } + + protected void processAnnotationOnClass(HttpMethodMeta data, Class targetType) { + if (targetType.isAnnotationPresent(HttpHeaders.class)) { + String[] headersOnType = targetType.getAnnotation(HttpHeaders.class).value(); + Asserts.isTrue(headersOnType.length > 0, + "Headers annotation was empty on type " + targetType.getName()); + + Map> headers = toMap(headersOnType); + headers.putAll(data.getHttpRequest().headers()); + data.getHttpRequest().headers(null); // to clear + data.getHttpRequest().headers(headers); + } + } + + protected void processAnnotationOnMethod(HttpMethodMeta metadata, Annotation methodAnnotation, Method method) throws UnsupportedEncodingException { + Class annotationType = methodAnnotation.annotationType(); + if (annotationType == HttpLine.class) { + String httpLine = ((HttpLine) methodAnnotation).value(); + Asserts.notBlank(httpLine, "RequestLine annotation was empty on method " + method.getName()); + + if (httpLine.indexOf(' ') == -1) { + Asserts.isTrue(httpLine.indexOf('/') == -1, + "RequestLine annotation didn't start with an HTTP verb on method " + method.getName()); + metadata.getHttpRequest().method(httpLine); + return; + } + + metadata.getHttpRequest().method(httpLine.substring(0, httpLine.indexOf(' '))); + if (httpLine.indexOf(' ') == httpLine.lastIndexOf(' ')) { + // no HTTP version is ok + metadata.getHttpRequest().append(httpLine.substring(httpLine.indexOf(' ') + 1)); + } else { + // skip HTTP version + metadata.getHttpRequest().append(httpLine.substring(httpLine.indexOf(' ') + 1, httpLine.lastIndexOf(' '))); + } + metadata.getHttpRequest().decodeSlash(((HttpLine) methodAnnotation).decodeSlash()); + + } else if (annotationType == HttpBody.class) { + String body = ((HttpBody) methodAnnotation).value(); + Asserts.notBlank(body, "Body annotation was empty on method " + method.getName()); + + if (body.indexOf('{') == -1) { + metadata.getHttpRequest().body(body); + } else { + metadata.getHttpRequest().bodyTemplate(body); + } + } else if (annotationType == HttpHeaders.class) { + String[] headersOnMethod = ((HttpHeaders) methodAnnotation).value(); + Asserts.isTrue(headersOnMethod.length > 0, + "Headers annotation was empty on method " + method.getName()); + metadata.getHttpRequest().headers(toMap(headersOnMethod)); + } else if (annotationType == HttpOptions.class) { + HttpOptions httpOptions = (HttpOptions) methodAnnotation; + metadata.setReadTimeout(httpOptions.readTimeout()); + metadata.setConnectTimeout(httpOptions.connectTimeout()); + metadata.setRetryTimes(httpOptions.retryTimes()); + metadata.setRetryInterval(httpOptions.retryInterval()); + } + } + + protected boolean processAnnotationsOnParameter(HttpMethodMeta metadata, Annotation[] annotations, int paramIndex) { + boolean isHttpAnnotation = false; + for (Annotation annotation : annotations) { + Class annotationType = annotation.annotationType(); + if (annotationType == HttpParam.class) { + String paramName = ((HttpParam) annotation).value(); + Asserts.notBlank(paramName, "HttpParam annotation was empty on param " + paramIndex); + Collection paramNames = + metadata.getParamIndexName().containsKey(paramIndex) ? metadata.getParamIndexName().get(paramIndex) : new ArrayList<>(); + paramNames.add(paramName); + metadata.getParamIndexName().put(paramIndex, paramNames); + isHttpAnnotation = true; + } else if (annotationType == HttpParamMap.class) { + Asserts.isNull(metadata.getParamMapIndex(), + "ParamMap annotation was present on multiple parameters."); + metadata.setParamMapIndex(paramIndex); + metadata.setParamMapEncoded(((HttpParamMap) annotation).encoded()); + isHttpAnnotation = true; + } else if (annotationType == HttpHeaderMap.class) { + Asserts.isNull(metadata.getHeaderMapIndex(), + "HeaderMap annotation was present on multiple parameters."); + metadata.setHeaderMapIndex(paramIndex); + isHttpAnnotation = true; + } else if (annotationType == HttpHost.class) { + metadata.setHostIndex(paramIndex); + isHttpAnnotation = true; + } else if (annotationType == HttpMultiForm.class) { + metadata.setMultipartFormIndex(paramIndex); + isHttpAnnotation = true; + } else if (annotationType == HttpMultiFile.class) { + HttpMultiFile httpMultiFile = (HttpMultiFile) annotation; + metadata.setMultipartFileIndex(paramIndex); + metadata.setMultipartFileName(httpMultiFile.fileName()); + metadata.setMultipartFileBoundary(httpMultiFile.boundary()); + isHttpAnnotation = true; + } else if (annotationType == HttpMultiParam.class) { + String paramName = ((HttpMultiParam) annotation).value(); + Asserts.notBlank(paramName, "HttpMultiParam annotation was empty on param " + paramIndex); + metadata.getMultiParamIndexName().put(paramIndex, paramName); + metadata.getMultipartParams().add(paramName); // 判断标记 + isHttpAnnotation = true; + } + } + return isHttpAnnotation; + } + + private static Map> toMap(String[] input) { + Map> result = new LinkedHashMap<>(input.length); + for (String header : input) { + int colon = header.indexOf(':'); + String name = header.substring(0, colon); + if (!result.containsKey(name)) { + result.put(name, new ArrayList<>(1)); + } + result.get(name).add(header.substring(colon + 2)); + } + return result; + } +} diff --git a/src/main/java/org/springframework/feign/invoke/method/Types.java b/src/main/java/com/cowave/commons/client/http/request/meta/Types.java similarity index 97% rename from src/main/java/org/springframework/feign/invoke/method/Types.java rename to src/main/java/com/cowave/commons/client/http/request/meta/Types.java index 7285aec..8ccaee8 100644 --- a/src/main/java/org/springframework/feign/invoke/method/Types.java +++ b/src/main/java/com/cowave/commons/client/http/request/meta/Types.java @@ -1,4 +1,4 @@ -package org.springframework.feign.invoke.method; +package com.cowave.commons.client.http.request.meta; import java.lang.reflect.*; import java.util.Arrays; @@ -193,14 +193,14 @@ static Type resolve(Type context, Class contextRawType, Type toResolve) { Class original = (Class) toResolve; Type componentType = original.getComponentType(); Type newComponentType = resolve(context, contextRawType, componentType); - return componentType == newComponentType ? original : new Types.GenericArrayTypeImpl( + return componentType == newComponentType ? original : new GenericArrayTypeImpl( newComponentType); } else if (toResolve instanceof GenericArrayType) { GenericArrayType original = (GenericArrayType) toResolve; Type componentType = original.getGenericComponentType(); Type newComponentType = resolve(context, contextRawType, componentType); - return componentType == newComponentType ? original : new Types.GenericArrayTypeImpl( + return componentType == newComponentType ? original : new GenericArrayTypeImpl( newComponentType); } else if (toResolve instanceof ParameterizedType) { @@ -222,7 +222,7 @@ static Type resolve(Type context, Class contextRawType, Type toResolve) { } return changed - ? new Types.ParameterizedTypeImpl(newOwnerType, original.getRawType(), args) + ? new ParameterizedTypeImpl(newOwnerType, original.getRawType(), args) : original; } else if (toResolve instanceof WildcardType) { @@ -233,12 +233,12 @@ static Type resolve(Type context, Class contextRawType, Type toResolve) { if (originalLowerBound.length == 1) { Type lowerBound = resolve(context, contextRawType, originalLowerBound[0]); if (lowerBound != originalLowerBound[0]) { - return new Types.WildcardTypeImpl(new Type[]{Object.class}, new Type[]{lowerBound}); + return new WildcardTypeImpl(new Type[]{Object.class}, new Type[]{lowerBound}); } } else if (originalUpperBound.length == 1) { Type upperBound = resolve(context, contextRawType, originalUpperBound[0]); if (upperBound != originalUpperBound[0]) { - return new Types.WildcardTypeImpl(new Type[]{upperBound}, EMPTY_TYPE_ARRAY); + return new WildcardTypeImpl(new Type[]{upperBound}, EMPTY_TYPE_ARRAY); } } return original; diff --git a/src/main/java/com/cowave/commons/client/http/request/ssl/NoopHostnameVerifier.java b/src/main/java/com/cowave/commons/client/http/request/ssl/NoopHostnameVerifier.java new file mode 100644 index 0000000..171e83d --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/request/ssl/NoopHostnameVerifier.java @@ -0,0 +1,22 @@ +package com.cowave.commons.client.http.request.ssl; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; + +/** + * + * @author shanhuiming + * + */ +public class NoopHostnameVerifier implements HostnameVerifier { + + @Override + public boolean verify(final String s, final SSLSession sslSession) { + return true; + } + + @Override + public final String toString() { + return "NO_OP"; + } +} diff --git a/src/main/java/com/cowave/commons/client/http/request/ssl/NoopTlsSocketFactory.java b/src/main/java/com/cowave/commons/client/http/request/ssl/NoopTlsSocketFactory.java new file mode 100644 index 0000000..b29d860 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/request/ssl/NoopTlsSocketFactory.java @@ -0,0 +1,121 @@ +package com.cowave.commons.client.http.request.ssl; + +import javax.net.ssl.*; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; + +/** + * + * @author shanhuiming + * + */ +public class NoopTlsSocketFactory extends SSLSocketFactory implements X509TrustManager, X509KeyManager { + + private final SSLSocketFactory sslSocket; + + public NoopTlsSocketFactory() throws Exception { + // 忽略证书 + TrustManager[] trustAllCertificates = new TrustManager[]{ + new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { + // 接受所有证书 + return null; + } + public void checkClientTrusted(X509Certificate[] certs, String authType) { + // 忽略客户端证书校验 + } + public void checkServerTrusted(X509Certificate[] certs, String authType) { + // 忽略服务器证书校验 + } + } + }; + SSLContext sslcontext = SSLContext.getInstance("TLS"); + sslcontext.init(null, trustAllCertificates, new SecureRandom()); + sslSocket = sslcontext.getSocketFactory(); + } + + @Override + public String[] getDefaultCipherSuites() { + return null; + } + + @Override + public String[] getSupportedCipherSuites() { + return null; + } + + @Override + public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { + return sslSocket.createSocket(s, host, port, autoClose); + } + + @Override + public Socket createSocket(String host, int port) throws IOException { + return sslSocket.createSocket(host, port); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + return sslSocket.createSocket(host, port); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { + return sslSocket.createSocket(host, port, localHost, localPort); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { + return sslSocket.createSocket(address, port, localAddress, localPort); + } + + @Override + public String[] getClientAliases(String keyType, Principal[] issuers) { + return null; + } + + @Override + public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) { + return null; + } + + @Override + public String[] getServerAliases(String keyType, Principal[] issuers) { + return null; + } + + @Override + public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { + return null; + } + + @Override + public X509Certificate[] getCertificateChain(String alias) { + return null; + } + + @Override + public PrivateKey getPrivateKey(String alias) { + return null; + } + + @Override + public void checkClientTrusted(X509Certificate[] paramArrayOfX509Certificate, String paramString) { + + } + + @Override + public void checkServerTrusted(X509Certificate[] paramArrayOfX509Certificate, String paramString) { + + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } +} diff --git a/src/main/java/com/cowave/commons/client/http/response/Action.java b/src/main/java/com/cowave/commons/client/http/response/Action.java new file mode 100644 index 0000000..7642198 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/response/Action.java @@ -0,0 +1,12 @@ +package com.cowave.commons.client.http.response; + +/** + * + * @author shanhuiming + * + */ +@FunctionalInterface +public interface Action { + + void exec() throws Exception; +} diff --git a/src/main/java/com/cowave/commons/client/http/response/HttpResponse.java b/src/main/java/com/cowave/commons/client/http/response/HttpResponse.java new file mode 100644 index 0000000..e4a56b7 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/response/HttpResponse.java @@ -0,0 +1,167 @@ +package com.cowave.commons.client.http.response; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Getter; +import lombok.Setter; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.util.MultiValueMap; + +import java.util.Arrays; +import java.util.List; + +import static com.cowave.commons.client.http.HttpCode.SUCCESS; + +/** + * + * @author shanhuiming + * + */ +@Getter +@Setter +public class HttpResponse extends ResponseEntity { + + @JsonIgnore + private String message; + + @JsonIgnore + private Throwable cause; + + @JsonIgnore + private final int status; + + public HttpResponse(){ + super(HttpStatus.OK); + this.status = HttpStatus.OK.value(); + } + + public HttpResponse(ResponseCode responseCode){ + super(null, null, responseCode.getStatus()); + this.status = responseCode.getStatus(); + } + + public HttpResponse(ResponseCode responseCode, MultiValueMap headers, T body) { + super(body, headers, responseCode.getStatus()); + this.status = responseCode.getStatus(); + } + + public HttpResponse(ResponseCode responseCode, MultiValueMap headers, T body, String message) { + super(body, headers, responseCode.getStatus()); + this.message = message; + this.status = responseCode.getStatus(); + } + + public HttpResponse(int httpStatus, MultiValueMap headers, T body) { + super(body, headers, httpStatus); + this.status = httpStatus; + } + + public HttpResponse(int httpStatus, MultiValueMap headers, T body, String message) { + super(body, headers, httpStatus); + this.message = message; + this.status = httpStatus; + } + + /** + * 获取Http Header + */ + public String getHeader(String headerName){ + HttpHeaders headers = this.getHeaders(); + List list = headers.get(headerName); + if(list == null || list.isEmpty()){ + return null; + } + return list.get(0); + } + + /** + * 获取Http Header + */ + public List getHeaders(String headerName){ + HttpHeaders headers = this.getHeaders(); + return headers.get(headerName); + } + + /** + * status == 200 + */ + public boolean isSuccess(){ + return getStatusCodeValue() == 200; + } + + /** + * status != 200 + */ + public boolean isFailed(){ + return getStatusCodeValue() != 200; + } + + /** + * status=#{responseCode.status}, body=null + */ + public static HttpResponse header(int status, String key, String... values){ + HttpHeaders headers = new HttpHeaders(); + headers.put(key, Arrays.asList(values)); + return new HttpResponse<>(status, headers, null); + } + + /** + * status=#{responseCode.status}, body=#{responseCode.msg} + */ + public static HttpResponse code(ResponseCode responseCode) { + return new HttpResponse<>(responseCode, null, responseCode.getMsg()); + } + + /** + * status=#{responseCode.status}, body=#{data} + */ + public static HttpResponse body(ResponseCode responseCode, V data) { + return new HttpResponse<>(responseCode, null, data); + } + + /** + * status=200, body=null + */ + public static HttpResponse success(){ + return new HttpResponse<>(SUCCESS, null, null); + } + + /** + * status=200, body=null + */ + public static HttpResponse success(Action action) throws Exception { + if (action != null) { + action.exec(); + } + return success(); + } + + /** + * status=200, body=#{data} + */ + public static HttpResponse success(V data) { + return new HttpResponse<>(SUCCESS, null, data); + } + + @Override + public HttpStatus getStatusCode(){ + for (HttpStatus statusEnum : HttpStatus.values()) { + if (statusEnum.value() == this.status) { + return statusEnum; + } + } + + if(this.status >= HttpStatus.INTERNAL_SERVER_ERROR.value()){ + return HttpStatus.INTERNAL_SERVER_ERROR; + }else if(this.status >= HttpStatus.BAD_REQUEST.value()){ + return HttpStatus.BAD_REQUEST; + }else if(this.status >= HttpStatus.MULTIPLE_CHOICES.value()){ + return HttpStatus.MULTIPLE_CHOICES; + }else if(this.status >= HttpStatus.OK.value()){ + return HttpStatus.OK; + }else{ + return HttpStatus.CONTINUE; + } + } +} diff --git a/src/main/java/com/cowave/commons/client/http/response/HttpResponseTemplate.java b/src/main/java/com/cowave/commons/client/http/response/HttpResponseTemplate.java new file mode 100644 index 0000000..f6d8620 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/response/HttpResponseTemplate.java @@ -0,0 +1,23 @@ +package com.cowave.commons.client.http.response; + +import lombok.Data; +import lombok.RequiredArgsConstructor; + +import java.io.InputStream; +import java.util.Collection; +import java.util.Map; + +/** + * + * @author shanhuiming + * + */ +@Data +@RequiredArgsConstructor +public class HttpResponseTemplate { + private final int status; + private final Map> headers; + private final String reason; + private final InputStream inputStream; + private final Integer length; +} diff --git a/src/main/java/com/cowave/commons/client/http/response/Response.java b/src/main/java/com/cowave/commons/client/http/response/Response.java new file mode 100644 index 0000000..6c43107 --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/response/Response.java @@ -0,0 +1,284 @@ +package com.cowave.commons.client.http.response; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import static com.cowave.commons.client.http.HttpCode.INTERNAL_SERVER_ERROR; +import static com.cowave.commons.client.http.HttpCode.SUCCESS; + +/** + * + * @author shanhuiming + * + */ +@Slf4j +@Data +public class Response { + + /** + * 响应数据 + */ + private T data; + + /** + * 响应码 + */ + private String code; + + /** + * 响应描述 + */ + private String msg; + + /** + * 错误堆栈信息 + */ + private List cause; + + public Response() { + + } + + public Response(String code, String msg, T data) { + this.code = code; + this.msg = msg; + this.data = data; + } + + @Override + public String toString() { + return "{code=" + code + ", msg=" + msg + ", data=" + data + "}"; + } + + /** + * status=200, code=#{responseCode.code}, msg=#{responseCode.msg}, data=null + */ + public static Response code(ResponseCode responseCode) { + return new Response<>(responseCode.getCode(), responseCode.getMsg(), null); + } + + /** + * status=200, code=#{responseCode.code}, msg=#{responseCode.msg}, data=#{data} + */ + public static Response data(ResponseCode responseCode, V data) { + return new Response<>(responseCode.getCode(), responseCode.getMsg(), data); + } + + /** + * status=200, code=#{resp.code}, msg=#{msg}, data=null + */ + public static Response msg(ResponseCode responseCode, String msg) { + return new Response<>(responseCode.getCode(), msg, null); + } + + /** + * status=200, code=200, msg="success", data=null + */ + public static Response success() { + return new Response<>(SUCCESS.getCode(), SUCCESS.getMsg(), null); + } + + /** + * status=200, code=200, msg="success", data=null + */ + public static Response success(Action action) throws Exception { + if (action != null) { + action.exec(); + } + return success(); + } + + /** + * status=200, code=200, msg="success", data=#{data} + */ + public static Response success(V data) { + return new Response<>(SUCCESS.getCode(), SUCCESS.getMsg(), data); + } + + /** + * status=200, code=200, msg=#{msg}, data=#{data} + */ + public static Response success(V data, String msg) { + return new Response<>(SUCCESS.getCode(), msg, data); + } + + /** + * status=200, code=500, msg="Internal Server Error", data=null + */ + public static Response error() { + return new Response<>(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg(), null); + } + + /** + * status=200, code=500, msg=#{msg}, data=null + */ + public static Response error(String msg) { + return new Response<>(INTERNAL_SERVER_ERROR.getCode(), msg, null); + } + + /** + * status=200, code=200, msg="success", data=#{page} + */ + public static Response> page(List list) { + Response> response = new Response<>(SUCCESS.getCode(), SUCCESS.getMsg(), null); + if (list == null) { + list = new ArrayList<>(); + } + + if (list instanceof com.github.pagehelper.Page) { + com.github.pagehelper.Page page = (com.github.pagehelper.Page) list; + response.setData(new Page<>(page, page.getTotal())); + } else { + response.setData(new Page<>(list, list.size())); + } + return response; + } + + /** + * status=200, code=200, msg="success", data=#{page} + */ + public static Response> page(com.baomidou.mybatisplus.extension.plugins.pagination.Page page) { + Response> response = new Response<>(SUCCESS.getCode(), SUCCESS.getMsg(), null); + response.setData(new Page<>(page.getRecords(), page.getTotal())); + return response; + } + + /** + * status=200, code=200, msg="success", data=#{page} + */ + public static Response> page(com.baomidou.mybatisplus.extension.plugins.pagination.Page page, Class clazz) { + Response> response = new Response<>(SUCCESS.getCode(), SUCCESS.getMsg(), null); + response.setData(new Page<>(copyList(page.getRecords(), clazz), page.getTotal())); + return response; + } + + /** + * status=200, code=200, msg="success", data=#{page} + */ + public static Response> page(com.baomidou.mybatisplus.extension.plugins.pagination.Page page, Function mapper) { + Response> response = new Response<>(SUCCESS.getCode(), SUCCESS.getMsg(), null); + response.setData(new Page<>(page.getRecords().stream().map(mapper).collect(Collectors.toList()), page.getTotal())); + return response; + } + + /** + * status=200, code=200, msg="success", data=#{page} + */ + public static Response> page(com.baomidou.mybatisplus.extension.plugins.pagination.Page page, Function mapper, Predicate filter) { + Response> response = new Response<>(SUCCESS.getCode(), SUCCESS.getMsg(), null); + response.setData(new Page<>(page.getRecords().stream().filter(filter).map(mapper).collect(Collectors.toList()), page.getTotal())); + return response; + } + + static E copyBean(T src, Class clazz) { + E target = null; + try { + target = clazz.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + BeanUtils.copyProperties(src, target); + return target; + } + + static List copyList(List srcList, Class clazz) { + if (CollectionUtils.isEmpty(srcList)) { + return Collections.emptyList(); + } + return srcList.stream().map(src -> copyBean(src, clazz)).collect(Collectors.toList()); + } + + @Data + public static class Page { + + /** + * 总数 + */ + private int total; + + /** + * 列表数据 + */ + private Collection list; + + /** + * 页码 + */ + @JsonIgnore + private int page; + + /** + * 每页行数 + */ + @JsonIgnore + private int pageSize; + + /** + * 总页数 + */ + @JsonIgnore + private int totalPage; + + public Page() { + + } + + public Page(Collection list, int total) { + this.list = list; + this.total = total; + } + + public Page(Collection list, long total) { + this.list = list; + this.total = (int) total; + } + + public void setTotal(Number total) { + this.total = total.intValue(); + } + +// public void setTotal(Long total) { +// this.total = total.intValue(); +// } + + public void setTotalPage(int totalPage) { + this.totalPage = totalPage; + } + + public void setTotalPage(long totalPage) { + this.totalPage = (int) totalPage; + } + + public void setPage(int page) { + this.page = page; + } + + public void setPage(long page) { + this.page = (int) page; + } + + public void setPageSize(int pageSize) { + this.pageSize = pageSize; + } + + public void setPageSize(long pageSize) { + this.pageSize = (int) pageSize; + } + + @Override + public String toString() { + return "{total=" + total + ", list=" + list + "}"; + } + } +} diff --git a/src/main/java/com/cowave/commons/client/http/response/ResponseCode.java b/src/main/java/com/cowave/commons/client/http/response/ResponseCode.java new file mode 100644 index 0000000..207fc0b --- /dev/null +++ b/src/main/java/com/cowave/commons/client/http/response/ResponseCode.java @@ -0,0 +1,24 @@ +package com.cowave.commons.client.http.response; + +/** + * + * @author shanhuiming + * + */ +public interface ResponseCode { + + /** + * 状态 + */ + int getStatus(); + + /** + * 响应码 + */ + String getCode(); + + /** + * 响应描述 + */ + String getMsg(); +} diff --git a/src/main/java/org/springframework/feign/FeignExceptionHandler.java b/src/main/java/org/springframework/feign/FeignExceptionHandler.java deleted file mode 100644 index bd11ad0..0000000 --- a/src/main/java/org/springframework/feign/FeignExceptionHandler.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.springframework.feign; - -import com.cowave.commons.response.exception.HttpException; - -/** - * - * @author shanhuiming - * - */ -public interface FeignExceptionHandler { - - void handle(HttpException e); -} diff --git a/src/main/java/org/springframework/feign/FeignFactory.java b/src/main/java/org/springframework/feign/FeignFactory.java deleted file mode 100644 index 5142c27..0000000 --- a/src/main/java/org/springframework/feign/FeignFactory.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.springframework.feign; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.FactoryBean; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.EmbeddedValueResolverAware; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.feign.annotation.FeignClient; -import org.springframework.feign.invoke.FeignBuilder; -import org.springframework.lang.NonNull; -import org.springframework.util.StringValueResolver; - -/** - * - * @author shanhuiming - * - */ -public class FeignFactory implements FactoryBean, EmbeddedValueResolverAware, ApplicationContextAware { - - private Class feignClass; - - private StringValueResolver valueResolver; - - private ApplicationContext applicationContext; - - public FeignFactory() { - } - - public Class getFeignClass() { - return feignClass; - } - - public void setFeignClass(Class feignClass) { - this.feignClass = feignClass; - } - - @Override - public void setEmbeddedValueResolver(@NonNull StringValueResolver valueResolver) { - this.valueResolver = valueResolver; - FeignManager.setStringValueResolver(valueResolver); - } - - @Override - public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - FeignManager.setApplicationContext(applicationContext); - } - - @Override - public T getObject() { - FeignClient feign = AnnotationUtils.getAnnotation(feignClass, FeignClient.class); - assert feign != null; - FeignBuilder builder = FeignManager.builder(feign); - return builder.target(feignClass, feign.url(), feign.name(), applicationContext, valueResolver, feign.level()); - } - - @Override - public Class getObjectType() { - return feignClass; - } -} diff --git a/src/main/java/org/springframework/feign/FeignManager.java b/src/main/java/org/springframework/feign/FeignManager.java deleted file mode 100644 index 24d0856..0000000 --- a/src/main/java/org/springframework/feign/FeignManager.java +++ /dev/null @@ -1,185 +0,0 @@ -package org.springframework.feign; - -import java.lang.reflect.Constructor; -import java.util.Collections; -import java.util.Map; -import java.util.Objects; -import java.util.TimeZone; -import java.util.concurrent.ConcurrentHashMap; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; - -import feign.Client; -import feign.Request; -import feign.RequestInterceptor; -import feign.codec.Encoder; -import feign.jackson.JacksonEncoder; -import org.springframework.context.ApplicationContext; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.feign.annotation.FeignClient; -import org.springframework.feign.codec.EJacksonDecoder; -import org.springframework.feign.codec.EJacksonEncoder; -import org.springframework.feign.codec.FeignDecoder; -import org.springframework.feign.codec.ResponseDecoder; -import org.springframework.feign.invoke.FeignBuilder; -import org.springframework.feign.invoke.FeignInvocationHandlerFactory; -import org.springframework.feign.retryer.DefaultRetryer; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.util.StringValueResolver; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLSocketFactory; - -/** - * - * @author shanhuiming - * - */ -public class FeignManager { - - private static ApplicationContext applicationContext; - - private static StringValueResolver valueResolver; - - private static final Map localFeigns = new ConcurrentHashMap<>(); - - static void setApplicationContext(ApplicationContext applicationContext){ - FeignManager.applicationContext = applicationContext; - } - - static void setStringValueResolver(StringValueResolver valueResolver){ - FeignManager.valueResolver = valueResolver; - } - - public static T get(Class clazz, String url, int connectTimeoutMillis, int readTimeoutMillis) { - FeignClient feign = AnnotationUtils.getAnnotation(clazz, FeignClient.class); - Assert.notNull(feign, clazz + " is not a FeignClient"); - return builder(feign, new Request.Options(connectTimeoutMillis, readTimeoutMillis)) - .target(clazz, url, null, applicationContext, valueResolver, feign.level()); - } - - public static T get(Class clazz, String url) { - FeignClient feign = AnnotationUtils.getAnnotation(clazz, FeignClient.class); - Assert.notNull(feign, clazz + " is not a FeignClient"); - return builder(feign).target(clazz, url, null, applicationContext, valueResolver, feign.level()); - } - - @SuppressWarnings("unchecked") - public static T getCache(Class clazz, String url) { - FeignClient feign = AnnotationUtils.getAnnotation(clazz, FeignClient.class); - Assert.notNull(feign, clazz + " is not a FeignClient"); - - String key = clazz.getName() + url; - T exist = (T) localFeigns.get(key); - if (exist != null) { - return exist; - } - return (T) localFeigns.computeIfAbsent(key, - k -> builder(feign).target(clazz, url, null, applicationContext, valueResolver, feign.level())); - } - - static FeignBuilder builder(FeignClient feign) { - int connectTimeoutMillis = getInt(feign.connectTimeoutMillis(), feign.connectTimeoutMillisStr()); - int readTimeoutMillis = getInt(feign.readTimeoutMillis(), feign.readTimeoutMillisStr()); - return builder(feign, new Request.Options(connectTimeoutMillis, readTimeoutMillis)); - } - - @SuppressWarnings("deprecation") - static FeignBuilder builder(FeignClient feign, Request.Options options) { - long period = getLong(feign.period(), feign.periodStr()); - long maxPeriod = getLong(feign.maxPeriod(), feign.maxPeriodStr()); - int maxAttempts = getInt(feign.maxAttempts(), feign.maxAttemptsStr()); - - FeignBuilder builder = new FeignBuilder().options(options) - .retryer(new DefaultRetryer(period, maxPeriod, maxAttempts)) - .invocationHandlerFactory(new FeignInvocationHandlerFactory()); - - String[] interceptors = applicationContext.getBeanNamesForType(RequestInterceptor.class); - for (String interceptorName : interceptors) { - RequestInterceptor requestInterceptor = applicationContext.getBean(interceptorName, RequestInterceptor.class); - builder.requestInterceptor(requestInterceptor); - } - - String[] handlers = applicationContext.getBeanNamesForType(FeignExceptionHandler.class); - if(handlers.length > 0) { - String handler = handlers[0]; - FeignExceptionHandler exceptionHandler = applicationContext.getBean(handler, FeignExceptionHandler.class); - builder.exceptionHandler(exceptionHandler); - } - - try{ - builder.encoder(encoder(feign)).decoder(decoder(feign)); - // Https设置 - Class sslSocketFactoryClass = feign.sslSocketFactory(); - Class hostnameVerifierClass = feign.hostnameVerifier(); - if (SSLSocketFactory.class.isAssignableFrom(sslSocketFactoryClass)) { - SSLSocketFactory sslSocketFactory; - if (StringUtils.hasText(feign.sslCertPath()) && StringUtils.hasText(feign.sslPasswd())) { - Constructor constructor = sslSocketFactoryClass.getConstructor(String.class, String.class); - sslSocketFactory = (SSLSocketFactory) constructor.newInstance(feign.sslCertPath(), feign.sslPasswd()); - } else { - sslSocketFactory = (SSLSocketFactory) sslSocketFactoryClass.newInstance(); - } - - if (!HostnameVerifier.class.isAssignableFrom(hostnameVerifierClass)) { - builder.client(new Client.Default(sslSocketFactory, null)); - } else { - builder.client(new Client.Default(sslSocketFactory, (HostnameVerifier) hostnameVerifierClass.newInstance())); - } - } - }catch(Exception e){ - throw new RuntimeException(e); - } - return builder; - } - - @SuppressWarnings("deprecation") - static Encoder encoder(FeignClient feign) throws Exception { - if (JacksonEncoder.class.isAssignableFrom(feign.encoder())) { - ObjectMapper encoderMapper = new ObjectMapper(); - encoderMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL) - .registerModules(Collections.emptyList()); - encoderMapper.setTimeZone(TimeZone.getDefault()); - encoderMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) - .configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false); - return new EJacksonEncoder(encoderMapper); - } else { - return (Encoder) feign.encoder().newInstance(); - } - } - - static FeignDecoder decoder(FeignClient feign){ - ObjectMapper decoderMapper = new ObjectMapper(); - decoderMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL) - .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false) - .registerModules(Collections.emptyList()); - decoderMapper.setTimeZone(TimeZone.getDefault()); - - if(ResponseDecoder.class == feign.decoder()){ - return new ResponseDecoder(decoderMapper); - }else{ - return new EJacksonDecoder(decoderMapper); - } - } - - private static int getInt(int defaultValue, String regex) { - if (!StringUtils.hasText(regex)) { - return defaultValue; - } - return Integer.parseInt(Objects.requireNonNull(valueResolver.resolveStringValue(regex))); - } - - private static long getLong(long defaultValue, String regex) { - if (!StringUtils.hasText(regex)) { - return defaultValue; - } - return Long.parseLong(Objects.requireNonNull(valueResolver.resolveStringValue(regex))); - } -} diff --git a/src/main/java/org/springframework/feign/FeignServiceChooser.java b/src/main/java/org/springframework/feign/FeignServiceChooser.java deleted file mode 100644 index 1a27dd4..0000000 --- a/src/main/java/org/springframework/feign/FeignServiceChooser.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.springframework.feign; - -/** - * - * @author shanhuiming - * - */ -public interface FeignServiceChooser { - - String choose(String name); -} diff --git a/src/main/java/org/springframework/feign/NULL.java b/src/main/java/org/springframework/feign/NULL.java deleted file mode 100644 index b0beb75..0000000 --- a/src/main/java/org/springframework/feign/NULL.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.springframework.feign; - -/** - * - * @author shanhuiming - * - */ -public class NULL { - -} diff --git a/src/main/java/org/springframework/feign/annotation/FeignClient.java b/src/main/java/org/springframework/feign/annotation/FeignClient.java deleted file mode 100644 index 3b0cb4a..0000000 --- a/src/main/java/org/springframework/feign/annotation/FeignClient.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.springframework.feign.annotation; - -import org.slf4j.event.Level; -import org.springframework.core.annotation.AliasFor; -import org.springframework.feign.NULL; -import org.springframework.feign.codec.EJacksonDecoder; -import org.springframework.feign.codec.EJacksonEncoder; -import org.springframework.stereotype.Component; - -import java.lang.annotation.*; - -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -/** - * - * @author shanhuiming - * - */ -@Target({TYPE}) -@Retention(RUNTIME) -@Documented -@Component -public @interface FeignClient { - - @AliasFor(attribute = "url") - String value() default ""; - - @AliasFor(attribute = "value") - String url() default ""; - - Level level() default Level.WARN; - - String name() default ""; - - Class encoder() default EJacksonEncoder.class; - - Class decoder() default EJacksonDecoder.class; - - Class sslSocketFactory() default NULL.class; - - Class hostnameVerifier() default NULL.class; - - String sslPasswd() default ""; - - String sslCertPath() default ""; - - int connectTimeoutMillis() default 10000; - - String connectTimeoutMillisStr() default ""; - - int readTimeoutMillis() default 60000; - - String readTimeoutMillisStr() default ""; - - @Deprecated - int maxAttempts() default 1; - - @Deprecated - String maxAttemptsStr() default ""; - - @Deprecated - long period() default 1000; - - @Deprecated - String periodStr() default ""; - - @Deprecated - long maxPeriod() default 1000; - - @Deprecated - String maxPeriodStr() default ""; -} diff --git a/src/main/java/org/springframework/feign/codec/EJacksonDecoder.java b/src/main/java/org/springframework/feign/codec/EJacksonDecoder.java deleted file mode 100644 index c4fd1ce..0000000 --- a/src/main/java/org/springframework/feign/codec/EJacksonDecoder.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.springframework.feign.codec; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.Module; -import com.fasterxml.jackson.databind.ObjectMapper; -import feign.Response; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.slf4j.event.Level; - -import java.io.BufferedReader; -import java.io.Reader; -import java.lang.reflect.Type; -import java.util.Collections; -import java.util.Objects; - -import static com.cowave.commons.response.HttpResponseCode.SUCCESS; -import static org.slf4j.event.Level.WARN; - -/** - * - * @author shanhuiming - * - */ -public class EJacksonDecoder implements FeignDecoder { - private static final Logger LOGGER = LoggerFactory.getLogger(EJacksonDecoder.class); - private final ObjectMapper mapper; - - public EJacksonDecoder() { - this(Collections.emptyList()); - } - - public EJacksonDecoder(Iterable modules) { - this(new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .registerModules(modules)); - } - - public EJacksonDecoder(ObjectMapper mapper) { - this.mapper = mapper; - } - - @Override - public Object decode(Response response, Type type, String url, long cost, int status, Level level) throws Exception { - Reader reader = response.body().asReader(); - if (!reader.markSupported()) { - reader = new BufferedReader(reader, 1); - } - - reader.mark(1); - if (reader.read() == -1) { - return null; - } - reader.reset(); - - if (void.class == type) { - if(LOGGER.isDebugEnabled() || level.toInt() < WARN.toInt()){ - LOGGER.info(">< {} {}ms {}", status, cost, url); - } - return null; - } - - Object obj = mapper.readValue(reader, mapper.constructType(type)); - if(obj != null){ - if(com.cowave.commons.response.Response.class.isAssignableFrom(obj.getClass())){ - com.cowave.commons.response.Response resp = (com.cowave.commons.response.Response)obj; - if(!Objects.equals(SUCCESS.getCode(), resp.getCode())){ - LOGGER.error(">< {} {}ms {} {code={}, msg={}}", status, cost, url, resp.getCode(), resp.getMsg()); - }else if(LOGGER.isDebugEnabled() || level.toInt() < WARN.toInt()){ - LOGGER.info(">< {} {}ms {} {code={}, msg={}}", status, cost, url, resp.getCode(), resp.getMsg()); - } - }else if(LOGGER.isDebugEnabled() || level.toInt() < WARN.toInt()){ - LOGGER.info(">< {} {}ms {}", status, cost, url); - } - }else if(LOGGER.isDebugEnabled() || level.toInt() < WARN.toInt()){ - LOGGER.info(">< {} {}ms {}", status, cost, url); - } - return obj; - } -} diff --git a/src/main/java/org/springframework/feign/codec/EJacksonEncoder.java b/src/main/java/org/springframework/feign/codec/EJacksonEncoder.java deleted file mode 100644 index 0b5647f..0000000 --- a/src/main/java/org/springframework/feign/codec/EJacksonEncoder.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.springframework.feign.codec; - -import java.lang.reflect.Type; -import java.util.Collections; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.Module; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; - -import feign.RequestTemplate; -import feign.codec.EncodeException; -import feign.jackson.JacksonEncoder; - -/** - * - * @author shanhuiming - * - */ -public class EJacksonEncoder extends JacksonEncoder { - - private final ObjectMapper mapper; - - public EJacksonEncoder() { - this(Collections.emptyList()); - } - - public EJacksonEncoder(Iterable modules) { - this(new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL).configure(SerializationFeature.INDENT_OUTPUT, true) - .registerModules(modules)); - } - - public EJacksonEncoder(ObjectMapper mapper) { - this.mapper = mapper; - } - - @Override - public void encode(Object object, Type bodyType, RequestTemplate template) { - try { - //JavaType javaType = mapper.getTypeFactory().constructType(bodyType); - //template.body(mapper.writerFor(javaType).writeValueAsString(object)); - template.body(mapper.writeValueAsString(object)); - } catch (JsonProcessingException e) { - throw new EncodeException(e.getMessage(), e); - } - } -} diff --git a/src/main/java/org/springframework/feign/codec/FeignDecoder.java b/src/main/java/org/springframework/feign/codec/FeignDecoder.java deleted file mode 100644 index 57a5ee7..0000000 --- a/src/main/java/org/springframework/feign/codec/FeignDecoder.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.springframework.feign.codec; - -import feign.Response; -import feign.Util; -import lombok.extern.slf4j.Slf4j; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.slf4j.event.Level; -import org.springframework.feign.invoke.FeignSyncInvoker; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.Type; - -import static org.slf4j.event.Level.WARN; - -/** - * - * @author shanhuiming - * - */ -public interface FeignDecoder { - - Object decode(Response response, Type type, String url, long cost, int httpCode, Level level) throws Exception; - - class StringDecoder implements FeignDecoder { - - private static final Logger LOGGER = LoggerFactory.getLogger(StringDecoder.class); - - @Override - public Object decode(Response response, Type type, String url, long cost, int status, Level level) throws IOException { - if(LOGGER.isDebugEnabled() || level.toInt() < WARN.toInt()){ - LOGGER.info(">< {} {}ms {}", status, cost, url); - } - - if (byte[].class.equals(type)) { - return Util.toByteArray(response.body().asInputStream()); - } - - Response.Body body = response.body(); - if (String.class.equals(type)) { - return Util.toString(body.asReader()); - } - - throw new UnsupportedEncodingException(); - } - } -} diff --git a/src/main/java/org/springframework/feign/codec/ResponseDecoder.java b/src/main/java/org/springframework/feign/codec/ResponseDecoder.java deleted file mode 100644 index a6ce819..0000000 --- a/src/main/java/org/springframework/feign/codec/ResponseDecoder.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.springframework.feign.codec; - -import com.cowave.commons.response.exception.HttpHintException; -import com.fasterxml.jackson.databind.*; -import com.fasterxml.jackson.databind.Module; -import feign.Response; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.slf4j.event.Level; - -import java.io.BufferedReader; -import java.io.Reader; -import java.lang.reflect.Type; -import java.util.Collections; -import java.util.Objects; - -import static com.cowave.commons.response.HttpResponseCode.SUCCESS; -import static org.slf4j.event.Level.WARN; - -/** - * - * @author shanhuiming - * - */ -public class ResponseDecoder implements FeignDecoder { - private static final Logger LOGGER = LoggerFactory.getLogger(ResponseDecoder.class); - private final ObjectMapper mapper; - - public ResponseDecoder() { - this(Collections.emptyList()); - } - - public ResponseDecoder(Iterable modules) { - this(new ObjectMapper().configure( - DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).registerModules(modules)); - } - - public ResponseDecoder(ObjectMapper mapper) { - this.mapper = mapper; - } - - @Override - public Object decode(Response response, Type type, String url, long cost, int status, Level level) throws Exception { - if (response.body() == null) { - if(LOGGER.isDebugEnabled() || level.toInt() < WARN.toInt()){ - LOGGER.info(">< {} {}ms {}", status, cost, url); - } - return null; - } - - Reader reader = response.body().asReader(); - if (!reader.markSupported()) { - reader = new BufferedReader(reader, 1); - } - - // Read the first byte to see if we have any data - reader.mark(1); - // Eagerly returning null avoids "No content to map due to end-of-input" - if (reader.read() == -1) { - if(LOGGER.isDebugEnabled() || level.toInt() < WARN.toInt()){ - LOGGER.info(">< {} {}ms {}", status, cost, url); - } - return null; - } - reader.reset(); - - com.cowave.commons.response.Response resp = mapper.readValue(reader, com.cowave.commons.response.Response.class); - if(!Objects.equals(SUCCESS.getCode(), resp.getCode())){ - LOGGER.error(">< {} {}ms {} {code={}, msg={}}", status, cost, url, resp.getCode(), resp.getMsg()); - throw new HttpHintException(status, resp.getCode(), resp.getMsg()); - } - - if (void.class == type) { - if(LOGGER.isDebugEnabled() || level.toInt() < WARN.toInt()){ - LOGGER.info(">< {} {}ms {} {code={}, msg={}}", status, cost, url, resp.getCode(), resp.getMsg()); - } - return null; - } - - if(LOGGER.isDebugEnabled() || level.toInt() < WARN.toInt()){ - LOGGER.info(">< {} {}ms {} {code={}, msg={}}", status, cost, url, resp.getCode(), resp.getMsg()); - } - String data = mapper.writeValueAsString(resp.getData()); - return mapper.readValue(data, mapper.constructType(type)); - } -} diff --git a/src/main/java/org/springframework/feign/invoke/FeignBuilder.java b/src/main/java/org/springframework/feign/invoke/FeignBuilder.java deleted file mode 100644 index d03a432..0000000 --- a/src/main/java/org/springframework/feign/invoke/FeignBuilder.java +++ /dev/null @@ -1,94 +0,0 @@ -package org.springframework.feign.invoke; - -import feign.*; -import feign.codec.Encoder; -import org.slf4j.event.Level; -import org.springframework.context.ApplicationContext; -import org.springframework.feign.FeignExceptionHandler; -import org.springframework.feign.codec.FeignDecoder; -import org.springframework.feign.invoke.method.DefaultFeignContract; -import org.springframework.feign.invoke.method.FeignContract; -import org.springframework.feign.retryer.DefaultRetryer; -import org.springframework.util.StringValueResolver; - -import java.util.ArrayList; -import java.util.List; - -/** - * - * @author shanhuiming - * - */ -public class FeignBuilder { - private Client client = new Client.Default(null, null); - private final FeignContract contract = new DefaultFeignContract(); - private org.springframework.feign.retryer.Retryer retryer = new DefaultRetryer(); - private Encoder encoder = new Encoder.Default(); - private FeignDecoder decoder = new FeignDecoder.StringDecoder(); - private Request.Options options = new Request.Options(); - private InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default(); - private final List requestInterceptors = new ArrayList<>(); - private FeignExceptionHandler exceptionHandler; - - public void client(Client client) { - this.client = client; - } - - public FeignBuilder retryer(org.springframework.feign.retryer.Retryer retryer) { - this.retryer = retryer; - return this; - } - - public FeignBuilder encoder(Encoder encoder) { - this.encoder = encoder; - return this; - } - - public FeignBuilder decoder(FeignDecoder decoder) { - this.decoder = decoder; - return this; - } - - public FeignBuilder options(Request.Options options) { - this.options = options; - return this; - } - - /** - * Adds a single request interceptor to the builder. - */ - public FeignBuilder requestInterceptor(RequestInterceptor requestInterceptor) { - this.requestInterceptors.add(requestInterceptor); - return this; - } - - public FeignBuilder exceptionHandler(FeignExceptionHandler exceptionHandler) { - this.exceptionHandler = exceptionHandler; - return this; - } - - /** - * Allows you to override how reflective dispatch works inside of Feign. - */ - public FeignBuilder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) { - this.invocationHandlerFactory = invocationHandlerFactory; - return this; - } - - public T target(Class apiType, String url, String name, - ApplicationContext applicationContext, StringValueResolver valueResolver, Level level) { - return target(new FeignTarget<>(apiType, name, url, applicationContext, valueResolver), level); - } - - public T target(Target target, Level level) { - return build(level).newInstance(target); - } - - public Feign build(Level level) { - FeignMethodHandlerFactory methodHandlerFactory = - new FeignMethodHandlerFactory(client, retryer, requestInterceptors, exceptionHandler); - FeignParseHandlersByName parseHandlersByName = - new FeignParseHandlersByName(contract, options, encoder, decoder, methodHandlerFactory, level); - return new FeignImplement(parseHandlersByName, invocationHandlerFactory); - } -} diff --git a/src/main/java/org/springframework/feign/invoke/FeignImplement.java b/src/main/java/org/springframework/feign/invoke/FeignImplement.java deleted file mode 100644 index f185c45..0000000 --- a/src/main/java/org/springframework/feign/invoke/FeignImplement.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.springframework.feign.invoke; - -import feign.*; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -/** - * - * @author shanhuiming - * - */ -public class FeignImplement extends Feign { - private final FeignParseHandlersByName targetToHandlersByName; - private final InvocationHandlerFactory factory; - - FeignImplement(FeignParseHandlersByName targetToHandlersByName, InvocationHandlerFactory factory) { - this.targetToHandlersByName = targetToHandlersByName; - this.factory = factory; - } - - /** - * creates an api binding to the {@code target}. As this invokes reflection, care should be taken - * to cache the result. - */ - @SuppressWarnings("unchecked") - @Override - public T newInstance(Target target) { - Map nameToHandler = targetToHandlersByName.apply(target); - Map methodToHandler = new LinkedHashMap<>(); - List defaultMethodHandlers = new LinkedList<>(); - - for (Method method : target.type().getMethods()) { - if (method.getDeclaringClass() == Object.class) { - continue; - } else if(Util.isDefault(method)) { - FeignMethodHandler handler = new FeignMethodHandler(method); - defaultMethodHandlers.add(handler); - methodToHandler.put(method, handler); - } else { - methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method))); - } - } - - InvocationHandler handler = factory.create(target, methodToHandler); - T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler); - - for(FeignMethodHandler defaultMethodHandler : defaultMethodHandlers) { - defaultMethodHandler.bindTo(proxy); - } - return proxy; - } -} diff --git a/src/main/java/org/springframework/feign/invoke/FeignInvocationHandlerFactory.java b/src/main/java/org/springframework/feign/invoke/FeignInvocationHandlerFactory.java deleted file mode 100644 index a0a30d5..0000000 --- a/src/main/java/org/springframework/feign/invoke/FeignInvocationHandlerFactory.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.springframework.feign.invoke; - -import feign.InvocationHandlerFactory; -import feign.Target; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.util.Map; - -/** - * - * @author shanhuiming - * - */ -public class FeignInvocationHandlerFactory implements InvocationHandlerFactory { - - @SuppressWarnings("rawtypes") - @Override - public InvocationHandler create(Target target, Map dispatch) { - return new FeignInvocationHandler(target, dispatch); - } -} diff --git a/src/main/java/org/springframework/feign/invoke/FeignMethodHandlerFactory.java b/src/main/java/org/springframework/feign/invoke/FeignMethodHandlerFactory.java deleted file mode 100644 index 8a0161e..0000000 --- a/src/main/java/org/springframework/feign/invoke/FeignMethodHandlerFactory.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.springframework.feign.invoke; - -import feign.*; -import org.slf4j.event.Level; -import org.springframework.feign.FeignExceptionHandler; -import org.springframework.feign.codec.FeignDecoder; -import org.springframework.feign.invoke.method.FeignMethodMetadata; -import org.springframework.feign.invoke.template.FeignRequestFactory; - -import java.util.List; - -import static feign.Util.checkNotNull; - -/** - * - * @author shanhuiming - * - */ -public class FeignMethodHandlerFactory { - private final Client client; - private final org.springframework.feign.retryer.Retryer retryer; - private final List requestInterceptors; - - private final FeignExceptionHandler exceptionHandler; - - FeignMethodHandlerFactory(Client client, org.springframework.feign.retryer.Retryer retryer, - List requestInterceptors, FeignExceptionHandler exceptionHandler) { - this.client = checkNotNull(client, "client"); - this.retryer = checkNotNull(retryer, "retryer"); - this.requestInterceptors = checkNotNull(requestInterceptors, "requestInterceptors"); - this.exceptionHandler = exceptionHandler; - } - - public InvocationHandlerFactory.MethodHandler create(Target target, FeignMethodMetadata md, - FeignRequestFactory buildTemplateFromArgs, Request.Options options, FeignDecoder decoder, Level level) { - return new FeignSyncInvoker(target, - client, retryer, requestInterceptors, md, buildTemplateFromArgs, options, decoder, level, exceptionHandler); - } -} diff --git a/src/main/java/org/springframework/feign/invoke/FeignParseHandlersByName.java b/src/main/java/org/springframework/feign/invoke/FeignParseHandlersByName.java deleted file mode 100644 index 81d1ac8..0000000 --- a/src/main/java/org/springframework/feign/invoke/FeignParseHandlersByName.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.springframework.feign.invoke; - -import static feign.Util.checkNotNull; - -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.slf4j.event.Level; -import org.springframework.feign.codec.FeignDecoder; -import org.springframework.feign.invoke.method.FeignContract; -import org.springframework.feign.invoke.method.FeignMethodMetadata; -import org.springframework.feign.invoke.template.FeignBodyRequestFactory; -import org.springframework.feign.invoke.template.FeignFormRequestFactory; -import org.springframework.feign.invoke.template.FeignMultipartRequestFactory; -import org.springframework.feign.invoke.template.FeignRequestFactory; - -import feign.InvocationHandlerFactory; -import feign.Request; -import feign.Target; -import feign.codec.Encoder; - -/** - * - * @author shanhuiming - * - */ -public class FeignParseHandlersByName { - private final FeignContract contract; - private final Request.Options options; - private final Encoder encoder; - private final FeignDecoder decoder; - private final FeignMethodHandlerFactory factory; - private Level level; - - FeignParseHandlersByName(FeignContract contract, Request.Options options, Encoder encoder, FeignDecoder decoder, - FeignMethodHandlerFactory factory, Level level) { - this.contract = contract; - this.options = options; - this.factory = factory; - this.encoder = checkNotNull(encoder, "encoder"); - this.decoder = checkNotNull(decoder, "decoder"); - this.level = level; - } - - public Map apply(Target key) { - List metaList = contract.parseAndValidateMetadata(key.type()); - Map result = new LinkedHashMap<>(); - for (FeignMethodMetadata meta : metaList) { - FeignRequestFactory feignRequestFactory; - if(meta.multipartFileIndex() != null){ - // multipart/form-data - feignRequestFactory = new FeignMultipartRequestFactory(meta); - } else if (!meta.formParams().isEmpty() && meta.template().bodyTemplate() == null) { - // 存在表单参数,且body为null - feignRequestFactory = new FeignFormRequestFactory(meta, encoder); - } else if (meta.bodyIndex() != null) { - // 存在body - feignRequestFactory = new FeignBodyRequestFactory(meta, encoder); - } else { - feignRequestFactory = new FeignRequestFactory(meta); - } - result.put(meta.configKey(), factory.create(key, meta, feignRequestFactory, options, decoder, level)); - } - return result; - } -} diff --git a/src/main/java/org/springframework/feign/invoke/FeignSyncInvoker.java b/src/main/java/org/springframework/feign/invoke/FeignSyncInvoker.java deleted file mode 100644 index 63fd979..0000000 --- a/src/main/java/org/springframework/feign/invoke/FeignSyncInvoker.java +++ /dev/null @@ -1,306 +0,0 @@ -package org.springframework.feign.invoke; - -import com.cowave.commons.response.HttpResponse; -import com.cowave.commons.response.exception.HttpException; -import com.cowave.commons.response.exception.HttpHintException; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import feign.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.slf4j.event.Level; -import org.springframework.feign.FeignExceptionHandler; -import org.springframework.feign.codec.FeignDecoder; -import org.springframework.feign.invoke.method.FeignMethodMetadata; -import org.springframework.feign.invoke.template.FeignRequestTemplate; -import org.springframework.feign.invoke.template.FeignRequestFactory; -import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseEntity; -import org.springframework.util.StreamUtils; -import org.springframework.util.StringUtils; - -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.TimeUnit; - -import static com.cowave.commons.response.HttpResponseCode.SERVICE_ERROR; -import static feign.Util.checkNotNull; -import static java.lang.String.format; -import static org.slf4j.event.Level.WARN; - -/** - * @author shanhuiming - */ -public class FeignSyncInvoker implements InvocationHandlerFactory.MethodHandler { - private static final Logger LOGGER = LoggerFactory.getLogger(FeignSyncInvoker.class); - private static final long MAX_RESPONSE_BUFFER_SIZE = 8192L; - private static final ObjectMapper RESPONSE_MAPEER = new ObjectMapper(); - - static{ - RESPONSE_MAPEER.setSerializationInclusion(JsonInclude.Include.NON_NULL) - .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false) - .registerModules(Collections.emptyList()); - RESPONSE_MAPEER.setTimeZone(TimeZone.getDefault()); - } - private final Level level; - private final FeignMethodMetadata metadata; - private final Target target; - private final Client client; - private final org.springframework.feign.retryer.Retryer retryer; - private final List requestInterceptors; - private final FeignRequestFactory buildTemplateFromArgs; - private Request.Options options; - private final FeignDecoder decoder; - private final FeignExceptionHandler exceptionHandler; - - public FeignSyncInvoker(Target target, Client client, - org.springframework.feign.retryer.Retryer retryer, - List requestInterceptors, - FeignMethodMetadata metadata, - FeignRequestFactory buildTemplateFromArgs, - Request.Options options, - FeignDecoder decoder, - Level level, - FeignExceptionHandler exceptionHandler) { - this.target = checkNotNull(target, "target"); - this.client = checkNotNull(client, "client for %s", target); - this.retryer = checkNotNull(retryer, "retryer for %s", target); - this.requestInterceptors = checkNotNull(requestInterceptors, "requestInterceptors for %s", target); - this.metadata = checkNotNull(metadata, "metadata for %s", target); - this.buildTemplateFromArgs = checkNotNull(buildTemplateFromArgs, "metadata for %s", target); - this.options = checkNotNull(options, "options for %s", target); - this.decoder = checkNotNull(decoder, "decoder for %s", target); - this.level = level; - this.exceptionHandler = exceptionHandler; - } - - @Override - public Object invoke(Object[] argv) throws Throwable { - FeignRequestTemplate template = buildTemplateFromArgs.create(argv); - org.springframework.feign.retryer.Retryer retryer = this.retryer.clone(); - while (true) { - try { - return executeAndDecode(template); - } catch (RetryableException e) { - retryer.continueOrPropagate(e); - } - } - } - - Object executeAndDecode(FeignRequestTemplate template) throws Throwable { - Type returnType = metadata.returnType(); - Type httpType = getParamTypeOf(returnType, HttpResponse.class); - - Request request = targetRequest(template); - String url = request.url(); - Response response; - long start = System.nanoTime(); - try { - // http调用 - response = client.execute(request, options); - } catch (IOException e){ - long cost = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); - LOGGER.error(">< {}ms {} {}", cost, e.getMessage(), url); - throw new HttpHintException("{frame.remote.failed}"); - } - - long cost = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); - int status = response.status(); - try { - // 1.响应类型: feign.Response - if (Response.class.equals(returnType)) { - return parseFeignResponse(response, status, url, cost); - } - - Type entityType = getParamTypeOf(returnType, ResponseEntity.class); - // 2.响应类型: org.springframework.http.ResponseEntity - if(entityType != null){ - return parseResponseEntity(entityType, response, status, url, cost); - } - - // 3.响应类型: org.springframework.feign.codec.HttpResponse - if(httpType != null){ - return parseHttpResponse(httpType, response, status, url, cost); - } - - // 4.decoder解码 - if (status == 200) { - return decoder.decode(response, metadata.returnType(), url, cost, status, level); - } - - String body = null; - if (response.body() != null) { - body = StreamUtils.copyToString(response.body().asInputStream(), StandardCharsets.UTF_8); - LOGGER.error(">< {} {}ms {} {}", status, cost, url, body); - }else{ - LOGGER.error(">< {} {}ms {}", status, cost, url); - } - throw new HttpHintException(status, SERVICE_ERROR.getCode(), "{frame.remote.failed}"); - } catch(HttpException e) { - if(exceptionHandler != null){ - exceptionHandler.handle(e); - } - throw e; - } catch (IOException e) { - LOGGER.error(">< {}ms {} {}", cost, e.getMessage(), url); - throw new HttpHintException("{frame.remote.failed}"); - } - } - - private HttpResponse parseHttpResponse(Type paramType, Response response, int status, String url, long cost) throws IOException { - // Header信息 - HttpHeaders headers = new HttpHeaders(); - for (Map.Entry> entry : response.headers().entrySet()) { - headers.put(entry.getKey(), entry.getValue().stream().toList()); - } - - // InputStream交给调用者处理 - if(paramType.equals(InputStream.class)){ - if(status == 200){ - if(LOGGER.isDebugEnabled() || level.toInt() < WARN.toInt()){ - LOGGER.info(">< {} {}ms {}", status, cost, url); - } - }else{ - LOGGER.warn(">< {} {}ms {}", status, cost, url); - } - Response.Body body = response.body(); - InputStream inputStream = body != null ? body.asInputStream() : null; - return new HttpResponse<>(response.status(), headers, inputStream); - } - - // 获取响应主体 - String body = null; - if (response.body() != null) { - body = StreamUtils.copyToString(response.body().asInputStream(), StandardCharsets.UTF_8); - } - - if(status == 200){ - if(LOGGER.isDebugEnabled() || level.toInt() < WARN.toInt()){ - LOGGER.info(">< {} {}ms {}", status, cost, url); - } - if(!StringUtils.hasText(body) || paramType.equals(String.class)){ - return new HttpResponse<>(response.status(), headers, body); - }else{ - return new HttpResponse<>(response.status(), headers, readType(body, paramType)); - } - }else if(status > 200 && status < 300){ - LOGGER.warn(">< {} {}ms {} {}", status, cost, url, body); - HttpResponse httpResponse = new HttpResponse<>(response.status(), headers, null); - httpResponse.setMessage(body); - return httpResponse; - }else{ - LOGGER.error(">< {} {}ms {} {}", status, cost, url, body); - HttpResponse httpResponse = new HttpResponse<>(response.status(), headers, null); - httpResponse.setMessage(body); - return httpResponse; - } - } - - private ResponseEntity parseResponseEntity(Type paramType, Response response, int status, String url, long cost) throws IOException { - // Header信息 - HttpHeaders headers = new HttpHeaders(); - for (Map.Entry> entry : response.headers().entrySet()) { - headers.put(entry.getKey(), entry.getValue().stream().toList()); - } - - // 获取响应主体 - String body = null; - if (response.body() != null) { - body = StreamUtils.copyToString(response.body().asInputStream(), StandardCharsets.UTF_8); - } - - if(status >= 200 && status < 300){ - if(LOGGER.isDebugEnabled() || level.toInt() < WARN.toInt()){ - LOGGER.info(">< {} {}ms {}", status, cost, url); - } - if(body == null || paramType.equals(String.class)){ - return new ResponseEntity<>(body, headers, response.status()); - }else{ - return new ResponseEntity<>(readType(body, paramType), headers, response.status()); - } - }else if(body == null){ - LOGGER.error(">< {} {}ms {}", status, cost, url); - }else{ - LOGGER.error(">< {} {}ms {} {}", status, cost, url, body); - } - return new ResponseEntity<>(body, headers, response.status()); - } - - private Response parseFeignResponse(Response response, int status, String url, long cost) throws IOException { - if (response.body() == null) { - logging(status, cost, url, null); - return response; - } - - if (response.body().length() == null || response.body().length() > MAX_RESPONSE_BUFFER_SIZE) { - logging(status, cost, url, null); - return response; // 未关闭流 - } - - String body = StreamUtils.copyToString(response.body().asInputStream(), StandardCharsets.UTF_8); - logging(status, cost, url, body); - return Response.create(status, response.reason(), response.headers(), body, StandardCharsets.UTF_8); - } - - private Type getParamTypeOf(Type type, Class clazz) { - if (type instanceof ParameterizedType parameterizedType) { - Type rawType = parameterizedType.getRawType(); - if (rawType instanceof Class && clazz.equals(rawType)) { - Type[] paramTypes = parameterizedType.getActualTypeArguments(); - if (paramTypes == null || paramTypes.length == 0) { - return Object.class; - } else { - return paramTypes[0]; - } - } - } - return null; - } - - private void logging(int status, long cost, String url, String body){ - if(status >= 200 && status < 300){ - if(LOGGER.isDebugEnabled() || level.toInt() < WARN.toInt()){ - LOGGER.info(">< {} {}ms {}", status, cost, url); - } - }else if(body == null){ - LOGGER.error(">< {} {}ms {}", status, cost, url); - }else{ - LOGGER.error(">< {} {}ms {} {}", status, cost, url, body); - } - } - - Request targetRequest(FeignRequestTemplate feignTemplate) { - for (RequestInterceptor interceptor : requestInterceptors) { - interceptor.apply(feignTemplate.getTemplate()); - } - if(target instanceof FeignTarget feignTarget){ - // 方法Options注解指定的超时优先级更高 - int connectTimeoutMillis = options.connectTimeoutMillis(); - int readTimeoutMillis = options.readTimeoutMillis(); - if(metadata.connectTimeoutMillis() != -1){ - connectTimeoutMillis = metadata.connectTimeoutMillis(); - } - if(metadata.readTimeoutMillis() != -1){ - readTimeoutMillis = metadata.readTimeoutMillis(); - } - this.options = new Request.Options(connectTimeoutMillis, readTimeoutMillis); - // 设置url - return feignTarget.apply(new RequestTemplate(feignTemplate.getTemplate()), feignTemplate.getHostUrl()); - }else{ - return target.apply(new RequestTemplate(feignTemplate.getTemplate())); - } - } - - private static Object readType(String json, Type type) throws IOException { - return RESPONSE_MAPEER.readValue(json, RESPONSE_MAPEER.constructType(type)); - } -} diff --git a/src/main/java/org/springframework/feign/invoke/FeignTarget.java b/src/main/java/org/springframework/feign/invoke/FeignTarget.java deleted file mode 100644 index 5456418..0000000 --- a/src/main/java/org/springframework/feign/invoke/FeignTarget.java +++ /dev/null @@ -1,104 +0,0 @@ -package org.springframework.feign.invoke; - -import com.cowave.commons.response.exception.HttpHintException; -import feign.Request; -import feign.RequestTemplate; -import feign.Target; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.ApplicationContext; -import org.springframework.feign.FeignServiceChooser; -import org.springframework.util.StringUtils; -import org.springframework.util.StringValueResolver; - -/** - * - * @author shanhuiming - * - */ -@Slf4j -public class FeignTarget implements Target { - private final Class type; - private final String name; - private final String url; - private final ApplicationContext applicationContext; - private final StringValueResolver valueResolver; - - public FeignTarget(Class type, String name, String url, - ApplicationContext applicationContext, StringValueResolver valueResolver) { - this.type = type; - this.name = name; - this.url = url; - this.applicationContext = applicationContext; - this.valueResolver = valueResolver; - } - - @Override - public Class type() { - return type; - } - - @Override - public String name() { - return name; - } - - @Override - public String url() { - return url; - } - - public Request apply(RequestTemplate request, String protocolUrl) { - if(StringUtils.hasText(protocolUrl)){ - request.insert(0, protocolUrl); - return request.request(); - }else{ - return apply(request); - } - } - - @Override - public Request apply(RequestTemplate request) { - String prasedName = ""; - if(StringUtils.hasText(name)){ - FeignServiceChooser serviceChooser = applicationContext.getBean(FeignServiceChooser.class); - prasedName = serviceChooser.choose(name); - } - - String prasedUrl = url; - if(StringUtils.hasText(url) && url.contains("${")){ - prasedUrl = valueResolver.resolveStringValue(url); - } - - String parsed = prasedName + prasedUrl; - if (request.url().indexOf("http") != 0) { - if(parsed.indexOf("http") != 0){ - log.error(">< Remote failed due to illegal url, {}", parsed); - throw new HttpHintException("{frame.remote.failed}"); - } - request.insert(0, parsed); - } - return request.request(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof FeignTarget other) { - return type.equals(other.type) && name.equals(other.name) && url.equals(other.url); - } - return false; - } - - @Override - public int hashCode() { - int result = 17; - result = 3 * result + type.hashCode(); - result = 5 * result + name.hashCode(); - result = 7 * result + url.hashCode(); - return result; - } - - @Override - public String toString() { - return "{type=" + type.getSimpleName() + ", name=" + name + ", url=" + url + "}"; - } -} diff --git a/src/main/java/org/springframework/feign/invoke/method/AbstractFeignContract.java b/src/main/java/org/springframework/feign/invoke/method/AbstractFeignContract.java deleted file mode 100644 index 8f9f02e..0000000 --- a/src/main/java/org/springframework/feign/invoke/method/AbstractFeignContract.java +++ /dev/null @@ -1,129 +0,0 @@ -package org.springframework.feign.invoke.method; - -import feign.Feign; -import feign.Util; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.URI; -import java.util.*; - -import static feign.Util.checkState; - -/** - * - * @author shanhuiming - * - */ -public abstract class AbstractFeignContract implements FeignContract { - - @Override - public List parseAndValidateMetadata(Class targetType) { - checkState(targetType.getTypeParameters().length == 0, - "Parameterized types unsupported: %s", targetType.getSimpleName()); - checkState(targetType.getInterfaces().length <= 1, - "Only single inheritance supported: %s", targetType.getSimpleName()); - - if (targetType.getInterfaces().length == 1) { - checkState(targetType.getInterfaces()[0].getInterfaces().length == 0, - "Only single-level inheritance supported: %s", targetType.getSimpleName()); - } - - Map result = new LinkedHashMap<>(); - for (Method method : targetType.getMethods()) { - if (method.getDeclaringClass() == Object.class - || (method.getModifiers() & Modifier.STATIC) != 0 - || Util.isDefault(method)) { - continue; - } - - FeignMethodMetadata metadata = parseAndValidateMetadata(targetType, method); - checkState(!result.containsKey(metadata.configKey()), - "Overrides unsupported: %s", metadata.configKey()); - result.put(metadata.configKey(), metadata); - } - return new ArrayList<>(result.values()); - } - - @Deprecated - public FeignMethodMetadata parseAndValidateMetadata(Method method) { - return parseAndValidateMetadata(method.getDeclaringClass(), method); - } - - protected FeignMethodMetadata parseAndValidateMetadata(Class targetType, Method method) { - FeignMethodMetadata metadata = new FeignMethodMetadata(); - metadata.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType())); - metadata.configKey(Feign.configKey(targetType, method)); - - // class注解 - if(targetType.getInterfaces().length == 1) { - processAnnotationOnClass(metadata, targetType.getInterfaces()[0]); - } - processAnnotationOnClass(metadata, targetType); - - // method注解 - for (Annotation methodAnnotation : method.getAnnotations()) { - processAnnotationOnMethod(metadata, methodAnnotation, method); - } - checkState(metadata.template().method() != null, - "Method %s not annotated with HTTP method type (ex. GET, POST)", method.getName()); - - // parameter注解 - Class[] parameterTypes = method.getParameterTypes(); - Annotation[][] parameterAnnotations = method.getParameterAnnotations(); - for (int i = 0; i < parameterAnnotations.length; i++) { - boolean isHttpAnnotation = false; - if (parameterAnnotations[i] != null) { - isHttpAnnotation = processAnnotationsOnParameter(metadata, parameterAnnotations[i], i); - } - - if (parameterTypes[i] == URI.class) { - metadata.urlIndex(i); - } else if (!isHttpAnnotation) { - checkState(metadata.formParams().isEmpty(), - "Body parameters cannot be used with form parameters."); - checkState(metadata.bodyIndex() == null, - "Method has too many Body parameters: %s", method); - metadata.bodyIndex(i); - metadata.bodyType(Types.resolve(targetType, targetType, method.getGenericParameterTypes()[i])); - } - } - - if (metadata.headerMapIndex() != null) { - checkState(Map.class.isAssignableFrom(parameterTypes[metadata.headerMapIndex()]), - "HeaderMap parameter must be a Map: %s", parameterTypes[metadata.headerMapIndex()]); - } - - if (metadata.queryMapIndex() != null) { - checkState(Map.class.isAssignableFrom(parameterTypes[metadata.queryMapIndex()]), - "QueryMap parameter must be a Map: %s", parameterTypes[metadata.queryMapIndex()]); - } - - return metadata; - } - - protected void processAnnotationOnClass(FeignMethodMetadata data, Class clz) { - - } - - protected abstract void processAnnotationOnMethod(FeignMethodMetadata data, Annotation annotation, Method method); - - protected abstract boolean processAnnotationsOnParameter(FeignMethodMetadata data, Annotation[] annotations, int paramIndex); - - - protected Collection addTemplatedParam(Collection possiblyNull, String name) { - if (possiblyNull == null) { - possiblyNull = new ArrayList<>(); - } - possiblyNull.add(String.format("{%s}", name)); - return possiblyNull; - } - - protected void nameParam(FeignMethodMetadata data, String name, int i) { - Collection names = - data.indexToName().containsKey(i) ? data.indexToName().get(i) : new ArrayList(); - names.add(name); - data.indexToName().put(i, names); - } -} diff --git a/src/main/java/org/springframework/feign/invoke/method/DefaultFeignContract.java b/src/main/java/org/springframework/feign/invoke/method/DefaultFeignContract.java deleted file mode 100644 index fa680b5..0000000 --- a/src/main/java/org/springframework/feign/invoke/method/DefaultFeignContract.java +++ /dev/null @@ -1,172 +0,0 @@ -package org.springframework.feign.invoke.method; - -import feign.*; -import org.springframework.feign.annotation.Host; -import org.springframework.feign.annotation.MultipartFile; -import org.springframework.feign.annotation.MultipartForm; -import org.springframework.feign.annotation.Options; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Map; - -import static feign.Util.checkState; -import static feign.Util.emptyToNull; - -/** - * - * @author shanhuiming - * - */ -public class DefaultFeignContract extends AbstractFeignContract { - - @Override - protected void processAnnotationOnClass(FeignMethodMetadata data, Class targetType) { - if (targetType.isAnnotationPresent(Headers.class)) { - String[] headersOnType = targetType.getAnnotation(Headers.class).value(); - checkState(headersOnType.length > 0, - "Headers annotation was empty on type %s.", targetType.getName()); - - Map> headers = toMap(headersOnType); - headers.putAll(data.template().headers()); - data.template().headers(null); // to clear - data.template().headers(headers); - } - } - - @Override - protected void processAnnotationOnMethod(FeignMethodMetadata metadata, Annotation methodAnnotation, Method method) { - Class annotationType = methodAnnotation.annotationType(); - if (annotationType == RequestLine.class) { - String requestLine = ((RequestLine) methodAnnotation).value(); - checkState(emptyToNull(requestLine) != null, - "RequestLine annotation was empty on method %s.", method.getName()); - - if (requestLine.indexOf(' ') == -1) { - checkState(requestLine.indexOf('/') == -1, - "RequestLine annotation didn't start with an HTTP verb on method %s.", method.getName()); - metadata.template().method(requestLine); - return; - } - - metadata.template().method(requestLine.substring(0, requestLine.indexOf(' '))); - if (requestLine.indexOf(' ') == requestLine.lastIndexOf(' ')) { - // no HTTP version is ok - metadata.template().append(requestLine.substring(requestLine.indexOf(' ') + 1)); - } else { - // skip HTTP version - metadata.template().append(requestLine.substring(requestLine.indexOf(' ') + 1, requestLine.lastIndexOf(' '))); - } - metadata.template().decodeSlash(((RequestLine) methodAnnotation).decodeSlash()); - - } else if (annotationType == Body.class) { - String body = ((Body) methodAnnotation).value(); - checkState(emptyToNull(body) != null, - "Body annotation was empty on method %s.", method.getName()); - - if (body.indexOf('{') == -1) { - metadata.template().body(body); - } else { - metadata.template().bodyTemplate(body); - } - } else if (annotationType == Headers.class) { - String[] headersOnMethod = ((Headers) methodAnnotation).value(); - checkState(headersOnMethod.length > 0, - "Headers annotation was empty on method %s.", method.getName()); - metadata.template().headers(toMap(headersOnMethod)); - } else if (annotationType == Options.class) { - Options options = (Options) methodAnnotation; - metadata.readTimeoutMillis(options.readTimeoutMillis()); - metadata.connectTimeoutMillis(options.connectTimeoutMillis()); - } - } - - @Override - protected boolean processAnnotationsOnParameter(FeignMethodMetadata metadata, Annotation[] annotations, int paramIndex) { - boolean isHttpAnnotation = false; - for (Annotation annotation : annotations) { - Class annotationType = annotation.annotationType(); - if (annotationType == Param.class) { - String name = ((Param) annotation).value(); - checkState(emptyToNull(name) != null, - "Param annotation was empty on param %s.", paramIndex); - - nameParam(metadata, name, paramIndex); - Class expander = ((Param) annotation).expander(); - if (expander != Param.ToStringExpander.class) { - metadata.indexToExpanderClass().put(paramIndex, expander); - } - - isHttpAnnotation = true; - String varName = '{' + name + '}'; - if (!metadata.template().url().contains(varName) - && !searchMapValuesContainsExact(metadata.template().queries(), varName) - && !searchMapValuesContainsSubstring(metadata.template().headers(), varName)) { - metadata.formParams().add(name); - } - } else if (annotationType == QueryMap.class) { - checkState(metadata.queryMapIndex() == null, - "QueryMap annotation was present on multiple parameters."); - metadata.queryMapIndex(paramIndex); - metadata.queryMapEncoded(((QueryMap) annotation).encoded()); - isHttpAnnotation = true; - } else if (annotationType == HeaderMap.class) { - checkState(metadata.headerMapIndex() == null, - "HeaderMap annotation was present on multiple parameters."); - metadata.headerMapIndex(paramIndex); - isHttpAnnotation = true; - } else if(annotationType == Host.class) { - metadata.hostIndex(paramIndex); - isHttpAnnotation = true; - } else if(annotationType == MultipartForm.class) { - metadata.multipartFormIndex(paramIndex); - isHttpAnnotation = true; - } else if(annotationType == MultipartFile.class) { - MultipartFile multipartFile = (MultipartFile) annotation; - metadata.multipartFileIndex(paramIndex); - metadata.multipartFileName(multipartFile.fileName()); - metadata.multipartFileBoundary(multipartFile.boundary()); - isHttpAnnotation = true; - } - } - return isHttpAnnotation; - } - - private static boolean searchMapValuesContainsExact(Map> map, V search) { - Collection> values = map.values(); - for (Collection entry : values) { - if (entry.contains(search)) { - return true; - } - } - return false; - } - - private static boolean searchMapValuesContainsSubstring(Map> map, String search) { - Collection> values = map.values(); - for (Collection entry : values) { - for (String value : entry) { - if (value.contains(search)) { - return true; - } - } - } - return false; - } - - private static Map> toMap(String[] input) { - Map> result = new LinkedHashMap<>(input.length); - for (String header : input) { - int colon = header.indexOf(':'); - String name = header.substring(0, colon); - if (!result.containsKey(name)) { - result.put(name, new ArrayList<>(1)); - } - result.get(name).add(header.substring(colon + 2)); - } - return result; - } -} diff --git a/src/main/java/org/springframework/feign/invoke/method/FeignContract.java b/src/main/java/org/springframework/feign/invoke/method/FeignContract.java deleted file mode 100644 index 1a568f3..0000000 --- a/src/main/java/org/springframework/feign/invoke/method/FeignContract.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.springframework.feign.invoke.method; - -import java.util.*; - -/** - * - * @author shanhuiming - * - */ -public interface FeignContract { - - List parseAndValidateMetadata(Class targetType); -} diff --git a/src/main/java/org/springframework/feign/invoke/method/FeignMethodMetadata.java b/src/main/java/org/springframework/feign/invoke/method/FeignMethodMetadata.java deleted file mode 100644 index 59bf023..0000000 --- a/src/main/java/org/springframework/feign/invoke/method/FeignMethodMetadata.java +++ /dev/null @@ -1,212 +0,0 @@ -package org.springframework.feign.invoke.method; - -import feign.Param; -import feign.RequestTemplate; - -import java.io.Serializable; -import java.lang.reflect.Type; -import java.util.*; - -/** - * - * @author shanhuiming - * - */ -public class FeignMethodMetadata implements Serializable { - private String configKey; - private transient Type returnType; - private Integer urlIndex; - private Integer bodyIndex; - private Integer headerMapIndex; - private Integer queryMapIndex; - private boolean queryMapEncoded; - private transient Type bodyType; - private final RequestTemplate template = new RequestTemplate(); - private final List formParams = new ArrayList<>(); - private final Map> indexToName = new LinkedHashMap<>(); - private final Map> indexToExpanderClass = new LinkedHashMap<>(); - private transient Map indexToExpander; - private int connectTimeoutMillis = -1; - private int readTimeoutMillis = -1; - private Integer hostIndex; - private Integer multipartFileIndex; - private String multipartFileName; - private String multipartFileBoundary; - private Integer multipartFormIndex; - - FeignMethodMetadata() { - } - - public String configKey() { - return configKey; - } - - public FeignMethodMetadata configKey(String configKey) { - this.configKey = configKey; - return this; - } - - public Type returnType() { - return returnType; - } - - public FeignMethodMetadata returnType(Type returnType) { - this.returnType = returnType; - return this; - } - - public int connectTimeoutMillis(){ - return connectTimeoutMillis; - } - - public FeignMethodMetadata connectTimeoutMillis(int connectTimeoutMillis){ - this.connectTimeoutMillis = connectTimeoutMillis; - return this; - } - - public int readTimeoutMillis(){ - return readTimeoutMillis; - } - - public FeignMethodMetadata readTimeoutMillis(int readTimeoutMillis){ - this.readTimeoutMillis = readTimeoutMillis; - return this; - } - - public Integer hostIndex(){ - return hostIndex; - } - - public FeignMethodMetadata hostIndex(Integer hostIndex){ - this.hostIndex = hostIndex; - return this; - } - - public Integer multipartFileIndex(){ - return multipartFileIndex; - } - - public FeignMethodMetadata multipartFileIndex(Integer multipartFileIndex){ - this.multipartFileIndex = multipartFileIndex; - return this; - } - - public String multipartFileName(){ - return multipartFileName; - } - - public FeignMethodMetadata multipartFileName(String multipartFileName){ - this.multipartFileName = multipartFileName; - return this; - } - - public String multipartFileBoundary(){ - return multipartFileBoundary; - } - - public FeignMethodMetadata multipartFileBoundary(String multipartFileBoundary){ - this.multipartFileBoundary = multipartFileBoundary; - return this; - } - - public Integer multipartFormIndex(){ - return multipartFormIndex; - } - - public FeignMethodMetadata multipartFormIndex(Integer multipartFormIndex){ - this.multipartFormIndex = multipartFormIndex; - return this; - } - - public Integer urlIndex() { - return urlIndex; - } - - public FeignMethodMetadata urlIndex(Integer urlIndex) { - this.urlIndex = urlIndex; - return this; - } - - public Integer bodyIndex() { - return bodyIndex; - } - - public FeignMethodMetadata bodyIndex(Integer bodyIndex) { - this.bodyIndex = bodyIndex; - return this; - } - - public Integer headerMapIndex() { - return headerMapIndex; - } - - public FeignMethodMetadata headerMapIndex(Integer headerMapIndex) { - this.headerMapIndex = headerMapIndex; - return this; - } - - public Integer queryMapIndex() { - return queryMapIndex; - } - - public FeignMethodMetadata queryMapIndex(Integer queryMapIndex) { - this.queryMapIndex = queryMapIndex; - return this; - } - - public boolean queryMapEncoded() { - return queryMapEncoded; - } - - public FeignMethodMetadata queryMapEncoded(boolean queryMapEncoded) { - this.queryMapEncoded = queryMapEncoded; - return this; - } - - /** - * Type corresponding to {@link #bodyIndex()}. - */ - public Type bodyType() { - return bodyType; - } - - public FeignMethodMetadata bodyType(Type bodyType) { - this.bodyType = bodyType; - return this; - } - - public RequestTemplate template() { - return template; - } - - public List formParams() { - return formParams; - } - - public Map> indexToName() { - return indexToName; - } - - /** - * If {@link #indexToExpander} is null, classes here will be instantiated by newInstance. - */ - public Map> indexToExpanderClass() { - return indexToExpanderClass; - } - - /** - * After {@link #indexToExpanderClass} is populated, this is set by contracts that support - * runtime injection. - */ - public FeignMethodMetadata indexToExpander(Map indexToExpander) { - this.indexToExpander = indexToExpander; - return this; - } - - /** - * When not null, this value will be used instead of {@link #indexToExpander()}. - */ - public Map indexToExpander() { - return indexToExpander; - } -} diff --git a/src/main/java/org/springframework/feign/invoke/template/FeignBodyRequestFactory.java b/src/main/java/org/springframework/feign/invoke/template/FeignBodyRequestFactory.java deleted file mode 100644 index 1112bef..0000000 --- a/src/main/java/org/springframework/feign/invoke/template/FeignBodyRequestFactory.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.springframework.feign.invoke.template; - -import feign.RequestTemplate; -import feign.codec.EncodeException; -import feign.codec.Encoder; -import org.springframework.feign.invoke.method.FeignMethodMetadata; - -import java.io.IOException; -import java.util.Collection; -import java.util.Map; - -import static feign.Util.checkArgument; - -/** - * - * @author shanhuiming - * - */ -public class FeignBodyRequestFactory extends FeignRequestFactory { - - private final Encoder encoder; - - public FeignBodyRequestFactory(FeignMethodMetadata metadata, Encoder encoder) { - super(metadata); - this.encoder = encoder; - } - - @Override - protected FeignRequestTemplate resolve(Object[] argv, RequestTemplate template, Map variables) throws IOException { - Object body = argv[metadata.bodyIndex()]; - checkArgument(body != null, "Body parameter %s was null", metadata.bodyIndex()); - try { - encoder.encode(body, metadata.bodyType(), template); - } catch (EncodeException e) { - throw e; - } catch (RuntimeException e) { - throw new EncodeException(e.getMessage(), e); - } - - Map> headers = template.headers(); - if(!headers.containsKey("Content-Type")){ - template.header("Content-Type", "application/json"); - } - return super.resolve(argv, template, variables); - } -} diff --git a/src/main/java/org/springframework/feign/invoke/template/FeignFormRequestFactory.java b/src/main/java/org/springframework/feign/invoke/template/FeignFormRequestFactory.java deleted file mode 100644 index a294183..0000000 --- a/src/main/java/org/springframework/feign/invoke/template/FeignFormRequestFactory.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.springframework.feign.invoke.template; - -import feign.RequestTemplate; -import feign.codec.EncodeException; -import feign.codec.Encoder; -import org.springframework.feign.invoke.method.FeignMethodMetadata; - -import java.io.IOException; -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * - * @author shanhuiming - * - */ -public class FeignFormRequestFactory extends FeignRequestFactory { - - private final Encoder encoder; - - public FeignFormRequestFactory(FeignMethodMetadata metadata, Encoder encoder) { - super(metadata); - this.encoder = encoder; - } - - @Override - protected FeignRequestTemplate resolve(Object[] argv, RequestTemplate template, Map variables) throws IOException { - // 填充表单参数 - Map formVariables = new LinkedHashMap<>(); - for (Map.Entry entry : variables.entrySet()) { - if (metadata.formParams().contains(entry.getKey())) { - formVariables.put(entry.getKey(), entry.getValue()); - } - } - // 编码template.body - try { - encoder.encode(formVariables, Encoder.MAP_STRING_WILDCARD, template); - } catch (EncodeException e) { - throw e; - } catch (RuntimeException e) { - throw new EncodeException(e.getMessage(), e); - } - return super.resolve(argv, template, variables); - } -} diff --git a/src/main/java/org/springframework/feign/invoke/template/FeignMultipartRequestFactory.java b/src/main/java/org/springframework/feign/invoke/template/FeignMultipartRequestFactory.java deleted file mode 100644 index 9107b7e..0000000 --- a/src/main/java/org/springframework/feign/invoke/template/FeignMultipartRequestFactory.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.springframework.feign.invoke.template; - -import feign.RequestTemplate; -import org.springframework.feign.invoke.method.FeignMethodMetadata; -import org.springframework.web.multipart.MultipartFile; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.rmi.RemoteException; -import java.util.Map; - -import static feign.Util.checkArgument; - -/** - * - * @author shanhuiming - * - */ -public class FeignMultipartRequestFactory extends FeignRequestFactory { - - public FeignMultipartRequestFactory(FeignMethodMetadata metadata) { - super(metadata); - } - - @Override - protected FeignRequestTemplate resolve(Object[] argv, RequestTemplate template, Map variables) throws IOException { - Object multipartFile = argv[metadata.multipartFileIndex()]; - checkArgument(multipartFile != null, "MultipartFile parameter %s was null", metadata.multipartFileIndex()); - String boundary = metadata.multipartFileBoundary(); - String fileName = metadata.multipartFileName(); - - Map multipartForm = null; - if(metadata.multipartFormIndex() != null){ - Object form = argv[metadata.multipartFormIndex()]; - if(form instanceof Map ){ - multipartForm = (Map)form; - }else{ - throw new RemoteException("multipartForm can only supports type of Map"); - } - } - - byte[] fileBytes; - if(multipartFile instanceof InputStream inputStream){ - try{ - fileBytes = inputStream.readAllBytes(); - }finally { - inputStream.close(); - } - }else if(multipartFile instanceof File file){ - try(FileInputStream inputStream = new FileInputStream(file)){ - fileBytes = inputStream.readAllBytes(); - } - }else if(multipartFile instanceof MultipartFile file){ - fileBytes = file.getBytes(); - }else if(multipartFile instanceof byte[] bytes){ - fileBytes = bytes; - }else{ - throw new RemoteException("multipartFile can only supports type of (InputStream、File、MultipartFile、byte[])"); - } - - try(ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)){ - // file - writer.write("--" + boundary + "\r\n"); - writer.write("Content-Disposition: form-data; name=\"file\"; filename=\"" + fileName + "\"\r\n"); - writer.write("Content-Type: application/octet-stream\r\n\r\n"); - writer.flush(); - outputStream.write(fileBytes); - outputStream.write("\r\n".getBytes()); - - // form - if(multipartForm != null){ - for (Map.Entry entry : multipartForm.entrySet()) { - writer.write("--" + boundary + "\r\n"); - writer.write("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"\r\n\r\n"); - writer.write(entry.getValue() + "\r\n"); - } - } - - writer.write("--" + boundary + "--\r\n"); - writer.flush(); - - template.body(outputStream.toByteArray(), StandardCharsets.UTF_8); - template.header("Content-Type", "multipart/form-data; boundary=" + boundary); - } - return super.resolve(argv, template, variables); - } -} diff --git a/src/main/java/org/springframework/feign/invoke/template/FeignRequestFactory.java b/src/main/java/org/springframework/feign/invoke/template/FeignRequestFactory.java deleted file mode 100644 index adcfe1a..0000000 --- a/src/main/java/org/springframework/feign/invoke/template/FeignRequestFactory.java +++ /dev/null @@ -1,149 +0,0 @@ -package org.springframework.feign.invoke.template; - -import feign.Param; -import feign.RequestTemplate; -import org.springframework.feign.invoke.method.FeignMethodMetadata; - -import java.io.IOException; -import java.util.*; - -import static feign.Util.checkArgument; -import static feign.Util.checkState; - -/** - * - * @author shanhuiming - * - */ -public class FeignRequestFactory { - - protected final FeignMethodMetadata metadata; - private final Map indexToExpander = new LinkedHashMap<>(); - - @SuppressWarnings("deprecation") - public FeignRequestFactory(FeignMethodMetadata metadata) { - this.metadata = metadata; - if (metadata.indexToExpander() != null) { - indexToExpander.putAll(metadata.indexToExpander()); - return; - } - if (metadata.indexToExpanderClass().isEmpty()) { - return; - } - for (Map.Entry> indexToExpanderClass : metadata.indexToExpanderClass().entrySet()) { - try { - indexToExpander.put(indexToExpanderClass.getKey(), indexToExpanderClass.getValue().newInstance()); - } catch (InstantiationException | IllegalAccessException e) { - throw new IllegalStateException(e); - } - } - } - - public FeignRequestTemplate create(Object[] argv) throws IOException { - RequestTemplate template = new RequestTemplate(metadata.template()); - if (metadata.urlIndex() != null) { - int urlIndex = metadata.urlIndex(); - checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex); - template.insert(0, String.valueOf(argv[urlIndex])); - } - - Map varBuilder = new LinkedHashMap<>(); - for (Map.Entry> entry : metadata.indexToName().entrySet()) { - int i = entry.getKey(); - Object value = argv[entry.getKey()]; - if (value != null) { // Null values are skipped. - if (indexToExpander.containsKey(i)) { - value = expandElements(indexToExpander.get(i), value); - } - for (String name : entry.getValue()) { - varBuilder.put(name, value); - } - } - } - - FeignRequestTemplate feignTemplate = resolve(argv, template, varBuilder); - - if (metadata.queryMapIndex() != null) { - // add query map parameters after initial resolve so that they take - // precedence over any predefined values - feignTemplate.setTemplate(addQueryMapQueryParameters(argv, feignTemplate.getTemplate())); - } - - if (metadata.headerMapIndex() != null) { - feignTemplate.setTemplate(addHeaderMapHeaders(argv, feignTemplate.getTemplate())); - } - return feignTemplate; - } - - @SuppressWarnings("rawtypes") - private Object expandElements(Param.Expander expander, Object value) { - if (value instanceof Iterable) { - return expandIterable(expander, (Iterable) value); - } - return expander.expand(value); - } - - @SuppressWarnings("rawtypes") - private List expandIterable(Param.Expander expander, Iterable value) { - List values = new ArrayList<>(); - for (Object element : value) { - if (element!=null) { - values.add(expander.expand(element)); - } - } - return values; - } - - @SuppressWarnings("unchecked") - private RequestTemplate addHeaderMapHeaders(Object[] argv, RequestTemplate template) { - Map headerMap = (Map) argv[metadata.headerMapIndex()]; - for (Map.Entry currEntry : headerMap.entrySet()) { - checkState(currEntry.getKey().getClass() == String.class, "HeaderMap key must be a String: %s", currEntry.getKey()); - Collection values = new ArrayList(); - Object currValue = currEntry.getValue(); - if (currValue instanceof Iterable) { - Iterator iter = ((Iterable) currValue).iterator(); - while (iter.hasNext()) { - Object nextObject = iter.next(); - values.add(nextObject == null ? null : nextObject.toString()); - } - } else { - values.add(currValue == null ? null : currValue.toString()); - } - - template.header((String) currEntry.getKey(), values); - } - return template; - } - - @SuppressWarnings("unchecked") - private RequestTemplate addQueryMapQueryParameters(Object[] argv, RequestTemplate template) { - Map queryMap = (Map) argv[metadata.queryMapIndex()]; - for (Map.Entry currEntry : queryMap.entrySet()) { - checkState(currEntry.getKey().getClass() == String.class, "QueryMap key must be a String: %s", currEntry.getKey()); - Collection values = new ArrayList<>(); - Object currValue = currEntry.getValue(); - if (currValue instanceof Iterable) { - Iterator iter = ((Iterable) currValue).iterator(); - while (iter.hasNext()) { - Object nextObject = iter.next(); - values.add(nextObject == null ? null : nextObject.toString()); - } - } else { - values.add(currValue == null ? null : currValue.toString()); - } - template.query(metadata.queryMapEncoded(), (String) currEntry.getKey(), values); - } - return template; - } - - protected FeignRequestTemplate resolve(Object[] argv, RequestTemplate template, Map variables) throws IOException { - FeignRequestTemplate feignRequestTemplate = new FeignRequestTemplate(); - feignRequestTemplate.setTemplate(template.resolve(variables)); - if(metadata.hostIndex() != null){ - Object url = argv[metadata.hostIndex()]; - feignRequestTemplate.setHostUrl(url.toString()); - } - return feignRequestTemplate; - } -} diff --git a/src/main/java/org/springframework/feign/invoke/template/FeignRequestTemplate.java b/src/main/java/org/springframework/feign/invoke/template/FeignRequestTemplate.java deleted file mode 100644 index 3920b06..0000000 --- a/src/main/java/org/springframework/feign/invoke/template/FeignRequestTemplate.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.springframework.feign.invoke.template; - -import feign.RequestTemplate; -import lombok.Data; - -/** - * 这里包一下RequestTemplate,约定一个根路径参数,不希望放在url中编码 - * - * @author shanhuiming - */ -@Data -public class FeignRequestTemplate { - - private String hostUrl; - - private RequestTemplate template; - -} diff --git a/src/main/java/org/springframework/feign/retryer/DefaultRetryer.java b/src/main/java/org/springframework/feign/retryer/DefaultRetryer.java deleted file mode 100644 index 228d063..0000000 --- a/src/main/java/org/springframework/feign/retryer/DefaultRetryer.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.springframework.feign.retryer; - -import feign.RetryableException; - -import static java.util.concurrent.TimeUnit.SECONDS; - -/** - * @author shanhuiming - */ -public class DefaultRetryer implements Retryer{ - - private final int maxAttempts; - private final long period; - private final long maxPeriod; - int attempt; - long sleptForMillis; - - public DefaultRetryer() { - this(100, SECONDS.toMillis(1), 5); - } - - public DefaultRetryer(long period, long maxPeriod, int maxAttempts) { - this.period = period; - this.maxPeriod = maxPeriod; - this.maxAttempts = maxAttempts; - this.attempt = 1; - } - - // visible for testing; - protected long currentTimeMillis() { - return System.currentTimeMillis(); - } - - public void continueOrPropagate(RetryableException e) { - if (attempt++ >= maxAttempts) { - throw e; - } - - long interval; - if (e.retryAfter() != null) { - interval = e.retryAfter().getTime() - currentTimeMillis(); - if (interval > maxPeriod) { - interval = maxPeriod; - } - if (interval < 0) { - return; - } - } else { - interval = nextMaxInterval(); - } - try { - Thread.sleep(interval); - } catch (InterruptedException ignored) { - Thread.currentThread().interrupt(); - } - sleptForMillis += interval; - } - - /** - * Calculates the time interval to a retry attempt.
The interval increases exponentially - * with each attempt, at a rate of nextInterval *= 1.5 (where 1.5 is the backoff factor), to the - * maximum interval. - * - * @return time in nanoseconds from now until the next attempt. - */ - long nextMaxInterval() { - long interval = (long) (period * Math.pow(1.5, attempt - 1.0)); - return interval > maxPeriod ? maxPeriod : interval; - } - - @Override - public DefaultRetryer clone() { - return new DefaultRetryer(period, maxPeriod, maxAttempts); - } -} diff --git a/src/main/java/org/springframework/feign/retryer/Retryer.java b/src/main/java/org/springframework/feign/retryer/Retryer.java deleted file mode 100644 index 78fddfc..0000000 --- a/src/main/java/org/springframework/feign/retryer/Retryer.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.springframework.feign.retryer; - -import feign.RetryableException; - -/** - * - * @author shanhuiming - * - */ -public interface Retryer extends Cloneable { - - void continueOrPropagate(RetryableException e) throws Throwable; - - Retryer clone(); - - Retryer NEVER_RETRY = new Retryer() { - - @Override - public void continueOrPropagate(RetryableException e) throws Throwable { - if(e.getCause() != null){ - throw e.getCause(); - } - throw e; - } - - @Override - public Retryer clone() { - return this; - } - }; -} diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..adffe38 --- /dev/null +++ b/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.cowave.commons.client.http.HttpClientConfiguration diff --git a/src/test/java/com/cowave/commons/client/http/Application.java b/src/test/java/com/cowave/commons/client/http/Application.java new file mode 100644 index 0000000..9a58612 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/Application.java @@ -0,0 +1,52 @@ +package com.cowave.commons.client.http; + +import lombok.RequiredArgsConstructor; + +import org.apache.catalina.connector.Connector; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.context.annotation.Bean; + +/** + * + * @author shanhuiming + * + */ +@SpringBootApplication +@RequiredArgsConstructor +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + @Bean + public WebServerFactoryCustomizer tomcatCustomizer() { + return factory -> { + Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); + connector.setScheme("http"); + connector.setPort(8080); + connector.setAllowTrace(true); + factory.addAdditionalTomcatConnectors(connector); + }; + } + + @Bean + public HttpServiceChooser httpServiceChooser() { + return name -> { + if ("xxxService".equals(name)) { + return "http://127.0.0.1:8080"; + } else { + return ""; + } + }; + } + + @Bean + public HttpInterceptor httpInterceptor() { + return httpRequest -> httpRequest.header("X-id", "qwert"); + + } +} diff --git a/src/test/java/com/cowave/commons/client/http/MockDeleteTest.java b/src/test/java/com/cowave/commons/client/http/MockDeleteTest.java new file mode 100644 index 0000000..c4c9373 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/MockDeleteTest.java @@ -0,0 +1,60 @@ +package com.cowave.commons.client.http; + +import com.cowave.commons.client.http.delete.DeleteClientController; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * + * @author shanhuiming + * + */ +@ContextConfiguration(classes = Application.class) +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = DEFINED_PORT) +public class MockDeleteTest { + + @Autowired + private DeleteClientController deleteClientController; + + private MockMvc mockMvc; + + @BeforeEach + public void beforeEach() { + mockMvc = MockMvcBuilders.standaloneSetup(deleteClientController).build(); + } + + @Test + public void delete1() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/delete1") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.list").isArray()) + .andExpect(jsonPath("$.list", Matchers.contains(2, 3, 4, 5, 6))); + } + + @Test + public void delete2() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/delete2") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.list").isArray()) + .andExpect(jsonPath("$.list", Matchers.contains(2, 3, 4, 5, 6))); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/MockDownloadTest.java b/src/test/java/com/cowave/commons/client/http/MockDownloadTest.java new file mode 100644 index 0000000..f601033 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/MockDownloadTest.java @@ -0,0 +1,48 @@ +package com.cowave.commons.client.http; + +import com.cowave.commons.client.http.download.DownloadClientController; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * + * @author shanhuiming + * + */ +@ContextConfiguration(classes = Application.class) +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = DEFINED_PORT) +public class MockDownloadTest { + + @Autowired + private DownloadClientController downloadClientController; + + private MockMvc mockMvc; + + @BeforeEach + public void beforeEach() { + mockMvc = MockMvcBuilders.standaloneSetup(downloadClientController).build(); + } + + @Test + public void download1() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/download1") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(content().string("12345678")); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/MockErrorTest.java b/src/test/java/com/cowave/commons/client/http/MockErrorTest.java new file mode 100644 index 0000000..3aa44cf --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/MockErrorTest.java @@ -0,0 +1,47 @@ +package com.cowave.commons.client.http; + +import com.cowave.commons.client.http.error.ErrorClientController; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * + * @author shanhuiming + * + */ +@ContextConfiguration(classes = Application.class) +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = DEFINED_PORT) +public class MockErrorTest { + + @Autowired + private ErrorClientController errorClientController; + + private MockMvc mockMvc; + + @BeforeEach + public void beforeEach() { + mockMvc = MockMvcBuilders.standaloneSetup(errorClientController).build(); + } + + @Test + public void error1() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/error1") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/MockGetTest.java b/src/test/java/com/cowave/commons/client/http/MockGetTest.java new file mode 100644 index 0000000..bbe154f --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/MockGetTest.java @@ -0,0 +1,96 @@ +package com.cowave.commons.client.http; + +import com.cowave.commons.client.http.get.GetClientController; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * + * @author shanhuiming + * + */ +@ContextConfiguration(classes = Application.class) +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = DEFINED_PORT) +public class MockGetTest { + + @Autowired + private GetClientController getClientController; + + private MockMvc mockMvc; + + @BeforeEach + public void beforeEach() { + mockMvc = MockMvcBuilders.standaloneSetup(getClientController).build(); + } + + @Test + public void get1() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/get1") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(content().string("6")); + } + + @Test + public void get2() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/get2") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.name").value("xxx")); + } + + @Test + public void get3() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/get3") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(content().string("6")); + } + + @Test + public void get4() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/get4") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.name").value("xxx")); + } + + @Test + public void get5() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/get5") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(content().string("5")); + } + + @Test + public void get6() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/get6") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.name").value("xxx")); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/MockHeadTest.java b/src/test/java/com/cowave/commons/client/http/MockHeadTest.java new file mode 100644 index 0000000..7d4c7fe --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/MockHeadTest.java @@ -0,0 +1,48 @@ +package com.cowave.commons.client.http; + +import com.cowave.commons.client.http.head.HeadClientController; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * + * @author shanhuiming + * + */ +@ContextConfiguration(classes = Application.class) +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = DEFINED_PORT) +public class MockHeadTest { + + @Autowired + private HeadClientController headClientController; + + private MockMvc mockMvc; + + @BeforeEach + public void beforeEach() { + mockMvc = MockMvcBuilders.standaloneSetup(headClientController).build(); + } + + @Test + public void head1() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/head1") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(content().string("12345")); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/MockHttpsTest.java b/src/test/java/com/cowave/commons/client/http/MockHttpsTest.java new file mode 100644 index 0000000..41fc0f7 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/MockHttpsTest.java @@ -0,0 +1,50 @@ +package com.cowave.commons.client.http; + +import com.cowave.commons.client.http.https.HttpsClientController; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * + * @author shanhuiming + * + */ +@ContextConfiguration(classes = Application.class) +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = DEFINED_PORT) +public class MockHttpsTest { + + @Autowired + private HttpsClientController httpsClientController; + + private MockMvc mockMvc; + + @BeforeEach + public void beforeEach() { + mockMvc = MockMvcBuilders.standaloneSetup(httpsClientController).build(); + } + + @Test + public void https1() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/https1") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.id").value(1)) + .andExpect(jsonPath("$.data.name").value("xxx")); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/MockMultiTest.java b/src/test/java/com/cowave/commons/client/http/MockMultiTest.java new file mode 100644 index 0000000..112fb72 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/MockMultiTest.java @@ -0,0 +1,94 @@ +package com.cowave.commons.client.http; + +import com.cowave.commons.client.http.multipart.MultiClientController; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * + * @author shanhuiming + * + */ +@ContextConfiguration(classes = Application.class) +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = DEFINED_PORT) +public class MockMultiTest { + + @Autowired + private MultiClientController multiClientController; + + private MockMvc mockMvc; + + @BeforeEach + public void beforeEach() { + mockMvc = MockMvcBuilders.standaloneSetup(multiClientController).build(); + } + + @Test + public void multi1() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/multi1") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.id").value(1)) + .andExpect(jsonPath("$.data.name").value("xxx")); + } + + @Test + public void multi2() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/multi2") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.id").value(1)) + .andExpect(jsonPath("$.data.name").value("xxx")) + .andExpect(jsonPath("$.data.fileContent").value("12345678")); + } + + @Test + public void multi3() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/multi3") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.id").value(1)) + .andExpect(jsonPath("$.data.name").value("xxx")) + .andExpect(jsonPath("$.data.fileContent").value("12345678")); + } + + @Test + public void multi4() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/multi4") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.id").value(1)) + .andExpect(jsonPath("$.data.name").value("xxx")) + .andExpect(jsonPath("$.data.fileName").value("testFile")) + .andExpect(jsonPath("$.data.fileContent").value("12345678")); + } + + @Test + public void multi5() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/multi5") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.id").value(1)) + .andExpect(jsonPath("$.data.name").value("xxx")); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/MockOptionsTest.java b/src/test/java/com/cowave/commons/client/http/MockOptionsTest.java new file mode 100644 index 0000000..ef10c52 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/MockOptionsTest.java @@ -0,0 +1,49 @@ +package com.cowave.commons.client.http; + +import com.cowave.commons.client.http.options.OptionsClientController; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * + * @author shanhuiming + * + */ +@ContextConfiguration(classes = Application.class) +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = DEFINED_PORT) +public class MockOptionsTest { + + @Autowired + private OptionsClientController optionsClientController; + + private MockMvc mockMvc; + + @BeforeEach + public void beforeEach() { + mockMvc = MockMvcBuilders.standaloneSetup(optionsClientController).build(); + } + + @Test + public void options() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/options") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(content().string("12345")); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/MockPatchTest.java b/src/test/java/com/cowave/commons/client/http/MockPatchTest.java new file mode 100644 index 0000000..f5e915f --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/MockPatchTest.java @@ -0,0 +1,50 @@ +package com.cowave.commons.client.http; + +import com.cowave.commons.client.http.patch.PatchClientController; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * + * @author shanhuiming + * + */ +@ContextConfiguration(classes = Application.class) +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = DEFINED_PORT) +public class MockPatchTest { + + @Autowired + private PatchClientController patchClientController; + + private MockMvc mockMvc; + + @BeforeEach + public void beforeEach() { + mockMvc = MockMvcBuilders.standaloneSetup(patchClientController).build(); + } + + @Test + public void patch1() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/patch1") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.name").value("xxx")); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/MockPostTest.java b/src/test/java/com/cowave/commons/client/http/MockPostTest.java new file mode 100644 index 0000000..fd37394 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/MockPostTest.java @@ -0,0 +1,70 @@ +package com.cowave.commons.client.http; + +import com.cowave.commons.client.http.post.PostClientController; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * + * @author shanhuiming + * + */ +@ContextConfiguration(classes = Application.class) +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = DEFINED_PORT) +public class MockPostTest { + + @Autowired + private PostClientController postClientController; + + private MockMvc mockMvc; + + @BeforeEach + public void beforeEach() { + mockMvc = MockMvcBuilders.standaloneSetup(postClientController).build(); + } + + @Test + public void post1() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/post1") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.id").value(1)) + .andExpect(jsonPath("$.data.name").value("xxx")); + } + + @Test + public void post2() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/post2") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.name").value("xxx")); + } + + @Test + public void post3() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/post3") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.id").value(1)) + .andExpect(jsonPath("$.data.name").value("xxx")); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/MockPutTest.java b/src/test/java/com/cowave/commons/client/http/MockPutTest.java new file mode 100644 index 0000000..47255bc --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/MockPutTest.java @@ -0,0 +1,50 @@ +package com.cowave.commons.client.http; + +import com.cowave.commons.client.http.put.PutClientController; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * + * @author shanhuiming + * + */ +@ContextConfiguration(classes = Application.class) +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = DEFINED_PORT) +public class MockPutTest { + + @Autowired + private PutClientController putClientController; + + private MockMvc mockMvc; + + @BeforeEach + public void beforeEach() { + mockMvc = MockMvcBuilders.standaloneSetup(putClientController).build(); + } + + @Test + public void put1() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/put1") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(1)) + .andExpect(jsonPath("$.name").value("xxx")); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/MockTraceTest.java b/src/test/java/com/cowave/commons/client/http/MockTraceTest.java new file mode 100644 index 0000000..33e240a --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/MockTraceTest.java @@ -0,0 +1,47 @@ +package com.cowave.commons.client.http; + +import com.cowave.commons.client.http.trace.TraceClientController; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * + * @author shanhuiming + * + */ +@ContextConfiguration(classes = Application.class) +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = DEFINED_PORT) +public class MockTraceTest { + + @Autowired + private TraceClientController traceClientController; + + private MockMvc mockMvc; + + @BeforeEach + public void beforeEach() { + mockMvc = MockMvcBuilders.standaloneSetup(traceClientController).build(); + } + + @Test + public void trace1() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/client/trace1") + .accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/delete/DeleteClientController.java b/src/test/java/com/cowave/commons/client/http/delete/DeleteClientController.java new file mode 100644 index 0000000..56e7e43 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/delete/DeleteClientController.java @@ -0,0 +1,36 @@ +package com.cowave.commons.client.http.delete; + +import com.cowave.commons.client.http.response.Response; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/client") +public class DeleteClientController { + + private final HttpUrlDeleteService httpUrlDeleteService; + + @GetMapping("/delete1") + public Response.Page delete1() { + return httpUrlDeleteService.batchDelete(Arrays.asList(2, 3, 4, 5, 6)); + } + + @GetMapping("/delete2") + public Response.Page delete2() { + Map paramMap = new HashMap<>(); + paramMap.put("ids", Arrays.asList(2, 3, 4, 5, 6)); + return httpUrlDeleteService.batchDelete2(paramMap); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/delete/DeleteServiceController.java b/src/test/java/com/cowave/commons/client/http/delete/DeleteServiceController.java new file mode 100644 index 0000000..a0544fc --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/delete/DeleteServiceController.java @@ -0,0 +1,28 @@ +package com.cowave.commons.client.http.delete; + +import com.cowave.commons.client.http.response.Response; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/service") +public class DeleteServiceController { + + @DeleteMapping("/del/{ids}") + public Response> batchDelete(@PathVariable List ids){ + return Response.page(ids); + } + + @DeleteMapping("/del") + public Response> batchDelete2(@RequestParam("ids") List ids){ + return Response.page(ids); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/delete/HttpUrlDeleteService.java b/src/test/java/com/cowave/commons/client/http/delete/HttpUrlDeleteService.java new file mode 100644 index 0000000..ddf3893 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/delete/HttpUrlDeleteService.java @@ -0,0 +1,23 @@ +package com.cowave.commons.client.http.delete; + +import com.cowave.commons.client.http.annotation.*; +import com.cowave.commons.client.http.invoke.codec.decoder.ResponseDecoder; +import com.cowave.commons.client.http.response.Response; + +import java.util.List; +import java.util.Map; + +/** + * + * @author shanhuiming + * + */ +@HttpClient(url = "http://127.0.0.1:8080", decoder = ResponseDecoder.class) +public interface HttpUrlDeleteService { + + @HttpLine("DELETE /api/v1/service/del/{list}") + Response.Page batchDelete(@HttpParam("list") List list); + + @HttpLine("DELETE /api/v1/service/del") + Response.Page batchDelete2(@HttpParamMap Map paramMap); +} diff --git a/src/test/java/com/cowave/commons/client/http/download/DownloadClientController.java b/src/test/java/com/cowave/commons/client/http/download/DownloadClientController.java new file mode 100644 index 0000000..4a14607 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/download/DownloadClientController.java @@ -0,0 +1,33 @@ +package com.cowave.commons.client.http.download; + +import com.cowave.commons.client.http.patch.HttpUrlPatchService; +import com.cowave.commons.client.http.response.HttpResponse; +import lombok.RequiredArgsConstructor; +import org.apache.commons.io.IOUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/client") +public class DownloadClientController { + + private final HttpUrlDownloadService httpUrlDownloadService; + + @GetMapping("/download1") + public String download1() throws IOException { + HttpResponse httpResponse = httpUrlDownloadService.download(); + return new String(IOUtils.toByteArray(httpResponse.getBody())); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/download/DownloadServiceController.java b/src/test/java/com/cowave/commons/client/http/download/DownloadServiceController.java new file mode 100644 index 0000000..f6e1689 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/download/DownloadServiceController.java @@ -0,0 +1,33 @@ +package com.cowave.commons.client.http.download; + +import lombok.RequiredArgsConstructor; +import org.apache.tomcat.util.http.fileupload.IOUtils; +import org.springframework.core.io.ClassPathResource; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.URLEncoder; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/service") +public class DownloadServiceController { + + @GetMapping("/download") + public void download(HttpServletResponse response) throws IOException { + String filename = URLEncoder.encode("file.txt", "UTF-8").replace("\\+", "%20"); + response.setCharacterEncoding("utf-8"); + response.setContentType("application/octet-stream;charset=UTF-8"); + response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + filename + "\""); + try (InputStream input = new ClassPathResource("file.txt").getInputStream(); + OutputStream outPut = response.getOutputStream();) { + IOUtils.copy(input, outPut); + } + } +} diff --git a/src/test/java/com/cowave/commons/client/http/download/HttpUrlDownloadService.java b/src/test/java/com/cowave/commons/client/http/download/HttpUrlDownloadService.java new file mode 100644 index 0000000..69381dd --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/download/HttpUrlDownloadService.java @@ -0,0 +1,19 @@ +package com.cowave.commons.client.http.download; + +import com.cowave.commons.client.http.annotation.HttpClient; +import com.cowave.commons.client.http.annotation.HttpLine; +import com.cowave.commons.client.http.response.HttpResponse; + +import java.io.InputStream; + +/** + * + * @author shanhuiming + * + */ +@HttpClient(url = "http://127.0.0.1:8080") +public interface HttpUrlDownloadService { + + @HttpLine("GET /api/v1/service/download") + HttpResponse download(); +} diff --git a/src/test/java/com/cowave/commons/client/http/error/ErrorClientController.java b/src/test/java/com/cowave/commons/client/http/error/ErrorClientController.java new file mode 100644 index 0000000..20f909b --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/error/ErrorClientController.java @@ -0,0 +1,24 @@ +package com.cowave.commons.client.http.error; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/client") +public class ErrorClientController { + + private final HttpErrorService httpUrlGetService; + + @GetMapping("/error1") + public String error1() { + return httpUrlGetService.error1("http://127.0.1.1:8080", 5).getMessage(); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/error/ErrorServiceController.java b/src/test/java/com/cowave/commons/client/http/error/ErrorServiceController.java new file mode 100644 index 0000000..84ecf74 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/error/ErrorServiceController.java @@ -0,0 +1,25 @@ +package com.cowave.commons.client.http.error; + +import com.cowave.commons.client.http.asserts.Asserts; +import com.cowave.commons.client.http.get.GetParam; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import static com.cowave.commons.client.http.HttpHeader.X_Request_ID; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/service") +public class ErrorServiceController { + + @GetMapping("/error/{id}") + public Integer getById(@PathVariable Integer id, @RequestHeader(X_Request_ID) String xId){ + Asserts.equals(xId, "12345", ""); + return id; + } +} diff --git a/src/test/java/com/cowave/commons/client/http/error/HttpErrorService.java b/src/test/java/com/cowave/commons/client/http/error/HttpErrorService.java new file mode 100644 index 0000000..ad0a4af --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/error/HttpErrorService.java @@ -0,0 +1,17 @@ +package com.cowave.commons.client.http.error; + +import com.cowave.commons.client.http.annotation.*; +import com.cowave.commons.client.http.response.HttpResponse; + +/** + * + * @author shanhuiming + * + */ +@HttpClient(ignoreError = true) +public interface HttpErrorService { + + @HttpOptions(connectTimeout = 500) + @HttpLine("GET /api/v1/service/get/{id}") + HttpResponse error1(@HttpHost String httpUrl, @HttpParam("id") Integer id); +} diff --git a/src/test/java/com/cowave/commons/client/http/get/GetClientController.java b/src/test/java/com/cowave/commons/client/http/get/GetClientController.java new file mode 100644 index 0000000..691b65a --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/get/GetClientController.java @@ -0,0 +1,74 @@ +package com.cowave.commons.client.http.get; + +import com.cowave.commons.client.http.response.HttpResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +import static com.cowave.commons.client.http.HttpHeader.X_Request_ID; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/client") +public class GetClientController { + + private final HttpUrlGetService httpUrlGetService; + + private final HttpNameGetService httpNameGetService; + + private final HttpHostGetService httpHostGetService; + + @GetMapping("/get1") + public HttpResponse urlGet1() { + return httpUrlGetService.getWithHttpParam(6); + } + + @GetMapping("/get2") + public HttpResponse urlGet2() { + Map headMap = new HashMap<>(); + headMap.put(X_Request_ID, "23456"); + + Map paramMap = new HashMap<>(); + paramMap.put("id", 1); + paramMap.put("name", "xxx"); + return httpUrlGetService.getWithHttpParamMap(headMap, paramMap); + } + + @GetMapping("/get3") + public HttpResponse nameGet1() { + return httpNameGetService.getWithHttpParam(6); + } + + @GetMapping("/get4") + public HttpResponse nameGet2() { + Map headMap = new HashMap<>(); + headMap.put(X_Request_ID, "23456"); + + Map paramMap = new HashMap<>(); + paramMap.put("id", 1); + paramMap.put("name", "xxx"); + return httpNameGetService.getWithHttpParamMap(headMap, paramMap); + } + + @GetMapping("/get5") + public HttpResponse hostGet1() { + return httpHostGetService.getWithHttpParam("http://127.0.0.1:8080", 5); + } + + @GetMapping("/get6") + public HttpResponse hostGet2() { + Map map = new HashMap<>(); + map.put("id", 1); + map.put("name", "xxx"); + return httpHostGetService.getWithHttpParamMap("http://127.0.0.1:8080", "23456", map); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/get/GetParam.java b/src/test/java/com/cowave/commons/client/http/get/GetParam.java new file mode 100644 index 0000000..e1aef92 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/get/GetParam.java @@ -0,0 +1,16 @@ +package com.cowave.commons.client.http.get; + +import lombok.Data; + +/** + * + * @author shanhuiming + * + */ +@Data +public class GetParam { + + private Integer id; + + private String name; +} diff --git a/src/test/java/com/cowave/commons/client/http/get/GetServiceController.java b/src/test/java/com/cowave/commons/client/http/get/GetServiceController.java new file mode 100644 index 0000000..4b0d3c2 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/get/GetServiceController.java @@ -0,0 +1,30 @@ +package com.cowave.commons.client.http.get; + +import com.cowave.commons.client.http.asserts.Asserts; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import static com.cowave.commons.client.http.HttpHeader.X_Request_ID; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/service") +public class GetServiceController { + + @GetMapping("/get/{id}") + public Integer getById(@PathVariable Integer id, @RequestHeader(X_Request_ID) String xId){ + Asserts.equals(xId, "12345", ""); + return id; + } + + @GetMapping("/getByMap") + public GetParam getByMap(GetParam getParam, @RequestHeader(X_Request_ID) String xId){ + Asserts.equals(xId, "23456", ""); + return getParam; + } +} diff --git a/src/test/java/com/cowave/commons/client/http/get/HttpHostGetService.java b/src/test/java/com/cowave/commons/client/http/get/HttpHostGetService.java new file mode 100644 index 0000000..51e06ec --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/get/HttpHostGetService.java @@ -0,0 +1,25 @@ +package com.cowave.commons.client.http.get; + +import com.cowave.commons.client.http.annotation.*; +import com.cowave.commons.client.http.response.HttpResponse; + +import java.util.Map; + +import static com.cowave.commons.client.http.HttpHeader.X_Request_ID; + +/** + * + * @author shanhuiming + * + */ +@HttpClient +public interface HttpHostGetService { + + @HttpHeaders({X_Request_ID + ": 12345"}) + @HttpLine("GET /api/v1/service/get/{id}") + HttpResponse getWithHttpParam(@HttpHost String httpUrl, @HttpParam("id") Integer id); + + @HttpHeaders({X_Request_ID + ": {requestId}"}) + @HttpLine("GET /api/v1/service/getByMap") + HttpResponse getWithHttpParamMap(@HttpHost String httpUrl, @HttpParam("requestId") String requestId, @HttpParamMap Map map); +} diff --git a/src/test/java/com/cowave/commons/client/http/get/HttpNameGetService.java b/src/test/java/com/cowave/commons/client/http/get/HttpNameGetService.java new file mode 100644 index 0000000..9e1c9a5 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/get/HttpNameGetService.java @@ -0,0 +1,24 @@ +package com.cowave.commons.client.http.get; + +import com.cowave.commons.client.http.annotation.*; +import com.cowave.commons.client.http.response.HttpResponse; + +import java.util.Map; + +import static com.cowave.commons.client.http.HttpHeader.X_Request_ID; + +/** + * + * @author shanhuiming + * + */ +@HttpClient(name = "xxxService") +public interface HttpNameGetService { + + @HttpHeaders({X_Request_ID + ": 12345"}) + @HttpLine("GET /api/v1/service/get/{id}") + HttpResponse getWithHttpParam(@HttpParam("id") Integer id); + + @HttpLine("GET /api/v1/service/getByMap") + HttpResponse getWithHttpParamMap(@HttpHeaderMap Map headMap, @HttpParamMap Map paramMap); +} diff --git a/src/test/java/com/cowave/commons/client/http/get/HttpUrlGetService.java b/src/test/java/com/cowave/commons/client/http/get/HttpUrlGetService.java new file mode 100644 index 0000000..ce357bb --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/get/HttpUrlGetService.java @@ -0,0 +1,24 @@ +package com.cowave.commons.client.http.get; + +import com.cowave.commons.client.http.annotation.*; +import com.cowave.commons.client.http.response.HttpResponse; + +import java.util.Map; + +import static com.cowave.commons.client.http.HttpHeader.X_Request_ID; + +/** + * + * @author shanhuiming + * + */ +@HttpClient(url = "http://127.0.0.1:8080") +public interface HttpUrlGetService { + + @HttpHeaders({X_Request_ID + ": 12345"}) + @HttpLine("GET /api/v1/service/get/{id}") + HttpResponse getWithHttpParam(@HttpParam("id") Integer id); + + @HttpLine("GET /api/v1/service/getByMap") + HttpResponse getWithHttpParamMap(@HttpHeaderMap Map headMap, @HttpParamMap Map paramMap); +} diff --git a/src/test/java/com/cowave/commons/client/http/head/HeadClientController.java b/src/test/java/com/cowave/commons/client/http/head/HeadClientController.java new file mode 100644 index 0000000..8486014 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/head/HeadClientController.java @@ -0,0 +1,26 @@ +package com.cowave.commons.client.http.head; + +import com.cowave.commons.client.http.response.HttpResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/client") +public class HeadClientController { + + private final HttpUrlHeadService httpUrlHeadService; + + @GetMapping("/head1") + public String head1() { + HttpResponse httpResponse = httpUrlHeadService.head(); + return httpResponse.getHeader("X-id"); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/head/HeadServiceController.java b/src/test/java/com/cowave/commons/client/http/head/HeadServiceController.java new file mode 100644 index 0000000..7826b15 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/head/HeadServiceController.java @@ -0,0 +1,24 @@ +package com.cowave.commons.client.http.head; + +import com.cowave.commons.client.http.response.HttpResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import static com.cowave.commons.client.http.HttpCode.SUCCESS; +import static com.cowave.commons.client.http.HttpHeader.X_Request_ID; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/service") +public class HeadServiceController { + + @RequestMapping(value = "/head", method = RequestMethod.HEAD) + public HttpResponse head(@RequestHeader(X_Request_ID) String requestId){ + return HttpResponse.header(SUCCESS.getStatus(), "X-id", requestId); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/head/HttpUrlHeadService.java b/src/test/java/com/cowave/commons/client/http/head/HttpUrlHeadService.java new file mode 100644 index 0000000..9f140ab --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/head/HttpUrlHeadService.java @@ -0,0 +1,19 @@ +package com.cowave.commons.client.http.head; + +import com.cowave.commons.client.http.annotation.*; +import com.cowave.commons.client.http.response.HttpResponse; + +import static com.cowave.commons.client.http.HttpHeader.X_Request_ID; + +/** + * + * @author shanhuiming + * + */ +@HttpClient(url = "http://127.0.0.1:8080") +public interface HttpUrlHeadService { + + @HttpHeaders({X_Request_ID + ": 12345"}) + @HttpLine("HEAD /api/v1/service/head") + HttpResponse head(); +} diff --git a/src/test/java/com/cowave/commons/client/http/https/HttpsClientController.java b/src/test/java/com/cowave/commons/client/http/https/HttpsClientController.java new file mode 100644 index 0000000..103c89a --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/https/HttpsClientController.java @@ -0,0 +1,31 @@ +package com.cowave.commons.client.http.https; + +import com.cowave.commons.client.http.response.HttpResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/client") +public class HttpsClientController { + + private final HttpsPostService httpsPostService; + + @GetMapping("/https1") + public HttpResponse https1() { + Map bodyMap = new HashMap<>(); + bodyMap.put("id", 1); + bodyMap.put("name", "xxx"); + return httpsPostService.create(bodyMap); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/https/HttpsPostService.java b/src/test/java/com/cowave/commons/client/http/https/HttpsPostService.java new file mode 100644 index 0000000..fba5810 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/https/HttpsPostService.java @@ -0,0 +1,24 @@ +package com.cowave.commons.client.http.https; + +import com.cowave.commons.client.http.annotation.HttpClient; +import com.cowave.commons.client.http.annotation.HttpHeaders; +import com.cowave.commons.client.http.annotation.HttpLine; +import com.cowave.commons.client.http.response.HttpResponse; + +import java.util.Map; + +import static com.cowave.commons.client.http.HttpHeader.X_Request_ID; + +/** + * + * @author shanhuiming + * + */ +@HttpClient(url = "https://127.0.0.1:8443") +public interface HttpsPostService { + + @HttpHeaders({X_Request_ID + ": 12345"}) + @HttpLine("POST /api/v1/service/create") + HttpResponse create(Map bodyMap); + +} diff --git a/src/test/java/com/cowave/commons/client/http/multipart/HttpUrlMultiService.java b/src/test/java/com/cowave/commons/client/http/multipart/HttpUrlMultiService.java new file mode 100644 index 0000000..d92ff54 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/multipart/HttpUrlMultiService.java @@ -0,0 +1,33 @@ +package com.cowave.commons.client.http.multipart; + +import com.cowave.commons.client.http.annotation.*; +import com.cowave.commons.client.http.response.HttpResponse; + +import java.io.File; +import java.io.InputStream; +import java.util.Map; + +/** + * + * @author shanhuiming + * + */ +@HttpClient(url = "http://127.0.0.1:8080") +public interface HttpUrlMultiService { + + @HttpLine("POST /api/v1/service/submit") + HttpResponse submit(@HttpMultiForm Map multiBody); + + @HttpLine("POST /api/v1/service/submit") + HttpResponse submitParam(@HttpMultiParam("id") Integer id, @HttpMultiParam("name") String name); + + @HttpLine("POST /api/v1/service/submit2") + HttpResponse submit2(@HttpMultiForm Map multiBody, @HttpMultiFile(fileName = "file") File file); + + @HttpOptions(connectTimeout = 3000, readTimeout = 5000, retryTimes = 2, retryInterval = 3) + @HttpLine("POST /api/v1/service/submit2") + HttpResponse submit3(@HttpMultiForm Map multiBody, @HttpMultiFile(fileName = "file")InputStream inputStream); + + @HttpLine("POST /api/v1/service/submit3") + HttpResponse submit4(@HttpMultiForm Map multiBody, @HttpMultiFile(fileName = "testFile") byte[] bytes); +} diff --git a/src/test/java/com/cowave/commons/client/http/multipart/MultiBody.java b/src/test/java/com/cowave/commons/client/http/multipart/MultiBody.java new file mode 100644 index 0000000..270b008 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/multipart/MultiBody.java @@ -0,0 +1,20 @@ +package com.cowave.commons.client.http.multipart; + +import lombok.Data; + +/** + * + * @author shanhuiming + * + */ +@Data +public class MultiBody { + + private Integer id; + + private String name; + + private String fileName; + + private String fileContent; +} diff --git a/src/test/java/com/cowave/commons/client/http/multipart/MultiClientController.java b/src/test/java/com/cowave/commons/client/http/multipart/MultiClientController.java new file mode 100644 index 0000000..189c973 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/multipart/MultiClientController.java @@ -0,0 +1,63 @@ +package com.cowave.commons.client.http.multipart; + +import com.cowave.commons.client.http.response.HttpResponse; +import lombok.RequiredArgsConstructor; +import org.apache.commons.io.IOUtils; +import org.springframework.core.io.ClassPathResource; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/client") +public class MultiClientController { + + private final HttpUrlMultiService httpUrlMultiService; + + @GetMapping("/multi1") + public HttpResponse multi1() { + Map bodyMap = new HashMap<>(); + bodyMap.put("id", 1); + bodyMap.put("name", "xxx"); + return httpUrlMultiService.submit(bodyMap); + } + + @GetMapping("/multi5") + public HttpResponse multi5() { + return httpUrlMultiService.submitParam(1, "xxx"); + } + + @GetMapping("/multi2") + public HttpResponse multi2() throws IOException { + Map bodyMap = new HashMap<>(); + bodyMap.put("id", 1); + bodyMap.put("name", "xxx"); + return httpUrlMultiService.submit2(bodyMap, new ClassPathResource("file.txt").getFile()); + } + + @GetMapping("/multi3") + public HttpResponse multi3() throws IOException { + Map bodyMap = new HashMap<>(); + bodyMap.put("id", 1); + bodyMap.put("name", "xxx"); + return httpUrlMultiService.submit3(bodyMap, new ClassPathResource("file.txt").getInputStream()); + } + + @GetMapping("/multi4") + public HttpResponse multi4() throws IOException { + Map bodyMap = new HashMap<>(); + bodyMap.put("id", 1); + bodyMap.put("name", "xxx"); + return httpUrlMultiService.submit4(bodyMap, IOUtils.toByteArray(new ClassPathResource("file.txt").getInputStream())); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/multipart/MultiServiceController.java b/src/test/java/com/cowave/commons/client/http/multipart/MultiServiceController.java new file mode 100644 index 0000000..be8675c --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/multipart/MultiServiceController.java @@ -0,0 +1,39 @@ +package com.cowave.commons.client.http.multipart; + +import com.cowave.commons.client.http.response.Response; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/service") +public class MultiServiceController { + + @PostMapping("/submit") + public Response submit(MultiBody multiBody){ + return Response.success(multiBody); + } + + @PostMapping("/submit2") + public Response submit2(MultiBody multiBody, MultipartFile file) throws IOException { + multiBody.setFileContent(new String(file.getBytes())); + return Response.success(multiBody); + } + + @PostMapping("/submit3") + public Response submit3(MultiBody multiBody, MultipartFile file) throws IOException { + multiBody.setFileName(file.getOriginalFilename()); + multiBody.setFileContent(new String(file.getBytes())); + return Response.success(multiBody); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/options/HttpUrlOptionsService.java b/src/test/java/com/cowave/commons/client/http/options/HttpUrlOptionsService.java new file mode 100644 index 0000000..4f11872 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/options/HttpUrlOptionsService.java @@ -0,0 +1,21 @@ +package com.cowave.commons.client.http.options; + +import com.cowave.commons.client.http.annotation.HttpClient; +import com.cowave.commons.client.http.annotation.HttpHeaders; +import com.cowave.commons.client.http.annotation.HttpLine; +import com.cowave.commons.client.http.response.HttpResponse; + +import static com.cowave.commons.client.http.HttpHeader.X_Request_ID; + +/** + * + * @author shanhuiming + * + */ +@HttpClient(url = "http://127.0.0.1:8080") +public interface HttpUrlOptionsService { + + @HttpHeaders({X_Request_ID + ": 12345"}) + @HttpLine("OPTIONS /api/v1/service/options") + HttpResponse options(); +} diff --git a/src/test/java/com/cowave/commons/client/http/options/OptionsClientController.java b/src/test/java/com/cowave/commons/client/http/options/OptionsClientController.java new file mode 100644 index 0000000..e34222a --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/options/OptionsClientController.java @@ -0,0 +1,27 @@ +package com.cowave.commons.client.http.options; + +import com.cowave.commons.client.http.head.HttpUrlHeadService; +import com.cowave.commons.client.http.response.HttpResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/client") +public class OptionsClientController { + + private final HttpUrlOptionsService httpUrlOptionsService; + + @GetMapping("/options") + public String options() { + HttpResponse httpResponse = httpUrlOptionsService.options(); + return httpResponse.getHeader("X-id"); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/options/OptionsServiceController.java b/src/test/java/com/cowave/commons/client/http/options/OptionsServiceController.java new file mode 100644 index 0000000..b966560 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/options/OptionsServiceController.java @@ -0,0 +1,27 @@ +package com.cowave.commons.client.http.options; + +import com.cowave.commons.client.http.response.HttpResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import static com.cowave.commons.client.http.HttpCode.SUCCESS; +import static com.cowave.commons.client.http.HttpHeader.X_Request_ID; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/service") +public class OptionsServiceController { + + @RequestMapping(value = "/options", method = RequestMethod.OPTIONS) + public HttpResponse head(@RequestHeader(X_Request_ID) String requestId){ + return HttpResponse.header(SUCCESS.getStatus(), "X-id", requestId); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/patch/HttpUrlPatchService.java b/src/test/java/com/cowave/commons/client/http/patch/HttpUrlPatchService.java new file mode 100644 index 0000000..b4fde90 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/patch/HttpUrlPatchService.java @@ -0,0 +1,21 @@ +package com.cowave.commons.client.http.patch; + +import com.cowave.commons.client.http.annotation.*; +import com.cowave.commons.client.http.response.HttpResponse; + +import java.util.Map; + +import static com.cowave.commons.client.http.HttpHeader.X_Request_ID; + +/** + * + * @author shanhuiming + * + */ +@HttpClient(url = "http://127.0.0.1:8080") +public interface HttpUrlPatchService { + + @HttpHeaders({X_Request_ID + ": 12345"}) + @HttpLine("PATCH /api/v1/service/modify/{id}") + HttpResponse modifyWithHttpParam(@HttpParam("id") Integer id, Map bodyMap); +} diff --git a/src/test/java/com/cowave/commons/client/http/patch/PatchBody.java b/src/test/java/com/cowave/commons/client/http/patch/PatchBody.java new file mode 100644 index 0000000..5d189ac --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/patch/PatchBody.java @@ -0,0 +1,16 @@ +package com.cowave.commons.client.http.patch; + +import lombok.Data; + +/** + * + * @author shanhuiming + * + */ +@Data +public class PatchBody { + + private Integer id; + + private String name; +} diff --git a/src/test/java/com/cowave/commons/client/http/patch/PatchClientController.java b/src/test/java/com/cowave/commons/client/http/patch/PatchClientController.java new file mode 100644 index 0000000..98cec3e --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/patch/PatchClientController.java @@ -0,0 +1,31 @@ +package com.cowave.commons.client.http.patch; + +import com.cowave.commons.client.http.response.HttpResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/client") +public class PatchClientController { + + private final HttpUrlPatchService httpUrlPatchService; + + @GetMapping("/patch1") + public HttpResponse urlPatch1() { + Map bodyMap = new HashMap<>(); + bodyMap.put("id", 1); + bodyMap.put("name", "xxx"); + return httpUrlPatchService.modifyWithHttpParam(5, bodyMap); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/patch/PatchServiceController.java b/src/test/java/com/cowave/commons/client/http/patch/PatchServiceController.java new file mode 100644 index 0000000..82b8da8 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/patch/PatchServiceController.java @@ -0,0 +1,25 @@ +package com.cowave.commons.client.http.patch; + +import com.cowave.commons.client.http.asserts.Asserts; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import static com.cowave.commons.client.http.HttpHeader.X_Request_ID; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/service") +public class PatchServiceController { + + @PatchMapping("/modify/{id}") + public PatchBody modifyById(@RequestHeader(X_Request_ID) String requestId, @PathVariable Integer id, @RequestBody PatchBody patchBody){ + Asserts.equals(requestId, "12345", ""); + Asserts.equals(id, 5, ""); + return patchBody; + } +} diff --git a/src/test/java/com/cowave/commons/client/http/post/HttpUrlPostService.java b/src/test/java/com/cowave/commons/client/http/post/HttpUrlPostService.java new file mode 100644 index 0000000..eb54048 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/post/HttpUrlPostService.java @@ -0,0 +1,28 @@ +package com.cowave.commons.client.http.post; + +import com.cowave.commons.client.http.annotation.HttpClient; +import com.cowave.commons.client.http.annotation.HttpHeaders; +import com.cowave.commons.client.http.annotation.HttpLine; +import com.cowave.commons.client.http.response.HttpResponse; +import com.cowave.commons.client.http.response.Response; + +import java.util.Map; + +import static com.cowave.commons.client.http.HttpHeader.X_Request_ID; + +/** + * + * @author shanhuiming + * + */ +@HttpClient(url = "http://127.0.0.1:8080") +public interface HttpUrlPostService { + + @HttpHeaders({X_Request_ID + ": 12345"}) + @HttpLine("POST /api/v1/service/create") + HttpResponse create(Map bodyMap); + + @HttpHeaders({X_Request_ID + ": 12345"}) + @HttpLine("POST /api/v1/service/create") + Response create2(Map bodyMap); +} diff --git a/src/test/java/com/cowave/commons/client/http/post/PostBody.java b/src/test/java/com/cowave/commons/client/http/post/PostBody.java new file mode 100644 index 0000000..3899ea6 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/post/PostBody.java @@ -0,0 +1,16 @@ +package com.cowave.commons.client.http.post; + +import lombok.Data; + +/** + * + * @author shanhuiming + * + */ +@Data +public class PostBody { + + private Integer id; + + private String name; +} diff --git a/src/test/java/com/cowave/commons/client/http/post/PostClientController.java b/src/test/java/com/cowave/commons/client/http/post/PostClientController.java new file mode 100644 index 0000000..fd74915 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/post/PostClientController.java @@ -0,0 +1,50 @@ +package com.cowave.commons.client.http.post; + +import com.cowave.commons.client.http.response.HttpResponse; +import com.cowave.commons.client.http.response.Response; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/client") +public class PostClientController { + + private final HttpUrlPostService httpUrlPostService; + + private final ResponseDecoderService responseDecoderService; + + @GetMapping("/post1") + public HttpResponse urlPost1() { + Map bodyMap = new HashMap<>(); + bodyMap.put("id", 1); + bodyMap.put("name", "xxx"); + return httpUrlPostService.create(bodyMap); + } + + @GetMapping("/post2") + public PostBody urlPost2() { + Map bodyMap = new HashMap<>(); + bodyMap.put("id", 1); + bodyMap.put("name", "xxx"); + return responseDecoderService.create(bodyMap); + } + + @GetMapping("/post3") + public Response urlPost3() { + Map bodyMap = new HashMap<>(); + bodyMap.put("id", 1); + bodyMap.put("name", "xxx"); + return httpUrlPostService.create2(bodyMap); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/post/PostServiceController.java b/src/test/java/com/cowave/commons/client/http/post/PostServiceController.java new file mode 100644 index 0000000..82bdb16 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/post/PostServiceController.java @@ -0,0 +1,22 @@ +package com.cowave.commons.client.http.post; + +import com.cowave.commons.client.http.patch.PatchBody; +import com.cowave.commons.client.http.response.Response; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/service") +public class PostServiceController { + + @PostMapping("/create") + public Response create(@RequestBody PostBody postBody){ + return Response.success(postBody); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/post/ResponseDecoderService.java b/src/test/java/com/cowave/commons/client/http/post/ResponseDecoderService.java new file mode 100644 index 0000000..2bbdee4 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/post/ResponseDecoderService.java @@ -0,0 +1,24 @@ +package com.cowave.commons.client.http.post; + +import com.cowave.commons.client.http.annotation.HttpClient; +import com.cowave.commons.client.http.annotation.HttpHeaders; +import com.cowave.commons.client.http.annotation.HttpLine; +import com.cowave.commons.client.http.invoke.codec.decoder.ResponseDecoder; +import com.cowave.commons.client.http.response.HttpResponse; + +import java.util.Map; + +import static com.cowave.commons.client.http.HttpHeader.X_Request_ID; + +/** + * + * @author shanhuiming + * + */ +@HttpClient(url = "http://127.0.0.1:8080", decoder = ResponseDecoder.class) +public interface ResponseDecoderService { + + @HttpHeaders({X_Request_ID + ": 12345"}) + @HttpLine("POST /api/v1/service/create") + PostBody create(Map bodyMap); +} diff --git a/src/test/java/com/cowave/commons/client/http/put/HttpUrlPutService.java b/src/test/java/com/cowave/commons/client/http/put/HttpUrlPutService.java new file mode 100644 index 0000000..aee25fe --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/put/HttpUrlPutService.java @@ -0,0 +1,19 @@ +package com.cowave.commons.client.http.put; + +import com.cowave.commons.client.http.annotation.HttpClient; +import com.cowave.commons.client.http.annotation.HttpLine; +import com.cowave.commons.client.http.response.HttpResponse; + +import java.util.Map; + +/** + * + * @author shanhuiming + * + */ +@HttpClient(url = "http://127.0.0.1:8080") +public interface HttpUrlPutService { + + @HttpLine("PUT /api/v1/service/put") + HttpResponse put1(Map bodyMap); +} diff --git a/src/test/java/com/cowave/commons/client/http/put/PutBody.java b/src/test/java/com/cowave/commons/client/http/put/PutBody.java new file mode 100644 index 0000000..83da3ed --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/put/PutBody.java @@ -0,0 +1,17 @@ +package com.cowave.commons.client.http.put; + +import lombok.Data; + +/** + * + * @author shanhuiming + * + */ +@Data +public class PutBody { + + private Integer id; + + private String name; + +} diff --git a/src/test/java/com/cowave/commons/client/http/put/PutClientController.java b/src/test/java/com/cowave/commons/client/http/put/PutClientController.java new file mode 100644 index 0000000..27606f8 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/put/PutClientController.java @@ -0,0 +1,31 @@ +package com.cowave.commons.client.http.put; + +import com.cowave.commons.client.http.response.HttpResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/client") +public class PutClientController { + + private final HttpUrlPutService httpUrlPutService; + + @GetMapping("/put1") + public HttpResponse urlPut1() { + Map bodyMap = new HashMap<>(); + bodyMap.put("id", 1); + bodyMap.put("name", "xxx"); + return httpUrlPutService.put1(bodyMap); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/put/PutServiceController.java b/src/test/java/com/cowave/commons/client/http/put/PutServiceController.java new file mode 100644 index 0000000..1720cc2 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/put/PutServiceController.java @@ -0,0 +1,20 @@ +package com.cowave.commons.client.http.put; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/service") +public class PutServiceController { + + @PutMapping("/put") + public PutBody put1(@RequestBody PutBody putBody){ + return putBody; + } +} diff --git a/src/test/java/com/cowave/commons/client/http/trace/HttpUrlTraceService.java b/src/test/java/com/cowave/commons/client/http/trace/HttpUrlTraceService.java new file mode 100644 index 0000000..2ea101a --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/trace/HttpUrlTraceService.java @@ -0,0 +1,21 @@ +package com.cowave.commons.client.http.trace; + +import com.cowave.commons.client.http.annotation.HttpClient; +import com.cowave.commons.client.http.annotation.HttpHeaders; +import com.cowave.commons.client.http.annotation.HttpLine; +import com.cowave.commons.client.http.response.HttpResponse; + +import static com.cowave.commons.client.http.HttpHeader.X_Request_ID; + +/** + * + * @author shanhuiming + * + */ +@HttpClient(url = "http://127.0.0.1:8080") +public interface HttpUrlTraceService { + + @HttpHeaders({X_Request_ID + ": 12345"}) + @HttpLine("TRACE /api/v1/service/trace") + HttpResponse trace(); +} diff --git a/src/test/java/com/cowave/commons/client/http/trace/TraceClientController.java b/src/test/java/com/cowave/commons/client/http/trace/TraceClientController.java new file mode 100644 index 0000000..3966e71 --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/trace/TraceClientController.java @@ -0,0 +1,26 @@ +package com.cowave.commons.client.http.trace; + +import com.cowave.commons.client.http.response.HttpResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/client") +public class TraceClientController { + + private final HttpUrlTraceService httpUrlTraceService; + + @GetMapping("/trace1") + public String trace1() { + HttpResponse httpResponse = httpUrlTraceService.trace(); + return httpResponse.getBody(); + } +} diff --git a/src/test/java/com/cowave/commons/client/http/trace/TraceServiceController.java b/src/test/java/com/cowave/commons/client/http/trace/TraceServiceController.java new file mode 100644 index 0000000..62d565a --- /dev/null +++ b/src/test/java/com/cowave/commons/client/http/trace/TraceServiceController.java @@ -0,0 +1,27 @@ +package com.cowave.commons.client.http.trace; + +import com.cowave.commons.client.http.response.HttpResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import static com.cowave.commons.client.http.HttpCode.SUCCESS; +import static com.cowave.commons.client.http.HttpHeader.X_Request_ID; + +/** + * + * @author shanhuiming + * + */ +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/service") +public class TraceServiceController { + + @RequestMapping(value = "/trace", method = RequestMethod.TRACE) + public HttpResponse trace(@RequestHeader(X_Request_ID) String requestId){ + return HttpResponse.header(SUCCESS.getStatus(), "X-id", requestId); + } +} diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml new file mode 100644 index 0000000..cd28fc5 --- /dev/null +++ b/src/test/resources/application.yml @@ -0,0 +1,14 @@ +server: + port: 8443 + servlet: + context-path: / + ssl: + enabled: true + protocol: TLS + keyStoreType: JKS + keyAlias: test + key-store-password: 123456 + key-store: classpath:ssl.keystore + +# keytool -genkey -alias test -keyalg RSA -keystore ssl.keystore +# CN=shan, OU=cowave, O=cowave, L=nanjing, ST=jiangsu, C=zh diff --git a/src/test/resources/file.txt b/src/test/resources/file.txt new file mode 100644 index 0000000..e9a9ea1 --- /dev/null +++ b/src/test/resources/file.txt @@ -0,0 +1 @@ +12345678 \ No newline at end of file diff --git a/src/test/resources/ssl.keystore b/src/test/resources/ssl.keystore new file mode 100644 index 0000000..2249a45 Binary files /dev/null and b/src/test/resources/ssl.keystore differ