兼容https和http协议的java代理服务器代码

it2022-05-05  117

最近做的一个http代理小程序,同时支持http和http。 1、获取http代理请求的头部信息,区分https还是http,做不同的处理 [java] view plain copy /** * 解析头部信息 * */ 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、任务处理 [java] view plain copy /** * 将客户端发送过来的数据转发给请求的服务器端,并将服务器返回的数据转发给客户端 * */ 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、用线程池分发任务 [java] view plain copy /** * 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(); } } } }

 


最新回复(0)