해당 글은 Notion에서 작성되어진 글 입니다.

https://hyeon0j0.notion.site/Swagger-6506f70d58ba46a79f8841a9e413b7bf

 

Swagger 사용법 그리고 커스터마이징

🗒️ 노트 주제

hyeon0j0.notion.site

🗒️ 노트 주제


  • Swagger란 무엇인가??
  • Swagger 사용방법
  • Swagger 커스터마이징 하는 방법

🗝️ 핵심


Swagger란 무엇인가?


서버는 클라이언트와 통신을 하기 위해서 인터페이스 문서를 작성을 하고, 이를 공유하면서 API 규격을 맞춰 나간다.

기존에 팀에서는 Excel을 공유하여 문서로 관리하고, 테스트는 Postman을 통해서 테스트하고 클라이언트에게 테스트 방법을 안내할 때에는 Postman의 Export 기능을 통해 JSON파일로 테스트 명세를 전달하였다.

그러나 Swagger의 도입으로 인해 서버가 정상 기동만 되어있으면 웹상에서 바로 확인 및 테스트가 가능하게 제공할 수 있다.

그리고 Swagger에서 제공하는 api-docs의 JSON 파일을 이용하면 클라이언트 측에서는 Swagger Build를 통해 객체 생성 및 호출 함수까지 자동으로 완성시켜주어 개발 속도가 향상된다.

그럼 Swagger란 정확하게 무엇인가?

Swagger 는 REST API를 설계, 구축, 문서화 및 사용하는 데 도움이 될 수 있는 OpenAPI 사양을 기반으로 구축된 오픈 소스 도구 세트입니다.

About Swagger Specification | Documentation | Swagger

기존의 명칭은 springfox swagger 라는 명칭으로 되어 있다가, 어느부터 springdoc-openapi로 변경이 되었고, springfox swagger의 경우에는 특정 버전만 springboot에서 정상 동작하는 오류가 있었는데 springdoc-openapi로 변경됨에 따라서는 호환성 이슈는 사라진 것 같다. 프로세스가 실행중일때 excute 버튼이 비활성화 되는 기능도 추가됨

Swagger가 제공해주는 도구 중에서 Swagger-ui 툴을 통해서 웹상에서 표출하는 방법에 대해서 작성해보겠다.

Swagger 사용 방법


Swagger의 사용방법은 Java Spring Boot + Gradle을 기준으로 설명하겠다.

    1. Gradle 설정

Swagger-ui 버전정보는 확인을 통해 설정 할 것

  1.  
  2. implementation 'org.springframework.boot:spring-boot-starter' implementation 'org.springframework.boot:spring-boot-starter-web:2.7.0' testImplementation 'org.springframework.boot:spring-boot-starter-test' /* SWAGGER */ implementation group: 'org.springdoc', name: 'springdoc-openapi-ui', version: '1.6.9'
  3. API 설명 작성
    • 애노테이션 방법
    • @OpenAPIDefinition(info = @Info(title = "Hustar API 명세서", description = "API 명세서", version = "v1", contact = @Contact(name = "taehyeonkim", email = "taehyeonkim@kakao.com") ) )
    • Java Config 파일 방법

두가지중 한가지 방법으로 선택

  •  
  • @Configuration public class SwaggerConfig { @Bean public OpenAPI openAPI() { Info info = new Info().title("Hustar API 명세서").version("v1") .description("API 명세서") .contact(new Contact().name("taehyeonkim").email("taehyeonkim@kakao.com")) .license(new License().name("Apache License Version 2.0").url("http://www.apache.org/licenses/LICENSE-2.0.html")); return new OpenAPI() .components(new Components()) .info(info); } }
    1. Controller, Vo 에 설명 작성
       @Getter
       @Setter
       @ToString
       public class TestParmVo {
           @Schema(description = "이름", example = "김태현")
           String name;
      
           @Schema(description = "인사말", example = "안녕하세요")
           String message;
       }

Swagger Annotaion 추가 설명은 공식 wiki에서 확인

    1.  

Annotations · swagger-api/swagger-core Wiki

  1.  
  2. // Controller @RestController public class TestController { @Operation(summary = "이름을 출력하는 Controller", description = "입력 파라미터 이름 출력") @PostMapping("/test") public TestResultVo test(@RequestBody TestParmVo testParmVo){ return TestResultVo.builder() .result(testParmVo.getName()) .build(); } @Operation(summary = "메세지를 출력하는 Controller", description = "입력 파라미터 메세지 출력") @PostMapping("/test2") public TestResultVo test2(@RequestBody TestParmVo testParmVo){ return TestResultVo.builder() .result(testParmVo.getMessage()) .build(); } }
  3. 로컬 서버 기동 후 localhost:8080/swagger-ui.html 접속
  4. 스크린샷 2022-07-02 시간: 10.44.51.png
  5. 확인설명 작성한 부분 위치결과 확인
  6. 결과 확인
  7. 설명 작성한 부분 위치

Swagger 커스터마이징


OpenAPI이므로 Swagger에 로고도 바꾸고 색상도 바꾸고 사용자의 입맛에 바꿀수가 있는데 그중 로고와 헤더 색상 바꾸는 것만 설명을 하고자 한다.

  • GIt Issue에 있는 css path를 통한 커스터마이징

https://github.com/springdoc/springdoc-openapi/issues/737

해당 방식으로 하게 되면 Swagger Path에 해당 Controller를 예외 처리 해줘야 되며, 내가 바꾸고 싶은 부분 마다 Endpoint를 만드는 느낌이라 개인적으로는 비추

  • Swagger UI Resource를 통한 직접 커스터마이징
  1. swagger-ui resource 다운또는 Gradle에서 Import된 Resource를 사용
  2. Untitled
  3. https://github.com/swagger-api/swagger-ui 에서 dist 폴더 다운
  1. 인터넷 탭 아이콘 , 이름 변경
  2. <!-- index.html --> <!-- HTML for static distribution bundle build --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> **<!-- 해당 부분 이름 변경 --> <title>Hustar Swagger UI</title> <!-- 해당 부분 이름 변경 끝 -->** <link rel="stylesheet" type="text/css" href="./swagger-ui.css" /> <link rel="stylesheet" type="text/css" href="index.css" /> **<!-- 해당 부분 아이콘 리소스 변경 --> <link rel="icon" type="image/png" href="./hustar_32x32.png" sizes="32x32" /> <link rel="icon" type="image/png" href="./hustar_32x32.png" sizes="16x16" /> <!-- 해당 부분 아이콘 리소스 변경 끝 -->** </head> <body> <div id="swagger-ui"></div> <script src="./swagger-ui-bundle.js" charset="UTF-8"> </script> <script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script> <script src="./swagger-initializer.js" charset="UTF-8"> </script> </body> </html>
  1. Swagger-ui 왼쪽 상단 로고 변경
    1. 적당한 이미지 로고(배경이 없는 png 파일 추천) 준비해서 Resource폴더 안에 넣어놓기
    2. Untitled
    3. swagger-ui-standalone-preset.js 수정 (배너 이미지 변경)
       // 변경
      
       return p.createElement("div", {className: "topbar"}, p.createElement("div", {className: "wrapper"}, p.createElement("div", {className: "topbar-wrapper"}, p.createElement(o, null, p.createElement("img", {
                                   height: "40",
                                   // TODO :: 바꿈
                                   src: "./Hustar.png",
                                   // TODO :: 바꿈
                                   alt: "Hustar - Swagger UI"
                               })), p.createElement("form", {
                                   className: "download-url-wrapper",
                                   onSubmit: f
                               }, b()(c).call(c, (function (t, e) {
                                   return (0, p.cloneElement)(t, {key: e})
                               }))))))
    4. // 기존 return p.createElement("div", {className: "topbar"}, p.createElement("div", {className: "wrapper"}, p.createElement("div", {className: "topbar-wrapper"}, p.createElement(o, null, p.createElement("img", { height: "40", src: j, alt: "Swagger UI" })), p.createElement("form", { className: "download-url-wrapper", onSubmit: f }, b()(c).call(c, (function (t, e) { return (0, p.cloneElement)(t, {key: e}) }))))))
4. 상단 배너 Back-ground 색상 변경

Index.css 파일 수정

```css
/** index.css **/

.swagger-ui .topbar {
    background-color: lightgray;
}
```

1. 결과 확인

    ![Untitled](Swagger%20%E1%84%89%E1%85%A1%E1%84%8B%E1%85%AD%E1%86%BC%E1%84%87%E1%85%A5%E1%86%B8%20%E1%84%80%E1%85%B3%E1%84%85%E1%85%B5%E1%84%80%E1%85%A9%20%E1%84%8F%E1%85%A5%E1%84%89%E1%85%B3%E1%84%90%E1%85%A5%E1%84%86%E1%85%A1%E1%84%8B%E1%85%B5%E1%84%8C%E1%85%B5%E1%86%BC%20839b56b3485a41b489336dd45b2c9321/Untitled%202.png)

 

소스코드

https://github.com/taehyeon3549/SwaggerCustomizing

 

GitHub - taehyeon3549/SwaggerCustomizing: Swagger 문서를 개인의 입맛에 맞게 수정 변경

Swagger 문서를 개인의 입맛에 맞게 수정 변경. Contribute to taehyeon3549/SwaggerCustomizing development by creating an account on GitHub.

github.com

 

🔗 참고 노트


Custom RestTemplateBuilder

  1. Builder 패턴을 활용한 RestTemplateBuilder
  2. Spring의 IoC를 활용한 싱글톤 RestTemplate
  3. RestTemplate의 Customizing
  4. RestTemplateBuilder 상속을 통한 기능 확장
[ 구조 ] 

TaehyeonRestTemple = RestTemplateBuilder + Taehyeon's Function    // Builder-pattern
                              |
                              |- HttpClient
                              |     ㄴ Connection Pool
                              |- HttpComponentsClientHttpRequestFactory
                              |     ㄴ Time Out
                              ㄴ CustomRestTemplateCustomizer
                                    ㄴ Logging                                    

[클래스 구조] TaehyeonRestTemplateBuilder 구조

Example

@Autowired
private TaehyeonRestTempleBuilder taehyeonRestTempleBuilder;


/** GET 요청 예시(String return) **/
String result = taehyeonRestTempleBuilder
                .requestUriAndPath(DATA_BASE_URL
                        , "/VilageFcstInfoService_2.0/getVilageFcst"
                        , new LinkedMultiValueMap<>() {{
                            add("serviceKey", SERVICE_KEY);
                            add("pageNo", "1");
                            add("dataType", "JSON");
                            add("base_date", "20220207");
                            add("base_time", "1200");
                            add("nx", "57");
                            add("ny", "127");
                        }})
                .getForObject();

/** GET 요청 예시 **/
ResponseEntity<Object> result = taehyeonRestTempleBuilder
                .headers(new HashMap<>(){
                    {
                        put("X-Auth-Token", authKey);
                    }
                })
                .requestUriAndPath(crowdworksDefaultUrl
                        , "/project/output"
                        , new LinkedMultiValueMap<>() {{
                            add("queryingField", "");
                            add("queryingWord", "");
                            add("dataStatus", "");
                            add("edited", "");
                            add("rangingField", "");
                            add("page", String.valueOf(page));
                        }})
                .get();


/** POST 요청 예시 **/
ResponseEntity<Object> result = taehyeonRestTempleBuilder
                .contentType(MediaType.APPLICATION_JSON)
                .body(loginInfo)
                .requestUriAndPath(apiUrl, loginpath, null)
                .post();


/** PATCH 요청 예시 **/
ResponseEntity<Object> result = taehyeonRestTempleBuilder
                .headers(new HashMap<>(){{
                    put("X-Auth-Token", authKey);
                }})
                .requestUriAndPath(apiUrl, authPath, null)
                .patch();

개발 목표

  • 비즈니스 로직에서 구현하던 RestTemplate 생성과 처리 부분을 분리
  • 추후 연계 시스템 개발에서도 해당 모듈을 사용하여 단순히 호출만을 통한 처리를 할 수 있게끔 개발
  • Spring과 디자인 패턴을 공부하고 있는 것을 복습하며 실적용을 노림
  • RestTemplate의 ConnectionPool 설정을 통해 3-Hankshake의 지연을 줄임
    (연계 서버이므로 API 호출 서버가 keep-alive 상태임을 가정 )
  • 기존의 Resttemplate 과 Custom한 TaehyeonRestTemplate을 공존하여 Client의 선택에 따라 사용할 수 있도록 개발
  • jar 라이브러리화 하여 추후 연계 서버 개발시 개발 시간을 단축
  • 회사 연계 공통 모듈로 사용할 수 있게??? :D


[개발 Note]

  • 2022-02-22

    • 프로젝트 Init
    • Builder patter 초기 동작 코드 작성
    • Get 요청시의 parameter를 명시적으로 주입하기 위해 MultiValueMap 과 UriComponentsBuilder를 활용하여 요청 URI를 생성
    • 모듈 자체 Exception 생성 (수정 보완필요)
  • 2022-02-23

    • RestTemplate이 Connection Pool이 없이 요청할때마다 3-HandShack를 진행한다는 것을 공부, 이에 따라 Connection Pool을 설정하여 오버헤드를 줄임
    • application.properties 를 통한 Connection Pool과 TimeOut 설정을 할 수 있게 설정
    • 초기의 TaehyeonRestTemplateBuilder의 코드에 Connection Pool과 TimeOut 설정을 하여 Bean 생성을 하려고 하였으나 ' RestTemplateBuilder를 사용한 RestTemplate 설정 방법 : https://www.baeldung.com/spring-rest-template-builder ' 을 보고 Customizing Logging을 추가 및 각 기능을 분리 시킨 Bean으로 생성하여 주입되어 Application Context가 자동 관리 되도록 함
    • RestTempleBuilder의 생성자에 CustomRestTemplateCustomizer가 parm으로 주입되는데 이에 따른 TaehyeonRestTemplateBuilder를 RestTemplateBuilder를 상속 받아 기능을 확장 시키는 방향으로 전환
    • (중요!!) 상속받은 Builder를 통한 build를 시키면 return이 super의 RestTemplate 이고, sub의 TaehyeonRestTempleBuilder로 casting이 안됨.
      해당 문제는 Builder 형태로 생성하고 마지막에 Casting을 하는 것이 아닌, Casting 한 Builder를 생성하고 생성한 객체에서 추가적인 작업을 진행
      (때문에 알고있던 Upcasting과 DownCasting 다시 공부....)
    • 새롭게 만들어낸 TaehyeonRestTemplate과 기존의 RestTemplate을 공용으로 사용할 수 있도록 2가지 type의 Bean 생성하는 방향으로 생각중
    • 테스트 코드 추가
  • 2022-02-25

    • Default RestTemplate Bean 추가 하여 Client가 선택적으로 사용할 수 있게 수정
    • TestCode에 기존 방식과 TaehyeonRestTemplate 방식 비교 코드 추가


[개발 이슈]

  1. RestTemplate Get 요청시 String 으로 parameter를 주입시 자동 Encoding 처리 되어 요청 [해결]

parmeter 를 URI로 주입하여 Encoding 되지 않게 처리

restTemplate.getForObject(new URI(URLDecoder.decode(requestUri.toString(), "UTF-8")), String.class);
  1. 상속받은 Builder를 통한 build를 시키면 return이 super의 RestTemplate 이고, sub의 TaehyeonRestTempleBuilder로 casting이 안됨 [해결]

Casting 한 Builder를 생성하고 생성한 객체에서 추가적인 작업을 진행

//불가능
TaehyeonRestTempleBuilder taehyeonRestTempleBuilder = (TaehyeonRestTempleBuilder)new TaehyeonRestTempleBuilder(customRestTemplateCustomizer())
        .requestFactory(() -> new BufferingClientHttpRequestFactory(factory))
        .additionalMessageConverters(new StringHttpMessageConverter(Charset.forName("UTF-8")));

// 가능
TaehyeonRestTempleBuilder taehyeonRestTempleBuilder = new TaehyeonRestTempleBuilder(customRestTemplateCustomizer());

taehyeonRestTempleBuilder.requestFactory(() -> new BufferingClientHttpRequestFactory(factory))
        .additionalMessageConverters(new StringHttpMessageConverter(Charset.forName("UTF-8")));
  1. TaehyeonRestTempleBuilder 는 Builder인가 RestTemplate 인가.....?

  2. RestTemple를 통해 Get 요청시 MessageConverter 오류**[해결]**

  • TaehyeonRestTemplateBuilder은 "additionalMessageConverters(new StringHttpMessageConverter(Charset.forName("UTF-8")));" 만으로 Response Message Converting이 정상 이루어 졌지만, 기본 RestTemplateBuilder에도 똑같이 하였으나 Converting 오류 발생

혹시 몰라 TaehyeonRestTemplate 에도 MessageCoverter 설정 추가

// 오류 메세지
org.springframework.web.client.UnknownContentTypeException: Could not extract response: no suitable HttpMessageConverter found for response type [class java.lang.Object] and content type [application/json;charset=UTF-8]

// 해결 코드

RestTemplate restTemplate1 =restTemplateBuilder
.additionalMessageConverters(new StringHttpMessageConverter(Charset.forName("UTF-8")))
.build(); // restemplate 생성

List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL));  //converter 설정
messageConverters.add(converter);  

restTemplate1.setMessageConverters(messageConverters);   // messageConverter 직접 추가

 

* 원문과 예제 코드는 Git에 있습니다.

https://github.com/taehyeon3549/RestTemplateBuilder

 

GitHub - taehyeon3549/RestTemplateBuilder: Builder 패턴을 활용한 RestTemplateBuilder

Builder 패턴을 활용한 RestTemplateBuilder. Contribute to taehyeon3549/RestTemplateBuilder development by creating an account on GitHub.

github.com

 

배포시 문제 발생으로는

 

1. jar 생성 안됨

2. Mainfest 없음

3. webservelt 없음

 

가 주로 있다.

 

구글링을 통해 JAR 파일 생성시 

 

jar.enabled = true를 해주고

 

Gradle Task로 

clean - jar - build를 했으나 오류 발생

 

 

해결방법]

 

build.gradle 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bootJar {
   jar.enabled = true // 빌드시 jar 을 실행
}
 
jar {    
   manifest { // 빌드시 Main-Class 설정
       attributes 'Main-Class': 'com.testing.test.TestingApplication'
    }    
}
cs

 

Gradle Tasks

 

Clean - bootJar 순으로 실행

 

로컬 서버나 TestBed 서버에서 해당 jar 파일

 

java -jar _____.jar 해서 실행

실서버 배포 이후 유지보수를 할때,

90% 이상이 쿼리나 웹 단 쪽 수정이 대부분.

 

그럴때마다 새롭게 jar파일을 만들어서 배포 할순 없으니

WEB-INF 와 .xml 을 외부로 빼서 이후에 수정을 할 수 있겠끔 설정을 하는 것이 필요.

 

 

1. application.properties에서

    spring.resource.static-locations = 리소스파일 경로

    spring.datasource.config-location = DB config 파일 경로

    를 설정 해준다.

 

2. DB config 파일에서 불러올 .xml 파일 경로 설정

 

3. jar 파일을 실행 시킬때 외부 properties 실행 옵션을 준다.

  ex) nohup java -jar ______.jar --spring.config.location='내 properties'

 

 

  • application.properties
1
2
3
4
5
6
7
8
9
10
11
# DB 설정    
 
# 기존 환경 설정 방식
spring.datasource.config-location=classpath:/mybatis/HELP/SqlMapConfig.xml
# 내 설정 파일 위치 지정(url or file)
spring.datasource.config-location=file://'파일경로위치'/SqlMapConfig.xml
 
# Resource 설정
 
#WEB-INF 와 같은 resource 위치 지정 (url or file)
spring.resources.static-locations=file://'파일경로위치'
cs

 

  • SqlMapConfig.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
 
    
<configuration>
    <settings>
      <setting name="callSettersOnNulls" value="true"/>
      </settings>
      
    <mappers>
        <!-- 기존 resource 형태 mapping -->
        <mapper resource="mybatis/AMM/job.xml"></mapper>
 
        <!-- url을 통한 file mapping -->
        <mapper url="file://'파일경로위치'/job.xml"></mapper>
    </mappers>  
</configuration>   
cs

 

  • 전체 형태

 

회사에서 사용하려는 open JDK는 Azul 의 Java 11 입니다.

첫 사업이라 미리 공부할겸해서 로컬로 환경설정하는 도중 Open JDK 설정도 하고,

만났던 오류 해결 방법을 정리 해보겠습니다.

 

www.azul.com/downloads/zulu-community/?architecture=x86-64-bit&package=jdk

 

Download OpenJDK Open Source Java Zulu Linux Windows macOS

Download Java 11 Zulu OpenJDK Linux Windows macOS Solaris Update open source Java Alpine Java SE free download Java 8 Java 7 Java 6 JDK

www.azul.com

 

jdk버전에 맞는 apache Tomcat 버전 확인

 

http://tomcat.apache.org/whichversion.html

 

Apache Tomcat® - Which Version Do I Want?

Apache Tomcat® is an open source software implementation of a subset of the Jakarta EE (formally Java EE) technologies. Different versions of Apache Tomcat are available for different versions of the specifications. The mapping between the specifications

tomcat.apache.org

결론은 상관없습니다 ㅎㅎㅎㅎㅎ 그래서 최신 버전인 9로 설치,

 

https://tomcat.apache.org/download-90.cgi

 

Apache Tomcat® - Apache Tomcat 9 Software Downloads

Welcome to the Apache Tomcat® 9.x software download page. This page provides download links for obtaining the latest version of Tomcat 9.0.x software, as well as links to the archives of older releases. Unsure which version you need? Specification version

tomcat.apache.org

 

*자바 환경변수는 꼭 잡으셔야 됩니다.

 

sts 에서 maven 프로젝트 생성하는데 archtype은 (maven-archtype-web)으로 생성.

그리고 JRE 설정

 

 

 

1. HttpServlet 오류

 

server 창 열기

window - show view - other 에서 검색해서 찾기

다운 받은 apache 서버 설정

servers tab 에서 다운 받은 apache 버전 설정

중요! 꼭 JRE는 OpenJDK로 설정 해주기!!

 

 tomcat 설치 된 경로 잡아주고 JRE 꼭 OpenJDK로 잡아주기

프로젝트 설정에서 Java 버전 다시 잡아주고, Runtime 설정 해주기

Runtimes 에서 설정잡은 tomcat 체크 하기

 

 

2. Port 오류

 

이후 실행 하면 사진과 같이 오류가 뜬다,

포트에 문제가 있습니다?

포트 설정이 안잡혀서 오류가 뜨는것이기에 포트 잡아주고, 포트는 현재 사용중인 포트 말고 잡아 줍니다.

 

Tomcat admin port 설정 해주기

 

netstat -ano 로 사용중인 프로토콜 확인

 

 

 

그럼 이후 정상적으로 동작합니다.

 

그럼 화이팅

+ Recent posts