Android: 一个简单的http proxy server

如果你想知道浏览器打开一个网页的时候,访问了哪些连接,那么你可以在本地建立一个http代理服务器,浏览器请求的连接首先会发送到这里,再由代理服务器将请求转发出去。因此,你可以控制浏览器可以访问哪些地址。

  • 原理

简单的http proxy server只是对HTTP request进行转发。在转发的过程中,可以选择不修改request/response,这种属于透明代理(transparent proxy), 当然你也可以对request/response进行修改,提供额外的信息,如group annotation services, media type transformation, protocol reduction, anonymity filtering。

而另外一种http proxy server允许HTTP CONNECT允许在连接中发送任意的数据,这种主要是用做https连接,请看https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods:

The CONNECT method converts the request connection to a transparent TCP/IP tunnel, usually to facilitate SSL-encrypted communication (HTTPS) through an unencrypted HTTP proxy.[17][18] See HTTP CONNECT tunneling.

参考文档:

  1. https://tools.ietf.org/html/rfc2616
  2. https://www.w3.org/Protocols/rfc2616/rfc2616.html
  3. https://en.wikipedia.org/wiki/Proxy_server
  4. https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods
  • 代码

代码是在android-6.0.1_r9/frameworks/base/packages/services/Proxy的基础上作了小小的改动。已经上传到github服务器,可以从这里下载:

$ git clone https://github.com/brobwind/BroNil.git

主要改动:

a. 选择代理服务器,如果使用代理服务器,并且代理服务器不是127.0.0.1:8182,则将代理请求指向真正的代理服务器:

public class ProxyServer extends Thread {
    // ...
    private class ProxyConnection implements Runnable {
         // ...
        @Override
        public void run() {
            // ...
                for (Proxy proxy : list) {
                    try {
                        boolean forward = false;
                        if (!proxy.equals(Proxy.NO_PROXY)) {
                            // Only Inets created by PacProxySelector.
                            InetSocketAddress inetSocketAddress =
                                    (InetSocketAddress)proxy.address();
                            server = new Socket(inetSocketAddress.getHostName(),
                                    inetSocketAddress.getPort());
                            if ("127.0.0.1".equals(InetAddress.getByName(
                                    inetSocketAddress.getHostName()).getHostAddress()) != true ||
                                    inetSocketAddress.getPort() != ProxyService.PORT) {
                                server = new Socket(inetSocketAddress.getHostName(),
                                        inetSocketAddress.getPort());
                                sendLine(server, requestLine);
                                forward = true;
                            }
                        }
                        if (forward != true) {
                            server = new Socket(host, port);
                            if (requestType.equals(CONNECT)) {
                                skipToRequestBody(connection);
                                // No proxy to respond so we must.
                                sendLine(connection, HTTP_OK);
                            } else {
                                // Proxying the request directly to the origin server.
                                sendAugmentedRequestToHost(connection, server,
                                        requestType, url, httpVersion);
                            }
                        }
                    }
                    // ...
                }
                // ...
            }
            // ...
        }
    }
}

b. 本地代理使用固定端口号ProxyService.PORT – 8182:

public class ProxyServer extends Thread {
    // ...
    @Override
    public void run() {
        try {
            serverSocket = new ServerSocket(ProxyService.PORT);

            setPort(serverSocket.getLocalPort());

            // ...
            }
        } catch (SocketException e) {
            Log.e(TAG, "Failed to start proxy server", e);
        } catch (IOException e1) {
            Log.e(TAG, "Failed to start proxy server", e1);
        }

        mIsRunning = false;
    }
}
  • 编译

代码是在android-5.1.1_r15项目中编译,注意代码中使用了CardView:

$ cd android-5.1.1_r15
$ lunch m_e_arm-userdebug
$ cd packages/apps
$ git clone https://github.com/brobwind/BroNil.git
$ cd BroNil && mm

最终的APK放在:

out/target/product/mini-emulator-armv7-a-neon/data/app/BroNil/BroNil.apk

  • 使用方法

安装完apk后,找到Bro Nil并打开,可以看到类似如下界面,显示代理服务器的IP地址与端口号:

2016_02_27_bronil

配置WiFi网络的代理:

2016_02_27_wifi_proxy_setting

最后打开chrome浏览器,访问网页。

NOTE:

  1. 在chrome中输入”chrome://net-internals/#proxy”可以查看当前的代理服务器
  2. 在logcat中,你可以查看当前访问的网站,所有打开的连接(REQUEST)
V/ProxyServer( 2755):  -> REQUEST: CONNECT clients4.google.com:443 HTTP/1.1
V/ProxyServer( 2755):  -> REQUEST: CONNECT translate.googleapis.com:443 HTTP/1.1
V/ProxyServer( 2755):  -> REQUEST: CONNECT www.google-analytics.com:443 HTTP/1.1
V/ProxyServer( 2755):  -> CONNECT: www.google-analytics.com:443
V/ProxyServer( 2755):  -> REQUEST: CONNECT translate.googleapis.com:443 HTTP/1.1
V/ProxyServer( 2755):  -> REQUEST: CONNECT www.google.com:443 HTTP/1.1
V/ProxyServer( 2755):  -> REQUEST: HEAD http://qwccnenbutemnxt/ HTTP/1.1
V/ProxyServer( 2755):  -> REQUEST: HEAD http://oezfveeq/ HTTP/1.1
V/ProxyServer( 2755):  -> REQUEST: HEAD http://wlcdposax/ HTTP/1.1
  • 相关参考

android-5.1.1_r15 -> android-6.0.1_r9中的重要改动:

From 1e64ab3e56f6efafc25301df5da21d69bb75b470 Mon Sep 17 00:00:00 2001
From: Andrei Kapishnikov <kapishnikov@google.com>
Date: Fri, 19 Dec 2014 16:01:15 -0500
Subject: [PATCH] Replace absolute_uri with absolute_path when HTTP request is forwarded to non-proxy server.

According to the spec (http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html):
When Request-URI is used to identify a resource on an origin server or gateway, the absolute path of the URI MUST be transmitted as the Request-URI.

Before the change,the proxy sent absolute_uri, which is used to send HTTP requests to proxies: “The absoluteURI form is REQUIRED when the request is being made to a proxy.”

Related Bug 18776631

Other changes:
1. Remove proxy-connection header when the request is sent to an origin server.

2. Added “connection = close” header to indicate that the origin server needs to close the connection right after the response.  Currently, our proxy does not support keep-alive (persistent) connections because it analyses only the first request for a given connection and; therefore, cannot perform required request content substitutions.

3. Fixed an issue when a non-numeric host port number resulted in forwarding requests to default 443 port for SSL connections, e.g.  request to https://google.com:ZZZ, would be translated to https://google.com:443.

4. Fixed an issue when the proxy tries to establish direct connection to the origin server even when it is not supposed to do that according to PAC. That happened when PAC returned a proxy server that is not available.

5. Prevent ProxyServer from crashing when PacService throws an exception by intercepting all exception types.

 

评论

3 Comments on "Android: 一个简单的http proxy server"

提醒我
avatar

zejie
游客
zejie
1 年 2 月 之前

请教一下,如何获取response的body数据?

wpDiscuz