您可以自己扩展
ProxiedHttpsConnection和处理所有与底层相关的内容。
需要执行以下步骤以通过HTTP代理与https网站建立连接:
注意:与代理服务器和http服务器的通信应使用ASCII7。
- 发送
ConNECT stackoverflow.com:443 HTTP/1.0rn
给代理 - 发送您的身份验证:
Proxy-Authorization: Basic c2F5WW91SGF2ZVNlZW5UaGlzSW5UaGVDb21tZW50cw==rn
。 - 结束第一个请求:
rn
- 阅读来自代理的响应,直到看到组合“ r n r n”。
- 解析您从代理获得的响应的第一行,并检查其是否以开头
HTTP/1.0 200
。 - 在现有连接上就地启动SSL会话。
- 发送http请求的开始:
GET /questions/3304006/persistent-httpurlconnection-in-java HTTP/1.0rn
- 设置正确的主机头:
Host: stackoverflow.comrn
- 将请求结束到http服务器:
rn
- 阅读直到
rn
并将第一行解析为状态消息 - 读取直到流结束以获取请求正文
当我们想要实现HttpUrlConnection类时,我们还需要考虑以下几点:
- 在构造类时,该类应存储数据以备将来连接,但不要直接使其
- 可以按任何顺序调用任何方法
- 闭合
OutputStream
意味着数据传输已经完成,而不是意味着连接必须完成 - 每个api以不同的顺序使用方法
- HTTP标头不区分大小写,Java映射区分大小写。
迅速地说,有很多陷阱
在我设计的类中,它使用布尔标志来记住
connect方法和
afterPostClosure方法是否被调用,并且还具有
getInputStream()在
OutputStream关闭之前调用if的支持。
此类还对套接字返回的流使用了尽可能少的包装,以防止过于复杂。
public class ProxiedHttpsConnection extends HttpURLConnection { private final String proxyHost; private final int proxyPort; private static final byte[] newline = "rn".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 enpred = base64.enpre((username + ":" + password).getBytes()) .replace("rn", ""); proxyheaders.put("Proxy-Authorization", new ArrayList<>(Arrays.asList("Basic " + enpred))); } @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.0rn"); for (Map.Entry<String, List<String>> header : proxyheaders.entrySet()) { for (String l : header.getValue()) { msg.append(header.getKey()).append(": ").append(l); msg.append("rn"); } } msg.append("Connection: closern"); msg.append("rn"); 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; 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.0rn"); for (Map.Entry<String, List<String>> h : sendheaders.entrySet()) { for (String l : h.getValue()) { msg.append(h.getKey()).append(": ").append(l); msg.append("rn"); } } if (method.equals("POST") || method.equals("PUT")) { msg.append("Transfer-Encoding: Chunkedrn"); } msg.append("Host: ").append(url.getHost()).append("rn"); msg.append("Connection: closern"); msg.append("rn"); 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; 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); } 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; }}上面代码的当前错误:
- 发布期间错误未关闭流
- 在首次与代理联系时发生错误时,流未关闭
- 它不支持http重定向
- 它不支持诸如分块和gzip编码之类的http 1.1,但这并不是问题,因为我们宣布自己为http1.0客户端。
上面的代码可以像这样使用:
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 " + enpred);为什么不使用httpsUrlConnection.setSSLSocketFactory
虽然Java有此方法,但尝试使用它会向您展示为什么它不起作用,但Java只会
createSocket(Socket s, String host,int port, booleanautoClose)通过已打开的连接不断调用,从而无法手动进行代理操作。



