Javaでの接続(およびこの接続のみ)に基本認証(ユーザー名、パスワード)でプロキシを使用したい。次のコードは、HTTP URLに対して機能します(例: " http://www.google.com "):
URL url = new URL("http://www.google.com");
HttpURLConnection httpURLConnection = null;
InetSocketAddress proxyLocation = new InetSocketAddress(proxyHost, proxyPort);
Proxy proxy = new Proxy(Proxy.Type.HTTP, proxyLocation);
httpURLConnection = (HttpURLConnection) url.openConnection(proxy);
// Works for HTTP only! Doesn't work for HTTPS!
String encoded = new Sun.misc.BASE64Encoder().encodeBuffer((proxyUserName + ":" + proxyPassword).getBytes()).replace("\r\n", "");
httpURLConnection.setRequestProperty("Proxy-Authorization", "Basic " + encoded);
InputStream is = httpURLConnection.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
int data = isr.read();
while(data != -1){
char c = (char) data;
data = isr.read();
System.out.print(c);
}
isr.close();
ただし、HTTPS URL(例: " https://www.google.com ")ではコードは機能しません! HTTPS URLにアクセスしようとするとJava.io.IOException: Unable to tunnel through proxy. Proxy returns "HTTP/1.0 407 Proxy Authentication Required"
を受け取ります。
このコードはHTTPおよびHTTPSで機能します。
URL url = new URL("https://www.google.com");
HttpURLConnection httpURLConnection = null;
InetSocketAddress proxyLocation = new InetSocketAddress(proxyHost, proxyPort);
Proxy proxy = new Proxy(Proxy.Type.HTTP, proxyLocation);
httpURLConnection = (HttpURLConnection) url.openConnection(proxy);
// Works for HTTP and HTTPS, but sets a global default!
Authenticator.setDefault(new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(proxyUserName, proxyPassword.toCharArray());
}
});
InputStream is = httpURLConnection.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
int data = isr.read();
while(data != -1){
char c = (char) data;
data = isr.read();
System.out.print(c);
}
isr.close();
2番目のコードの問題は、新しいデフォルトのAuthenticator
を設定することです。このプロキシはアプリケーションの一部でのみ使用され、アプリケーションの別の部分は別のプロキシを使用します。アプリケーション全体にグローバルなデフォルトを設定したくありません。 HTTPSで動作する最初のコードを取得する方法や、デフォルトとして設定せずにAuthenticator
を使用する方法はありますか?
HttpURLConnection
を返す必要があるクラスのメソッドをオーバーライドしているため、Java.net.HttpURLConnection
を使用する必要があるため、Apache HttpClientを使用できません。
ProxiedHttpsConnection
を拡張して、低レベルに関連するすべてのものを自分で処理できます。
HTTPプロキシを介してhttps Webサイトに接続するには、次の手順を実行する必要があります。
注:プロキシおよびhttpサーバーとの通信は ASCII7 である必要があります。
CONNECT stackoverflow.com:443 HTTP/1.0\r\n
をプロキシに送信しますProxy-Authorization: Basic c2F5WW91SGF2ZVNlZW5UaGlzSW5UaGVDb21tZW50cw==\r\n
。\r\n
HTTP/1.0 200
で始まるかどうかを確認します。GET /questions/3304006/persistent-httpurlconnection-in-Java HTTP/1.0\r\n
Host: stackoverflow.com\r\n
\r\n
\r\n
まで読み取り、最初の行をステータスメッセージとして解析しますHttpUrlConnectionクラスを実装する場合、考慮すべきことがいくつかあります。
OutputStream
のクローズは、データ転送が完了したことを意味し、接続を終了する必要はありません簡単に言って、落とし穴はたくさんあります
私が設計したクラスでは、connect
メソッドとafterPostClosure
メソッドが呼び出されるかどうかをブール値フラグを使用して記憶します。また、OutputStream
が閉じる前にgetInputStream()
が呼び出される場合もサポートします。
また、このクラスは、本当に複雑になるのを防ぐために、ソケットによって返されるストリームのラップを可能な限り少なくします。
public class ProxiedHttpsConnection extends HttpURLConnection {
private final String proxyHost;
private final int proxyPort;
private static final byte[] NEWLINE = "\r\n".getBytes();//should be "ASCII7"
private Socket socket;
private final Map<String, List<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private final Map<String, List<String>> sendheaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private final Map<String, List<String>> proxyheaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private final Map<String, List<String>> proxyreturnheaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private int statusCode;
private String statusLine;
private boolean isDoneWriting;
public ProxiedHttpsConnection(URL url,
String proxyHost, int proxyPort, String username, String password)
throws IOException {
super(url);
socket = new Socket();
this.proxyHost = proxyHost;
this.proxyPort = proxyPort;
String encoded = Base64.encode((username + ":" + password).getBytes())
.replace("\r\n", "");
proxyheaders.put("Proxy-Authorization", new ArrayList<>(Arrays.asList("Basic " + encoded)));
}
@Override
public OutputStream getOutputStream() throws IOException {
connect();
afterWrite();
return new FilterOutputStream(socket.getOutputStream()) {
@Override
public void write(byte[] b, int off, int len) throws IOException {
out.write(String.valueOf(len).getBytes());
out.write(NEWLINE);
out.write(b, off, len);
out.write(NEWLINE);
}
@Override
public void write(byte[] b) throws IOException {
out.write(String.valueOf(b.length).getBytes());
out.write(NEWLINE);
out.write(b);
out.write(NEWLINE);
}
@Override
public void write(int b) throws IOException {
out.write(String.valueOf(1).getBytes());
out.write(NEWLINE);
out.write(b);
out.write(NEWLINE);
}
@Override
public void close() throws IOException {
afterWrite();
}
};
}
private boolean afterwritten = false;
@Override
public InputStream getInputStream() throws IOException {
connect();
return socket.getInputStream();
}
@Override
public void setRequestMethod(String method) throws ProtocolException {
this.method = method;
}
@Override
public void setRequestProperty(String key, String value) {
sendheaders.put(key, new ArrayList<>(Arrays.asList(value)));
}
@Override
public void addRequestProperty(String key, String value) {
sendheaders.computeIfAbsent(key, l -> new ArrayList<>()).add(value);
}
@Override
public Map<String, List<String>> getHeaderFields() {
return headers;
}
@Override
public void connect() throws IOException {
if (connected) {
return;
}
connected = true;
socket.setSoTimeout(getReadTimeout());
socket.connect(new InetSocketAddress(proxyHost, proxyPort), getConnectTimeout());
StringBuilder msg = new StringBuilder();
msg.append("CONNECT ");
msg.append(url.getHost());
msg.append(':');
msg.append(url.getPort() == -1 ? 443 : url.getPort());
msg.append(" HTTP/1.0\r\n");
for (Map.Entry<String, List<String>> header : proxyheaders.entrySet()) {
for (String l : header.getValue()) {
msg.append(header.getKey()).append(": ").append(l);
msg.append("\r\n");
}
}
msg.append("Connection: close\r\n");
msg.append("\r\n");
byte[] bytes;
try {
bytes = msg.toString().getBytes("ASCII7");
} catch (UnsupportedEncodingException ignored) {
bytes = msg.toString().getBytes();
}
socket.getOutputStream().write(bytes);
socket.getOutputStream().flush();
byte reply[] = new byte[200];
byte header[] = new byte[200];
int replyLen = 0;
int headerLen = 0;
int newlinesSeen = 0;
boolean headerDone = false;
/* Done on first newline */
InputStream in = socket.getInputStream();
while (newlinesSeen < 2) {
int i = in.read();
if (i < 0) {
throw new IOException("Unexpected EOF from remote server");
}
if (i == '\n') {
if (newlinesSeen != 0) {
String h = new String(header, 0, headerLen);
String[] split = h.split(": ");
if (split.length != 1) {
proxyreturnheaders.computeIfAbsent(split[0], l -> new ArrayList<>()).add(split[1]);
}
}
headerDone = true;
++newlinesSeen;
headerLen = 0;
} else if (i != '\r') {
newlinesSeen = 0;
if (!headerDone && replyLen < reply.length) {
reply[replyLen++] = (byte) i;
} else if (headerLen < reply.length) {
header[headerLen++] = (byte) i;
}
}
}
String replyStr;
try {
replyStr = new String(reply, 0, replyLen, "ASCII7");
} catch (UnsupportedEncodingException ignored) {
replyStr = new String(reply, 0, replyLen);
}
// Some proxies return http/1.1, some http/1.0 even we asked for 1.0
if (!replyStr.startsWith("HTTP/1.0 200") && !replyStr.startsWith("HTTP/1.1 200")) {
throw new IOException("Unable to tunnel. Proxy returns \"" + replyStr + "\"");
}
SSLSocket s = (SSLSocket) ((SSLSocketFactory) SSLSocketFactory.getDefault())
.createSocket(socket, url.getHost(), url.getPort(), true);
s.startHandshake();
socket = s;
msg.setLength(0);
msg.append(method);
msg.append(" ");
msg.append(url.toExternalForm().split(String.valueOf(url.getPort()), -2)[1]);
msg.append(" HTTP/1.0\r\n");
for (Map.Entry<String, List<String>> h : sendheaders.entrySet()) {
for (String l : h.getValue()) {
msg.append(h.getKey()).append(": ").append(l);
msg.append("\r\n");
}
}
if (method.equals("POST") || method.equals("PUT")) {
msg.append("Transfer-Encoding: Chunked\r\n");
}
msg.append("Host: ").append(url.getHost()).append("\r\n");
msg.append("Connection: close\r\n");
msg.append("\r\n");
try {
bytes = msg.toString().getBytes("ASCII7");
} catch (UnsupportedEncodingException ignored) {
bytes = msg.toString().getBytes();
}
socket.getOutputStream().write(bytes);
socket.getOutputStream().flush();
}
private void afterWrite() throws IOException {
if (afterwritten) {
return;
}
afterwritten = true;
socket.getOutputStream().write(String.valueOf(0).getBytes());
socket.getOutputStream().write(NEWLINE);
socket.getOutputStream().write(NEWLINE);
byte reply[] = new byte[200];
byte header[] = new byte[200];
int replyLen = 0;
int headerLen = 0;
int newlinesSeen = 0;
boolean headerDone = false;
/* Done on first newline */
InputStream in = socket.getInputStream();
while (newlinesSeen < 2) {
int i = in.read();
if (i < 0) {
throw new IOException("Unexpected EOF from remote server");
}
if (i == '\n') {
if (headerDone) {
String h = new String(header, 0, headerLen);
String[] split = h.split(": ");
if (split.length != 1) {
headers.computeIfAbsent(split[0], l -> new ArrayList<>()).add(split[1]);
}
}
headerDone = true;
++newlinesSeen;
headerLen = 0;
} else if (i != '\r') {
newlinesSeen = 0;
if (!headerDone && replyLen < reply.length) {
reply[replyLen++] = (byte) i;
} else if (headerLen < header.length) {
header[headerLen++] = (byte) i;
}
}
}
String replyStr;
try {
replyStr = new String(reply, 0, replyLen, "ASCII7");
} catch (UnsupportedEncodingException ignored) {
replyStr = new String(reply, 0, replyLen);
}
/* We asked for HTTP/1.0, so we should get that back */
if ((!replyStr.startsWith("HTTP/1.0 200")) && !replyStr.startsWith("HTTP/1.1 200")) {
throw new IOException("Server returns \"" + replyStr + "\"");
}
}
@Override
public void disconnect() {
try {
socket.close();
} catch (IOException ex) {
Logger.getLogger(ProxiedHttpsConnection.class.getName()).log(Level.SEVERE, null, ex);
}
}
@Override
public boolean usingProxy() {
return true;
}
}
上記のコードの現在のバグ:
上記のコードは次のように使用できます。
ProxiedHttpsConnection n = new ProxiedHttpsConnection(
new URL("https://stackoverflow.com:443/questions/3304006/persistent-httpurlconnection-in-Java"),
"proxy.example.com", 8080, "root", "flg83yvem#");
n.setRequestMethod("GET");
n.addRequestProperty("User-Agent", "Java test https://stackoverflow.com/users/1542723/ferrybig");
//try (OutputStream out = n.getOutputStream()) {
// out.write("Hello?".getBytes());
//}
try (InputStream in = n.getInputStream()) {
byte[] buff = new byte[1024];
int length;
while ((length = in.read(buff)) >= 0) {
System.out.write(buff, 0, length);
}
}
プロキシセレクターの種類でこれを使用する場合は、URLのプロトコルを確認して、httpまたはhttpsを確認し、httpの場合はこのクラスを使用せず、代わりに次のようにヘッダーを手動で添付する必要があります。
httpURLConnection.setRequestProperty("Proxy-Authorization", "Basic " + encoded);
Javaにはこのメソッドがありますが、使用しようとすると、なぜ機能しないのかがわかります。Javaは単に createSocket(Socket s, String Host, int port, boolean autoClose)
既に開かれている接続で、プロキシを手動で行うことができなくなります。
残念ながら、あなたが達成しようとしていることに対する簡単な解決策はありません。認証ヘッダーを直接設定しているため、最初のコードはHTTPSでは機能しません。クライアントはすべてのデータを暗号化するため、プロキシサーバーにはリクエストから情報を抽出する方法がありません。
実際、HTTPSとプロキシサーバーは相反する方法で機能します。プロキシサーバーは、クライアントと最終サーバーの間を流れるすべてのデータを表示し、表示内容に基づいてアクションを実行する必要があります。一方、HTTPSプロトコルはすべてのデータを暗号化するため、最終的な宛先に到達するまで誰もデータを見ることができません。暗号化アルゴリズムは、クライアントと最終宛先の間でネゴシエートされるため、プロキシサーバーは情報を解読できません。実際、クライアントが使用しているプロトコルを知ることさえできません。
HTTPS接続でプロキシサーバーを使用するには、クライアントがトンネルを確立する必要があります。これを行うには、プロキシにCONNECTコマンドを直接発行する必要があります。次に例を示します。
_CONNECT www.google.com:443 HTTP/1.0
_
資格情報を送信して、プロキシサーバーで認証します。
接続が成功すると、クライアントは接続を介してデータを送受信できます。プロキシサーバーは、データに対して完全にブラインドです。データは、クライアントとサーバーの間の途中でのみ通過します。
HTTP URLでurl.openConnection(proxy)
を実行すると、HttpURLConnection
のインスタンスが返され、2番目のコードのようにHTTPS URLで実行すると、HttpsURLConnection
のインスタンスが返されます。
プロキシサーバーは、送信したヘッダーから認証情報を抽出できないため、407エラーコードを受信しています。例外スタックを見ると、プロキシを介してHTTPSトンネルを確立するためにCONNECTコマンドを発行するSun.net.www.protocol.http.HttpURLConnection.doTunneling()
で例外がスローされていることがわかります。 _Sun.net.www.protocol.http.HttpURLConnection
_のソースコードでは、次のことがわかります。
_/* We only have a single static authenticator for now.
* REMIND: backwards compatibility with JDK 1.1. Should be
* eliminated for JDK 2.0.
*/
private static HttpAuthenticator defaultAuth;
_
したがって、プロキシ認証情報を提供する唯一の方法はデフォルトの認証システムであるようです。
必要なことを行うには、接続レベルまで下げてHTTPプロトコルを自分で処理する必要があります。これは、Googleサーバーと直接通信するのではなく、プロキシサーバーと通信する必要があるためです。
HttpsUrlConnectionを使用できますか? HttpUrlConnectionを拡張するため、クラスから戻るときにHttpUrlConnectionにキャストしても問題ない場合があります。
コードは似ていますが、HttpUrlConnectionの代わりに、名前にhttpsを含むコードを使用します。
次のコードを使用します。
if (testUrlHttps.getProtocol().toLowerCase().equals("https")) {
trustAllHosts();
HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
https.setHostnameVerifier(DO_NOT_VERYFY);
urlCon = https;
} else {
urlCon = (HttpURLConnection) url.openConnection();
}
出典:
[1] https://docs.Oracle.com/javase/7/docs/api/javax/net/ssl/HttpsURLConnection.html
[2] HttpURLConnection-"https://" vs. "http://" (スニペット)
OKこれはあなたがする必要があることです、
public class ProxyAuth extends Authenticator {
private PasswordAuthentication auth;
ProxyAuth(String user, String password) {
auth = new PasswordAuthentication(user, password == null ? new char[]{} : password.toCharArray());
}
protected PasswordAuthentication getPasswordAuthentication() {
return auth;
}
}
。
public class ProxySetup {
public HttpURLConnection proxySetup(String urlInput)
{
URL url;
try {
url = new URL(urlInput);
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("10.66.182.100", 80)); // or whatever your proxy is
HttpURLConnection uc = (HttpURLConnection)url.openConnection(proxy);
System.setProperty("https.proxyHost", "10.66.182.100");
System.setProperty("https.proxyPort", "80");
System.setProperty("http.proxyHost", "10.66.182.100");
System.setProperty("http.proxyPort", "80");
String encoded = new String(Base64.encodeBase64(("domain\\Username" + ":" + "Password").getBytes()));
uc.setRequestProperty("Proxy-Authorization", "Basic " + encoded);
Authenticator.setDefault(new ProxyAuth("domain\\Username", "Password"));
System.out.println("ProxySetup : proxySetup");
return uc;
} catch (Exception e) {
// TODO Auto-generated catch block
System.out.println("ProxySetup : proxySetup - Failed");
e.printStackTrace();
}
return null;
}
}
のように使用します。
HttpURLConnection conn = new ProxySetup().proxySetup(URL)