Nginx – Forward Proxy

Crawling을 통한 차단을 피하기 위해 proxy를 이용하던 도중 실제로 forward proxy가 어떻게 동작하고, 원하는 방향으로 동작하고 있는지를 확인하기 위하여 Nginx를 통해 구축했습니다.

Forward proxy와 Reverse proxy에 대해서 간략하게 보면,

Forward proxy

Forward proxy는 target이 되는 URL을 client에서 직접 요청하는 것이 아니라 proxy server가 대신 요청을 하고, 그 결과를 client에 전달하는 형태입니다.

이 때, proxy server에 content를 caching 할 경우 응답속도 향상을 기대할 수 있습니다.

Reverse proxy

요청을 target이 되는 server가 직접 받는 것이 아니라 앞단의 proxy server가 받는 형태입니다. 운영해본 경험이 있는 in-house cache server의 경우 이러한 reverse proxy 구조를 하고 있었습니다.

In-house cache server는 Nginx + Apache Trafficserver 조합으로, origin( target server )의 static content caching이 가능하였습니다. 이것을 통해 origin의 load 감소와 SSD를 통한 disk read 향상 혹은 memory hit를 통해 보다 빠른 content delivery가 가능했습니다.

Skill

  • Python의 requests module를 통해서 요청을 proxy를 통해 우회하도록 하였습니다.
  • Nginx에서는 forward proxy가 가능하도록 ngx_http_proxy_connect_module을 통해 proxy connection 설정해 놓았습니다.

Python

import requests

# url = 'http://(http_server_url)'
# url = 'https://(https_server_url)'

proxies = {
    # 'http':'http://(http_proxy_server_url):(http_proxy_server_port)',
    # 'https':'https://(https_proxy_server_url):(https_proxy_server_port)'
}

try :
    r = requests.get(url, proxies=proxies)

    print(r.status_code)
    for k,v in r.headers.items() :
        print(k, v)

except Exception as e :
    print(e)

해당 code를 통해, Python requests module에서 http, https 요청에 대해 각각 proxies dictionary의 http와 https key value를 정확하게 경유 하는지가 궁금하였습니다. 실제로 Nginx에서 보내온 response header를 통해서 정상적으로 경유를 하고 있구나라는 것을 확인하였습니다.

Nginx

HTTP

events {}
http {
        log_format detail
                '\n============================\n'
                '$server_protocol $status \n'
                'Client : $remote_addr \n'
                'Time   : [$time_local]  \n'
                'URI    : $request_uri\n'
                '============================\n';

        server {
                listen ( http_proxy_server_port );
                resolver ( DNS_server_IP);

                access_log logs/detail.log detail;
                location / {
                        proxy_pass http://$host$uri$is_args$args;
                }
        }
}

HTTP의 경우 단순히 proxy_pass를 통해서 target server로 요청을 보냈습니다. proxy chaining이나 tunneling을 하는 test가 아니므로connect method는 불필요하여 ngx_http_proxy_connect_module를 사용하지 않았습니다. Target server에서 add_header host $remote_addr를 response header로 추가해 주면, Python에서 response를 받을 때 어떤 proxy를 경유했는지 쉽게 확인할 수 있었습니다.

HTTPS

events {}
http {
        log_format detail
                '\n============================\n'
                '$server_protocol $status \n'
                'Client : $remote_addr \n'
                'Time   : [$time_local]  \n'
                'URI    : $request_uri\n'
                '============================\n';

        server {
                listen ( https_proxy_server_port );
                resolver 127.0.0.1;

                access_log logs/detail.log detail;

                proxy_connect;
                proxy_connect_allow 443;
        }
}

HTTPS proxy는 ngx_http_proxy_connect_module 필요 했습니다. 그 이유는 HTTPS 통신에서는 proxy server가 encrypt된 content를 알 수 없기 때문에, 해당 content를 확ㅇ니하지 않고 connect method를 통해서 전달 하기 위해서입니다.

Resolver를 127.0.0.1 둔 이유는 구성이 SSL key를 가진 domain에 여러 host가 등록된 상태 에서 /etc/hosts의 수정을 통해 domain을 한 대의 host IP에 고정시키게 하기 위해서 입니다.

Proxy chain: https://johyungen.tistory.com/16
ngx_http_proxy_connect_module: https://github.com/chobits/ngx_http_proxy_connect_module
Connect method: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/CONNECT
Connect tunnel: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/CONNECT
Forward proxy & Reverse proxy 차이: https://www.lesstif.com/pages/viewpage.action?pageId=21430345

Post Author: Jinn