文档章节

HttpClient4基于Shadowsocks-netty的Socks代理

ksfzhaohui
 ksfzhaohui
发布于 2017/10/18 12:27
字数 1131
阅读 257
收藏 8
点赞 0
评论 0

前言
最近想批量下载一些国外网站的视频,之前写过一个代理程序shadowsocks-netty,打算直接
用它来当作客户端代理程序,而HttpClient4也支持Socks代理;所有准备用HttpClient4来访问国外网站和视频资源

HttpClient4版本

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.3.6</version>
</dependency>

访问网站
设置代理ip和port分别是:localhost和1080
访问国外网站hostname为:www.google.com
具体代码如下:

public class ClientExecuteSOCKS {
 
    /** 代理参数 IP+PORT **/
    private static String PROXY_IP = "localhost";
    private static int PROXY_PORT = 1080;
 
    public static void main(String[] args) throws Exception {
        Registry<ConnectionSocketFactory> reg = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", new MyConnectionSocketFactory()).build();
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg);
        CloseableHttpClient httpclient = HttpClients.custom().setConnectionManager(cm).build();
        try {
            InetSocketAddress socksaddr = new InetSocketAddress(PROXY_IP, PROXY_PORT);
            HttpClientContext context = HttpClientContext.create();
            context.setAttribute("socks.address", socksaddr);
 
            HttpHost target = new HttpHost("www.google.com", 80, "http");
            HttpGet request = new HttpGet("/");
 
            System.out.println("Executing request " + request + " to " + target + " via SOCKS proxy " + socksaddr);
            CloseableHttpResponse response = httpclient.execute(target, request, context);
            try {
                System.out.println("----------------------------------------");
                System.out.println(response.getStatusLine());
                String htmlStr = EntityUtils.toString(response.getEntity());
                System.out.println(htmlStr);
            } finally {
                response.close();
            }
        } finally {
            httpclient.close();
        }
    }
 
    static class MyConnectionSocketFactory implements ConnectionSocketFactory {
 
        public Socket createSocket(final HttpContext context) throws IOException {
            InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute("socks.address");
            Proxy proxy = new Proxy(Proxy.Type.SOCKS, socksaddr);
            return new Socket(proxy);
        }
 
        public Socket connectSocket(final int connectTimeout, final Socket socket, final HttpHost host,
                final InetSocketAddress remoteAddress, final InetSocketAddress localAddress, final HttpContext context)
                throws IOException, ConnectTimeoutException {
            Socket sock;
            if (socket != null) {
                sock = socket;
            } else {
                sock = createSocket(context);
            }
            if (localAddress != null) {
                sock.bind(localAddress);
            }
            try {
                sock.connect(remoteAddress, connectTimeout);
            } catch (SocketTimeoutException ex) {
                throw new ConnectTimeoutException(ex, host, remoteAddress.getAddress());
            }
            return sock;
        }
    }
}

以上代码是Httpclient提供的实例,稍作修改;
先启动shadowsocks-netty
然后运行ClientExecuteSOCKS

1.结果报如下错误:

I/O exception (org.apache.http.NoHttpResponseException) caught when processing request to {}->http://www.google.com:80: 
The target server failed to respond

可以观察shadowsocks-netty的服务器端shadowsocks-netty-server,有如下日志:

org.netty.proxy.ClientProxyHandler$2 - connect fail host = 67.15.129.210,port = 80,inetAddress = /67.15.129.210

域名解析后的ip地址连接失败,多次试验ip地址是会变动的,导致有时候能成功,有时候失败;

针对此问题可以直接使用域名访问,代码做如下修改:

sock.connect(remoteAddress, connectTimeout);

将如上代码改成:

sock.connect(InetSocketAddress.createUnresolved(remoteAddress.getHostName(), remoteAddress.getPort()),connectTimeout);

2.重新运行,报如下错误:

Caused by: org.apache.http.ProtocolException: The server failed to respond with a valid HTTP response
    at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:151)
    at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:57)
    at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:260)
    at org.apache.http.impl.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:161)
    at org.apache.http.impl.conn.CPoolProxy.receiveResponseHeader(CPoolProxy.java:153)
    at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:271)
    at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:123)
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:254)
    at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:195)
    at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:86)
    at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:108)
    at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
    ... 2 more

通过debug进入DefaultHttpResponseParser的parseHead方法中发现,每次读取http协议的状态行是” HTTP/1.1 200 OK”,有两个空格cunz,
导致比对失败,经分析发现是在Socks四次握手的时候没有将握手数据读取干净,导致后面的真实数据出现脏数据;
分别看netty提供的SocksCmdResponse和jdk中的SocksSocketImpl类:

public void encodeAsByteBuf(ByteBuf byteBuf) {
        byteBuf.writeByte(protocolVersion().byteValue());
        byteBuf.writeByte(cmdStatus.byteValue());
        byteBuf.writeByte(0x00);
        byteBuf.writeByte(addressType.byteValue());
        switch (addressType) {
            case IPv4: {
                byte[] hostContent = host == null ?
                        IPv4_HOSTNAME_ZEROED : NetUtil.createByteArrayFromIpAddressString(host);
                byteBuf.writeBytes(hostContent);
                byteBuf.writeShort(port);
                break;
            }
            case DOMAIN: {
                byte[] hostContent = host == null ?
                        DOMAIN_ZEROED : host.getBytes(CharsetUtil.US_ASCII);
                byteBuf.writeByte(hostContent.length);   // domain length
                byteBuf.writeBytes(hostContent);   // domain value
                byteBuf.writeShort(port);  // port value
                break;
            }
            case IPv6: {
                byte[] hostContent = host == null
                        ? IPv6_HOSTNAME_ZEROED : NetUtil.createByteArrayFromIpAddressString(host);
                byteBuf.writeBytes(hostContent);
                byteBuf.writeShort(port);
                break;
            }
        }
    }

SocksSocketImpl类connect方法部分代码如下:

switch (data[1]) {
        case REQUEST_OK:
            // success!
            switch(data[3]) {
            case IPV4:
                addr = new byte[4];
                i = readSocksReply(in, addr, deadlineMillis);
                if (i != 4)
                    throw new SocketException("Reply from SOCKS server badly formatted");
                data = new byte[2];
                i = readSocksReply(in, data, deadlineMillis);
                if (i != 2)
                    throw new SocketException("Reply from SOCKS server badly formatted");
                break;
            case DOMAIN_NAME:
                len = data[1];
                byte[] host = new byte[len];
                i = readSocksReply(in, host, deadlineMillis);
                if (i != len)
                    throw new SocketException("Reply from SOCKS server badly formatted");
                data = new byte[2];
                i = readSocksReply(in, data, deadlineMillis);
                if (i != 2)
                    throw new SocketException("Reply from SOCKS server badly formatted");
                break;
        ......
}

shadowsocks-netty返回的addressType为DOMAIN类型,会发现写入的数据格式和读取的格式不一致,导致产生脏数据;

此问题可以修改shadowsocks-netty返回的addressType为IPV4类型,具体代码在SocksServerConnectHandler中:

private SocksCmdResponse getSuccessResponse(SocksCmdRequest request) {
    return new SocksCmdResponse(SocksCmdStatus.SUCCESS, SocksAddressType.IPv4);
}

修改之后运行正确结果如下:

HTTP/1.1 200 OK
<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="en"><head><meta content="Search the world's ...具体网页内容省略...</body></html>

下载视频
下载视频的部分代码如下:

public class ClientExecuteSOCKS2 {
 
    /** 代理参数 IP+PORT **/
    private static String PROXY_IP = "localhost";
    private static int PROXY_PORT = 1080;
 
    public static void main(String[] args) throws Exception {
        Registry<ConnectionSocketFactory> reg = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", new MyConnectionSocketFactory()).build();
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg);
        CloseableHttpClient httpclient = HttpClients.custom().setConnectionManager(cm).build();
        try {
            InetSocketAddress socksaddr = new InetSocketAddress(PROXY_IP, PROXY_PORT);
            HttpClientContext context = HttpClientContext.create();
            context.setAttribute("socks.address", socksaddr);
            HttpGet request = new HttpGet("http://xxxxxxx.mp4");
            CloseableHttpResponse response = httpclient.execute(request, context);
 
            InputStream is = null;
            OutputStream os = null;
            try {
                System.out.println(response.getStatusLine());
                is = response.getEntity().getContent();
                System.out.println(response.getEntity().getContentLength());
                os = new FileOutputStream(new File("D:\\tmp.mp4"));
                byte tmp[] = new byte[1024];
                int l;
                while ((l = is.read(tmp)) != -1) {
                    os.write(tmp, 0, l);
                }
                os.flush();
            } finally {
                if (response != null) {
                    response.close();
                }
                if (is != null) {
                    is.close();
                }
                if (os != null) {
                    os.close();
                }
            }
        } finally {
            httpclient.close();
        }
    }
}

总结
下载国外视频有很多种方式,比如浏览器插件,本文依赖客户端Socks5代理程序,使用Httpclient4进行资源下载,更容易自动化和可控性;本文主要用于学习使用。

个人博客:codingo.xyz

© 著作权归作者所有

共有 人打赏支持
ksfzhaohui

ksfzhaohui

粉丝 301
博文 128
码字总数 158547
作品 3
南京
高级程序员
python模块介绍-shadowsocks:穿越防火墙的快速隧道代理(实现自由冲浪)

Shadowsocks 是一个安全的socks5代理,用于保护网络流量,是一个开源项目。 由于Shadowsocks使用socks5协议和可自定义密码的工业级算法加密,使得流量在网络传输过程中不易被他人读取。但是使...

磁针石
2015/06/09
0
0
CentOS6.5安装部署Shadowsocks服务器

一、环境介绍:   1、服务器:     CentOS6.5x8664   2、Windows客户端     Windows 10 二、安装部署:   1、Shadowsocks是什么?     Shadowsocks是一个安全的Socks代理,...

yangxuncai110
06/27
0
0
CentOS 搭建ss proxy

一、shadowsocks简介(以下来自wiki百科) shadowsocks是一种基于Socks5代理方式的网络数据加密传输包,并采用Apache许可证、GPL、MIT许可证等多种自由软件许可协议开放源代码。shadowsocks...

tivenwang
2017/11/07
0
0
CentOS/Linux 下安装 shadowsocks-qt5客户端实现科学上网

方法一(DNF指令): 1、如果未安装DNF,请跳转至:http://dev.fjuts.com:83/blog/index.php/linux/261.html2、添加shadowsocks 的 copr源(copr是dnf下的一个插件,但是我的CentOS7怎么提示找...

刘草
2015/06/22
0
3
CentOS6.5安装部署Shadowsocks服务器

一、环境介绍:   1、服务器:     CentOS6.5x8664   2、Windows客户端     Windows 10 二、安装部署:   1、Shadowsocks是什么?     Shadowsocks是一个安全的Socks代理,...

崔小凯
2017/11/04
0
0
Ubuntu一键安装Shadowsocks脚本

基于科学上网:VPS上搭建shadowsocks写了一个一键安装shadowsocks的shell脚本。只在Vultr上的Ubunbu 16.04做了测试。内容包括安装shadowsocks+设置shadowsocks开机启动+开启BBR加速。 原文链...

flyzy2005
01/31
0
0
写给非专业人士看的Shadowsocks 简介

这个文章来源于一个朋友在科学上网的过程中,搞不清楚 Shadowsocks 的配置问题,在这里我想按照我对 Shadowsocks 的理解简单梳理一下,以便一些非专业人士也能了解 long long ago… 在很久很...

borey
2015/04/18
0
1
写给非专业人士看的 Shadowsocks 简介

这个文章来源于一个朋友在科学上网的过程中,搞不清楚 Shadowsocks 的配置问题,在这里我想按照我对 Shadowsocks 的理解简单梳理一下,以便一些非专业人士也能了解 long long ago… 在很久很...

吴伟祥
06/29
0
0
aws搭建shadowsocks服务器

前段时间一直在忙open***的事情,现在手头有台aws服务器,打算利用起来。 如何利用呢?利用这台服务器进行科学上网,实际需求如下。 一、实际需求 在国内搜索技术文章,如果使用baidu的话,你...

wenhuifu
06/27
0
0
烂泥:aws搭建shadowsocks服务器

本文由秀依林枫提供友情赞助,首发于烂泥行天下 前段时间一直在忙openvpn的事情,现在手头有台aws服务器,打算利用起来。 如何利用呢?利用这台服务器进行科学上网,实际需求如下。 一、实际...

烂泥行天下
2015/08/08
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

32.filter表案例 nat表应用 (iptables)

10.15 iptables filter表案例 10.16/10.17/10.18 iptables nat表应用 10.15 iptables filter表案例: ~1. 写一个具体的iptables小案例,需求是把80端口、22端口、21 端口放行。但是,22端口我...

王鑫linux
57分钟前
0
0
shell中的函数&shell中的数组&告警系统需求分析

20.16/20.17 shell中的函数 20.18 shell中的数组 20.19 告警系统需求分析

影夜Linux
今天
0
0
Linux网络基础、Linux防火墙

Linux网络基础 ip addr 命令 :查看网口信息 ifconfig命令:查看网口信息,要比ip addr更明了一些 centos 7默认没安装ifconfig命令,可以使用yum install -y net-tools命令来安装。 ifconfig...

李超小牛子
今天
1
0
[机器学习]回归--Decision Tree Regression

CART决策树又称分类回归树,当数据集的因变量为连续性数值时,该树算法就是一个回归树,可以用叶节点观察的均值作为预测值;当数据集的因变量为离散型数值时,该树算法就是一个分类树,可以很...

wangxuwei
昨天
1
0
Redis做分布式无锁CAS的问题

因为Redis本身是单线程的,具备原子性,所以可以用来做分布式无锁的操作,但会有一点小问题。 public interface OrderService { public String getOrderNo();} public class OrderRe...

算法之名
昨天
9
0
143. Reorder List - LeetCode

Question 143. Reorder List Solution 题目大意:给一个链表,将这个列表分成前后两部分,后半部分反转,再将这两分链表的节点交替连接成一个新的链表 思路 :先将链表分成前后两部分,将后部...

yysue
昨天
1
0
数据结构与算法1

第一个代码,描述一个被称为BankAccount的类,该类模拟了银行中的账户操作。程序建立了一个开户金额,显示金额,存款,取款并显示余额。 主要的知识点联系为类的含义,构造函数,公有和私有。...

沉迷于编程的小菜菜
昨天
1
0
从为什么别的队伍总比你的快说起

在机场候检排队的时候,大多数情况下,别的队伍都要比自己所在的队伍快,并常常懊悔当初怎么没去那个队。 其实,最快的队伍只能有一个,而排队之前并不知道那个队快。所以,如果有六个队伍你...

我是菜鸟我骄傲
昨天
1
0
分布式事务常见的解决方案

随着互联网的发展,越来越多的多服务相互之间的调用,这时候就产生了一个问题,在单项目情况下很容易实现的事务控制(通过数据库的acid控制),变得不那么容易。 这时候就产生了多种方案: ...

小海bug
昨天
3
0
python从零学——scrapy初体验

python从零学——scrapy初体验 近日因为一些事情,需要从网上爬取一些东西,故而想通过使用爬虫来顺便学习下强大的python。现将一些学习中遇到的问题记录下来,以便日后查询 1. 开发环境的准...

咾咔叽
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部