Ich möchte einen Proxy mit Basisauthentifizierung (Benutzername, Kennwort) für eine Verbindung (und nur diese Verbindung) in Java verwenden. Der folgende Code funktioniert für HTTP-URLs (z. B. " 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();
Der Code funktioniert jedoch nicht für HTTPS-URLs (z. B. " https://www.google.com ")! Ich bekomme Java.io.IOException: Unable to tunnel through proxy. Proxy returns "HTTP/1.0 407 Proxy Authentication Required"
, wenn ich versuche, auf eine HTTPS-URL zuzugreifen.
Dieser Code funktioniert für HTTP und 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();
Das Problem mit dem zweiten Code ist, dass er einen neuen Standardwert Authenticator
setzt. Ich möchte das nicht tun, da dieser Proxy nur von einem Teil der Anwendung verwendet wird und ein anderer Teil der Anwendung möglicherweise einen anderen Proxy verwendet. Ich möchte keinen globalen Standard für die gesamte Anwendung festlegen. Gibt es eine Möglichkeit, den ersten Code mit HTTPS zu arbeiten, oder eine Authenticator
verwenden, ohne sie als Standard festzulegen?
Ich muss Java.net.HttpURLConnection
verwenden, da ich eine Methode einer Klasse überschreibe, die eine HttpURLConnection
zurückgeben muss, sodass ich Apache HttpClient nicht verwenden kann.
Sie können ProxiedHttpsConnection
erweitern und alle damit zusammenhängenden Dinge selbst erledigen.
Die folgenden Schritte müssen ausgeführt werden, um eine Verbindung über einen HTTP-Proxy zu einer https-Website herzustellen:
Hinweis: Die Kommunikation mit dem Proxy- und http-Server sollte in ASCII7 erfolgen.
CONNECT stackoverflow.com:443 HTTP/1.0\r\n
an den ProxyProxy-Authorization: Basic c2F5WW91SGF2ZVNlZW5UaGlzSW5UaGVDb21tZW50cw==\r\n
.\r\n
HTTP/1.0 200
beginnt.GET /questions/3304006/persistent-httpurlconnection-in-Java HTTP/1.0\r\n
Host: stackoverflow.com\r\n
\r\n
\r\n
und parsen Sie die erste Zeile als StatusmeldungWenn wir die HttpUrlConnection-Klasse implementieren möchten, müssen wir ein paar Dinge beachten:
OutputStream
bedeutet, dass die Datenübertragung abgeschlossen ist und nicht, dass die Verbindung beendet werden mussSchnell gesagt, es gibt nur viele Fallstricke
In der von mir entworfenen Klasse verwendet es boolesche Flags, um sich zu erinnern, ob die Methoden connect
und afterPostClosure
aufgerufen werden. Außerdem wird unterstützt, wenn getInputStream()
aufgerufen wird, bevor der OutputStream
geschlossen wird.
Diese Klasse verwendet auch so wenig Wrap wie möglich für die vom Socket zurückgegebenen Streams, um zu verhindern, dass sie wirklich komplex sind.
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;
}
}
Aktuelle Fehler mit dem obigen Code:
Der obige Code kann wie folgt verwendet werden:
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);
}
}
Wenn Sie dies mit einer Art Proxy-Selector verwenden möchten, sollten Sie das Protokoll der URL überprüfen, um festzustellen, ob http oder https (falls vorhanden) diese Klasse nicht verwenden, sondern den Header manuell wie folgt anhängen:
httpURLConnection.setRequestProperty("Proxy-Authorization", "Basic " + encoded);
Während Java diese Methode hat, zeigen Versuche, es zu verwenden, warum es nicht funktioniert. Java ruft einfach die createSocket(Socket s, String Host, int port, boolean autoClose)
mit einer bereits offenen Verbindung auf, wodurch es unmöglich wird, den Proxy-Vorgang manuell auszuführen.
Leider gibt es keine einfache Lösung für das, was Sie erreichen wollen. Ihr 1. Code funktioniert nicht mit HTTPS, da Sie den Authentifizierungsheader direkt festlegen. Da der Client alle Daten verschlüsselt, kann der Proxy-Server keine Informationen aus der Anforderung extrahieren.
In der Tat arbeiten HTTPS- und Proxyserver gegensätzlich. Der Proxyserver möchte alle Daten sehen, die zwischen dem Client und dem endgültigen Server übertragen werden, und auf der Grundlage dessen, was er sieht, Maßnahmen ergreifen. Andererseits verschlüsselt das HTTPS-Protokoll alle Daten, sodass niemand die Daten sehen kann, bis sie das endgültige Ziel erreichen. Der Verschlüsselungsalgorithmus wird zwischen dem Client und dem endgültigen Ziel ausgehandelt, sodass der Proxyserver keine Informationen entschlüsseln kann. Er kann auch nicht wissen, welches Protokoll der Client verwendet.
Um einen Proxyserver für eine HTTPS-Verbindung zu verwenden, muss der Client einen Tunnel einrichten. Dazu muss ein CONNECT-Befehl direkt an den Proxy gesendet werden. Beispiel:
CONNECT www.google.com:443 HTTP/1.0
und senden Sie die Anmeldeinformationen, um sich beim Proxy-Server zu authentifizieren.
Wenn die Verbindung erfolgreich ist, kann der Client Daten über die Verbindung senden und empfangen. Der Proxy-Server ist für die Daten vollständig blind. Die Daten werden nur auf dem Weg zwischen Client und Server durchlaufen.
Wenn Sie url.openConnection(proxy)
für eine HTTP-URL ausführen, wird eine Instanz von HttpURLConnection
zurückgegeben. Wenn sie unter einer HTTPS-URL wie in Ihrem zweiten Code ausgeführt wird, führt sie eine Instanz von HttpsURLConnection
aus.
Sie erhalten den 407-Fehlercode, da der Proxyserver die Authentifizierungsinformationen nicht aus dem von Ihnen gesendeten Header extrahieren kann. Wenn Sie den Exception-Stack betrachten, sehen Sie, dass die Exception an Sun.net.www.protocol.http.HttpURLConnection.doTunneling()
ausgelöst wird, die den CONNECT-Befehl ausgibt, um den HTTPS-Tunnel über den Proxy einzurichten. Im Quellcode für Sun.net.www.protocol.http.HttpURLConnection
sehen wir:
/* 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;
Es scheint also, dass der Standardauthentifizierer die einzige Möglichkeit ist, die Proxy-Anmeldeinformationen bereitzustellen.
Um das zu tun, was Sie möchten, müssen Sie zur Verbindungsebene gehen und das HTTP-Protokoll selbst handhaben, da Sie mit dem Proxy-Server und nicht direkt mit dem Google-Server sprechen müssen.
Können Sie HttpsUrlConnection verwenden? Es erweitert HttpUrlConnection, sodass das Umschalten auf HttpUrlConnection in Ordnung sein kann, wenn Sie von der Klasse zurückkehren.
Der Code ist ähnlich, verwenden Sie statt HttpUrlConnection einen mit https im Namen.
Verwenden Sie folgenden Code:
if (testUrlHttps.getProtocol().toLowerCase().equals("https")) {
trustAllHosts();
HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
https.setHostnameVerifier(DO_NOT_VERYFY);
urlCon = https;
} else {
urlCon = (HttpURLConnection) url.openConnection();
}
Quellen:
[1] https://docs.Oracle.com/javase/7/docs/api/javax/net/ssl/HttpsURLConnection.html
[2] HTTP-Verbindung - "https: //" vs. "http: //" (Snippet)
Ok, das ist was Sie tun müssen,
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;
}
}
Verwenden Sie es gerne.
HttpURLConnection conn = new ProxySetup().proxySetup(URL)