プロキシ経由の HTTP アクセス (proxy)

Java

プロキシ host = atomic.jpn.ph, port = 3128 を通して url = http://www.google.com/ からの一行目を出力するサンプル。

その1:直接指定

プロキシを直接指定。プロキシオブジェクトを作り、それを使ってコネクションを開く。

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;

public class Sono1
{
	private static final String PROXY_HOST = "atomic.jpn.ph";
	private static final int PROXY_PORT = 3128;

	private static final String TARGET_URL = "http://www.google.com/";


	public static void main(String[] args) {
		try {
			// Proxy
			Proxy proxy = new Proxy(
				Proxy.Type.HTTP,
				new InetSocketAddress(PROXY_HOST, PROXY_PORT));

			// Access
			URLConnection conn = new URL(TARGET_URL).openConnection(proxy);
			BufferedReader br = new BufferedReader(
				new InputStreamReader(conn.getInputStream()));
			System.out.println(br.readLine());
			br.close();
		} catch (Exception e) {
			System.out.println(e);
		}
	}
}

その2:簡易方法

システムのプロキシ設定を使用する。Java がその機能を持っているので、プロキシを使用する設定をするのみ。

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;

public class Sono2
{
	private static final String TARGET_URL = "http://www.google.com/";


	public static void main(String[] args) {
		System.setProperty("java.net.useSystemProxies", "true");
		System.setProperty("http.proxyHost", "");
		System.setProperty("http.proxyPort", "");
		try {
			// Access
			// Java 実装がプロパティを見てプロキシを使ってくれるので、
			// プロキシに関してやることは無い。
			URL url = new URL(TARGET_URL);
			BufferedReader br = new BufferedReader(
				new InputStreamReader(url.openStream()));
			System.out.println(br.readLine());
			br.close();
		} catch (Exception e) {
			System.out.println(e);
		}
	}
}

システム管理者ならば、3つの setProperty は net.properties にて環境設定してもよい。この場合、プログラムではプロキシに関するコードは書かなくて良いことになる。

上記コードのようにプログラムでプロパティを設定すると、このプログラム内ではこの設定が net.properties の記述より優先される。

なお、各プロパティ値の意味は以下の通り。

java.net.useSystemProxies true でシステムのプロキシ設定を使う。
http.proxyHost 指定したプロキシを使う。java.net.useSystemProxies より優先する。
http.proxyPort http.proxyHost が指定されているときのプロキシのポートの指定。

動作確認上の注意

Java 実装はプロキシを利用せずに直接のアクセスも試みるらしく、直接接続できる場合はプロキシが使われない場合がある。

その3:複数プロキシの使い分け

プロキシセレクタを使う。アクセス先によってプロキシを変更するなど、プログラムで自在にプロキシを制御できる。

もちろん、「その1」の方法でも自在に制御できるが、プロキシ選択部分をオブジェクト化する仕組みがあるので、これを使用する。

まずは、HTTP アクセスする部分。最初のプロキシセレクタの設定部分以外は「その2」と同じ。

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.ProxySelector;
import java.net.URL;

public class Sono3
{
	private static final String TARGET_URL = "http://www.google.com/";


	public static void main(String[] args) {
		// Proxy
		ProxySelector.setDefault(new MySelector());
		try {
			// Access
			URL url = new URL(TARGET_URL);
			BufferedReader br = new BufferedReader(
				new InputStreamReader(url.openStream()));
			System.out.println(br.readLine());
			br.close();
		} catch (Exception e) {
			System.out.println(e);
		}
	}
}

プロキシセレクタは ProxySelector を継承して作る。固定のプロキシを使用するシンプルなものは、以下の通り。HTTP アクセスなどがあると、Java 実装が select を呼び出してくるので、プロキシのリストを返してやれば良い。

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.SocketAddress;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

public class MySelector extends ProxySelector
{
	private static final String PROXY_HOST = "atomic.jpn.ph";
	private static final int PROXY_PORT = 3128;

	ArrayList<Proxy> proxy = new ArrayList<Proxy>();

	public MySelector() {
		proxy.add(new Proxy(Proxy.Type.HTTP,
				  new InetSocketAddress(PROXY_HOST, PROXY_PORT)));
	}

	public java.util.List<Proxy> select(URI uri) {
		return proxy;
	}

	public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
	}
}

以下は、もう少しマシなプロキシセレクタの例。

import java.io.IOException;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.SocketAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.ArrayList;


public class MySelector extends ProxySelector
{
	// Initialize data
	class ProxyInfo {
		String host;
		int port;
		ProxyInfo(String host, int port) {
			this.host = host;
			this.port = port;
		}
	}

	ProxyInfo[] httpProxyInit = {
		new ProxyInfo("atomic.jpn.ph", 3128),
		new ProxyInfo("atomic.jpn.ph", 8080),
	};
	ProxyInfo[] ftpProxyInit = {
		new ProxyInfo("atomic.jpn.ph", 8021),
		new ProxyInfo("atomic.jpn.ph", 8021),
	};
	ProxyInfo[] socksProxyInit = {
		new ProxyInfo("atomic.jpn.ph", 1080),
		new ProxyInfo("atomic.jpn.ph", 1080),
	};


	ArrayList<Proxy> httpProxy = new ArrayList<Proxy>();
	ArrayList<Proxy> ftpProxy = new ArrayList<Proxy>();
	ArrayList<Proxy> socksProxy = new ArrayList<Proxy>();
	ArrayList<Proxy> noProxy = new ArrayList<Proxy>();

	public MySelector() {
		init(httpProxy, Proxy.Type.HTTP, httpProxyInit);
		init(ftpProxy, Proxy.Type.HTTP, ftpProxyInit);
		init(socksProxy, Proxy.Type.SOCKS, socksProxyInit);
		noProxy.add(Proxy.NO_PROXY);
	}

	private void init(ArrayList<Proxy> proxies, Proxy.Type type,
						ProxyInfo[] pis) {
		if (pis != null) {
			for (ProxyInfo pi: pis) {
				proxies.add(
					new Proxy(type, new InetSocketAddress(pi.host, pi.port)));
			}
		}
	}

	public java.util.List<Proxy> select(URI uri) {
		// uri が null の時は IllegalArgumentException を投げるべき。
		if (uri == null) {
			throw new IllegalArgumentException();
		}

		// 用途別に異るプロキシを返す。
		String scheme = uri.getScheme();
		if ("http".equalsIgnoreCase(scheme) ||
			"https".equalsIgnoreCase(scheme)) {
			return httpProxy;
		} else if ("ftp".equalsIgnoreCase(scheme)) {
			return ftpProxy;
		} else if ("socket".equalsIgnoreCase(scheme)) {
			return socksProxy;
		}

		// どうしようもない時は Proxy.NO_PROXY のリストを返すべき。
		return noProxy;
	}

	// select で返されたプロキシによってアクセスに失敗すると、
	// 失敗したプロキシの情報でこれが呼ばれる。
	public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
		// 失敗したプロキシはリストの最後へ。
		String scheme = uri.getScheme();
		if ("http".equalsIgnoreCase(scheme) ||
			"https".equalsIgnoreCase(scheme)) {
			lower(httpProxy, new Proxy(Proxy.Type.HTTP, sa));
		} else if ("ftp".equalsIgnoreCase(scheme)) {
			lower(ftpProxy, new Proxy(Proxy.Type.HTTP, sa));
		} else if ("socket".equalsIgnoreCase(scheme)) {
			lower(socksProxy, new Proxy(Proxy.Type.SOCKS, sa));
		}
	}
	private void lower(ArrayList<Proxy> list, Proxy proxy) {
		if (list.remove(proxy)) {
			list.add(proxy);
		}
	}
}

その4:自前プロトコル処理

プロキシや HTTP をサポートする class を使わずに、TCP ソケットを使って自前でプロトコル処理も行う。

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;

public class Sono4 {
	static String PROXY_HOST = "atomic.jpn.ph";
	static int PROXY_PORT = 3128;

	static String TARGET_HOST = "www.google.com";
	static int TARGET_PORT = 80;
	static String TARGET_PATH = "/";

	static int TIMEOUT = 3000;

	public static void main(String [] args) {
		SocketReaderWriter srw = null;
		String line;

		try {
			/////// プロキシ経由で接続

			srw = new SocketReaderWriter(PROXY_HOST, PROXY_PORT, TIMEOUT);
			// "CONNECT www.google.com:80 HTTP/1.0"
			srw.write("CONNECT " + TARGET_HOST + ":" + TARGET_PORT +
					  " HTTP/1.0\n\n");
			srw.flush();

			// 接続確認
			line = srw.readLine();
			if (!"HTTP/1.0 200 Connection established".equals(line)) {
				System.out.println("Connection error (" + line + ")");
				System.exit(1);
			}
			System.out.println("--- connected ---");


			/////// ページ取得

			// "GET / HTTP/1.0"
			srw.write("GET " + TARGET_PATH + " HTTP/1.0\n\n");
			srw.flush();

			// ヘッダ出力&ページ長さ取得
			System.out.println("--- header ---");
			int size = 0;
			while ((line = srw.readLine()) != null) {
				System.out.println(line);
				if (size > 0 && line.length() == 0) {
					// Header 終了
					break;
				}
				if (line.startsWith("Content-Length: ")) {
					size = Integer.valueOf(line.substring(16));
				}
			}

			// ページ本体出力
			System.out.println("--- body ---");
			char[] buf = new char[size];
			int len = srw.read(buf);
			if (len == -1) {
				System.out.println("Raed error");
				System.exit(1);
			}
			System.out.println(new String(buf, 0, len));
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (srw != null) {
					srw.close();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return;
	}
}

class SocketReaderWriter
{
	Socket socket = new Socket();
	BufferedWriter writer;
	BufferedReader reader;

	SocketReaderWriter(String host, int port, int timeout)
		throws IOException {

		socket.connect(new InetSocketAddress(host, port), timeout);
		socket.setSoTimeout(timeout);
		writer = new BufferedWriter(
			new OutputStreamWriter(socket.getOutputStream()));
		reader = new BufferedReader(
			new InputStreamReader(socket.getInputStream()));
	}

	void write(String str) throws IOException {
		writer.write(str);
	}

	void flush() throws IOException {
		writer.flush();
	}

	String readLine() throws IOException {
		return reader.readLine();
	}

	int read(char[] buf) throws IOException {
		return reader.read(buf);
	}

	void close() throws IOException {
		socket.close();
	}
}

メニューに戻る

Ruby

プロキシ host = atomic.jpn.ph, port = 3128 を通して url = http://www.google.com/ からの一行目を出力するサンプル。

その1

$ export http_proxy="http://atomic.jpn.ph:3128/"
require 'open-uri'

open("http://www.google.com/") { |file|
	puts file.readline
}

ステータスコード 302 などを受けとると、移動先ページを自動的に取得するようです。

その2

require 'open-uri'

open("http://www.google.com/",
	 :proxy => "http://atomic.jpn.ph:3128/") { |file|
	puts file.readline
}

ステータスコード 302 などを受けとると、移動先ページを自動的に取得するようです。

プロキシを無効化したい場合は nil を指定する。

その3

require 'net/http'

http = Net::HTTP.new("www.google.com", 80, "atomic.jpn.ph", 3128)
response = http.get("/")
puts response.body.split("\n")[0]

open-uri より原始的なアクセスを行い、ステータスコード 302 などを受けとっても、移動先ページを自動的に取得することはないようです。

メニューに戻る