[11장] Proxy Pattern

ss0510s·2023년 8월 2일
0

Proxy Pattern

특정 객체로의 접근을 제어하는 대리인(특정 객체를 대변하는 객체)를 제공하는 패턴이다.

  • 대상 객체에 접근하기 전 그 접근에 대한 흐름을 가로채 대상 객체 앞단의 인터페이스 역할을 하는 디자인 패턴이다.

접근 제어 방법

  • 원격 프록시를 사용하여 원격 개체로의 접근을 제어할 수 있다.
  • 가상 프록시를 써서 생성하기 힘든 자원으로의 접근을 제어할 수 있다.
  • 보호 프록시를 써서 접근 권한이 필요한 자원으로의 접근을 제어할 수 있다.

모니터링 코드

로컬 모니터링

모니터링할 클래스

public class GumballMachine {
	String location;
    
    public GumballMachine(String location) {
    	this.location = location;
    }
    public String getLocation() { return location; }

모니터링 클래스

public class GumballMonitor{
	GumballMachine machine;
    
    // 생성자로부터 객체를 전달받아서 인스턴스 변수에 저장한다.
    public GumballMonitor(GumballMachine machine) {
    	this.machine = machine;
    }
    public void report() {
    	System.out.println(machine.getLocation());
    }
}

원격 프록시

  • 원격 객체(JVM의 힙에 살고 있는 객체 - 다른 주소 공간에서 돌아가고 있는 객체)의 로컬 대변자(어떤 메서드를 호출하면, 다른 원격 객체에게 그 메서드 호출을 전달해 주는 객체) 역할을 한다.
  • 클라이언트 객체(Gumble Machine)은 원격 객체의 메소드 호출을 하는 것처럼 행동하지만, 로컬 힙에 들어있는 프록시 객체의 메서드를 호출한다.
  • RMI: 클라이언트와 서비스 보조 객체를 생성해준다. 보조 객체에는 원격 서비스와 동일한 메소드가 들어있어, RMI를 사용하면 네트워킹 및 입출력 관련 코드를 직접 접근하지 않아도 된다.
    • RMI에서 클라이언트 보조 객체는 스텁(프록시 역할), 서비스 보조 객체는 스켈레톤이라 부란다.

원격 인터페이스

  • java.rmi.remote를 확장한다.
  • 모든 메서드가 RemoteException을 던지도록 한다. -> 원격 메소드 호출은 위험이 동반하는 것으로 간주한다.
  • 원격 메소드의 인자와 리턴값은 반드시 primitive 또는 Serializable형식으로 선언한다.
import java.rmi.*;

public interface GumballMachineRemote extends Remote{
	public String getLocation() throws RemoteException;
}

서비스 구현 클래스

  • 원격 인터페이스를 구현하고, UnicastRemoteObject를 확장하여 원격 서비스 객체 역할을 수행하도록 한다.
  • RemoteException을 선언하는 생성자를 구현한다.
public class GumballMachine extends UnicastRemoteObject implements MyRemote {
	private static final long serverVersionUID = 1L; // serializable
    String location;
	public GumballMachine(String location) throws RemoteException{ this.location = location; }
	public String getLocation(){ return location};
}

서버 테스트

  • 서비스를 RMI 레지스트리에 등록하여 원격 서비스를 원격 클라이언트에서 사용할 수 있도록 한다.
public class GUmballMachineTestDrive{
	public static void main(String[] args) {
    	GumballMachineRemote gumballMachine = null;
        
        try {
        	gumballMachine = new GumballMachines(args[0]);
            // Naming.rebind함수로 GumballMachine 스텁을 GumballMahine이라는 이름으로 등록한다.
            Naming.rebind("//" + args[0] + "/gumballmachine", gumballMachine);
        } catch(Exception e) {
        	e.printStackTrace();
        }
    }
}

클라이언트 클래스

  • 메소드를 네트워크로 호출하므로 Remote Exception을 잡을 try~catch문이 필요하다.
public class GumballMonitor{
	GumballMachineRemote machine; // 원격 인터페이스 사용
    
    public GumballMonitor(GumballMachineRemote machine) {
    	this.machine = machine;
    }
    public void report() {
    	try {
        		System.out.println(machine.getLocation());
            } catch(Exception e) {
            	ex.printStackTrace();
            }
        
        }
    }
}

Test Class

public static void main(String[] args) {
	String[] location = {"rmi:// ..", "rmi://"}; // 모니터링할 위치
	GumballMonitor[] monitor = GumballMonitor[location.length];
    
    for(int i = 0; i<location.length; i++){
    	try{
        // 서비스를 RMI 레지스트리에 등록
        	GumballMachineRemote machine = (GumballMachineRemote) Naming.lookup(location[i]); // 각 기계의 프록시
            // 생성자에 모니터링할 객체의 프록시를 넘겨준다.
            monitor[i] = new GumballMonitor(machine);
        } catch (Exception e){
        	e.printStackTrace();
        }
    }
	
}

원격 메소드

  • 클라이언트 힙
    • 클라이언트 객체: 실제 서비스의 메소드를 호출한다고 생각한다. 메소드 호출이 어디로 전달되고 리턴되었는지 알 수 없다.
    • 클라이언트 보조 객체: 클라이언트에서 온 요청을 원격 객체에게 전달한다.
  • 서버 힙
    • 서비스 보조 객체: 클라이언트 보조 객체로 부터 받은 요청을 해석하여 진짜 서비스에 있는 메서드를 호출한다.
    • 서비스 객체: 실제 작업을 처리하는 메소드가 있는 객체

동작 과정

  • 클라이언트 객체는 프록시(클라이언트 보조 객체)가 서비스를 제공한다고 생각하고 프록시의 메소드를 호출한다.
  • 프록시의 메소드가 호출되면 프록시는 그 호출을 서버에 전달한다.
  • 스켈레톤(서비스 보조 객체)은 그 요청을 받아 서비스 객체에 전달한다.
  • 서비스 객체는 스켈레톤에게 상태를 리턴하고, 스켈레톤은 리턴 값을 직렬화한 다음 네트워크로 프록시에게 전달한다.
  • 프록시는 리턴값을 역직렬화해서 클라이언트 객체에게 전달한다.

프록시 패턴 활용

원격 프록시

  • 다른 JVM에 들어있는 객체의 대리인에 해당하는 로컬 객체이다.
  • 프록시의 메소드를 호출하면 그 호출이 네트워크로 전달되어 원격 객체의 메소드를 호출하고, 그 결과가 다시 프록시를 거쳐 클라이언트에게 전달된다.

가상 프록시

  • 생성하는데 많은 비용이 드는 객체를 대신한다.
  • 진짜 객체가 필요한 상황이 오기 전까지 객체의 생성을 미룬다.
  • 객체 생성 전이나 생성 도중에 객체를 대신한다.
  • 객체 생성이 종료되면 실제 객체에게 직접 요청을 전달한다.

동적 프록시

  • java.lang.reflect 패키지 안에 내장된 프록시 기능을 사용하여 즉석에서 하나 이상의 인터페이스를 구현하고, 지정한 클래스에 메소드 호출을 전달하는 프록시 클래스를 생성할 수 있다. 런타임 시점에 프록시가 생성된다.
  • InvocationHandler(프록시에 호출되는 모든 메소드에 응답)를 사용하여 실제 객체의 메소드로의 접근을 제어한다.

보호 프록시

  • 접근 권한을 바탕으로 객체로의 접근을 제어하는 프록시이다.
  • 동적 프록시를 활용해서 동적으로 프록시 클래스를 생성하는 프록시 객체 인스턴스를 생성한다.

프록시 서버

서버와 클라이언트 사이에서 클라이언트가 자신을 통해 다른 네트워크 서비스에 간접적으로 접근할 수 있도록 해주는 컴퓨터 시스템이나 응용 프로그램이다.

nginx

  • 비동기 이벤트 기반의 구조와 다수의 연결을 효과적으로 처리 가능한 웹서버로, node.js 서버 앞단에서 프록시 서버로 활용한다. → 보안성 강화

CloudFlare

  • 전세계적으로 분산된 서버가 있고, 이를 통해 어떠한 시스템의 콘텐츠 전달을 빠르게 할 수 있는 CDN서비스
    • DDOS 공격 방어: 의심스러운 트래픽 또는 시스템을 통해 오는 트래픽을 자동으로 차단해서, 짧은 시간 동안 네트워크에 많은 요청을 보내 네트워크를 마비시켜 웹사이트의 가용성을 방해하는 사이버 공격인 DDOS 방지 → 거대한 네트워크 용량과 캐싱 전략으로 소규모 DDOS 공격도 방지
    • HTTPS 구축: 별도의 인증서 필요없이 쉽게 구축 가능

** CDN: 각 사용자가 인터넷에 접속하는 곳과 가까운 곳에서 콘텐츠를 캐싱 또는 배포하는 서버 네트워크

CORS의 프론트엔드

  • CORS: 서버가 웹 브라우저에서 리소스를 로드할 때 다른 오리진(프로토콜, 호스트 이름, 포트의 조합)을 통해 로드하지 못하게 하는 HTTP 헤더 기반 메커니즘 (동일 출처 정책)
  • 프론트엔드와 백엔드 서버의 포트번호가 다르면 CORS 에러 발생 → 프록시 서버를 둬서 프론트엔드 서버에서 요청되는 오리진을 백엔드 서버 포트번호로 변경
profile
개발자가 되기 위해 성장하는 중입니다.

1개의 댓글

comment-user-thumbnail
2023년 8월 2일

정보 감사합니다.

답글 달기