FeginClient 어노테이션을 추가하고, yml 파일에 추가한 설정값으로 Timeout을 테스트한 결과입니다.

 

송신 서버는 아래 java 코드와 같이 단순 호출만 하고, 시간을 체크합니다.

수신 서버는 Thread.sleep(1000 * 120); 과 같이 매우 긴 시간을 대기하게 설정했습니다.

 

- Controller Class

import com.test.project.feign.client.TestClient;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
public class TestController {
        private final TestClient testClient;

        @PostMapping("/test")
        public ResponseEntity<?> zxc() throws InterruptedException {

            long startTime = System.currentTimeMillis();

            try {
                testClient.test();
            } catch (Exception e) {
                e.printStackTrace();
            }

            long timeTaken = System.currentTimeMillis() - startTime;
            System.err.println("################## 걸린 시간 : " + timeTaken);

            return ResponseEntity.ok().build();
        }
}

- Feign Interface

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(name = "test", url = "http://localhost:28010", path = "/test")
public interface TestClient {

    @GetMapping(value = "/do")
    void test();
}

 

테스트 결과 Timeout에 영향을 받는 yml 설정은 다음 2가지가 있습니다.

 

feign.client.config

feign:
  hystrix:
    # true 값 설정시 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 설정과 비교하여 짧은 값이 적용됨
    # feign.client.config.default.readTimeout 기본값 : 60_000
    # hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 기본값 : 1_000
    enabled: false
  client:
    config:
      # FeignClient의 name값으로 타겟팅 가능
      default: # 기본값
        loggerLevel: BASIC
        connectTimeout: 5000	# 3 Way Handshake가 5초가 넘는다면 수신 서버와의 통신 자체에 문제가 있는것.
        readTimeout: 5000	# 수신 서버의 처리시간을 고려하여 설정
      test: # FeignClient의 name이 test인 인터페이스
        loggerLevel: BASIC
        connectTimeout: 5000
        readTimeout: 5000

hystrix

feign:
  hystrix:
    # true 값 설정시 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 설정과 비교하여 짧은 값이 적용됨
    # feign.client.config.default.readTimeout 기본값 : 60_000
    # hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 기본값 : 1_000
    enabled: true

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 5000

테스트 케이스

Case 1.

  feign.hystrix.enabled=false

  feign.client.config.default.connectTimeout=5000

  feign.client.config.default.readTimeout=15000

  hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000

 

실행 로그

feign.RetryableException: Read timed out executing GET http://localhost:22222/test/do
	at feign.FeignException.errorExecuting(FeignException.java:213)
	...
Caused by: java.net.SocketTimeoutException: Read timed out
	at java.base/java.net.SocketInputStream.socketRead0(Native Method)
	...
################## 걸린 시간 : 15024

 

Case 2.

  feign.hystrix.enabled=false

  feign.client.config.default.connectTimeout=5000

  feign.client.config.default.readTimeout=5000

  hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=15000

 

실행 로그

feign.RetryableException: Read timed out executing GET http://localhost:22222/test/do
	at feign.FeignException.errorExecuting(FeignException.java:213)
	...
Caused by: java.net.SocketTimeoutException: Read timed out
	at java.base/java.net.SocketInputStream.socketRead0(Native Method)
	...
################## 걸린 시간 : 5029

 

Case 3.

  feign.hystrix.enabled=true

  feign.client.config.default.connectTimeout=5000

  feign.client.config.default.readTimeout=15000

  hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000

 

실행 로그

com.netflix.hystrix.exception.HystrixRuntimeException: TestClient#test() failed and no fallback available.
	at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:822)
    ...
feign.RetryableException: Read timed out executing GET http://localhost:22222/test/do
	at feign.FeignException.errorExecuting(FeignException.java:213)
	...
Caused by: java.net.SocketTimeoutException: Read timed out
	at java.base/java.net.SocketInputStream.socketRead0(Native Method)
	...
################## 걸린 시간 : 5280

Case 4.

  feign.hystrix.enabled=true

  feign.client.config.default.connectTimeout=5000

  feign.client.config.default.readTimeout=5000

  hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=15000

 

실행 로그

com.netflix.hystrix.exception.HystrixRuntimeException: TestClient#test() failed and no fallback available.
	at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:822)
    ...
feign.RetryableException: Read timed out executing GET http://localhost:22222/test/do
	at feign.FeignException.errorExecuting(FeignException.java:213)
	...
Caused by: java.net.SocketTimeoutException: Read timed out
	at java.base/java.net.SocketInputStream.socketRead0(Native Method)
	...
################## 걸린 시간 : 5211

Case 5.

  feign.hystrix.enabled=true

  feign.client.config.default.connectTimeout=5000

  feign.client.config.default.readTimeout=15000

  hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=15000

 

실행 로그

com.netflix.hystrix.exception.HystrixRuntimeException: TestClient#test() failed and no fallback available.
	at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:822)
    ...
feign.RetryableException: Read timed out executing GET http://localhost:22222/test/do
	at feign.FeignException.errorExecuting(FeignException.java:213)
	...
Caused by: java.net.SocketTimeoutException: Read timed out
	at java.base/java.net.SocketInputStream.socketRead0(Native Method)
	...
################## 걸린 시간 : 15157

Case 6.

  feign.hystrix.enabled=true

  hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=15000

 

실행 로그

com.netflix.hystrix.exception.HystrixRuntimeException: TestClient#test() failed and no fallback available.
	at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:822)
    ...
Caused by: java.util.concurrent.TimeoutException
	at com.netflix.hystrix.AbstractCommand.handleTimeoutViaFallback(AbstractCommand.java:997)
################## 걸린 시간 : 15168

Case 7.

  feign.hystrix.enabled=true

  feign.client.config.default.connectTimeout=5000

  feign.client.config.default.readTimeout=15000

 

실행 로그

com.netflix.hystrix.exception.HystrixRuntimeException: TestClient#test() failed and no fallback available.
	at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:822)
    ...
Caused by: java.util.concurrent.TimeoutException
	at com.netflix.hystrix.AbstractCommand.handleTimeoutViaFallback(AbstractCommand.java:997)
################## 걸린 시간 : 1018

Case 8.

  feign.hystrix.enabled=true

 

실행 로그

com.netflix.hystrix.exception.HystrixRuntimeException: TestClient#test() failed and no fallback available.
	at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:822)
    ...
Caused by: java.util.concurrent.TimeoutException
	at com.netflix.hystrix.AbstractCommand.handleTimeoutViaFallback(AbstractCommand.java:997)
################## 걸린 시간 : 1146

Case 9.

아무런 설정을 하지 않음

 

실행 로그

feign.RetryableException: Read timed out executing GET http://localhost:22222/test/do
	at feign.FeignException.errorExecuting(FeignException.java:213)
	...
Caused by: java.net.SocketTimeoutException: Read timed out
	at java.base/java.net.SocketInputStream.socketRead0(Native Method)
	...
################## 걸린 시간 : 60008

 


 

테스트 케이스의 설정을 다음과 같다.

 

  • Case 1, 2
    feign.hystrix.enabled=false인 경우, feign.client.config.default.readTimeout 설정이 적용된다.

  • Case 3, 4, 5, 6, 7, 8
    feign.hystrix.enabled=true인 경우
    1. 설정값 feign.client.config.default.readTimeout와
      hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 중 낮은 값이 적용된다.
    2. hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds만 설정할 경우,
      feign.client.config.default.readTimeout는 고려하지 않는다.
    3. Case 7, 8이 약 1초가 적용된 이유는 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 의 기본값은 1,000을 따르기 때문이다.
  • Case 9
    아무런 설정을 하지 않은 경우, Case1, 2와 동일한 에러 로그가 출력된다.
    feign.client.config.default.readTimeout 설정이 적용된것이고, 기본값은 60,000이다.

  • Case 6, 7, 8, 9
    feign.client.config.default.readTimeout 기본값은 60,000
    hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 기본값은 1,000
    더 낮은 값은 hystrix 설정이기 때문에 Case 8에서 1,000이 적용됐다.

 

정리

hystrix를 사용하지 않을 경우 feign.client.config.default.readTimeout 설정을 하고

hystrix를 사용해야 한다면 feign.client.config.default.readTimeout와 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 두가지 값을 설정하되 기본값을 고려해야 한다.

 

 


 

※ Timeout은 10초도 길다고 볼 수 있다.

로직 처리 자체가 문제있는 것이므로 Timeout을 조절하기 보다는 로직을 수정 하는것이 옳바른 방법이다.

 

 

알고리즘은 문제를 해결하기 위한 방법을 추상화하여 일련의 단계적 절차를 논리적으로 기술한 명세서이다.

예를 들어, 요리 레시피도 알고리즘의 하나라고 할 수 있다.

 


표현 방식

알고리즘을 표현할 때, 다음과 같은 방식이 있다.

  • 자연어
    사람이 사용하는 언어로 서술
  • 순서도(Flow Chart)
    순서도의 작성 규칙에 따라 도식화

 

  • 프로그래밍 언어
  • 가상 코드(Pseudo code)

 

성능분석

알고리즘을 분석, 판단하는 기준으로 정확성, 명확성, 수행량, 메모리 사용량, 최적성 등이 있다.

이 중 가장 중요한 항목은 최적성이며, 알고리즘을 적용할 시스템의 사용 환경에 따라 수행량과 메모리 사용량이 달라지기 때문에 가장 좋은 알고리즘이란 최적의 알고리즘이라고 할 수 있다.

 

성능 분석 방법

성능 분석 방법은 공간 복잡도와 시간 복잡도, 일반적으로 이 두가지를 평가한다.

 

  1.  공간 복잡도 (Space Complexity)
    프로그램을 실행하여 완료하기 까지 필요한 저장 공간을 의미한다.
    따라서 필요한 고정 공간 + 가변 공간으로 계산된다.

  2. 시간 복잡도 (Time Complexity)
    프로그램을 실행하여 완료하는데 걸리는 시간이다, 컴파일 시간 + 실행 시간의 합으로 표현한다.
    일반적으로 빅-오(Big Oh) 표기법을 사용하여 표기한다.


빅-오(Big Oh) 표기법

① 빅-오 표기법은 실행 빈도수를 구하여 실행 시간 함수를 찾고, ②가장 큰 영향을 주는 n에 대한 항을 선택하여, ③ 계수는 생략하고 O의 오른쪽 괄호 안에 표시한다.

 

알고리즘에 따라 표현되는 예시는 다음과 같다.

 

logn, n, nlogn, n², n³, 2ⁿ...

 

=> O(logn), O(n), O(nlogn), O(n²), O(n³), O(2ⁿ)...

 

 


<<참고 자료>>

 

이지영, "C로 배우는 쉬운 자료구조 (개정판)", 한빛아카데미, 2013년, p63 ~ p77

노드 패키지 매니저를 이용해 설치해보자.

 

윈도우 콘솔창(윈도우키 + R을 누르고 cmd를 실행)을 기본으로 진행된다.

 

먼저 원하는 위치에 폴더를 생성을 하고 콘솔창을 이용해 해당 경로로 이동한다.

 

cd /~~/~~

 

 

이동 후 패키지 매니저를 이용해 전역으로 Vue-cli를 설치한다.

 

전역으로 사용한다는 키워드는 -g를 붙인다.

 

npm install -g vue-cli

 

 설치되있다면 위와같이 업데이트 작업이 진행된다.

 

이제 해당 경로에 webpack을 설치

 

웹팩은 일반 설치가 있고 simple, 필요한 기능만 설치하는 방법이 있는데

 

simple의 경우 기능이 너무 없어서 일반 설치를 한다.

 

vue init webpack

 

이와 같이 설치 진행 과정들을 보여주며 설치가 진행된다.

 

처음에 설치 키워드를 입력하면

 

? Generate project in current directory? Yes
? Project name webpack
? Project description A Vue.js project
? Author hawee
? Vue build standalone
? Install vue-router? Yes
? Use ESLint to lint your code? Yes
? Pick an ESLint preset Standard
? Set up unit tests Yes
? Pick a test runner karma
? Setup e2e tests with Nightwatch? Yes
? Should we run `npm install` for you after the project has been created? (recommended) npm

 

이와같은 기본설정이 나타난다. 버전에 따라서 기본 설정은 다른걸 요구하지만 대부분

 

영어문장 그대로 요구하는 값을 넣어주고 기본값이나 자주 사용하는 기능을 선택하면 된다.

 

vue-router : vue의 페이지 이동 기능을 담당

 

ESLint : 문장을 검사해주는 툴, 본인이 코드를 뒤죽박죽 쓴다면 추천

 

설치가 정상적으로 됫다면 프로젝트를 최신으로 업데이트(의존성을 통해 가져온 기능들)하고나서

 

개발자 모드로 웹팩을 실행시켜보자.

 

먼저 업데이트 키워드

 

npm install

 

외부 기능을 사용하고 싶어서 추가했다면 npm install을 통해서 업데이트를 진행하자.

 

의존성을 통해서 누군가 만들어놓은 외부 기능을 install 키워드를 사용하면

 

node_modules, Dependencies 에 추가된다.

 

외부 기능들은 정말 많이 있고 사용하기 쉽기 때문에 Vue를 사용하는 이유 중 하나이기도 하다.

 

아래 사이트에서 검색해 볼 수 있다.

 

https://www.npmjs.com/

 

npm | build amazing things

Bring the best of open source to your company npm is the tool used by over 11,000,000 JavaScript developers around the world. Your developers already use it. Your company depends on it. Create an Org and get more out of the tools your team already knows an

www.npmjs.com

 

 

개발자 모드 키워드를 통해 실행시켜보자

 

npm run dev

 

 

개발자 모드로 빌드가 완료되면 접속할 수 있는 url이 나오고 해당 url로 접속하면 vue의 Hello world 페이지가 뜬다.

 

만약 포트를 8080에서 원하는 포트로 바꾸고 싶다면

 

npm run dev -- --port 1234

 

키워드를 사용하면 되고

 

종료하고 싶다면 콘솔창에서 Ctrl + C를 누르면 빠져나온다.

 

설치된 웹팩 프로젝트는 다음과 같은 구조로 이루어져있다.

 

 

 

build 폴더는 프로젝트 빌드 설정이 저장된 js 파일들이다.

 

빌드 경로나 포트 등 다양한 설정을 한다.

 

 

 

config 폴더는 개발자 모드, 실제 패키지 빌드 모드 등 구동할 환경에 대해 상세 설정을 할 수 있다.

 

 

 

node_modules 폴더는 많은 의존성 기능들을 다운받은 폴더이다.

 

 - 이에 대한 의존성 내용은 가장 아래의 package.json의 Dependencies 값에서 확인

 

 - java 프로젝트에서 메이븐이나 Gradle을 이용해 api를 가져오고 의존성관리를 하는것과 비슷하다.

 

 

 

src 폴더는 실제 Vue 소스를 작성할 폴더들의 집합이다.

 

 

 

 

 

 

이로써 Vue Webpack 설치 및 환경설정은 끝났다.

 

프로젝트에서 Vue는 화면 역할을 하게 될 것이고

 

나중에 포스트 할 Spring Boot는 Spring Framework MVC 패턴은 html 파일, View를 리턴하는것과 달리

 

Rest API라고 Request가 오면 알맞은 JSON 형태의 Response 데이터를

 

return하는 서버로 DB에서 데이터를 뽑아오는 역할을 하게 된다.

Vue를 다룰 수 있는 프로그램은 크게 InteliJ, Eclipse, Atom, Visual Studio Code 등이 있다.

 

 

 

InteliJ (유료)

 

돈에 여유가 있다면 InteliJ를 구매하여 사용하는것을 추천한다.

 

성능, 기능 등 개발에 있어 중요한 부분이 다른 개발 툴에 비해 좋다고 생각한다.

 

스프링, DB툴 등 여러가지가 포함되어 있다.

 

 

 

Eclipse (무료)

 

이클립스는 이클립스 마켓 스페이스에서 CodeMix 의 Vue 툴을 받아서 사용하면 되는데

 

CodeMix는 몇일동안 체험판으로 사용할 수 있지만 시간이 지나면 유료 라이선스를 요구한다.

 

그리고 이클립스는 성능이 좋은편은 아니다. 대표적으로 자동완성 기능이 돌 때 프로그램이 살짝 멈춘다..

 

 

 

Visual Studio Code (무료)

 

사용하지 않아봤지만 Atom 만큼 다양한 툴이 많지 않았던것으로 기억

 

 

 

Atom (무료)

 

아톰은 이런 패키지가 있을까? 하면 다 있다. 하지만 패키지를 설치하면 설치 할 수록 프로그램이 무거워짐

 

블로그에서는 무료버전인 아톰으로 Vue Webpack을 포스트 하겠습니다.

 

 

 

아톰은 https://atom.io/ 공식 사이트에서 다운로드, 설치

 

메뉴 상단바 Setting - install에서 다양한 패키지를 받을 수 있다.

 

 

내가 사용하는 패키지

 

active-power-mode - 타자를 칠 때 마다 이펙트 효과, 화려해용...
atom-beautify - 코드에 색깔을 적절하게 부여해서 가시성을 높여줌
autoclose-html-plus - html을 작성할 때 > 꺽쇠를 이용해 닫으면 마무리 태그</> 자동 완성
busy-signal - 로딩 표시
color-picker - 색상 코드 선택 UI 추가
eclipse-keybindings - 이클립스 단축키 적용
emmet - 에밋 코드 적용
highlight-selected - 코드 중 커서로 선택한(하이라이트) 글자 중 같은 글자를 보기 쉽게 표시
javascript-snippets - js 자동완성
language-vue - Vue 환경 적용
minimap - 스크롤에 화면을 작게 표시
minimap-hide - 여러창을 띄었을때 보고있는 창만 미니맵 표시
minimap-lens - 미니맵에 마우스 올리면 미리보기 렌즈 표시
open-html-in-browser - 현재 페이지 브라우저로 열기

Vue를 이용하는 방법으로는 CDN을 이용하여 스크립트를 불러오는 방법과

 

Node Package Manager, 노드 JS의 패키지 매니저를 이용해 인스톨하는 방법이 있다.

 

CDN을 통해 일일히 추가하기 보다는 Node Package Manager를 이용해서 추가하고 관리하는 방법이 더 편하다.

 

https://nodejs.org

 

Node.js

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

nodejs.org

노드 JS 메인 페이지의 화면에 바로 다운로드가 있다.

 

좌측은 안정화된 버전이고 우측은 막 출시된 불안정한 버전이라고 한다.

 

좌측 X.X.X LTS 버튼을 눌러 설치 파일을 내려받아 실행하자.

 

약관 동의하고 Next를 누르다 보면 설치 파일 구성목록 중

 

 

npm package manager가 있다. 노드 JS를 설치하는 단 하나의 이유이다. 계속 Next를 눌러 설치하고

 

정상적으로 설치가 되었는지 콘솔을 열어 확인을 해보자

 

커맨드 경로 상관없이 node -v 라고 입력하면 설치된 버전이 출력되면 정상적으로 설치 된 것이다. 

 

추가로

 

node js를 쓰기 위한 키워드는 node 이고

 

node package manager를 이용하기 위해선 npm 키워드를 사용하게 된다.

 

 

 

 

만약 설치 파일이 정상적으로 실행이 되었는데 버전이 출력되지 않는다면 환경변수가 등록되지 않은것이다.

 

윈도우키 + R 을 눌러 sysdm.cpl을 실행시키고 아래 사진과 같이

 

환경변수  시스템 변수 Path 항목에 nodejs가 설치된 경로를 추가하고 다시 콘솔창에 node -v를 찍어보자.

 

 

 

 

+ 추가 - Vue 기능에 대한 설명은 Vue 공식 홈페이지 번역이 정말 잘 되어 있어요.

 

          - 시중의 책들도 거의다 홈페이지 복사 붙여넣기 정도여서 홈페이지 보고 공부하시길 추천드립니다.

 

Vue는 중국에서 만든 스크립트 언어로 ECMA Script 5 기능을 사용한다.

 

ECMA Script란 쉽게 말하면 Java Script 언어의 버전이라고 생각하면 된다.

 

새로운 버전에 새로운 함수, 기능들이 추가되고 있다.

 

특히 ECMA Script 6에 유용한 추가기능이 많고 Vue에서 많이 사용되므로 정리된 글을 읽어 보시는걸 추천합니다.

 

Java Script가 업데이트 되면 구버전의 브라우저에서 호환에 문제가 생기는 이유는

 

새로운 함수들을 업데이트 하지 않기 때문에 페이지가 정상적으로 작동하지 않게 되기 때문이고

 

Vue Js 역시 구버전의 브라우저에서는 문제가 발생하게 된다.

 

대표적인 예로 Internet Explorer 11 아래 버전이 있고 11버전도 따로 설정을 하지 않으면 오류가 발생한다.

 

ECMA Script에 대해서는 다음 블로그가 정리를 잘해주셔서 링크를 남깁니다.

 

 

 

ECMA Script 2015 (ES6) : https://itstory.tk/entry/JavaScript-ES6-%EB%AC%B8%EB%B2%95-%EC%A0%95%EB%A6%AC

 

 

[JavaScript] ES6 문법 정리

ECMAScript 6 이 문서는 https://github.com/lukehoban/es6features 를 번역한 내용입니다. 번역 문서를 읽는 중, 오타나 어색한 문장이 있으면 이슈를 등록해주세요! Introduction ECMAScript 2015로도 알려져 있..

itstory.tk

ECMA Script 2016 & 2017 : https://gomugom.github.io/ecmascript-proposals-1-intro/

 

 

ECMAScript Proposals 1 - ES2016 & ES2017

tc39에서 진행하고 있는 ECMAScript의 다음 버전들 및 그 후보들을 알아본다.그 중 본 글에서는 Stage 4(ES2016 및 ES2017에 새로 도입되기로 확정된 기능들)을 살펴보겠다.

gomugom.github.io

 

ECMA Script 2018 : https://www.zerocho.com/category/ECMAScript/post/5adae68aca91b1001b14dd29

 

 

(ECMAScript) ES2018(ES9)의 변화

안녕하세요. 이번 시간에는 ES2018(ES9)에서 추가된 기능에 대해 알아보겠습니다. 2018년이 되었으니 EcmaScript도 2018 버전이 나왔겠죠? 같이 살펴봅시다. 작년 ES2017의 Stage-3 기능들이 대거 정식 스펙으로 추가되었습니다. Object r

www.zerocho.com

 

ECMA Script 2019 : https://www.zerocho.com/category/ECMAScript/post/5c909bfe5a8005001ffb3f14

 

 

(ECMAScript) ES2019(ES10)의 변화

안녕하세요. 어느덧 한 해가 지나서 ES2019가 나왔습니다. ES10이라고도 부르죠. 보통 짝수 번째 버전에서 큰 변화가 있었는데요. ES6(새로운 자바스크립트), ES8(async/await)에서 편리한 기능들이 많이 추가되었죠. 그에 반해 ES7, ES9에서는 소

www.zerocho.com

 

 

 

 

 

Webpack은 프로그래밍 한 웹 관련 파일들을 패키징, 묶어주는것으로 이해하면 된다.

 

html, js, css와 같은 파일을 묶어 간단한 파일 목록으로 만들어주는 개념이다.

 

기존의 html에서 js 파일, css 파일을 호출하고 폴더 구조로 관리하는데 

 

Vue에서는 단순한 구조로 관리하게 된다.

 

https://webpack.js.org/

 

webpack

webpack is a module bundler. Its main purpose is to bundle JavaScript files for usage in a browser, yet it is also capable of transforming, bundling, or packaging just about any resource or asset.

webpack.js.org

 

웹팩 페이지에 가면 위와 같은 그림이 보이는데

 

웹 환경에서 사용하는 다양한 파일들의 꼬여있는 관계를 풀어주는 그림으로 이해하면 된다.

 

처음에는 잘 안느껴지는데 프로젝트를 진행하면서 구조에 대해 이해하기 쉬울 것이다.

우선 첫번째 전제로 페이지 폼 태그에서


<form action="insert.me" method="POST" enctype="multipart/form-data">

<input type="file" name="photo">

</form>


과 같이 enctype="multipart/form-data"으로 데이터를 전송해야 한다.






두번째 전제로 라이브러리 다운로드


업로드 관련 라이브러리를 pom.xml에 추가하자( https://qdgbjsdnb.tistory.com/237 )




세번째 전제로 업로드 관련 서블릿 xml 설정들


    <!-- 파일 업로드를 하기 위해 bean 등록 -->
    <beans:bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver">
        <beans:property name="maxUploadSize" value="10000000"></beans:property>
    </beans:bean>


용량 10MB 이하만 등록가능 하기 때문에 10MB보다 조금 적게 max를 설정








구현


    @RequestMapping("insert.me")
    public String insertMember(Member m, Model model, HttpServletRequest request, @RequestParam(value = "photo", required = false) MultipartFile photo) {
        System.out.println("Member : " + m);
        System.out.println("photo : " + photo);
       
        String root = request.getSession().getServletContext().getRealPath("resources");
       
        String filePath = root + "\\uploadFiles";
       
        // 파일명 변경
        String originFileName = photo.getOriginalFilename();
        String ext = originFileName.substring(originFileName.lastIndexOf("."));
        String changeName = CommonUtils.getRandomString();
       
        try {
            photo.transferTo(new File(filePath + "\\" + changeName + ext));
           
            ms.insertMember(m);
           
            return "redirect:goMain.me";
           
        } catch (Exception e) {
            new File(filePath + "\\" + changeName + ext).delete();
           
            model.addAttribute("msg", "회원가입 실패!");
            return "common/errorPage";
        }
    }




주목할 부분은 빨간색 글씨로 입력


ext는 기존의 파일의 확장자를 따옵니다.


DB의 중복을 피하기위해 랜덤 String을 생성해 이름을 변경


photo.transferTo(new File(filePath + "\\" + changeName + ext)); 를 통해 저장


만약 DB 등록에 실패하면 new File(filePath + "\\" + changeName + ext).delete();로 저장된 파일 삭제






사용자가 View를 통해서 로그인 정보(ID, PW)를 입력하여 로그인을 요청했다는 가정하에 진행


Controller에 로그인 정보가 넘어왓을 때







위와 같은 구조로 순서는


View - Controller - try( Service - Dao ), catch(LoginException)


진행


Controller 데이터 처리는 저번 포스트와 같이 하면 된다. ( https://qdgbjsdnb.tistory.com/238 )


소개한 8가지 방법 중 아무거나 써도 된다.


이 중 7번째


@Controller
public class MemberController {

    @Autowired
    private MemberService ms;


    @RequestMapping("login.me")
    public String loginCheck(Member m, Model model, HttpSession session) {
        Member loginUser;
        try {
            loginUser = ms.loginMember(m);
           
            session.setAttribute("loginUser", loginUser);
           
            return "redirect:goMain.me";
        } catch (LoginException e) {
            model.addAttribute("msg", e.getMessage());
           
            return "common/errorPage";
        }
       
        return null;
    }

}




Member 타입 객체 loginUser는


Spring이 객체화 해 준 Service 인터페이스인 MemberService 타입 객체 ms의


loginMember 메소드를 이용하여 로그인 정보(ID, PW)를 넘긴다.




loginUser = ms.loginMember(m);의 loginMembe메소드나 그 하위 메소드에서


throw new LoginException을 하용하기 때문에 try catch문이 필요.





MemberService.java


public interface MemberService {

    Member loginMember(Member m) throws LoginException;

}

MemberServiceImpl.java


@Service

public class MemberServiceImpl implements MemberService {
    @Autowired
    private SqlSessionTemplate sqlSession;
    // 이렇게만 쓰면 Autowired로 생성되지 않기 때문에 xml에 따로 명시해야함
    // root-context.xml에서 진행
    
    @Autowired
    private MemberDao md;


    @Override
    public Member loginMember(Member m) throws LoginException {


        System.out.println(sqlSession.hashCode());


        Member loginUser = md.loginCheck(sqlSession, m);
        return loginUser;
    }

}




SqlSessionTemplate sqlSession은 DB를 연결해주는 라이브러리를 이용한 객체


저번에 설명한 대로 설정파일을 읽어 Spring에서 자동 생성한다. ( https://qdgbjsdnb.tistory.com/236 )


MemberDao md; 또한 컨트롤러에서 Service생성과 똑같이 생성된다.


이번엔 굳이 try catch를 사용하지 않고 throw를 사용






※참고 - 기존의 서블릿 방식으로 객체를 수작업으로 생성하면 .hashcode를


여러번 호출 하였을 때 값이 계속 바뀌는것을 확인 할 수 있다.


Spring에서는 Spring 컨테이너로 생성해준 객체의 .hash코드를


여러번 호출하면 매번 같은 값을 출력하는걸 확인할 수 있다.


이를 통해 같은 객체를 매번 새롭게 생성하는것이 아닌 재사용을 통해


메모리를 좀 더 효율적으로 사용하고 있다고 확인할 수 있다.






MemberDao.java


public interface MemberDao {

    Member loginCheck(SqlSessionTemplate sqlSession, Member m) throws LoginException;

}




MemberDaoImpl.java



@Repository
public class MemberDaoImpl implements MemberDao{

    @Override
    public Member loginCheck(SqlSessionTemplate sqlSession, Member m) throws LoginException {
        Member loginUser = sqlSession.selectOne("Member.loginCheck", m);
       
        System.out.println("Dao Member : " + loginUser);
       
        if (loginUser == null) {
            throw new LoginException("로그인 정보가 존재하지 않습니다.");
        }
       
        return loginUser;
    }

}


MemberDaoImple에서는 SqlSessionTemplat과 myBatis 메소드를 이용해서


DB를 조회해온다.


메소드 명, 사용법은 저번에 설명한 mybatis와 완전 같음.


( https://qdgbjsdnb.tistory.com/228?category=733892, https://qdgbjsdnb.tistory.com/229?category=733892 )


sqlSession.selectOne("Member.loginCheck", m);


조회 결과를 통해 try catch




mybatis와 DB 관련 라이브러리를 이용하기 때문에 pom.xml에 라이브러리 추가는 필수이다.( https://qdgbjsdnb.tistory.com/237 )








LoginException.java의 경우는 간단하게 다음과 같다.


public class LoginException extends Exception{
    public LoginException(String msg) {
        super(msg);
    }
}







이와 같이 Spring을 이용하면 이와같이 코드의 길이가 매우 짧아지고


자동으로 해주는것(프로그래머가 아닌 Spring Container가)이 많아져 편리하다.


특히 누구든 코드 분석하기가 훨씬 쉬워져 다른 사람과 작업에 좋다.


협업에 알맞지만 Spring 구조와 설정 사용법 등 너무 복잡하고 어렵다는 단점이 있다.





다음과 같은 Ajax 요청이 있다고 하자.


    <script type="text/javascript">
        function duplicationCheck() {
            var userId = $("#userId").val();
           
            $.ajax({
                url:"duplicationCheck.me",
                type:"post",
                data:{userId:userId},
                success:function(data){
                    /* alert(data); */
                    console.log(data);
                    console.log(data.userId);
                },
                error:function(status){
                    console.log(status);
                }
               
            });
           
            return false
        }
    </script>





duplicationCheck.me로 ID가 중복되는지 체크해달라는 요청이다.


Controller, Service, Dao 중 Service, Dao 사용방법은 단순 DB 조회로 생략


Controller에서 4가지 방법을 확인해보자.


기본적으로 pom.xml을 통해서 관련 라이브러리가 추가되었음을 전제로 진행 ( https://qdgbjsdnb.tistory.com/237?category=733876 )


또한 관련 라이브러리 추가로 인한 xml 설정


servlet.xml, 서블릿 xml에 다음 태그들을 추가




    <!-- jsonView 설정 -->
    <beans:bean id="jsonView" class="net.sf.json.spring.web.servlet.view.JsonView"></beans:bean>
    <!-- beanNameViewResolver는 없는 자원에 대해 논리적 이름을 가지고 view를 지정한다. -->
    <beans:bean id="viewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver">
        <beans:property name="order" value="1"></beans:property>
    </beans:bean>
    
    <!-- 메세지 컨버터 추가 -->
    <!-- 자바 객체를 자바스크립트 객체로 바꿔주는 역할을 한다. -->
    <beans:bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"></beans:bean>
    <beans:bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <beans:property name="messageConverters">
            <beans:list>
                <beans:ref bean="jacksonMessageConverter"></beans:ref>
            </beans:list>
        </beans:property>
    </beans:bean>


xml 설정은 이것으로 마치고 구현



1. 스트림을 이용, 다른 툴은 이용하지 않고 기본 기능을 통해서 값 전달



    // 1. 스트림을 이용한 ajax 처리
    @RequestMapping("duplicationCheck.me")
    public void duplicationCheck(@RequestParam String userId, HttpServletResponse response) {
        System.out.println(userId);
       
        try {
            response.getWriter().print(false);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }



2. ObjectMapper를 이용, 잭슨 라이브러리를 사용


ObjectMapper라는 객체를 생성하여 메소드를 이용해서 객체 자체를 전달



    // 2. ObjectMapper를 이용한 ajax
    @RequestMapping("duplicationCheck.me")
    public void duplicationCheck(@RequestParam String userId, HttpServletResponse response) {
        // 잭슨 라이브러리를 사용함
        ObjectMapper mapper = new ObjectMapper();
       
        Member m = new Member();
       
        m.setUserId(userId);
       
        try {
            response.getWriter().print(mapper.writeValueAsString(m));
        } catch (JsonGenerationException e) {
            e.printStackTrace();
        } catch (JsonMappingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }




결과 Data 값은 위와 같이 출력된다.


배열이나 객체 타입이 아닌 String 형으로 나열되어 나온다.








3. jsonView를 이용, Controller의 ModelAndView 방식과 유사


반환 타입이 ModelAndView



    // 3. jsonView를 사용한 방식
    @RequestMapping("duplicationCheck.me")
    public ModelAndView duplicationCheck(String userId, ModelAndView mv) {
       
        Member m = new Member();
        m.setUserId(userId);
       
        mv.addObject("member", m);
       
        mv.setViewName("jsonView");
       
        // 한글의 경우 인코딩 처리를 해야함
       
        return mv;
    }




결과는 이와같이 오브젝트 타입으로 안에 배열 형식으로 객체가 담겨있다.





4. @ResponseBody를 이용, HashMap을 통해 원하는값을 보냄




    // 4. @ResponseBody를 이용한 ajax
    @RequestMapping("duplicationCheck.me")
    public @ResponseBody HashMap<String, Object> duplicationCheck(@RequestParam String userId, HttpServletResponse response) {
       
        HashMap<String, Object> hmap = new HashMap<String, Object>();
       
        hmap.put("userId", userId);
       
        return hmap;
    }






결과는 위와 같다.


HashMap을 통해서 원하는 값만 간편하게 보내면 된다.








4가지 방식을 보면 사용방법은 모두 쉽다.


문제는 반환되는 형식이 어떠하냐인데 마음에 드는 방법에 익숙해지거나


상황에 맞는 방법을 골라서 하면 될거같다.





Servlet을 사용할 땐 SHA-512 방식의 암호화를 사용해 보았는데 이번엔


BCrypt 방식을 이용해보자.


예제는 회원가입 시 Controller에서 입력받은 암호를 암호화 처리를 통해 변경하는 작업



MemberController.java


@Controller
public class MemberController {
    // 의존성 주입용 필드 선언
    // 어노테이션 + Interface 타입
    @Autowired
    private MemberService ms;
    // 인터페이스를 상속받는 객체에 @Component나 @Service 어노테이션이 필요
    // 객체 생성 시 new Service와 같은 부분을 Spring에게 맡기는것.
   
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;


    @RequestMapping("insert.me")
    public String insertMember(Member m, Model model) {
       
        System.out.println("member : " + m);
       
        String encPassword = passwordEncoder.encode(m.getUserPwd());
       
        System.out.println(encPassword);
       
        m.setUserPwd(encPassword);
       
        int result = ms.insertMember(m);
       
        if (result > 0) {
            return "redirect:goMain.me";
        } else {
            model.addAttribute("msg", "회원가입 실패!");
            return "common/errorPage";
        }
       
    }

}



먼저 bcrypt란?
DB에 비밀번호를 저장할 목적으로 설계된 알고리즘

단방향 해쉬 함수는 암호화된 메세지를 수학적인 연산을 통해 암호화 된 메세지인 다이제스트를 생성한다.


원본 메세지를 가지고 암호화된 메세지를 복호화 할 수 있는 것을 양방향이라고 하고


암호화된 메세지를 복호화 할 수 없는 것을 단방향이라고 한다.


다이제스트란 결과값들이라고 생각하면 된다.


단방향 해쉬 함수도 사용하면 안되는 이유


1. 단방향 해쉬함수는 많은 다이제스트가 확보되면 평문을 찾아낼 수 있다.


2. 비밀번호를 저장하기 위한 목적으로 설계된 알고리즘이 아닌,


검색을 위해 설계된 알고리즘이다.


그만큼 다이제스트의 생성이 매우 빨라 뚫리는데 시간이 짧다.


이를 해결하기 위해 슬링(salting)기법이 추가되었다.


원본 메세지에 문자열을 추가하여 동일한 길이의 다이제스트를 생성하는 것을 슬링이라 한다.

하지만 salt 값을 알아내면 나머지는 단방향 해쉬함수를 통한 다이제스트를 복호화 하는 것과 별 차이가 없다.


bcrypt 방식은 이러한 salt값을 랜덤하게 생성하여 암호화를 하는 방식이다.


추가적으로 다이제스트를 생성하는데 걸릴 시간을 결정할 수도 있다.


생성 시간에 오래 걸린다면 대량의 다이제스트 생성 역시 오래걸리게 된다.


1999년에 발표되어 현재까지 자주 사용되는 강력한 비밀번호 저장용 매커니즘이다.





설명은 이렇고 사용 자체는 라이브러리 등록, 선언


암호화 메소드 or 암호 일치하는지?에 대한 메소드 선언밖에 없는 간단한 방법이다.






작업 순서로는


메이븐 시큐리티 라이브러리 추가 - Autowired 추가 - security xml 추가 - web.xml에 추가된 xml들 추가이다.







메이븐 시큐리티 라이브러리 pom.xml에 추가한다. - https://qdgbjsdnb.tistory.com/237


Autowired 추가는 빨간글자로 표시해 두었다.



다음은 web.xml 파일 추가부분


/WEB-INF/config/spring-security.xml 추가


    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:root-context.xml
            /WEB-INF/config/spring-security.xml
        </param-value>
    </context-param>



/WEB-INF/config/spring-security.xml의 설정을 읽어오라고 명시하였으니


해당 경로에 파일을 생성한다.


<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
   



    <beans:bean id="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></beans:bean>
   



</beans:beans>






기존에 있는 기본 beans 선언 안에


<beans:bean id="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></beans:bean>


BCryptPasswordEncoder Bean 선언만 하면 끝난다.








메소드의 경우는 BCryptPasswordEncoder 로 선언된 객체를 통해


encode(String형)을 사용하면 String형이 암호화되고


matches(입력한 암호, DB에 저장된 암호)를 입력했을 땐 암호가 일치하는치 체크해준다.(true or false)





        String encPassword = passwordEncoder.encode(m.getUserPwd());



        if (!passwordEncoder.matches(m.getUserPwd(), encPassword)) {
            throw new LoginException("로그인 실패!");
        } else {
            loginUser = md.selectMember(sqlSession, m);
        }






+ Recent posts