自己用java写一个http和https代理服务器

         本文是基于socket实现的http,https代理服务器,资源利用率上肯定也是没有nio实现的效率要好。但是,秉持学习的态度,我还是来来实践一下。当然,如果这个实现的代理器只是你自己用的话或者少数几个人用的话,我觉得完全没问题,自己也试了,看视频啥的也没啥问题(如果你看的视频需要全部下载到本地后才能播放,那就只能把socket的过期时间设置长点了,不过现在一般都是可以缓冲一段就可以播放了,所以你自己斟酌吧——当别人还在用网上下的shadowsock来翻墙时,你自己用自己写的脚本来流畅的看视频的感觉肯定会不一样吧哈哈,后续如果有时间的话,我也会实现代理客户端来实现身份认证,别让坏蛋占用你的带宽。)

         首先,我觉得你很有必要去看看http协议详解,网上有很多,大家自行查询,这里我简单说下主要使用了http的请求行(请求方法 host 协议版本),请求头(host,keep-alive等等),至于https,其实就是http和ssl结合,这里的结合比较巧妙,因为考虑到安全和性能,https会事先请求建立socket链接,同时https利用http进行牵手双方交换了客户端传递信息加密的秘钥(就是这个过程使用了ssh),以后传递的消息都在客户端和服务器端建立的这个socket并且利用牵手约定好的秘钥进行加密消息传递信息,这时你要是解析这些消息会发现全是乱码(包括请求头等都经过加密)。我这里只是尽量简约但是比较形象的解释了https与http的关系,以便让读者可很方便的编程实现,详细的过程可以去自查http详解。

         有了上面的知识储备,实现http代理服务器,其实就是代理服务器去解析http请求头消息找到目标服务器,然后建立代理服务器到目标服务器的socket连接,将http的全部消息全部转发给目标服务器即可。而针对https的代理,主要是实现牵手过程即可,这个牵手过程主要是利用http请求获取目标服务器地址来建立socket连接,并在建立连接的同时告诉客户端连接已经建立好(返回HTTP/1.1 200 Connection Established\r\n\r\n),接下来客户端就会自动通过代理服务器建立的与目标服务器的连接发消息给目标服务器进行交换秘钥,然后,也会利用此连接传递消息,当然,传递消息时代理服务器只需要转发即可(你也看不懂加密后传递的消息)。

         下面给出我的代码,当然欢迎大家给出意见或建议,在此先谢谢大家:

public class test {

	@SuppressWarnings("resource")
	public static void main(String[] s) {
		
			ServerSocket ss = null;
			try {
				ss = new ServerSocket(11111);
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			while(true) {
				try {
				Socket socket = ss.accept();
				socket.setSoTimeout(1000*60);//设置代理服务器与客户端的连接未活动超时时间
				String line = "";
				InputStream is = socket.getInputStream();
				String tempHost="",host;
				int port =80;
				String type=null;
				OutputStream os = socket.getOutputStream();
				BufferedReader br = new BufferedReader(new InputStreamReader(is));
//				System.out.println("========+++++++++++++=======");
				int temp=1;
				StringBuilder sb =new StringBuilder();
				while((line = br.readLine())!=null) {
					System.out.println(line+"-----------------");
					if(temp==1) {       //获取请求行中请求方法,下面会需要这个来判断是http还是https
//						System.out.println("+++++++++"+line);
						type = line.split(" ")[0];
						if(type==null)continue;
					}
					temp++;
					String[] s1 = line.split(": ");
					if(line.isEmpty()) {
						break;
					}
					for(int i=0;i<s1.length;i++) {
						if(s1[i].equalsIgnoreCase("host")) {
							tempHost=s1[i+1];
						}
					}
					sb.append(line + "\r\n");
					line=null;
				}
					sb.append("\r\n");          //不加上这行http请求则无法进行。这其实就是告诉服务端一个请求结束了
					if(tempHost.split(":").length>1) {
						port = Integer.valueOf(tempHost.split(":")[1]);
					}
					host = tempHost.split(":")[0];
					Socket proxySocket = null;
					if(host!=null&&!host.equals("")) {
						proxySocket = new Socket(host,port);
						proxySocket.setSoTimeout(1000*60);//设置代理服务器与服务器端的连接未活动超时时间
						OutputStream proxyOs = proxySocket.getOutputStream();
						InputStream proxyIs = proxySocket.getInputStream();
						if(type.equalsIgnoreCase("connect")) {     //https请求的话,告诉客户端连接已经建立(下面代码建立)
							os.write("HTTP/1.1 200 Connection Established\r\n\r\n".getBytes());
							os.flush();
						}else {//http请求则直接转发
							proxyOs.write(sb.toString().getBytes("utf-8"));
							proxyOs.flush();
						}
						new ProxyHandleThread(is, proxyOs,host).start();//监听客户端传来消息并转发给服务器
						new ProxyHandleThread(proxyIs, os,host).start(); //监听服务器传来消息并转发给客户端
					}
			} catch (IOException e) {
				e.printStackTrace();
			}
			}
			
		
	}
}
class ProxyHandleThread extends Thread {

    private InputStream input;
    private OutputStream output;
    private String host;//debug时需要的东西,实际不需要
    public ProxyHandleThread(InputStream input, OutputStream output,String host) {
        this.input = input;
        this.output = output;
        this.host = host;
    }

    @Override
    public void run() {
        try {
            while (true) {
            	BufferedInputStream bis = new BufferedInputStream(input);
				byte[] buffer =new byte[1024];
				int length=-1;
				while((length=bis.read(buffer))!=-1) {//这里最好是字节转发,不要用上面的InputStreamReader,因为https传递的都是密文,那样会乱码,消息传到服务器端也会出错。
					output.write(buffer, 0, length);
					length =-1;
//					System.out.println("客户端通过代理服务器给服务器发送消息"+input+host);
				}
				output.flush();
				try {
					Thread.sleep(10);     //避免此线程独占cpu
				} catch (InterruptedException e) {
					
					e.printStackTrace();
				}
            }
        } catch (SocketTimeoutException e) {
        	try {
				input.close();
				output.close();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
        }catch (IOException e) {
			System.out.println(e);
		}finally {
			try {
				input.close();
				output.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			
		}
    }
}

          写代码的过程中有些地方可能会有疑问,不要略过它们,因为它们能让你对问题认识更加深刻。我也在想把http和https分开,因为好多http的Connection不是keep-alive,那么就可以在进行一次请求响应后就可以断开连接,这样就可以提前断开socket连接结束线程,更早的释放资源了。

  • 7
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 21
    评论
抱歉,作为AI语言模型,我没有能力编代码。不过,我可以提供一些思路和参考资料,希望对您有所帮助。 实现一个代理服务器,需要以下步骤: 1. 创建一个ServerSocket对象,监听客户端的连接请求。 2. 接收到客户端的连接请求后,创建一个Socket对象,连接到目标服务器。 3. 创建两个线程:一个线程从客户端读取数据,并将数据转发到目标服务器;另一个线程从目标服务器读取数据,并将数据转发到客户端。 4. 启动两个线程,等待数据转发完成。 以下是一个简单的Java代理服务器的代码示例,供参考: ```java import java.net.*; import java.io.*; public class ProxyServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8080); System.out.println("Proxy server started."); while (true) { Socket clientSocket = serverSocket.accept(); System.out.println("Client connected: " + clientSocket.getInetAddress()); Thread clientToServer = new Thread(() -> { try { Socket serverSocket = new Socket("www.example.com", 80); InputStream clientIn = clientSocket.getInputStream(); OutputStream serverOut = serverSocket.getOutputStream(); byte[] buffer = new byte[1024]; int len; while ((len = clientIn.read(buffer)) > 0) { serverOut.write(buffer, 0, len); } clientSocket.close(); serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } }); Thread serverToClient = new Thread(() -> { try { InputStream serverIn = serverSocket.getInputStream(); OutputStream clientOut = clientSocket.getOutputStream(); byte[] buffer = new byte[1024]; int len; while ((len = serverIn.read(buffer)) > 0) { clientOut.write(buffer, 0, len); } clientSocket.close(); serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } }); clientToServer.start(); serverToClient.start(); } } } ``` 在这个示例中,代理服务器监听端口8080,并将客户端请求转发到www.example.com网站。客户端和目标服务器之间的数据交换通过两个线程实现,一个线程从客户端读取数据并将其发送到目标服务器,另一个线程从目标服务器读取数据并将其发送到客户端。这两个线程是独立的,可以并行执行。 需要注意的是,这个示例只是一个简单的演示,实际的代理服务器需要处理更多的细节和异常情况。例如,需要支持HTTPS协议,需要处理代理认证等等。如果需要开发一个完整的代理服务器,建议参考一些成熟的开源实现,例如Squid、Apache HTTP Server等,学习其实现原理和代码结构。
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值