最近做的一個http代理小程序,同時支持http和http。
1、獲取http代理請求的頭部信息,區分https還是http,做不同的處理
/**
* 解析頭部信息
*
*/
public final class HttpHeader {
private List<String> header=new ArrayList<String>();
private String method;
private String host;
private String port;
public static final int MAXLINESIZE = 4096;
public static final String METHOD_GET="GET";
public static final String METHOD_POST="POST";
public static final String METHOD_CONNECT="CONNECT";
private HttpHeader(){}
/**
* 從數據流中讀取請求頭部信息,必須在放在流開啓之後,任何數據讀取之前
* @param in
* @return
* @throws IOException
*/
public static final HttpHeader readHeader(InputStream in) throws IOException {
HttpHeader header = new HttpHeader();
StringBuilder sb = new StringBuilder();
//先讀出交互協議來,
char c = 0;
while ((c = (char) in.read()) != '\n') {
sb.append(c);
if (sb.length() == MAXLINESIZE) {//不接受過長的頭部字段
break;
}
}
//如能識別出請求方式則則繼續,不能則退出
if(header.addHeaderMethod(sb.toString())!=null){
do {
sb = new StringBuilder();
while ((c = (char) in.read()) != '\n') {
sb.append(c);
if (sb.length() == MAXLINESIZE) {//不接受過長的頭部字段
break;
}
}
if (sb.length() > 1 && header.notTooLong()) {//如果頭部包含信息過多,拋棄剩下的部分
header.addHeaderString(sb.substring(0, sb.length() - 1));
} else {
break;
}
} while (true);
}
return header;
}
/**
*
* @param str
*/
private void addHeaderString(String str){
str=str.replaceAll("\r", "");
header.add(str);
if(str.startsWith("Host")){//解析主機和端口
String[] hosts= str.split(":");
host=hosts[1].trim();
if(method.endsWith(METHOD_CONNECT)){
port=hosts.length==3?hosts[2]:"443";//https默認端口爲443
}else if(method.endsWith(METHOD_GET)||method.endsWith(METHOD_POST)){
port=hosts.length==3?hosts[2]:"80";//http默認端口爲80
}
}
}
/**
* 判定請求方式
* @param str
* @return
*/
private String addHeaderMethod(String str){
str=str.replaceAll("\r", "");
header.add(str);
if(str.startsWith(METHOD_CONNECT)){//https鏈接請求代理
method=METHOD_CONNECT;
}else if(str.startsWith(METHOD_GET)){//http GET請求
method=METHOD_GET;
}else if(str.startsWith(METHOD_POST)){//http POST請求
method=METHOD_POST;
}
return method;
}
@Override
public String toString() {
StringBuilder sb=new StringBuilder();
for(String str : header){
sb.append(str).append("\r\n");
}
sb.append("\r\n");
return sb.toString();
}
public boolean notTooLong(){
return header.size()<=16;
}
public List<String> getHeader() {
return header;
}
public void setHeader(List<String> header) {
this.header = header;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getPort() {
return port;
}
public void setPort(String port) {
this.port = port;
}
}
2、任務處理
/**
* 將客戶端發送過來的數據轉發給請求的服務器端,並將服務器返回的數據轉發給客戶端
*
*/
public class ProxyTask implements Runnable {
private Socket socketIn;
private Socket socketOut;
private long totalUpload=0l;//總計上行比特數
private long totalDownload=0l;//總計下行比特數
public ProxyTask(Socket socket) {
this.socketIn = socket;
}
private static final SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
/** 已連接到請求的服務器 */
private static final String AUTHORED = "HTTP/1.1 200 Connection established\r\n\r\n";
/** 本代理登陸失敗(此應用暫時不涉及登陸操作) */
//private static final String UNAUTHORED="HTTP/1.1 407 Unauthorized\r\n\r\n";
/** 內部錯誤 */
private static final String SERVERERROR = "HTTP/1.1 500 Connection FAILED\r\n\r\n";
@Override
public void run() {
StringBuilder builder=new StringBuilder();
try {
builder.append("\r\n").append("Request Time :" + sdf.format(new Date()));
InputStream isIn = socketIn.getInputStream();
OutputStream osIn = socketIn.getOutputStream();
//從客戶端流數據中讀取頭部,獲得請求主機和端口
HttpHeader header = HttpHeader.readHeader(isIn);
//添加請求日誌信息
builder.append("\r\n").append("From Host :" + socketIn.getInetAddress());
builder.append("\r\n").append("From Port :" + socketIn.getPort());
builder.append("\r\n").append("Proxy Method:" + header.getMethod());
builder.append("\r\n").append("Request Host :" + header.getHost());
builder.append("\r\n").append("Request Port :" + header.getPort());
//如果沒解析出請求請求地址和端口,則返回錯誤信息
if (header.getHost() == null || header.getPort() == null) {
osIn.write(SERVERERROR.getBytes());
osIn.flush();
return ;
}
// 查找主機和端口
socketOut = new Socket(header.getHost(), Integer.parseInt(header.getPort()));
socketOut.setKeepAlive(true);
InputStream isOut = socketOut.getInputStream();
OutputStream osOut = socketOut.getOutputStream();
//新開一個線程將返回的數據轉發給客戶端,串行會出問題,尚沒搞明白原因
Thread ot = new DataSendThread(isOut, osIn);
ot.start();
if (header.getMethod().equals(HttpHeader.METHOD_CONNECT)) {
// 將已聯通信號返回給請求頁面
osIn.write(AUTHORED.getBytes());
osIn.flush();
}else{
//http請求需要將請求頭部也轉發出去
byte[] headerData=header.toString().getBytes();
totalUpload+=headerData.length;
osOut.write(headerData);
osOut.flush();
}
//讀取客戶端請求過來的數據轉發給服務器
readForwardDate(isIn, osOut);
//等待向客戶端轉發的線程結束
ot.join();
} catch (Exception e) {
e.printStackTrace();
if(!socketIn.isOutputShutdown()){
//如果還可以返回錯誤狀態的話,返回內部錯誤
try {
socketIn.getOutputStream().write(SERVERERROR.getBytes());
} catch (IOException e1) {}
}
} finally {
try {
if (socketIn != null) {
socketIn.close();
}
} catch (IOException e) {}
if (socketOut != null) {
try {
socketOut.close();
} catch (IOException e) {}
}
//紀錄上下行數據量和最後結束時間並打印
builder.append("\r\n").append("Up Bytes :" + totalUpload);
builder.append("\r\n").append("Down Bytes :" + totalDownload);
builder.append("\r\n").append("Closed Time :" + sdf.format(new Date()));
builder.append("\r\n");
logRequestMsg(builder.toString());
}
}
/**
* 避免多線程競爭把日誌打串行了
* @param msg
*/
private synchronized void logRequestMsg(String msg){
System.out.println(msg);
}
/**
* 讀取客戶端發送過來的數據,發送給服務器端
*
* @param isIn
* @param osOut
*/
private void readForwardDate(InputStream isIn, OutputStream osOut) {
byte[] buffer = new byte[4096];
try {
int len;
while ((len = isIn.read(buffer)) != -1) {
if (len > 0) {
osOut.write(buffer, 0, len);
osOut.flush();
}
totalUpload+=len;
if (socketIn.isClosed() || socketOut.isClosed()) {
break;
}
}
} catch (Exception e) {
try {
socketOut.close();// 嘗試關閉遠程服務器連接,中斷轉發線程的讀阻塞狀態
} catch (IOException e1) {}
}
}
/**
* 將服務器端返回的數據轉發給客戶端
*
* @param isOut
* @param osIn
*/
class DataSendThread extends Thread {
private InputStream isOut;
private OutputStream osIn;
DataSendThread(InputStream isOut, OutputStream osIn) {
this.isOut = isOut;
this.osIn = osIn;
}
@Override
public void run() {
byte[] buffer = new byte[4096];
try {
int len;
while ((len = isOut.read(buffer)) != -1) {
if (len > 0) {
// logData(buffer, 0, len);
osIn.write(buffer, 0, len);
osIn.flush();
totalDownload+=len;
}
if (socketIn.isOutputShutdown() || socketOut.isClosed()) {
break;
}
}
} catch (Exception e) {}
}
}
}
3、用線程池分發任務
/**
* http 代理程序
* @author lulaijun
*
*/
public class SocketProxy {
static final int listenPort=8002;
public static void main(String[] args) throws Exception {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
ServerSocket serverSocket = new ServerSocket(listenPort);
final ExecutorService tpe=Executors.newCachedThreadPool();
System.out.println("Proxy Server Start At "+sdf.format(new Date()));
System.out.println("listening port:"+listenPort+"……");
System.out.println();
System.out.println();
while (true) {
Socket socket = null;
try {
socket = serverSocket.accept();
socket.setKeepAlive(true);
//加入任務列表,等待處理
tpe.execute(new ProxyTask(socket));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}