java通過sockt實現代理伺服器
阿新 • • 發佈:2019-02-08
HTTP 代理有分兩種:
- RFC 7230 - HTTP/1.1: Message Syntax and Routing(即修訂後的 RFC 2616,HTTP/1.1 協議的第一部分)描述的普通代理。這種代理扮演的是「中間人」角色,對於連線到它的客戶端來說,它是服務端;對於要連線的服務端來說,它是客戶端。它就負責在兩端之間來回傳送 HTTP 報文。
- Tunneling TCP based protocols through Web proxy servers(通過 Web 代理伺服器用隧道方式傳輸基於 TCP 的協議)描述的隧道代理。它通過 HTTP 協議正文部分(Body)完成通訊,以 HTTP 的方式實現任意基於 TCP 的應用層協議代理。這種代理使用 HTTP 的 CONNECT 方法建立連線,但 CONNECT 最開始並不是 RFC 2616 - HTTP/1.1 的一部分,直到 2014 年釋出的 HTTP/1.1 修訂版中,才增加了對 CONNECT 及隧道代理的描述,詳見 RFC 7231 - HTTP/1.1: Semantics and Content。實際上這種代理早就被廣泛實現。
HTTP代理
http請求經過代理伺服器,代理伺服器只要負責轉發相應的http響應體就可以了。
HTTPS代理
https請求經過代理伺服器,會發送一個CONNECT報文,用於和代理伺服器建立隧道,如果代理伺服器返回HTTP 200,則建立成功,後續代理伺服器只要負責轉發資料就行,實際上SSL/TLS握手還是發生在客戶端和真實伺服器。
思路
建立SocketServer監聽埠,根據http請求頭方法如果是CONNECT就是HTTPS請求否則都為HTTP請求,接著根據HOST頭建立代理伺服器與目標伺服器的連線,然後轉發資料。HTTPS請求需要特殊處理,因為CONNECT請求並不需要轉發,要返回一個HTTP 200的響應建立隧道,之後才進行轉發。
實現
//監聽埠
ServerSocket serverSocket = new ServerSocket(port);
for (; ; ) {
new SocketHandle(serverSocket.accept()).start();
}
static class SocketHandle extends Thread {
private Socket socket;
public SocketHandle(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
OutputStream clientOutput = null;
InputStream clientInput = null;
Socket proxySocket = null;
InputStream proxyInput = null;
OutputStream proxyOutput = null;
try {
clientInput = socket.getInputStream();
clientOutput = socket.getOutputStream();
String line;
String host = "";
LineBuffer lineBuffer = new LineBuffer(1024);
StringBuilder headStr = new StringBuilder();
//讀取HTTP請求頭,並拿到HOST請求頭和method
while (null != (line = lineBuffer.readLine(clientInput))) {
System.out.println(line);
headStr.append(line + "\r\n");
if (line.length() == 0) {
break;
} else {
String[] temp = line.split(" ");
if (temp[0].contains("Host")) {
host = temp[1];
}
}
}
String type = headStr.substring(0, headStr.indexOf(" "));
//根據host頭解析出目標伺服器的host和port
String[] hostTemp = host.split(":");
host = hostTemp[0];
int port = 80;
if (hostTemp.length > 1) {
port = Integer.valueOf(hostTemp[1]);
}
//連線到目標伺服器
proxySocket = new Socket(host, port);
proxyInput = proxySocket.getInputStream();
proxyOutput = proxySocket.getOutputStream();
//根據HTTP method來判斷是https還是http請求
if ("CONNECT".equalsIgnoreCase(type)) {//https先建立隧道
clientOutput.write("HTTP/1.1 200 Connection Established\r\n\r\n".getBytes());
clientOutput.flush();
} else {//http直接將請求頭轉發
proxyOutput.write(headStr.toString().getBytes());
}
//新開執行緒轉發客戶端請求至目標伺服器
new ProxyHandleThread(clientInput, proxyOutput).start();
//轉發目標伺服器響應至客戶端
while (true) {
clientOutput.write(proxyInput.read());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (proxyInput != null) {
try {
proxyOutput.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (proxyOutput != null) {
try {
proxyOutput.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (proxySocket != null) {
try {
proxySocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (clientInput != null) {
try {
clientInput.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (clientOutput != null) {
try {
clientOutput.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
static class ProxyHandleThread extends Thread {
private InputStream input;
private OutputStream output;
public ProxyHandleThread(InputStream input, OutputStream output, CountDownLatch cdl) {
this.input = input;
this.output = output;
}
@Override
public void run() {
try {
while (true) {
output.write(input.read());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
後記
以上一個簡單的HTTP代理伺服器就實現了,不過其中問題也有很多,如BIO模型的缺陷,異常處理機制。
下一篇會用netty來實現一個高效能的HTTP代理伺服器。
程式碼託管在github上,歡迎start