본문 바로가기
JAVA

Server Networking Proxy

by oncerun 2021. 11. 14.
반응형

내부망에서 외부망과의 통신 시 Direct connection보다는 보안상 이유로 Proxy Server을 통하여 연결하는 경우가 생각보다 많습니다. 

 

그렇기에 시스템의 설정 부분부터 실제 애플리케이션 코드상에서 두 가지 작업을 진행해 주어야 합니다.

 

 

Linux 설정

 

해당 애플리케이션이 Linux 기반으로 동작하는 서버라면 우선 외부로 나가는 트래픽을 프락시를 통해 외부로 나가는 요청을 할 수 있도록 해주어야 합니다.

 

.bashrc,. bash_profile,. bashrc_profile에 설정합니다.

vi ~/.bashrc

export http_proxy=http://www.proxyserver.com:port
export https_proxy=http://www.proxyserver.com:port

# 저장후
source ~/.bashrc

 이제 curl 혹은 wget을 통해 외부데이터를 가져오면 프락시 설정이 제대로 됐습니다.

 

 

소스 코드 설정

소스코드상에서 지정할 수 있는 프로토콜의 종류는 다양합니다. 예를 들어 HTTP, HTTPS, FTP SOCKS 등 존재합니다. 

 

Java SE 1.4 이하를 사용하고 있다면 유연한 방법을 사용하지 못합니다.

Java SE 1.4에서 네트워크 프락시를 설정하는 방법은 2가지가 존재합니다.

 

  • System.setProperty(String, String)
  • VM command line option

 

1. 시스템 속성

 

시스템 속성 사용의 가장 문제점은 "설정된 모든 프로토콜에 대한 적용" or "전무" 설정을 진행하기 때문입니다. 특정 프로토콜에 대해 프락시가 설정되면 해당 프로토콜에 대한 모든 연결에 영향을 미칩니다. 또한 시스템 속성을 설정할 수 있는 권한이 있어야 합니다.

String proxyIP = "127.0.0.1";
String proxyHostName = "www.proxy.com"

System.setProperty("http.proxyHost", proxyHostName 이나 proxyIP);
System.setProperty("http.proxyPort", "8080") // 포트 설정값을 지정하지 않으면 기본 포트인 80번을 사용합니다.


URL url = new URL("연결하고 싶은 서버 주소");
InputStream ins = url.openSream();

System.clearProperty("http.proxyHost");

 

 

2. VM Command Line Option

 

VM구동 시 전역 설정을 할 수 있습니다.

java 
-Dhttp.proxyHost=www.proxy.com 
-Dhttp.proxyPort=8080 
-Dhttp.nonProxyHosts="localhost|Direct Connection해야하는host" GetURL

 nonProxyHosts : 프락시로 우회하여 직접 도달해야 하는 호스트 목록입니다. "|"로 구분된 패턴을 적용하거나, 와일드카드로 범위 설정 가능합니다. 패턴에 일치된 호스트는 직접 연결을 합니다.

 

socksProxyHost를 제외하고 다른 프로콜은 http대신 해당 프로콜을 적어주면 됩니다.

 

SOCKS란 RFC 1928에서 정의된 클라이언트 서버 응용 프로그램이 TCP 및 UDP 수준에서 방화벽을 안전하게 통과할 수 있는 프레임워크를 제공합니다.  이러한 의미에서 HTTP 또는 FTP 특정 프락시와 같은 상위 수준 프락시보다 훨씬 더 일반적입니다.  Jave SE 5.0은 클라이언트 TCP 소켓에 대한 SOCKS 지원을 제공합니다.

socksProxyHost : SOCKS 프록시 서버의 호스트 이름
socksProxyPort : 포트 번호의 경우 기본값은 1080


java -DsocksProxyHost=socks.proxy.com GetURL

이후 나가는 모든 TCP 소켓은 SOCKS 프록시 서버를 통과합니다. 

다만 SOCKS 프락시와 HTTP 프록시가 모두 정의되면 우선순위 규칙은 HTTP 또는 FTP와 같은 상위 수준 프로토콜에 대한 설정이 SOCKS 설정보다 우선합니다. 따라서 특정 경우에 HTTP 연결을 설정할 때 SOCKS 프락시 설정이 무시되고 HTTP 프락시에 연결됩니다.

 

 

시스템 속성 설정의 문제점

 

이 시스템 속성은 문제점은 특정 프로토콜에 대한 모든 속성 적용이 문제점이라고 했습니다.

마지막 라인에 존재하는 clearProperty()라는 메서드로 시스템 속성 해제를 진행할 수 있지만

그 문제점은 다중 스레드 환경에서 발생합니다. 

시스템 속성이 VM 전체 설정이기 때문에 모든 스레드가 영향을 받는다는 점, 하나의 스레드의 부작용으로 다른 스레드에 영향을 미칠 수 있다는 점이 가장 큰 문제입니다.

 

이러한 시스템 속성은 유연하지 않기 때문에 개발자 입장에서는 매우 강력한 제한으로 적용됩니다. 이러한 프락시 설정을 좀더 유연하게 설정하기 위해 Java SE 5.0에서 새롭고 더 유연한 API를 도입했습니다.

 

Proxy Class

 

이 Proxy Class의 핵심은 프락시 정의, 유형 및 소켓 주소를 나타내는 proxy 클래스입니다.  기존 JVM 전체 프락시 설정이 있는 경우 프락시 클래스를 사용하는 연결 기반 프락시 설정이 기존 JVM 프락시 설정을 재정의합니다.

 

  • DIRECT  : 직접 연결 또는 프록시 부재를 나타냅니다.
  • HTTP :  HTTP 프로토콜을 사용하는 프락시를 나타냅니다.
  • SOCKS :  SOCKS v4 또는 v5를 사용하는 프락시를 나타냅니다.

 

HTTP 프록시 객체를 생성하려면 다음을 따릅니다.

SocketAddress addr = new InetSocketAddress("proxyHostNAME", 8080);
Proxy proxy = new Proxy(Proxy.Type.HTTP, addr);


or

URL url =new URL(URL_STRING);
Proxy webProxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8081));

HttpsURLConnection webProxyConnection = (HttpURLConnection) url.openConnection(webProxy);

 

프락시 객체는 프락시 정의를 나타내며 그 이상은 아닙니다. 이 프락시 객체는 openConnection() 메서드가 URL클래스에 추가되었으며 해당 인수로 사용됩니다.

 

 

만약 시스템 Default Proxy Server가 설정되어 있는 경우  해당 프록시 서버를 우회해서 직접 통신도 가능합니다.

이경우에는  단지 Proxy.NO_PROXY로 새로운 연결을 얻으면 됩니다.

 

 

ProxySelector

 

Java SE 5.0 을 통해 프락시 서버를 경유해야 하는 경우 상당히 많은 제어 능력과 유연성을 얻게 되었습니다.

추가적으로 프록시프락시 간에 약간의 로드 밸런싱을 하거나, 대상에 따라 어떤 프락시를 동적으로 사용할지 결정하고 싶은 상황에 적용할 수 있습니다. 기존 Proxy클래스를 통해 진행한다면 생각보다 번거롭습니다.

이 경우에 사용할 수 있는 클래스가 ProxySelector 입니다.

 

  JVM은 서로 다른 네트워크 연결에 필요한 프록시 서버를 찾기위해 java.net.ProxySelector를 사용한다. 시스템 설정과 프로토콜을 통해 다른 호스트에 대한 연결 방법을 찾는 방식이다. 그리고 ProxySelector의 서브클래스를 작성해 기본 셀렉터를 변경할 수도 있다. 

 

 

ProxySelector는 프로토콜 핸들러에게 주어진 URL에 대해 어떤 프락시를 사용할지 알려주는 코드입니다. 

 

public abstract class ProxySelector{

	public static ProxySelector getDefault();
    public static void setDefault();
    public abstract List select(URI uri);
    public abstarct void connectFailed(URI uri, SOocketAddress, IOException ioe);
    

}

 

 

구현 시 URI의 프로토콜이 실제로 프락시 서버를 경유해야하는 프로토콜인지 검사만 진행하고,  이 경우 프록시 목록을 반환하고, 그렇지 않으면 기본 프로토콜에 위임하면 된다.

 

public Class MyProxySelector extends ProxySelector { 
        ProxySelector defsel = null; 
        MyProxySelector(ProxySelector def) { 
                defsel = def; 
        } 
        
        public java.util.List<Proxy> select(URI uri) { 
                if (uri == null) { 
                        throw new IllegalArgumentException("URI는 null일 수 없습니다."); 
                } 
                String protocol = uri.getScheme(); 
                if ("http".equalsIgnoreCase(protocol) || 
                        "https".equalsIgnoreCase(protocol)) { 
                        ArrayList<Proxy> proxyList = new ArrayList<Proxy>();
                        return proxyList; 
                } 
                if (defsel != null) { 
                        return defsel.select(uri); 
                } else { 
                        ArrayList<Proxy> proxyList = new ArrayList<Proxy>(); 
                        proxyList.add(Proxy.NO_PROXY); 
                        return proxyList; 
                } 
        } 
}

 

이제 connectFailed() 메서드를 정의해야 합니다.

 

 connectFailed()메서드는 select() 메서드에서 반환된 프락시 중 하나에 연결하지 못할 때마다 프로토콜 처리기에 의해 호출됩니다.

총 3개의 인수가 전달됩니다.

 첫 번째 인수는 핸들러가 도달하려고 시도한 URI( select()호출 시 사용된 URI)

 두 번째 인수는 핸들러가 SocketAddress연결을 시도한 프락시 

 세 번째 인수는 프락시에 연결을 시도할 때 throw 된 IOException입니다.

 

만약 연결이 실패할 경우 콜백 메서드인 connectionFaild() 메서드가 자동 반환되어 실행된다.

 

 해당 정보를 사용하여 다음을 수행합니다. 프락시가 목록에 있고 3번 이상 실패한 경우 목록에서 제거하여 앞으로 다시 사용하지 않도록 합니다.

 

public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { 
        if (uri == null || sa == null || ioe == null) { 
            throw new IllegalArgumentException("인수는 null일 수 없습니다."); 
        } 
        
        InnerProxy p = proxies.get(sa); 
        if (p != null) { 
                if (p.failed() >= 3) 
                   proxies.remove(sa); 
        } else { 
                if (defsel != null) 
                    defsel.connectFailed(uri, sa, ioe); 
        } 
}

 

이제 애플리케이션에 적용 시

public static void main(String[] args) {
        MyProxySelector ps = new MyProxySelector(ProxySelector.getDefault());
        ProxySelector.setDefault(ps);
        // rest of the application
}

 

 

전체 예제 코드

import java.net.*;
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.io.IOException;

public class MyProxySelector extends ProxySelector {
        // Keep a reference on the previous default
    ProxySelector defsel = null;
        
        /*
         * Inner class representing a Proxy and a few extra data
         */
        class InnerProxy {
       			Proxy proxy;
                SocketAddress addr;
                // How many times did we fail to reach this proxy?
                int failedCount = 0;
                
                InnerProxy(InetSocketAddress a) {
                        addr = a;
                        proxy = new Proxy(Proxy.Type.HTTP, a);
                }
                
                SocketAddress address() {
                        return addr;
                }
                
                Proxy toProxy() {
                        return proxy;
                }
                
                int failed() {
                        return ++failedCount;
                }
        }
        
        /*
         * A list of proxies, indexed by their address.
         */
        HashMap<SocketAddress, InnerProxy> proxies = new HashMap<SocketAddress, InnerProxy>();

        MyProxySelector(ProxySelector def) {
          // Save the previous default
          defsel = def;
          
          // Populate the HashMap (List of proxies)
          InnerProxy i = new InnerProxy(new InetSocketAddress("webcache1.example.com", 8080));
          proxies.put(i.address(), i);
          i = new InnerProxy(new InetSocketAddress("webcache2.example.com", 8080));
          proxies.put(i.address(), i);
          i = new InnerProxy(new InetSocketAddress("webcache3.example.com", 8080));
          proxies.put(i.address(), i);
          }
          
          /*
           * This is the method that the handlers will call.
           * Returns a List of proxy.
           */
          public java.util.List<Proxy> select(URI uri) {
                // Let's stick to the specs. 
                if (uri == null) {
                        throw new IllegalArgumentException("URI can't be null.");
                }
                
                /*
                 * If it's a http (or https) URL, then we use our own
                 * list.
                 */
                String protocol = uri.getScheme();
                if ("http".equalsIgnoreCase(protocol) ||
                        "https".equalsIgnoreCase(protocol)) {
                        ArrayList<Proxy> l = new ArrayList<Proxy>();
                        for (InnerProxy p : proxies.values()) {
                          l.add(p.toProxy());
                        }
                        return l;
                }
                
                /*
                 * Not HTTP or HTTPS (could be SOCKS or FTP)
                 * defer to the default selector.
                 */
                if (defsel != null) {
                        return defsel.select(uri);
                } else {
                        ArrayList<Proxy> l = new ArrayList<Proxy>();
                        l.add(Proxy.NO_PROXY);
                        return l;
                }
        }
        
        /*
         * Method called by the handlers when it failed to connect
         * to one of the proxies returned by select().
         */
        public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
                // Let's stick to the specs again.
                if (uri == null || sa == null || ioe == null) {
                        throw new IllegalArgumentException("Arguments can't be null.");
                }
                
                /*
                 * Let's lookup for the proxy 
                 */
                InnerProxy p = proxies.get(sa); 
                        if (p != null) {
                                /*
                                 * It's one of ours, if it failed more than 3 times
                                 * let's remove it from the list.
                                 */
                                if (p.failed() >= 3)
                                        proxies.remove(sa);
                        } else {
                                /*
                                 * Not one of ours, let's delegate to the default.
                                 */
                                if (defsel != null)
                                  defsel.connectFailed(uri, sa, ioe);
                        }
     }
}
반응형

'JAVA' 카테고리의 다른 글

가변인수  (0) 2022.11.05
JDK Dynamic Proxy  (0) 2022.01.22
메일 발송  (0) 2021.11.06
JavaMail API  (0) 2021.10.24
[JAVA] CLASS 클래스  (0) 2020.04.23

댓글