Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 5 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading