JAVA实现网络请求代理之HTTP篇 (一)
JAVA实现网络请求代理之Socket篇(二)
Java代理服务器之截取,篡改HTTP请求(应用篇)
首先,需要弄明白代理服务的原理,和http协议几种请求类型的构成。
代理服务器原理
http协议详解
1、请求行以一个方法符号开头,以空格分开,后面跟着请求的URI和协议的版本,格式如下:
Method RequestURI HTTP-Version CRLF
其中 Method表示请求方法;Request-URI是一个统一资源标识符;HTTP-Version表示请求的HTTP协议版本;CRLF表示回车和换行(除了作为结尾的CRLF外,不允许出现单独的CR或LF字符)。
例如:
GET请求
GET http://www.baidu.com/ HTTP/1.1 注:请求行 【方法 统一资源标识符 HTTP协议版本】
Host: www.baidu.com 注:主机
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0 注:浏览器信息
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 注:返回数据类型
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 注:语言权重值
Accept-Encoding: gzip, deflate 注:压缩格式
Cookie: BAIDUID=528AC525A03A636F088835EED7DFA00F:FG=1; BIDUPSID=F7A856B7B734889B5BB1227150199A90; PSTM=1441605421; BD_UPN=13314752 注:cookie
Connection: keep-alive 注:连接类型
POST请求
POST /foo.php HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://localhost/test.php
Content-Type: application/x-www-form-urlencoded
Content-Length: 43 注:内容长度
first_name=John&last_name=Doe&action=Submit 注:提交数据
接下来就介绍如何具体实现HTTP代理服务器。
新建一个处理Http请求的服务线程,监听808端口
package com.mato.proxy.http; import java.net.ServerSocket; import java.net.Socket; /** * Created by cjl on 2015/9/8. */ public class HttpProxy extends Thread{ private ServerSocket server; public HttpProxy(ServerSocket _server){ server=_server; start(); } public void run(){ // 线程运行函数 Socket connection; while(true){ try{ connection=server.accept(); //接受到请求,就新建一个处理请求的服务线程,将当前请求传递到线程里面 HTTPServerThread handler =new HTTPServerThread(connection); } catch(Exception e){} } } }
接下来就是每一个请求处理线程的处理逻辑
package com.mato.proxy.http; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; /** * Created by cjl on 2015/9/8. */ public class HTTPServerThread extends Thread { private Socket client; public HTTPServerThread(Socket _connection) { client = _connection; start(); } public void run() { // 线程运行函数 byte buf[] = new byte[10000], buf1[] = new byte[10000], buf2[] = new byte[10000]; int creadlen = 0, sreadlen = 0; int i; String s = null, s1 = null, s2 = null; Socket server = null; int port = 80; DataInputStream cin = null, //客户端输入流 sin = null; //服务端输入流 DataOutputStream cout = null, //客户端输出流 sout = null; //服务端输出流 int method = 0; try { cin = new DataInputStream(client.getInputStream()); cout = new DataOutputStream(client.getOutputStream()); if (cin != null && cout != null) { creadlen = cin.read(buf, 0, 10000); // 从客户端读数据 if (creadlen > 0) { // 读到数据 s = new String(buf); if (s.indexOf("\r\n") != -1) s = s.substring(0, s.indexOf("\r\n")); if (s.indexOf("GET ") != -1) method = 0;// 如读到 GET 请求 if (s.indexOf("CONNECT ") != -1) { // 读到 CONNECT 请求 , 返回 HTTP 应答 s1 = s.substring(s.indexOf("CONNECT ") + 8, s.indexOf ("HTTP/")); s2 = s1; s1 = s1.substring(0, s1.indexOf(":")); s2 = s2.substring(s2.indexOf(":") + 1); s2 = s2.substring(0, s2.indexOf(" ")); port = Integer.parseInt(s2); method = 1; sendAck(cout); } if (s.indexOf("POST ") != -1)// 如读到 POST 请求 method = 2; if (s.indexOf("http://") != -1 && s.indexOf("HTTP/") != -1) { // 从所读数据中取域名和端口号 s1 = s.substring(s.indexOf("http://") + 7, s.indexOf("HTTP/")); s1 = s1.substring(0, s1.indexOf("/")); if (s1.indexOf(":") != -1) { s2 = s1; s1 = s1.substring(0, s1.indexOf(":")); s2 = s2.substring(s2.indexOf(":") + 1); port = Integer.parseInt(s2); method = 0; } } if (s1 != null) { server = new Socket(s1, port); // 根据读到的域名和端口号建立套接字 sin = new DataInputStream(server.getInputStream()); sout = new DataOutputStream(server.getOutputStream()); if (sin != null && sout != null && server != null) { if (method == 0) { sreadlen = doGet(buf, creadlen, sin, cout, sout); } if (method == 1) { // 如读到 CONNECT 请求 sreadlen = doConnect(buf, sreadlen, cin, sin, cout, sout); } if (method == 2) { // 如读到 POST 请求 // 向外网发送 POST 请求 doPost(buf, creadlen, sreadlen, cin, sin, cout, sout); } } } } } // 执行关闭操作 if (sin != null) sin.close(); if (sout != null) sout.close(); if (server != null) server.close(); if (cin != null) cin.close(); if (cout != null) cout.close(); if (client != null) client.close(); } catch (IOException e) { } } /** * 发生确认 * * @param cout 输出流 * @throws IOException */ private void sendAck(DataOutputStream cout) throws IOException { String s2; byte[] buf2; s2 = "HTTP/1.0 200 Connection established\r\n"; s2 = s2 + "Proxy-agent: proxy\r\n\r\n"; buf2 = s2.getBytes(); cout.write(buf2); cout.flush(); } /** * 处理POST 请求 * @param buf 数据缓存区 * @param creadlen 客户端读取的buf的长度 * @param sreadlen 服务端读取的buf的长度 * @param cin 客户端输入流 * @param sin 服务端输入流 * @param cout 客户端输出流 * @param sout 服务端输出流 * @throws IOException */ private void doPost(byte[] buf, int creadlen, int sreadlen, DataInputStream cin, DataInputStream sin, DataOutputStream cout, DataOutputStream sout) throws IOException { write(buf, creadlen, sout); // 建立线程 , 用于从外网读数据 , 并返回给内网客户端 HTTPChannel thread1 = new HTTPChannel(sin, cout); while ((sreadlen = cin.read(buf, 0, 10000))!=-1) { // 循环 try { System.out.println("post>>"+new String(buf)); if (sreadlen > 0) { // 读到数据 , 则发送给外网 write(buf, sreadlen, sout); } } catch (Exception e1) { break; } } } /** * 写数据 * @param buf 缓冲区 * @param creadlen 读取的偏移量 * @param sout 输出流 * @throws IOException */ private void write(byte[] buf, int creadlen, DataOutputStream sout) throws IOException { sout.write(buf, 0, creadlen); sout.flush(); } /** * 处理HTTPconnect 待定 * @param buf 缓冲区 * @param sreadlen 服务端读取的buf的长度 * @param cin 客户端输入流 * @param sin 服务端输入流 * @param cout 客户端输出流 * @param sout 服务端输出流 * @return */ private int doConnect(byte[] buf, int sreadlen, DataInputStream cin, DataInputStream sin, DataOutputStream cout, DataOutputStream sout) { // 建立线程 , 用于从外网读数据 , 并返回给内网客户端 HTTPChannel thread1 = new HTTPChannel(sin, cout); try { while ((sreadlen = cin.read(buf, 0, 10000))!=-1) { // 循环 // 从内网读数据 if (sreadlen > 0) { // 读到数据 , 则发送给外网 System.out.println("CONN>>" + new String(buf)); write(buf, sreadlen, sout); } } } catch (Exception e1) { } return sreadlen; } /** * 处理GET请求 * @param buf 缓冲区 * @param creadlen 客户端读取buf的长度 * @param sin 服务端输入流 * @param cout 客户端输出流 * @param sout 服务端输出流 * @return * @throws IOException */ private int doGet(byte[] buf, int creadlen, DataInputStream sin, DataOutputStream cout, DataOutputStream sout) throws IOException { int sreadlen;// 如读到 GET 请求,向外网发出 GET 请求 System.out.println("[get] >> " + new String(buf)); write(buf, creadlen, sout); while ((sreadlen = sin.read(buf, 0, 10000))!=-1) { // 循环 try { if (sreadlen > 0) { System.out.println("[get] << " + new String(buf)); write(buf, sreadlen, cout); } } catch (Exception e) { break; } // 异常则退出 } return sreadlen; } }
再接下来就是上面线程用到的通道类
package com.mato.proxy.http; import java.io.DataInputStream; import java.io.DataOutputStream; /** * Created by cjl on 2015/9/8. */ public class HTTPChannel extends Thread{ private DataInputStream in; private DataOutputStream out; public HTTPChannel(DataInputStream sin, DataOutputStream cout) { in = sin; out = cout; start(); } @Override public void run() { int len = 0; byte buf[] = new byte[10240]; while (true) { try { if (len == -1) { break; } if (len > 0) { System.out.println("post<<"+new String(buf)); out.write(buf, 0, len); out.flush(); } } catch (Exception e) { break; } } } }
最后就是启动代理服务器的代码
public static void main(String[] args) { try{ ServerSocket httpserver=new ServerSocket(808); // 建立 HTTP 侦听套接字 System.out.println ("HTTP Proxy started on "+httpserver.getLocalPort()); HttpProxy httpproxy=new HttpProxy(httpserver); // 建立HTTP 侦听线程 }catch(IOException e){} }
这样整个HTTP代理服务就构建好了,不过暂时只能代理HTTP请求,不能代理HTTPS
要使用代理服务的话还要在浏览器里面配置代理地址和上面的端口808,过程就不赘述了。可以自行百度。
这是我的火狐浏览器的配置:
最后效果图:
到这里就告一段落了,后面介绍Socket代理服务的实现