Skip to content

Commit

Permalink
feat: 함수형 인터페이스를 활용한 http 호출 추상화 (#9)
Browse files Browse the repository at this point in the history
* docs: update README.md

* refactor: 기존 requestProduct 로직 개선

* feat: 함수형 인터페이스를 활용한 http 호출 추상화

* feat: httpClient5 호출부 로직 각 클래스 단위로 분리

* chore: github action 불필요 JDK initialize 로직 삭제
  • Loading branch information
h-beeen authored Nov 14, 2024
1 parent ff7c22c commit 4a8ef47
Show file tree
Hide file tree
Showing 17 changed files with 263 additions and 160 deletions.
10 changes: 2 additions & 8 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
publish:
runs-on: ubuntu-latest
if: |
github.event.workflow_run.conclusion == 'success' &&github.event.workflow_run.head_branch == 'master'
github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'master'
steps:
- name: Checkout code
Expand All @@ -22,12 +22,6 @@ jobs:
fetch-depth: 0
ref: master

- name: Set up JDK
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'

- name: Grant execute permission for gradlew
run: chmod +x gradlew

Expand Down Expand Up @@ -68,4 +62,4 @@ jobs:
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }}
run: |
./gradlew publishAllPublicationsToMavenCentralRepository --stacktrace --debug
./gradlew publishAllPublicationsToMavenCentralRepository
76 changes: 21 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<h1 align="center">EasyCodef Java V2</h1>
<br>
<br>
<p align="center">
<a title="코드에프" href="https://codef.io/">
Expand All @@ -11,71 +11,37 @@

<br>

`easycodef-java-v2`는 codef API를 JDK 환경에서 더욱 더 편리하게 연동할 수 있도록 돕는 오픈소스 라이브러리입니다.

현재 알파 버전 개발중으로 v2.0.0-ALPHA-002 버전으로 Maven Central Repository를 통해 배포중입니다.

2024년 상반기 실제 고객사 대상으로 릴리즈 예정입니다.

<br><br>
<p align="center">
<span><code>easycodef-java-v2</code> - Open-Source library<br>With the <b>CODEF API</b> in a JDK environment</span>
</p>

## Release
<br>

[![Build Status](https://img.shields.io/github/actions/workflow/status/codef-io/easycodef-java-v2/publish.yml?style=for-the-badge&logo=gradle&color=02303A)](https://github.com/codef-io/easycodef-java-v2/actions?query=branch%3Amaster)<br>
[![Last Commit](https://img.shields.io/github/last-commit/codef-io/easycodef-java-v2/master?style=for-the-badge&label=LAST%20BUILD&logo=Github&color=181717)](https://github.com/codef-io/easycodef-java-v2)<br>
[![Maven Central](https://img.shields.io/maven-central/v/io.codef.api/easycodef-java-v2.svg?style=for-the-badge&label=Maven%20Central&logo=apache-maven&color=C71A36)](https://central.sonatype.com/artifact/io.codef.api/easycodef-java-v2)<br>
<p align="center">
<a href="https://github.com/codef-io/easycodef-java-v2/actions?query=branch%3Amaster"><img align="center" src="https://img.shields.io/github/actions/workflow/status/codef-io/easycodef-java-v2/publish.yml?style=for-the-badge&logo=gradle&color=02303A" alt="Build Status"/></a>
<a href="https://github.com/codef-io/easycodef-java-v2"><img align="center" src="https://img.shields.io/github/last-commit/codef-io/easycodef-java-v2/master?style=for-the-badge&label=LAST%20BUILD&logo=Github&color=181717" alt="Last Commit"/></a>
<a href="https://central.sonatype.com/artifact/io.codef.api/easycodef-java-v2"><img align="center" src="https://img.shields.io/maven-central/v/io.codef.api/easycodef-java-v2.svg?style=for-the-badge&label=Maven%20Central&logo=apache-maven&color=C71A36" alt="Maven Central"/></a>
</p>

## Snippets

- Gradle(Kotlin)
```gradle
implementation("io.codef.api:easycodef-java-v2:2.0.0-alpha-003")
```
- Gradle(short)
```gradle
implementation 'io.codef.api:easycodef-java-v2:2.0.0-alpha-003'
```
- Maven
```xml
<dependency>
<groupId>io.codef.api</groupId>
<artifactId>easycodef-java-v2</artifactId>
<version>2.0.0-alpha-003</version>
</dependency>
```
<br><br><br>

## Get It !
### EasyCodef V2 For Java
- **[EasyCodef V2 Wiki](https://github.com/codef-io/easycodef-java-v2/wiki)<br><br>**
- [Codef Homepage](https://codef.io/)
- [Codef API Developer Guide](https://developer.codef.io/)
- [Hectodata Homepage](https://hectodata.co.kr/)
- [Hecto Tech Blog](https://blog.hectodata.co.kr/)

- 예제 코드
```java
EasyCodef easyCodef = EasyCodefBuilder.builder()
.clientType(CodefClientType.DEMO)
.clientId("your-client-id")
.clientSecret("your-client-secret")
.publicKey("your-public-key")
.build();
EasyCodefRequest request = EasyCodefRequestBuilder.builder()
.path("/v1/kr/public/hw/nip-cdc-list/my-vaccination")
.organization("0011")
.requestBody("loginType", "1")
.requestBody("userId", "your-nhis-id")
.secureRequestBody("userPassword", "your-nhis-password")
.secureWith(easyCodef)
.build();
EasyCodefResponse easyCodefResponse = easyCodef.requestProduct(request);
final EasyCodefResponse.Result result = easyCodefResponse.result();
final Object data = easyCodefResponse.data();
```
<br>

---

<br>

<p align="center">
<img alt="헥토데이터" src="https://github.com/user-attachments/assets/ac6b7a7d-33f1-4b1e-9fbb-8231d56e7f33" height="20"><br>
<span>MIT © | <a href="https://github.com/codef-io/easycodef-java-v2/blob/master/LICENSE" target="_blank">LICENSE</a></span>
</p>

<br>
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ dependencies {
* https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j2-impl
*/
implementation("org.apache.httpcomponents.client5:httpclient5:5.4.1")
testImplementation("org.apache.logging.log4j:log4j-slf4j2-impl:2.24.1")
implementation("org.apache.logging.log4j:log4j-slf4j2-impl:2.24.1")

/**
* 2024-10-21 Latest
Expand Down
3 changes: 1 addition & 2 deletions src/main/java/io/codef/api/CodefValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
import java.util.Optional;
import java.util.UUID;

final class CodefValidator {

public class CodefValidator {
private CodefValidator() {
}

Expand Down
16 changes: 7 additions & 9 deletions src/main/java/io/codef/api/EasyCodef.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.codef.api.constants.CodefClientType;
import io.codef.api.dto.EasyCodefRequest;
import io.codef.api.dto.EasyCodefResponse;
import io.codef.api.error.CodefException;
import io.codef.api.util.RsaUtil;

import java.security.PublicKey;
Expand All @@ -21,15 +22,12 @@ protected EasyCodef(
this.easyCodefToken = easyCodefToken;
}

public EasyCodefResponse requestProduct(EasyCodefRequest request) {
final String requestUrl = generateRequestUrl(request);
easyCodefToken.validateAndRefreshToken();

return EasyCodefConnector.requestProduct(request, easyCodefToken, requestUrl);
}

private String generateRequestUrl(EasyCodefRequest request) {
return clientType.getHost() + request.path();
public EasyCodefResponse requestProduct(
EasyCodefRequest request
) throws CodefException {
final String requestUrl = clientType.getHost() + request.path();
final EasyCodefToken validToken = easyCodefToken.validateAndRefreshToken();
return EasyCodefConnector.requestProduct(request, validToken, requestUrl);
}

public PublicKey getPublicKey() {
Expand Down
100 changes: 45 additions & 55 deletions src/main/java/io/codef/api/EasyCodefConnector.java
Original file line number Diff line number Diff line change
@@ -1,86 +1,76 @@
package io.codef.api;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import io.codef.api.constants.CodefHost;
import io.codef.api.constants.CodefPath;
import io.codef.api.dto.EasyCodefRequest;
import io.codef.api.dto.EasyCodefResponse;
import io.codef.api.error.CodefError;
import io.codef.api.error.CodefException;
import io.codef.api.util.HttpClientUtil;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;

import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

import static org.apache.hc.client5.http.auth.StandardAuthScheme.BASIC;
import static org.apache.hc.client5.http.auth.StandardAuthScheme.BEARER;
import static io.codef.api.dto.EasyCodefRequest.BASIC_TOKEN_FORMAT;
import static io.codef.api.dto.EasyCodefRequest.BEARER_TOKEN_FORMAT;
import static org.apache.hc.core5.http.HttpHeaders.AUTHORIZATION;

final class EasyCodefConnector {
public final class EasyCodefConnector {
private static final ResponseHandler responseHandler = new ResponseHandler();

static String issueToken(String codefOAuthToken) {
System.out.println("issue Token !!!\n\n");
final String BASIC_TOKEN_FORMAT = BASIC + " %s";
final String accessTokenParameter = "access_token";

try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
HttpPost httpPost = new HttpPost(CodefHost.CODEF_OAUTH_SERVER + CodefPath.ISSUE_TOKEN);
httpPost.addHeader(AUTHORIZATION, String.format(BASIC_TOKEN_FORMAT, codefOAuthToken));
private EasyCodefConnector() {
throw new IllegalStateException("Utility class");
}

return httpClient.execute(httpPost, response -> {
switch (response.getCode()) {
case 200:
break;
case 401:
throw CodefException.from(CodefError.OAUTH_UNAUTHORIZED);
case 500:
default:
throw CodefException.from(CodefError.OAUTH_INTERNAL_ERROR);
}
public static String requestToken(
String codefOAuthToken
) throws CodefException {
HttpPost request = createTokenRequest(codefOAuthToken);
return executeRequest(request, responseHandler::handleTokenResponse);
}

String httpResponse = EntityUtils.toString(response.getEntity());
public static EasyCodefResponse requestProduct(
EasyCodefRequest request,
EasyCodefToken token,
String requestUrl
) throws CodefException {
HttpPost httpRequest = createProductRequest(request, token, requestUrl);
return executeRequest(httpRequest, responseHandler::handleProductResponse);
}

return JSON.parseObject(httpResponse).getString(accessTokenParameter);
});
} catch (CodefException exception) {
throw exception;
} catch (Exception exception) {
throw CodefException.of(CodefError.OAUTH_CONNECTION_ERROR, exception);
}
private static HttpPost createTokenRequest(String codefOAuthToken) {
HttpPost httpPost = new HttpPost(CodefHost.CODEF_OAUTH_SERVER + CodefPath.ISSUE_TOKEN);
httpPost.addHeader(AUTHORIZATION, String.format(BASIC_TOKEN_FORMAT, codefOAuthToken));
return httpPost;
}

static EasyCodefResponse requestProduct(
private static HttpPost createProductRequest(
EasyCodefRequest request,
EasyCodefToken token,
String requestUrl
) {
final String BEARER_TOKEN_FORMAT = BEARER + " %s";

try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
HttpPost httpPost = new HttpPost(requestUrl);
httpPost.addHeader(AUTHORIZATION, String.format(BEARER_TOKEN_FORMAT, token.getAccessToken()));
String rawRequest = JSON.toJSONString(request.requestParams());
httpPost.setEntity(new StringEntity(rawRequest));
HttpPost httpPost = new HttpPost(requestUrl);
httpPost.addHeader(AUTHORIZATION, String.format(BEARER_TOKEN_FORMAT, token.getAccessToken()));

return httpClient.execute(httpPost, response -> {
String httpResponse = EntityUtils.toString(response.getEntity());
String decodedResponse = URLDecoder.decode(httpResponse, "UTF-8");
String rawRequest = JSON.toJSONString(request.requestBody());
httpPost.setEntity(new StringEntity(rawRequest, StandardCharsets.UTF_8));

// TODO {"error":"invalid_token","error_description":"Cannot convert access token to JSON","code":"CF-09990","message":"OAUTH2.0 토큰 에러입니다. 메시지를 확인하세요."}
JSONObject jsonResponseObject = JSON.parseObject(decodedResponse);
return httpPost;
}

EasyCodefResponse.Result resultResponse = jsonResponseObject.getJSONObject("result").to(EasyCodefResponse.Result.class);
Object dataResponse = jsonResponseObject.getJSONObject("data").to(Object.class);
return new EasyCodefResponse(resultResponse, dataResponse);
});
} catch (CodefException exception) {
throw exception;
} catch (Exception exception) {
throw CodefException.of(CodefError.OAUTH_CONNECTION_ERROR, exception);
private static <T> T executeRequest(
HttpPost request,
ResponseProcessor<T> processor
) {
try (CloseableHttpClient httpClient = HttpClientUtil.createClient()) {
return httpClient.execute(request, processor::process);
} catch (CodefException e) {
throw e;
} catch (Exception e) {
throw CodefException.of(CodefError.INTERNAL_SERVER_ERROR, e);
}
}
}
}
4 changes: 2 additions & 2 deletions src/main/java/io/codef/api/EasyCodefToken.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ protected EasyCodefToken(EasyCodefBuilder builder) {

String combinedKey = String.join(DELIMITER, builder.getClientId().toString(), builder.getClientSecret().toString());
this.oauthToken = Base64.getEncoder().encodeToString(combinedKey.getBytes());
this.accessToken = EasyCodefConnector.issueToken(oauthToken);
this.accessToken = EasyCodefConnector.requestToken(oauthToken);
this.expiresAt = LocalDateTime.now().plusDays(VALIDITY_PERIOD_DAYS);
}

public EasyCodefToken validateAndRefreshToken() {
if (expiresAt.isBefore(LocalDateTime.now().plusHours(24))) {
this.accessToken = EasyCodefConnector.issueToken(oauthToken);
this.accessToken = EasyCodefConnector.requestToken(oauthToken);
this.expiresAt = LocalDateTime.now().plusDays(7);
}
return this;
Expand Down
72 changes: 72 additions & 0 deletions src/main/java/io/codef/api/ResponseHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package io.codef.api;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import io.codef.api.dto.EasyCodefResponse;
import io.codef.api.error.CodefError;
import io.codef.api.error.CodefException;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.io.entity.EntityUtils;

import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

import static io.codef.api.dto.EasyCodefRequest.ACCESS_TOKEN;
import static io.codef.api.dto.EasyCodefResponse.DATA;
import static io.codef.api.dto.EasyCodefResponse.RESULT;

public class ResponseHandler {
public ResponseHandler() {
}

public String handleTokenResponse(ClassicHttpResponse response) {
try {
final String responseBody = EntityUtils.toString(response.getEntity());

return switch (response.getCode()) {
case HttpStatus.SC_OK -> JSON.parseObject(responseBody).getString(ACCESS_TOKEN);
case HttpStatus.SC_UNAUTHORIZED -> throw CodefException.of(CodefError.OAUTH_UNAUTHORIZED, responseBody);
default -> throw CodefException.of(CodefError.OAUTH_INTERNAL_ERROR, responseBody);
};
} catch (IOException exception) {
throw CodefException.of(CodefError.IO_ERROR, exception);
} catch (ParseException exception) {
throw CodefException.of(CodefError.PARSE_ERROR, exception);
}
}

public EasyCodefResponse handleProductResponse(
ClassicHttpResponse response
) throws CodefException {
try {
final String httpResponse = EntityUtils.toString(response.getEntity());
final int httpStatusCode = response.getCode();

return switch (httpStatusCode) {
case HttpStatus.SC_OK -> {
final String decodedResponse = URLDecoder.decode(httpResponse, StandardCharsets.UTF_8);
yield parseProductResponse(decodedResponse);
}
case HttpStatus.SC_UNAUTHORIZED -> throw CodefException.of(CodefError.OAUTH_UNAUTHORIZED, httpResponse);
default -> throw CodefException.of(CodefError.OAUTH_INTERNAL_ERROR, httpResponse);
};
} catch (IOException exception) {
throw CodefException.of(CodefError.IO_ERROR, exception);
} catch (ParseException exception) {
throw CodefException.of(CodefError.PARSE_ERROR, exception);
}
}

private EasyCodefResponse parseProductResponse(String decodedResponse) {
JSONObject jsonResponseObject = JSON.parseObject(decodedResponse);

EasyCodefResponse.Result resultResponse = jsonResponseObject.getJSONObject(RESULT).to(EasyCodefResponse.Result.class);

Object dataResponse = jsonResponseObject.getJSONObject(DATA).to(Object.class);

return new EasyCodefResponse(resultResponse, dataResponse);
}
}
Loading

0 comments on commit 4a8ef47

Please sign in to comment.