文档章节

JAVA实现网络请求代理之HTTP篇

jlcao
 jlcao
发布于 2015/09/09 14:33
字数 1750
阅读 1364
收藏 5

‍‍

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代理服务的实现


© 著作权归作者所有

jlcao
粉丝 2
博文 6
码字总数 4620
作品 0
南岸
程序员
私信 提问
加载中

评论(2)

扎克扎克
扎克扎克
挺好 最近正在找一个这样的工具用于作测试
MarkSong
MarkSong
支持一下
解决Fiddler不能监听Java HttpURLConnection请求的方法

在默认情况下,Fiddler不能监听Java HttpURLConnection请求。究其原因,Java的网络通信协议栈可能浏览器的通信协议栈略有区别,Fiddler监听Http请求的原理是在应用程序和操作系统网络通信层之...

小克898
2013/05/18
346
0
关于 WEB/HTTP 调试利器 Fiddler 的一些技巧分享

1、原理简介: Fiddler 是目前最强大最好用的 Web 调试工具之一,它能记录所有客户端和服务器的http和https请求,允许你监视,设置 CGI 请求的断点,甚至修改输入输出数据。同类的工具还有h...

大数据之路
2013/08/08
22K
0
Java NIO原理 图文分析及代码实现

Java NIO原理图文分析及代码实现 前言: 最近在分析hadoop的RPC(Remote Procedure Call Protocol ,远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术...

囚兔
2015/04/29
289
1
JAVA RPC:从上手到爱不释手

文首,思考一个问题:为什么需要 RPC 服务? 在传统的开发模式中,我们通常将系统的各个服务部署在单台机器,随着服务的扩展,这种方式已经完全无法满足系统大规模的扩展需要,分布式系统由此...

编程SHA
03/28
46
0
20非常有用的Java程序片段

下面是20个非常有用的Java程序片段,希望能对你有用。 1. 字符串有整型的相互转换 2. 向文件末尾添加内容 3. 得到当前方法的名字 4. 转字符串到日期 或者是: 5. 使用JDBC链接Oracle 6. 把 ...

crazyinsomnia
2009/12/27
5.8K
25

没有更多内容

加载失败,请刷新页面

加载更多

nginx学习笔记

中间件位于客户机/ 服务器的操作系统之上,管理计算机资源和网络通讯。 是连接两个独立应用程序或独立系统的软件。 web请求通过中间件可以直接调用操作系统,也可以经过中间件把请求分发到多...

码农实战
今天
5
0
Spring Security 实战干货:玩转自定义登录

1. 前言 前面的关于 Spring Security 相关的文章只是一个预热。为了接下来更好的实战,如果你错过了请从 Spring Security 实战系列 开始。安全访问的第一步就是认证(Authentication),认证...

码农小胖哥
今天
11
0
JAVA 实现雪花算法生成唯一订单号工具类

import lombok.SneakyThrows;import lombok.extern.slf4j.Slf4j;import java.util.Calendar;/** * Default distributed primary key generator. * * <p> * Use snowflake......

huangkejie
昨天
12
0
PhotoShop 色调:RGB/CMYK 颜色模式

一·、 RGB : 三原色:红绿蓝 1.通道:通道中的红绿蓝通道分别对应的是红绿蓝三种原色(RGB)的显示范围 1.差值模式能模拟三种原色叠加之后的效果 2.添加-颜色曲线:调整图像RGB颜色----R色增强...

东方墨天
昨天
11
1
将博客搬至CSDN

将博客搬至CSDN

算法与编程之美
昨天
13
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部