自己用java写一个http和https代理服务器
本文是基于socket实现的http,https代理服务器,资源利用率上肯定也是没有nio实现的效率要好。但是,秉持学习的态度,我还是来来实践一下。当然,如果这个实现的代理器只是你自己用的话或者少数几个人用的话,我觉得完全没问题,自己也试了,看视频啥的也没啥问题(如果你看的视频需要全部下载到本地后才能播放,那就只能把socket的过期时间设置长点了,不过现在一般都是可以缓冲一段就可以播放了,所以你自己斟酌吧——当别人还在用网上下的*来*时,你自己用自己写的脚本来流畅的看视频的感觉肯定会不一样吧哈哈,后续如果有时间的话,我也会实现代理客户端来实现身份认证,别让坏蛋占用你的带宽。)
首先,我觉得你很有必要去看看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连接结束线程,更早的释放资源了。
上一篇: 抖音love手势图如何制作 love手势图片制作方法介绍
下一篇: JPA实体继承的映射