ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 80일차 - RESTAPI, Properties 활용, Spring FW 특징, AOP 활용(로그인 처리)
    백엔드(웹 서버, WAS)/Spring Boot 2024. 5. 23. 14:38
    • url 만으로도 원하는 정보를 얻어오거나 특정한 요청을 하는 것 : REST API
    • 사용 설명서 포함(첫 번째, 두 번째, 세 번째가 무엇을 의미하는지)
      • get/java/~
    • 자바스크립트에서 웹 서버로 직접 요청하는 방식 (보안성 떨어짐)
    • 실제로는 서버 대 서버로 요청 많이 한다.

    <서버가 한 대 일 경우>

    13_WebClient (서버 대 서버 요청)

    프로젝트 생성 시 라이브러리 설정 - webflux

    → 서버 대 서버 통신이 가능하도록 해주는 라이브러리+

    pom.xml

    <!-- WebClient 라이브러리(서버 대 서버 통신이 가능하도록 해주는 라이브러리) -->
    <!-- 프로젝트 생성 시 webflux 로 검색해서 찾으면 된다. -->
    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>

    SendController

    @RestController
    public class SendController {
    	
    	Logger logger = LoggerFactory.getLogger(getClass());
    	
    	private final APIService service;
    	
    	public SendController(APIService service) { // 생성자 주입
    		this.service = service;
    	}
    	
    	@GetMapping(value="/get/send/{val}")
    	public Map<String, String> getSend(@PathVariable String val){
    		logger.info("param : "+val);
    		return service.getSend(val);
    	}
    	
    	@PostMapping(value="/post/send/{val}")
    	public List<Map<String, Object>> postSend(@PathVariable String val, @RequestHeader Map<String, String> header){
    		logger.info("val : "+val);
    		logger.info("header : "+header);
    		String key = header.get("authorization");
    		logger.info("key = "+key);
    		return service.postSend(val,key);
    	}
    	
    	@GetMapping(value="/get/fluxTest")
    	public List<Map<String, Object>> fluxTest(){
    		return service.fluxTest();
    	}
    
    }

    RecvController

    @RestController
    public class RecvController {
    	
    	Logger logger = LoggerFactory.getLogger(getClass());
    
    	// map으로 보냈기 때문에 map 형식으로 받아줘야 함
    	@GetMapping(value="/return/{msg}")
    	public Map<String, String> getReturn(@PathVariable String msg){
    		logger.info("저쪽 서버로 부터 보낸 내용 : "+msg);
    		Map<String, String> map = new  HashMap<String, String>();
    		map.put("msg","네가 보낸 메세지 : "+msg);
    		return map;
    	}
    	
    	@PostMapping(value="/listReturn")
    	public List<Map<String, Object>> listReturn(String cnt, @RequestHeader Map<String, String> header){
    		logger.info("header : "+header);
    		int count = Integer.parseInt(cnt);
    		String key = header.get("authorization");
    		logger.info("cnt = "+cnt);
    		logger.info("key = "+key);
    		
    		List<Map<String, Object>> list = new ArrayList<Map<String,Object>>();
    		Map<String, Object> map = null;
    		
    		for (int i = 1; i < count; i++) {
    			map = new HashMap<String, Object>();
    			map.put("no", i);
    			map.put("name", "Kim");
    			map.put("salary", i*100000);
    			list.add(map);
    		}
    		
    		return list;
    	}
    	
    	@PostMapping(value="/fluxReturn")
    	public List<Map<String, Object>> fluxReturn(@RequestBody Map<String, Object> param){
    		logger.info("json param : "+param);
    
    		List<Map<String, Object>> list = new ArrayList<Map<String,Object>>();
    		Map<String, Object> map = null;
    		
    		for (int i = 1; i < 11; i++) {
    			map = new HashMap<String, Object>();
    			map.put("no", i);
    			map.put("name", "Lee");
    			map.put("salary", i*100000);
    			list.add(map);
    		}
    		
    		return list;
    	}
    
    }

    APIService

    @Service
    public class APIService {
    	
    	Logger logger = LoggerFactory.getLogger(getClass());
    	
    	// 다른 서버로 요청을 보내고, 응답을 받는 기능 만들기
    	// WebClient 는 Spring5.0  에서 부터 지원하는 라이브러리이다.
    	// HttpConnection(Spring 기본 내장) -> RestTemplate 등을 사용 했었다.
    	// Non-Blocking 방식을 지원하기 때문에 비동기 처리가 가능하여 속도 면에서 우월하다. (비동기는 기다리지 않는다. 먼저오면 먼저 수행)-> 통신 양이 많으면 유리하다.
    	// get 방식 전송과 post 방식 전송이 조금 다르다.
    	
    	public Map<String, String> getSend(String msg){
    		
    		//1. 어디로 보낼지
    		WebClient client = WebClient.create("http://localhost:8080"); 
    		
    		//2. 전송 방식 지정 + 3. 상세 url 추가(있으면) + 4. 전송
    		// retrieve() : 전송 후 body 값을 받아온다. 
    		// exchange() : 전송 후 상태 값 헤더 값도 받아온다.(테스트 이외에는 잘 사용하지 않음)
    		// bodyToMono() : 동기 통신 시 사용(한번에 1개 요청 처리 시)
    		// bodyToFlux() : 비동기 통신 시 사용(한번에 여러 개 요청을 처리 시)
    		Mono<Map> mono =  client.get().uri("/return/"+msg).retrieve()
    		.bodyToMono(Map.class);	 //5. 받을 방식 지정
    		
    		// 6. 받아온 Mono 로 부터 자바 객체를 빼 온다.
    		Map<String, String> resp =  mono.block();
    		logger.info("response : "+resp);
    		
    		return resp;
    	}
    
    	public List<Map<String, Object>> postSend(String val, String key) {
    
    		//1. 어디로 보낼지
    		WebClient client = WebClient.create("http://localhost:8080"); 
    		
    		// 2. 파라메터 추가
    		FormInserter<String> form = BodyInserters.fromFormData("cnt", val);
    		// 파라메터를 추가하고 싶다면 아래 방법도 있음 (MultiValueMap 에 담아서 한번에 보내는게 더 낫다.)
    		// form.with("name", "value");
    		
    		//3. 전송 방식 지정 + 4. 상세 url 추가(있으면) + 5. 헤더 값 + 6. 파라메터를 담은 form 추가 + 7. 전송
    		Mono<List> mono =  client.post().uri("/listReturn").header("authorization", key).body(form).retrieve().bodyToMono(List.class);
    		//List<Map<String, Object>> list =  mono.block(); // 이 방식이 간단하긴 하지만 속도 면에서 다른 방식으로 추천한다.
    		List<Map<String, Object>> list = mono.flux().toStream().findFirst().get();
    		
    		logger.info("response : "+list);
    		return list;
    	}
    
    	public List<Map<String, Object>> fluxTest() {
    		
    		//1. 어디로 보낼지
    		WebClient client = WebClient.create("http://localhost:8080"); 
    		
    		// 2. 파라메터 추가
    		Map<String, Object> json = new HashMap<String, Object>();
    		json.put("age", 40);
    		json.put("name", "kim");
    		json.put("married", false);
    		json.put("score", new int[] {30,40,50,60,70,80,90});
    		
    		// json 형태로 파라메터를 보내고 싶은 경우
    		// bodyValue() 를 사용한다.
    		// 받는 곳에서는 @RequestBody 로 받아줘야 한다.
    		//3. 전송 방식 지정 + 4. 상세 url 추가(있으면) + 5. bodyValue 값 + 6. 전송
    		Flux<Map> flux =  client.post().uri("/fluxReturn").bodyValue(json).retrieve().bodyToFlux(Map.class);
    		
    		List<Map<String, Object>> list =  flux.toStream().collect(Collectors.toList());
    		logger.info("reponse : "+list);
    		return list;
    	}
    
    
    }

    index.html

    <html>
    <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
    <style>
    
    </style>
    </head>
    <body>
        <input type="text" id="msg"/>
        <button onclick="getSend()">GET SEND</button>
        <br/><br/>
        <input type="number" id="cnt"/>
        <button onclick="postSend()">POST SEND</button>
        <p><button onclick="fluxTest()">FLUX TEST</button></p>
    </body>
    <script>
        // get 방식으로 서버로 데이터를 보내면 -> 받은 데이터를 다른 서버로 보낸다.
        // 다른 서버에서는 받은 데이터를 -> 보낸 서버로 되돌려 보내고 -> 이 것을 클라이언트로 되돌려 준다.
        function getSend(){
            var val = $('#msg').val();
            console.log('get send : ', val);
            $.ajax({
                type:'get',
                url:'/get/send/'+val,
                dataType:'JSON',
                success:function(data){
                    console.log(data);
                },
                error:function(e){
                    console.log(e);
                }
            })
        }
    
        // POST 방식
        // 입력한 숫자만큼의 크기를 갖는 리스트를 받는다.
        function postSend(){
            var val = $('#cnt').val();
            console.log('post send : ', val);
            // 전송 : 헤더, 바디 (둘의 경계는 공백 한 줄)
            // 헤더 : 지금부터 보낼 문서에 대한 사전 정보 (헤더에 보통 인증 키 등을 보내는 경우가 많다.)
            // 바디 : 보낼 본문
    
            $.ajax({
                type:'post',
                url:'/post/send/'+val,
                dataType:'json',
                beforeSend:function(obj){
                    obj.setRequestHeader('Authorization','KEYNAME3636F')
                },
                success:function(data){
                    console.log(data);
                },
                error:function(e){
                    console.log(e);
                }
            });
        }
    
        function fluxTest(){
            console.log('flux send');
            $.ajax({
                type:'get',
                url:'/get/fluxTest/',
                dataType:'JSON',
                success:function(data){
                    console.log(data);
                },
                error:function(e){
                    console.log(e);
                }
            })
        }
    </script>
    </html>

    proerties 에 슈퍼 어드민 계정을 생성하고 로그인해보는 예제

    application.properties

    # user setting
    user.id=superAdmin
    user.pw=pass@Goodee
    user.ip=127.0.0.1

    controller

    @PostMapping(value="/login")
    public ModelAndView login(String id, String pw, HttpServletRequest request) {
    
        String ip = request.getRemoteAddr();
        // IPv6 를 IPv4 로 변환할 경우 
        // Run As > Run Configuration > Arguments > Vm 선택 후 아래 내용 추가
        // -Djava.net.preferIPv4Stack = true
        logger.info("ip : {}", ip);
        logger.info("id : {}", id);
        logger.info("pw : {}", pw);		
        return service.login(id, pw, ip);
    }

    service

     

    // application.properties 에 있는 값 가져오기
    @Value("${user.id}") private String user_id;
    @Value("${user.pw}") private String user_pw;
    @Value("${user.ip}") private String user_ip;
    
    public ModelAndView login(String id, String pw, String ip) {
        ModelAndView mav = new ModelAndView();
        logger.info("user_id : {}", user_id);
        logger.info("user_pw : {}", user_pw);
        logger.info("user_ip : {}", user_ip);
    
        String msg = "관리자 계정으로 접속할 수 없습니다";
        String page = "login";
        if (id.equals(user_id) && pw.equals(user_pw) && ip.equals(user_ip)) {
            msg="관리자님 안녕하세요";
            page="result";
        }
        mav.addObject("msg", msg);
        mav.setViewName(page);
        return mav;
    }

    콘솔에 아이피 보이는 IPv6 형식을 IPv4 로 변경하기

    반복되는 로그인 처리를 간편하게 구현

     

    AOP (Aspect Oriented Programming, 관점 지향 프로그래밍)

    AOP 관점 지향 프로그래밍 : 특정 시점에서 확인해준다

    예시 ) AOP 같은 경우는 무언가를 감시하는 역할을 한다 

    성영이가 지하철을 타고 학원에 온다. <- 몇번 지하철 개찰구를 뛰어 넘는거 같아서 감시

    내가 성영이가 집에서 나오면 따라가서 개찰구에서 교통카드를 태그하는지 확인함

    하지만 너무 비효율적이므로 지하철 개찰구에서 태그하는지 안하는지 확인하면 효율적이다

     

    IOC : 마이바티스가 알아서 데이터베이스와 연결 과 종료를 자기 알아서함

    의존성 주입 : 싱글톤 패턴

    효울적인 자원 관리

    AOP : 하나 하나 흐름을 쫓아가지 않고 특정 시점에서 확인하는것

     

    39~40일차 스프링의 주요 개념

    IOC (Inversion Of Control, 제어 역행)

    모든 프로그램은 사용자가 내가 사용할래 나 그만 사용할래

    -> 커넥션을 맺어올때 필요할때만 쓰고, 커넥션 클로즈해서 내가(사용자) 닫았다

     

    스프링이 알아서 커넥션을 맺어오고 닫아준다

     

    DI(Dependency Injection, 의존성 주입)

    느슨하게 해라

     

    클래스 a가 클래스 b 를 사용하려면 일일히 객체화해야한다

    클래스 a는 빈을 통해서 클래스 b 의 객체를 사용한다(느슨한 결합)

    코드 내에서 결합이 낮다는건 수정할 곳이 적어진다

     

    어디에다가 미리 등록해두면 프로그램에서 가져다가 쓰기 때문에 최소한의 소스 수정

    특정한 설정에 의해서 최소한의 수정으로 소스를 변경할 수 있다

    대표적인 예가 

    	<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
    	<!-- @Controllers 에서 반환 타입이 String 일 경우 이름만으로 해당 .jsp 를 찾도록 해주는 설정 -->
    	<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    		<beans:property name="prefix" value="/WEB-INF/views/" />
    		<beans:property name="suffix" value=".jsp" />
    	</beans:bean>

    스프링 프레임워크에서 객체 자원을 효율적으로 관리

    싱글톤 패턴 : 원본에서 복사본을 하나만 놓고 다른 사람들도 사용할 수 있게 정적 영역에다가 옮겨둔다

    전체적으로 정적 영역에 있다고 공지함

    @Service 어노테이션을 붙인 MemberService 는

    다른 클래스에서 @Autowire MemberService serivce 해주면 싱글톤 객체로 사용할 수 있다

    단점으로는 누군가가 이 싱글톤 객체를 바꾸어버리면 큰일난다

    @Autowired MemberDAO dao;

     

     

    Interceptor

    스프링 보안의 기본이다

    예시 )

    현재 : detail 요청이 들어옴 -> 세션에 로그인 정보를 확인함

    인터셉터 : 세션 로그인 정보를 확인함 -> detail 요청이 처리된다

     

    preHandler : 컨트롤러 요청 전에 확인하기

    preHandler : 컨트롤러 요청 후에 확인하기

     

    예시 ) preHandler, 지하철에 탑승 전에 표검사 preHandler,  놀이공원의 입장 팔찌

     

     

     

     

    관련 참고 자료

    https://gngsn.tistory.com/154

     

    Spring WebClient, 어렵지 않게 사용하기

    WebClient는 스프링 5.0에서 추가된 Blocking과 Non-Blocking 방식을 지원하는 HTTP 클라이언트입니다. - Reactor, 제대로 사용하기 - Error Handling - Reactive Programming, 제대로 이해하기 👉🏻 WebClient 소개 - Spring W

    gngsn.tistory.com

     

Designed by Tistory.