兼容https和http協議的java代理服務器代碼

最近做的一個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();
			}
		}
	}
	
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章