Java实现Http代理服务器&通过http代理进行内网安装yum软件


本文通过Java进行http代理服务器实现,并在这个Java版http代理服务器的基础上,再描述如何通过http代理进行内网系统的yum软件安装

1.Http代理服务器简介

HTTP代理服务器是一种特殊的网络服务,允许一个网络终端(一般为客户端)通过这个服务与另一个网络终端进行非直接的连接。一些网关、路由器等网络设备具备网络代理功能。一般认为代理服务有利于保障网络终端的隐私或安全,防止攻击。

HTTP代理:http请求经过代理服务器,代理服务器只要负责转发相应的http响应体就可以

HTTPS代理:https请求经过代理服务器,会发送一个CONNECT报文,用于和代理服务器建立隧道,如果代理服务器返回HTTP 200,则建立成功,后续代理服务器只要负责转发数据。

2.Http代理服务器Java实现

常见的http代理服务器实现有ccproxy,haproxy,nginx等,这里通过Java实现简单的http代理服务器

通过Java进行http代理服务器实例,主要是解析http协议头里面的消息,解析出此请求是http还是https请求,如果是http请求,就解析出真实的请求的host,然后进行数据转发,如果是https请求,就进行CONNECT的解析,然后返回给客户端,再继续进行数据的转发。

2.1 Java源码

整个实现代码,采用一个Java类实现如下,将内容复制到你的idea或eclipse中编绎就可,无任何jar包依赖。

import java.io.BufferedReader;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Base64;
import java.util.Date;

/**
 * http代理服务器,隧道模式
 * @author liujh
 */
public class ProxyHttpsServer {
    private final int bufferSize = 8092;
    private int defaultPort = 1080; //默认端口
    private int localPort ;
    private ServerSocket localServerSocket;

    private boolean socksNeekLogin = true;//是否需要登录
    private String username = "admin";
    private String password = "admin123";

    public static void main(String[] args) {
        Integer port = args.length == 1 ? Integer.parseInt(args[0]) : null;
        new ProxyHttpsServer(port).startService();
    }

    public ProxyHttpsServer(Integer port){
        this.localPort = port == null ? defaultPort : port;
    }

    public void startService() {

        try {
        	
        	//开启一个ServerSocket服务器,监听请求的到来.
            localServerSocket = new ServerSocket(localPort);

            log("httpproxy server started , listen on " +localServerSocket.getInetAddress().getHostAddress()+":"+ localPort );

            // 一直监听,接收到新连接,则开启新线程去处理
            while (true) {
                Socket localSocket = localServerSocket.accept();
                new SocketThread(localSocket).start();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    //格式化打印方法,用来打印信息
    private final void log(Object message, Object... args) {
        Date dat = new Date();
        String msg = String.format("%1$tF %1$tT %2$-5s %3$s%n", dat, Thread.currentThread().getId(), String.format(message.toString(),args));
        System.out.print(msg);
    }

    /**
     * IO操作中共同的关闭方法
     * @param socket
     */
    protected final void closeIo(Socket closeable) {
        if (null != closeable) {
            try {
                closeable.close();
            } catch (IOException e) {
            }
        }
    }

    /**
     * IO操作中共同的关闭方法
     * @param socket
     */
    protected final void closeIo(Closeable closeable) {
        if (null != closeable) {
            try {
                closeable.close();
            } catch (IOException e) {
            }
        }
    }

    private class SocketThread extends Thread {
        private Socket localSocket;
        private Socket remoteSocket;
        private InputStream lin;
        private InputStream rin;
        private OutputStream lout;
        private OutputStream rout;

        public SocketThread(Socket socket) {
            this.localSocket = socket;
        }

        public void run() {

        	//获取远程socket的地址,然后进行打印
            String addr = localSocket.getRemoteSocketAddress().toString();
            log("process one socket : %s", addr);

            try {
                lin = localSocket.getInputStream();
                lout = localSocket.getOutputStream();

                StringBuilder headStr = new StringBuilder();
                BufferedReader br = new BufferedReader(new InputStreamReader(lin));
                //读取HTTP请求头,并拿到HOST请求头和method

                String line;
                String host = "";
                String proxy_Authorization = "";
                while ((line = br.readLine()) != null) {
                	//打印http协议头
                    log(line);
                    headStr.append(line + "\r\n");
                    if (line.length() == 0) {
                        break;
                    } else {
                        String[] temp = line.split(" ");
                        if (temp[0].contains("Host")) {
                            host = temp[1];
                        }

                        //如果配置了需要登陆,就解析消息头里面的Proxy-Authorization字段,认证的账号和密码信息是通过base64加密传过来的。
                        if(socksNeekLogin && (temp[0].contains("Proxy-Authorization"))){//获取认证信息
                            proxy_Authorization = temp[2];
                        }

                    }
                }

                String type = headStr.substring(0, headStr.indexOf(" "));
                //根据host头解析出目标服务器的host和port
                String[] hostTemp = host.split(":");
                host = hostTemp[0];
                int port = 80; //先设置成默认的Http端口80
                //hostTemp的长度大于1表示用户指定了非80或443端口,解析出对应的端口出来
                if (hostTemp.length > 1) {
                    port = Integer.valueOf(hostTemp[1]);
                }else{
                	//端口如果没有指定,有可能是443,也有可能是80,因此尝试根据HTTP method来判断是https还是http请求,有CONNECT的是HTTPS请求,采用443端口
                	if ("CONNECT".equalsIgnoreCase(type)){
                		port = 443;
                	}
                }
                
                //

                boolean isLogin = false;
                //如果需要登录,校验登录是否通过
                if(socksNeekLogin){
                	
                	//通过username和password进行base64加密得到一个串,然后和请求里面传过来的Proxy-Authorization比较,一致的话就认证成功。
                    String authenticationEncoding = Base64.getEncoder().encodeToString(new String(username + ":" + password).getBytes());
                    if(proxy_Authorization.equals(authenticationEncoding)){
                        isLogin = true;//登录通过
                        //log("login success, basic: %s", proxy_Authorization);
                    }else {
                        log("httpproxy server need login,but login failed .");
                    }
                }

                //不需要登录或已被校验登录成功,才进入代理,否则直接程序结束,关闭连接
                if(!socksNeekLogin || isLogin){

                    //连接到目标服务器
                    remoteSocket = new Socket(host, port);//进行远程连接
                    rin = remoteSocket.getInputStream();
                    rout = remoteSocket.getOutputStream();

                    //根据HTTP method来判断是https还是http请求
                    if ("CONNECT".equalsIgnoreCase(type)) {//https先建立隧道
                        lout.write("HTTP/1.1 200 Connection Established\r\n\r\n".getBytes());
                        lout.flush();
                    }else {//http直接将请求头转发
                        rout.write(headStr.toString().getBytes());
                        rout.flush();
                    }

                    new ReadThread().start();

                    //设置超时,超过时间未收到客户端请求,关闭资源
                    //remoteSocket.setSoTimeout(10000);
                    //写数据,负责读取客户端发送过来的数据,转发给远程
                    byte[] data = new byte[bufferSize];
                    int len = 0;
                    while((len = lin.read(data)) > 0){
                        if(len == bufferSize) {//读到了缓存大小一致的数据,不需要拷贝,直接使用
                            rout.write(data);
                            rout.flush();
                        }else {//读到了比缓存大小的数据,需要拷贝到新数组然后再使用
                            byte[] dest = new byte[len];
                            System.arraycopy(data, 0, dest, 0, len);
                            rout.write(dest);
                            rout.flush();
                        }

                    }

                }

            } catch (Exception e) {
                log("exception : %s %s", e.getClass(), e.getLocalizedMessage());
                //e.printStackTrace();
            } finally {
                log("close socket, system cleanning ...  %s ", addr);
                closeIo(lin);
                closeIo(rin);
                closeIo(lout);
                closeIo(rout);
                closeIo(localSocket);
                closeIo(remoteSocket);
            }
        }

        //读数据线程负责读取远程数据后回写到客户端
        class ReadThread extends Thread {
            @Override
            public void run() {
                try {
                    byte[] data = new byte[bufferSize];
                    int len = 0;
                    while((len = rin.read(data)) > 0){
                        if(len == bufferSize) {//读到了缓存大小一致的数据,不需要拷贝,直接使用
                            lout.write(data);
                            lout.flush();
                        }else {//读到了比缓存大小的数据,需要拷贝到新数组然后再使用
                            byte[] dest = new byte[len];
                            System.arraycopy(data, 0, dest, 0, len);
                            lout.write(dest);
                            lout.flush();
                        }
                    }
                } catch (IOException e) {
                    //log(remoteSocket.getLocalAddress() + ":"+ remoteSocket.getPort() + " remoteSocket InputStream disconnected.");
                } finally {
                }
            }

        }

    }

}

2.2 代码分析说明

  1. ##可支持http代理,认证或不认证功能通过socksNeekLogin参数来支持,如果socksNeekLogin设置为true,说明需要进行认证,并相应的username和password为认证的账号和密码。
    private boolean socksNeekLogin = true;//是否需要登录
    private String username = "admin";
    private String password = "admin123";
  1. ##首先定义一个ServerSocket服务器,监听端口1080,端口可以通过命令行参数进行修改。
        	//开启一个ServerSocket服务器,监听请求的到来.
            localServerSocket = new ServerSocket(localPort);

            log("httpproxy server started , listen on " +localServerSocket.getInetAddress().getHostAddress()+":"+ localPort );
  1. ##阻塞循环等待请求进来,收到一个请求后,开启一个线程进行处理
            // 一直监听,接收到新连接,则开启新线程去处理
            while (true) {
                Socket localSocket = localServerSocket.accept();
                new SocketThread(localSocket).start();
            }
  1. ##在新开启的线程中进行主要逻辑的处理。先进行简单的打印远程的连接
        	//获取远程socket的地址,然后进行打印
            String addr = localSocket.getRemoteSocketAddress().toString();
            log("process one socket : %s", addr);
  1. ##接着通过BufferedReader包装好inputstream流,然后一行一行的读。如果某一行含有Host,就解析出真实目标服务器的ip和端口
    在这里插入图片描述
  2. ##如何设置了需要登陆,就解析消息头中的Proxy-Authorization字段,然后进行账号密码判断是否一致,决定是否认证成功
//如果配置了需要登陆,就解析消息头里面的Proxy-Authorization字段,认证的账号和密码信息是通过base64加密传过来的。
if(socksNeekLogin && temp[0].contains("Proxy-Authorization")){//获取认证信息
    proxy_Authorization = temp[2];
}
boolean isLogin = false;
//如果需要登录,校验登录是否通过
if(socksNeekLogin){
	
	//通过username和password进行base64加密得到一个串,然后和请求里面传过来的Proxy-Authorization比较,一致的话就认证成功。
    String authenticationEncoding = Base64.getEncoder().encodeToString(new String(username + ":" + password).getBytes());
    if(proxy_Authorization.equals(authenticationEncoding)){
        isLogin = true;//登录通过
        //log("login success, basic: %s", proxy_Authorization);
    }else {
        log("httpproxy server need login,but login failed .");
    }
}
  1. ##如果不需要登陆,或认证成功,进一步进行是http还是https的判断,分别作不同的处理
//根据HTTP method来判断是https还是http请求
if ("CONNECT".equalsIgnoreCase(type)) {//https先建立隧道
    lout.write("HTTP/1.1 200 Connection Established\r\n\r\n".getBytes());
    lout.flush();
}else {//http直接将请求头转发
    rout.write(headStr.toString().getBytes());
    rout.flush();
}
  1. ##最后作真正的数据转发即可
    在这里插入图片描述

3.通过http代理进行内网安装yum软件

生产环境,常常是不给服务器有互联网访问的权限的,要进行yum安装一些软件,就有点麻烦,常用的做法有,在内网搭建一个私库,然后通过私库进行安装,见另一篇nexus3私库环境搭建(maven,yum,apt,nodejs), 或者进行离线rpm安装等,本篇采用http代理服务器+securt远程透传的方式进行,还是较简单的。

3.1 步骤2实现的代理服务器启动

一般我本地笔记本肯定是有外网访问权限的,连本地个人电脑都没有外网访问,就没办法了,先进行代理服务器的启动如下:

##这里指定了以1080端口启动,并默认是开了认证的,账号admin密码admin123
java -server -Xmx256m -Xms256m -Xmn128m ProxyHttpsServer 1080

在这里插入图片描述
##代理服务器启动成功截图:
在这里插入图片描述

3.2通过securecrt进行远程内网服务器的连接

一般可以通过vpn或其他方式能在个人笔记本电脑上直接连进远程的内网服务器,
连接远程服务器后,通过securecrt自带的远程端口透传功能进行远程端口到本地端口的反向映射

如下:通过crt连接我的内网服务器192.168.56.101,然后通过crt作一个远程到本地的端口转发,(其实这里我的192.168.56.101是能直连我的本地电脑192.168.56.1,但为了模拟只能单向从本地到远程,因此这里通过crt作一个远程到本地的反向转发)
在这里插入图片描述
经过以上的端口转发配置后,通过在服务器192.168.56.101上进行访问127.0.0.1:11080就相当于访问到我的本地电脑的192.168.56.1的1080了,测试如下:

因为我没有装telnet,因此采用curl测试端口情况,在192.168.56.101上执行curl http://127.0.0.1:11080, 显示Empty reply from server,其实是成功的。看本地的程序日志也能看出来有请求到达。说明端口从远程到本地是成功的。
在这里插入图片描述
在这里插入图片描述

3.3 进行export配置通过http代理访问

在远程服务器上进行export三条命令的执行,告诉服务器,通过代理访问相关资源。
注:export命令只对当前session生效,即关了crt再进入服务器时,要重新执行

export http_proxy=http://admin:admin123@127.0.0.1:11080
export https_proxy=http://admin:admin123@127.0.0.1:11080
export no_proxy='127.0.0.1'

上面的no_proxy是告诉服务器127.0.0.1相关的地址不需要走代理

以访问www.baidu.com为例,执行命令行,访问不了:
在这里插入图片描述
执行export后再执行curl http://www.baidu.com测试
在这里插入图片描述

3.4 通过代理进行yum软件的安装

由于3.3配置后,能在远程内网访问直接访问外面的网站了,因此,接下来正常直接按yum操作执行软件安装即可了,如:

yum install -y telnet 

安装日志正常,软件能正常安装,超级方便,相当于有外网状态下进行相关的yum软件安装
在这里插入图片描述

本地的代理程序,也显示已通过代理进行远程的yum.repo网址的访问,说明是真的通过代理服务器走资源的访问并转发。
在这里插入图片描述

4. 总结

本文讲述了如何通过Java实现一个http代理服务器,并进行简单的应用。如yum软件的安装等,另外,sock5代理服务器的原理与实现,以及应用也类似,后面再写一个Java版的Sock5代理服务器的实现及应用,如对这篇文章有什么疑问,请指正或回复我。

  • 3
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值